When testing an app, process exploration can provide the tester with deep insights into the app process memory. It can be achieved via runtime instrumentation and allows to perform tasks such as:
- Retrieving the memory map and loaded libraries.
- Searching for occurrences of certain data.
- After doing a search, obtaining the location of a certain offset in the memory map.
- Performing a memory dump and inspect or reverse engineer the binary data offline.
- Reverse engineering a native library while it's running.
As you can see, these passive tasks help us collect information. This Information is often used for other techniques, such as method hooking.
In the following sections you will be using r2frida to retrieve information straight from the app runtime. Please refer to r2frida's official installation instructions. First start by opening an r2frida session to the target app (e.g. HelloWorld JNI APK) that should be running on your Android phone (connected per USB). Use the following command:
See all options with
Once in the r2frida session, all commands start with
:. For example, in radare2 you'd run
i to display the binary information, but in r2frida you'd use
Memory Maps and Inspection¶
You can retrieve the app's memory maps by running
\dm. The output in Android can get very long (e.g. between 1500 and 2000 lines), to narrow your search and see only what directly belongs to the app apply a grep (
~) by package name
[0x00000000]> \dm~sg.vantagepoint.helloworldjni 0x000000009b2dc000 - 0x000000009b361000 rw- /dev/ashmem/dalvik-/data/app/sg.vantagepoint.helloworldjni-1/oat/arm64/base.art (deleted) 0x000000009b361000 - 0x000000009b36e000 --- /dev/ashmem/dalvik-/data/app/sg.vantagepoint.helloworldjni-1/oat/arm64/base.art (deleted) 0x000000009b36e000 - 0x000000009b371000 rw- /dev/ashmem/dalvik-/data/app/sg.vantagepoint.helloworldjni-1/oat/arm64/base.art (deleted) 0x0000007d103be000 - 0x0000007d10686000 r-- /data/app/sg.vantagepoint.helloworldjni-1/oat/arm64/base.vdex 0x0000007d10dd0000 - 0x0000007d10dee000 r-- /data/app/sg.vantagepoint.helloworldjni-1/oat/arm64/base.odex 0x0000007d10dee000 - 0x0000007d10e2b000 r-x /data/app/sg.vantagepoint.helloworldjni-1/oat/arm64/base.odex 0x0000007d10e3a000 - 0x0000007d10e3b000 r-- /data/app/sg.vantagepoint.helloworldjni-1/oat/arm64/base.odex 0x0000007d10e3b000 - 0x0000007d10e3c000 rw- /data/app/sg.vantagepoint.helloworldjni-1/oat/arm64/base.odex 0x0000007d1c499000 - 0x0000007d1c49a000 r-x /data/app/sg.vantagepoint.helloworldjni-1/lib/arm64/libnative-lib.so 0x0000007d1c4a9000 - 0x0000007d1c4aa000 r-- /data/app/sg.vantagepoint.helloworldjni-1/lib/arm64/libnative-lib.so 0x0000007d1c4aa000 - 0x0000007d1c4ab000 rw- /data/app/sg.vantagepoint.helloworldjni-1/lib/arm64/libnative-lib.so 0x0000007d1c516000 - 0x0000007d1c54d000 r-- /data/app/sg.vantagepoint.helloworldjni-1/base.apk 0x0000007dbd23c000 - 0x0000007dbd247000 r-- /data/app/sg.vantagepoint.helloworldjni-1/base.apk 0x0000007dc05db000 - 0x0000007dc05dc000 r-- /data/app/sg.vantagepoint.helloworldjni-1/oat/arm64/base.art
While you're searching or exploring the app memory, you can always verify where you're located at each moment (where your current offset is located) in the memory map. Instead of noting and searching for the memory address in this list you can simply run
\dm.. You'll find an example in the following section "In-Memory Search".
If you're only interested in the modules (binaries and libraries) that the app has loaded, you can use the command
\il to list them all:
[0x00000000]> \il 0x000000558b1fd000 app_process64 0x0000007dbc859000 libandroid_runtime.so 0x0000007dbf5d7000 libbinder.so 0x0000007dbff4d000 libcutils.so 0x0000007dbfd13000 libhwbinder.so 0x0000007dbea00000 liblog.so 0x0000007dbcf17000 libnativeloader.so 0x0000007dbf21c000 libutils.so 0x0000007dbde4b000 libc++.so 0x0000007dbe09b000 libc.so ... 0x0000007d10dd0000 base.odex 0x0000007d1c499000 libnative-lib.so 0x0000007d2354e000 frida-agent-64.so 0x0000007dc065d000 linux-vdso.so.1 0x0000007dc065f000 linker64
As you might expect you can correlate the addresses of the libraries with the memory maps: e.g. the native library of the app is located at
0x0000007d1c499000 and optimized dex (base.odex) at
You can also use objection to display the same information.
$ objection --gadget sg.vantagepoint.helloworldjni explore sg.vantagepoint.helloworldjni on (google: 8.1.0) [usb] # memory list modules Save the output by adding `--json modules.json` to this command Name Base Size Path ----------------------------------------------- ------------ -------------------- -------------------------------------------------------------------- app_process64 0x558b1fd000 32768 (32.0 KiB) /system/bin/app_process64 libandroid_runtime.so 0x7dbc859000 1982464 (1.9 MiB) /system/lib64/libandroid_runtime.so libbinder.so 0x7dbf5d7000 557056 (544.0 KiB) /system/lib64/libbinder.so libcutils.so 0x7dbff4d000 77824 (76.0 KiB) /system/lib64/libcutils.so libhwbinder.so 0x7dbfd13000 163840 (160.0 KiB) /system/lib64/libhwbinder.so base.odex 0x7d10dd0000 442368 (432.0 KiB) /data/app/sg.vantagepoint.helloworldjni-1/oat/arm64/base.odex libnative-lib.so 0x7d1c499000 73728 (72.0 KiB) /data/app/sg.vantagepoint.helloworldjni-1/lib/arm64/libnative-lib.so
You can even directly see the size and the path to that binary in the Android file system.
In-memory search is a very useful technique to test for sensitive data that might be present in the app memory.
See r2frida's help on the search command (
\/?) to learn about the search command and get a list of options. The following shows only a subset of them:
[0x00000000]> \/? / search /j search json /w search wide /wj search wide json /x search hex /xj search hex json ...
You can adjust your search by using the search settings
\e~search. For example,
\e search.quiet=true; will print only the results and hide search progress:
[0x00000000]> \e~search e search.in=perm:r-- e search.quiet=false
For now, we'll continue with the defaults and concentrate on string search. This app is actually very simple, it loads the string "Hello from C++" from its native library and displays it to us. You can start by searching for "Hello" and see what r2frida finds:
[0x00000000]> \/ Hello Searching 5 bytes: 48 65 6c 6c 6f ... hits: 11 0x13125398 hit0_0 HelloWorldJNI 0x13126b90 hit0_1 Hello World! 0x1312e220 hit0_2 Hello from C++ 0x70654ec5 hit0_3 Hello 0x7d1c499560 hit0_4 Hello from C++ 0x7d1c4a9560 hit0_5 Hello from C++ 0x7d1c51cef9 hit0_6 HelloWorldJNI 0x7d30ba11bc hit0_7 Hello World! 0x7d39cd796b hit0_8 Hello.java 0x7d39d2024d hit0_9 Hello; 0x7d3aa4d274 hit0_10 Hello
Now you'd like to know where these addresses actually are. You may do so by running the
\dm. command for all
@@ hits matching the glob
[0x00000000]> \dm.@@ hit0_* 0x0000000013100000 - 0x0000000013140000 rw- /dev/ashmem/dalvik-main space (region space) (deleted) 0x0000000013100000 - 0x0000000013140000 rw- /dev/ashmem/dalvik-main space (region space) (deleted) 0x0000000013100000 - 0x0000000013140000 rw- /dev/ashmem/dalvik-main space (region space) (deleted) 0x00000000703c2000 - 0x00000000709b5000 rw- /data/dalvik-cache/arm64/system@[email protected] 0x0000007d1c499000 - 0x0000007d1c49a000 r-x /data/app/sg.vantagepoint.helloworldjni-1/lib/arm64/libnative-lib.so 0x0000007d1c4a9000 - 0x0000007d1c4aa000 r-- /data/app/sg.vantagepoint.helloworldjni-1/lib/arm64/libnative-lib.so 0x0000007d1c516000 - 0x0000007d1c54d000 r-- /data/app/sg.vantagepoint.helloworldjni-1/base.apk 0x0000007d30a00000 - 0x0000007d30c00000 rw- 0x0000007d396bc000 - 0x0000007d3a998000 r-- /system/framework/arm64/boot-framework.vdex 0x0000007d396bc000 - 0x0000007d3a998000 r-- /system/framework/arm64/boot-framework.vdex 0x0000007d3a998000 - 0x0000007d3aa9c000 r-- /system/framework/arm64/boot-ext.vdex
Additionally, you can search for occurrences of the wide version of the string (
\/w) and, again, check their memory regions:
[0x00000000]> \/w Hello Searching 10 bytes: 48 00 65 00 6c 00 6c 00 6f 00 hits: 6 0x13102acc hit1_0 480065006c006c006f00 0x13102b9c hit1_1 480065006c006c006f00 0x7d30a53aa0 hit1_2 480065006c006c006f00 0x7d30a872b0 hit1_3 480065006c006c006f00 0x7d30bb9568 hit1_4 480065006c006c006f00 0x7d30bb9a68 hit1_5 480065006c006c006f00 [0x00000000]> \dm.@@ hit1_* 0x0000000013100000 - 0x0000000013140000 rw- /dev/ashmem/dalvik-main space (region space) (deleted) 0x0000000013100000 - 0x0000000013140000 rw- /dev/ashmem/dalvik-main space (region space) (deleted) 0x0000007d30a00000 - 0x0000007d30c00000 rw- 0x0000007d30a00000 - 0x0000007d30c00000 rw- 0x0000007d30a00000 - 0x0000007d30c00000 rw- 0x0000007d30a00000 - 0x0000007d30c00000 rw-
They are in the same rw- region as one of the previous strings (
0x0000007d30a00000). Note that searching for the wide versions of strings is sometimes the only way to find them as you'll see in the following section.
In-memory search can be very useful to quickly know if certain data is located in the main app binary, inside a shared library or in another region. You may also use it to test the behavior of the app regarding how the data is kept in memory. For instance, you could analyze an app that performs a login and search for occurrences of the user password. Also, you may check if you still can find the password in memory after the login is completed to verify if this sensitive data is wiped from memory after its use.
You can dump the app's process memory with objection and Fridump. To take advantage of these tools on a non-rooted device, the Android app must be repackaged with
frida-gadget.so and re-signed. A detailed explanation of this process is in the section Dynamic Analysis on Non-Rooted Devices. To use these tools on a rooted phone, simply have frida-server installed and running.
Note: When using these tools, you might get several memory access violation errors which can normally be ignored. These tools inject a Frida agent and try to dump all the mapped memory of the app regardless of the access permissions (read/write/execute). Therefore, when the injected Frida agent tries to read a region that's not readable, it'll return the corresponding memory access violation errors. Refer to previous section "Memory Maps and Inspection" for more details.
With objection it is possible to dump all memory of the running process on the device by using the command
memory dump all.
$ objection --gadget sg.vantagepoint.helloworldjni explore sg.vantagepoint.helloworldjni on (google: 8.1.0) [usb] # memory dump all /Users/foo/memory_Android/memory Will dump 719 rw- images, totalling 1.6 GiB Dumping 1002.8 MiB from base: 0x14140000 [------------------------------------] 0% 00:11:03(session detach message) process-terminated Dumping 8.0 MiB from base: 0x7fc753e000 [####################################] 100% Memory dumped to file: /Users/foo/memory_Android/memory
In this case there was an error, which is probably due to memory access violations as we already anticipated. This error can be safely ignored as long as we are able to see the extracted dump in the file system. If you have any problems, a first step would be to enable the debug flag
-dwhen running objection or, if that doesn't help, file an issue in objection's GitHub.
Next, we are able to find the "Hello from C++" strings with radare2:
$ r2 /Users/foo/memory_Android/memory [0x00000000]> izz~Hello from 1136 0x00065270 0x00065270 14 15 () ascii Hello from C++
Alternatively you can use Fridump. This time, we will input a string and see if we can find it in the memory dump. For this, open the MASTG Hacking Playground app, navigate to "OMTG_DATAST_002_LOGGING" and enter "owasp-mstg" to the password field. Next, run Fridump:
python3 fridump.py -U sg.vp.owasp_mobile.omtg_android -s Current Directory: /Users/foo/git/fridump Output directory is set to: /Users/foo/git/fridump/dump Starting Memory dump... Oops, memory access violation!-------------------------------] 0.28% Complete Progress: [##################################################] 99.58% Complete Running strings on all files: Progress: [##################################################] 100.0% Complete Finished!
Tip: Enable verbosity by including the flag
-vif you want to see more details, e.g. the regions provoking memory access violations.
It will take a while until it's completed and you'll get a collection of *.data files inside the dump folder. When you add the
-s flag, all strings are extracted from the dumped raw memory files and added to the file
strings.txt, which is also stored in the dump directory.
ls dump/ dump/1007943680_dump.data dump/357826560_dump.data dump/630456320_dump.data ... strings.txt
Finally, search for the input string in the dump directory:
$ grep -nri owasp-mstg dump/ Binary file dump//316669952_dump.data matches Binary file dump//strings.txt matches
The "owasp-mstg" string can be found in one of the dump files as well as in the processed strings file.