How to extract sensitive plaintext data from Android memory
Right, I’ve had enough of ripping IoT stuff apart for the moment. It’s time to review and share a technique that I’ve used on my favourite mobile operating system (Android, duh!), but haven’t had much use for as it requires the right circumstances.
What I’m going to show you is how to directly dump memory from an Android process.
This is similar to the process I wrote about at the end of my article about the debug mode on apps, but this is more flexible and works with native apps.
NOTE: It sounds cool, right? But you need to be root and need to have an app with “juicy” stuff in memory and stuff that you can’t get through the app’s files.
Needless to say, I found a use.
An app I was testing stored very little real data, had a lock functionality on it, performed certificate pinning and compared the unlock code over the Internets, so it was never stored locally. It also didn’t perform any form of checking to see whether I was root.
I like a challenge.
Okay, this requires some work to get your (rooted) device ready. You need to edit the system partition to add the GNU debugger (gdb) to it. Fortunately you don’t need to compile this a pre-compiled version is part of the Android Native Development Kit (NDK).
Grab the Native Development Kit (NDK), extract it somewhere and find the right version of gdbserver for your platform. I’ll be using the ARM version on my Nexus 4 as it makes sense.
The steps are the same as adding anything to the root partition:
- Upload the file
- Enter a shell and become root
- Remount /system as read/write
- Copy file to /system/xbin (or /system/bin)
- Change permissions to ensure that it is executable
- Clean up
push: gdbserver/gdbserver -> /sdcard/gdbserver
1 file pushed. 0 files skipped.
2668 KB/s (409940 bytes in 0.150s)C:toolsandroid-ndk-r10eprebuiltandroid-arm>adb shell
shell@mako:/ $ su
root@mako:/ # mount -o rw,remount /system
mount -o rw,remount /system
root@mako:/ # cp /sdcard/gdbserver /system/xbin
cp /sdcard/gdbserver /system/xbin
root@mako:/ # chmod 555 /system/xbin
chmod 555 /system/xbin
root@mako:/ # mount -o ro,remount /system
mount -o ro,remount /system
root@mako:/ # rm /sdcard/gdbserver
Now we need a gdb client to talk to the server process. We can’t use the standard gdb process on most Linux systems as that will not work correctly for the process in place on the device. So we need to perform a custom compile to ensure that we have a gdb that will, for example, run on Intel, but understand ARM.
First, download the gdb source code from gnu.org: (and yes I do write blog articles quite late at night, committed, that’s me).
–2015-08-13 22:47:26– http://ftp.gnu.org/gnu/gdb/gdb-7.7.tar.bz2
Resolving ftp.gnu.org (ftp.gnu.org)… 22.214.171.124, 2001:4830:134:3::b
Connecting to ftp.gnu.org (ftp.gnu.org)|126.96.36.199|:80… connected.
HTTP request sent, awaiting response… 200 OK
Length: 24846320 (24M) [application/x-bzip2]
Saving to: ‘gdb-7.7.tar.bz2’gdb-7.7.tar.bz2 100%[==================>] 23.69M 286KB/s in 65s
2015-08-13 22:48:32 (373 KB/s) – ‘gdb-7.7.tar.bz2’ saved [24846320/24846320]
[dave@jotunheim ~]$ bunzip2 gdb-7.7.tar.bz2
[dave@jotunheim ~]$ tar xf gdb-7.7.tar
[dave@jotunheim ~]$ cd gdb-7.7/
Then we can build it normally, by using the –target flag to specify that we want gdb’s target to be an ARM processor:
[dave@jotunheim gdb-7.7]$ make
And, we should now have a version of gdb in the gdb directory that runs on an Intel device and will understand an ARM target.
Examining the target
Now we have gdb running, we can attempt to read from memory. What we’re interested in (usually) is the heap memory – this is memory that’s been requested on the fly either from the Dalvik virtual machine, or if a native app, from the kernel.
We can find this information out using the /proc pseudo file system on the device. As an example, I’m going to dump the memory of the device’s keystore application. The first step is to find out the process id:
keystore 228 1 4248 1680 c0624a6c b6f92838 S /system/bin/keystore
Then we can go to the /proc entry for this process and check the memory map:
root@mako:/proc/228 # cat maps | tail
b6fc1000-b6fce000 r-xp 00000000 b3:15 159 /system/bin/linker
b6fce000-b6fcf000 r–p 0000c000 b3:15 159 /system/bin/linker
b6fcf000-b6fd0000 rw-p 0000d000 b3:15 159 /system/bin/linker
b6fd0000-b6fd1000 rw-p 00000000 00:00 0
b6fd1000-b6fda000 r-xp 00000000 b3:15 317 /system/bin/keystore
b6fdb000-b6fdc000 r–p 00009000 b3:15 317 /system/bin/keystore
b6fdc000-b6fdd000 rw-p 0000a000 b3:15 317 /system/bin/keystore
b7712000-b771f000 rw-p 00000000 00:00 0 [heap]
bee86000-beea7000 rw-p 00000000 00:00 0 [stack]
ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors]
What we’ll normally find are is the code that makes up the process and its libraries and then a copy of the important bits of the process:
- heap – memory assigned by the VM or by the kernel for data storage
- stack – memory used during function calls etc.
So above we can see that the heap runs from 0xb7712000 – 0xb771f000.
Right, it’s time to grab memory. This is slightly convoluted, but once you get used to it, it makes sense. The process is:
- Start gdbserver on the process listening on a port on the device
- Use adb to forward the port on the device to a local port
- Use a third party program to forward the local port to the device where you will be running gdb
- Connect via gdb
- Dump the memory
- Look for stuff
- $$$ Profit $four$
1. Start gdbserver
This is the simplest part of the process, once you have gdbserver install we can just attach it to our process, and specify a TCP port for it to listen on. I’ve used 1234/tcp below, because:
Attached; pid = 228
Listening on port 1234
Don’t ask me why the PID and connection string are that way around, it makes no sense to me either.
We are now listening to the network on port 1234, but as iptables gets in the way, we need to perform the following.
2. Use adb to forward the port
This will allow us to the use adb and our USB connection to the device to bridge between a network port on the host and the device:
C:Usersdave>adb forward tcp:1234 tcp:1234
This will now allow us to talk to the device on port 1234/tcp by connecting to 1234/tcp on the host device. There’s one problem:
TCP 127.0.0.1:1234 0.0.0.0:0 LISTENING
As you can see it’s only listening on localhost:1234, which means if our version of gdb is running on another host, or a VM then we need to:
3. Use a third party program to forward the port
This step can be skipped if you’re running gdb and adb on the same host.
I use a really old program called “Port Forwarding for Windows” to forward from my native OS to the virtual machine I run gdb on:
Using 1234/tcp for simplicity. If this does not work as expected, check your firewalls as they will often break stuff like this.
Now all this is out of the way we should now be able to connect with gdb.
4. Connect with gdb
So I’m going to connect from my Linux VM via my Windows native OS to the gdbserver running on my Nexus 4, using the custom compiled gdb that I made earlier:
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type “show copying”
and “show warranty” for details.
This GDB was configured as “–host=i686-pc-linux-gnu –target=arm-linux-gnueabi”.
Type “show configuration” for configuration details.
For bug reporting instructions, please see:
Find the GDB manual and other documentation resources online at:
For help, type “help”.
Type “apropos word” to search for commands related to “word”.
(gdb) target remote 192.168.0.41:1234
Remote debugging using 192.168.0.41:1234
0xb6f92834 in ?? ()
We now have a connection (bit anti-climatic really) so now we can dump the memory.
5. Dump the memory
This can be performed with the “dump memory” command very quickly. We’ll use the hex numbers we took from the process’ maps file earlier:
This will write the whole of the device’s heap to the file /tmp/heapout.
6. Look for stuff
There will be a format to the heap, but this will depend on who it’s been allocated by and what’s been allocated. For 90% of cases, a simple strings will do this.
Now, I picked the keystore program for a reason: this is the Android keystore service and manages all access to the aforementioned keystore. The keystore uses a masterkey as a KEK (key encryption key). This is derived from the PIN or password for the device.
Can you see where I’m going? Yes, can we find the device’s PIN? Yes, we can, we just need to look for user 0’s master key and look at data around it:
And there we have the device’s PIN in the old cleartext.
How to prevent it
As I said in the intro, as this process requires root it’s only useful in a handful of situations, mostly where important data is transient, such as credit card PANs and CVVs and the device has already been rooted.
You can protect yourself though, by a little bit of planning when writing your apps: ensure that confidential data is destroyed as soon as possible after use.
Also, don’t just mark your objects deleted after use and hope the garbage collector picks them up – destroy them fully by overwriting all critical data with random data.