Library injection for debuggable Android apps

Injecting native libraries (e.g. Frida) over JDWP, on non-rooted devices.

TLDR: released a script which can be used to inject native libraries like Frida into debuggable Android apps on non-rooted devices.

As discussed on a previous blogpost, security testers can use Frida to review the internals of Android apps on non-rooted Android devices, as long as they inject the library into the app via application repackaging.

Some time ago, Tim asked the following on twitter:

This looked like an interesting challenge. If an app is debuggable, then in theory you shouldn’t need root or even repackaging to use Frida. One way would of course be using run-as - but this is broken on several Samsung devices.

In this blog post I’ll describe how we can inject the Frida gadget library via JDWP - or any other native shared library for that matter, into a debuggable Android app - without root or repackaging.

Debuggable apps

How can an Android app be debuggable?

  • During security testing, you could request that the app to be tested is debuggable, but otherwise adequately protected.
  • An app may be marked as debuggable by mistake by the developers (unlikely to make it to play store though)
  • If you have a rooted device, you make configure it so that every app is treated as debuggable anyway
  • You may be able to turn ro.debuggable=True just with a small change through recovery, without ‘fully rooting’ a device
  • You could always repackage the app to turn the debuggable flag to true - this is a less invasive change compared to the one described in my previous blogpost.

As many of you might know, debuggable Android applications open up a JWDP socket allowing a Java debugger to connect to them. The debugger, when connected, can do whatever it pleases with the app - including injecting and executing arbitrary code.

So, since there’s code execution, we could just load a library. The technique is fairly simple:

  1. Download the correct library for your architecture (arm/arm64/x86/x86_64)
  2. Use adb to push the library to a world readable/writable location on the device, e.g. /data/local/tmp
  3. Use a debugger to connect to the app (or a script that implements the necessary parts of the JDWP protocol)
  4. Inject a small piece of code to move the library into the application’s private directory (to avoid selinux restrictions)
  5. Inject a Runtime.loadLibrary() call into one of the application’s activity constructors.

When you continue loading, the System.loadLibrary() you’ve injected will get called and load the Frida library. Frida will pause execution upon loading, waiting for the frida client to be connected to it.

jdwp-lib-injector

Turns out that automating this is pretty easy, due to jwdp-shellifier - a python script created by IOactive’s _hugsy_ and others some time ago, implementing large parts of the JDWP protocol. I forked this script and added some extra functionality needed for injecting libraries.

This small python script, along with a small shell script orchestrating things implements this whole “attack” and successfully injects Frida gadget (or any other library) into any Android application marked as debuggable on both rooted and non-rooted android devices. A couple of caveats are that the device must allow USB debugging and the app must have the INTERNET permission. Further improvements could potentially remove these requirements.

You can find jdwp-lib-injector in my github.

Using jdwp-lib-injector
  1. Go to developer options, “Select debug app” and select the debuggable application you want to inject the library into.
  2. In the same screen, enable the “Wait for debugger” option
  3. Start the application you want to inject the library into.
  4. On your shell, run ./jdwp-lib-injector.sh frida-gadget-10.1.5-android-arm64.so or similar.
  5. If all goes well, you’ll see something like the following:
$ ./jdwp-lib-injector.sh frida-gadget-10.1.5-android-arm.so
[**] Android JDWP library injector by @ikoz
[**] Pushing frida-gadget-10.1.5-android-arm.so to /data/local/tmp/
frida-gadget-10.1.5-android-arm.so: 1 file pushed. 25.0 MB/s (12613292 bytes in 0.481s)
[**] Retrieving pid of running JDWP-enabled app
[**] JDWP pid is /var/tmp/jdwpPidFile-1499607477. Will forward tcp:8700 to jdwp:25268
[**] Starting jdwp-shellifier.py to load library
[+] Targeting '127.0.0.1:8700'
[+] Reading settings for 'Dalvik - 1.6.0'
[+] Found Runtime class: id=c84
[+] Found Runtime.getRuntime(): id=70efce4c
[+] Created break event id=20000000
[+] Waiting for an event on 'android.app.Activity.onCreate'
[+] Received matching event from thread 0x1170
[+] getPackageMethod(): 'io.koz.my_debuggable_app'
[*] Copying library from /data/local/tmp/frida-gadget-10.1.5-android-arm.so to /data/data/io.koz.my_debuggable_app/frida-gadget-10.1.5-android-arm.so
[+] Selected payload 'cp /data/local/tmp/frida-gadget-10.1.5-android-arm.so /data/data/io.koz.my_debuggable_app/frida-gadget-10.1.5-android-arm.so'
[+] Command string object created id:1173
[+] Runtime.getRuntime() returned context id:0x1174
[+] found Runtime.exec(): id=70efcf6c
[+] Runtime.exec() successful, retId=1175
[*] Executing Runtime.load(/data/data/io.koz.my_debuggable_app/frida-gadget-10.1.5-android-arm.so)
[+] Runtime.load(/data/data/io.koz.my_debuggable_app/frida-gadget-10.1.5-android-arm.so) probably successful
[*] Library should now be loaded
[!] Command successfully executed

After these steps you should be able to use Frida scripts targeting the app named “Gadget” as usual.

$ frida-ps -U
Waiting for USB device to appear...
  PID  Name
-----  ------
25268  Gadget

mobile security, static & dynamic analysis, automation, payments

London, UK