Debug All the Android Things

on 29 November, 2013

29 November, 2013

Recently, I was thinking about interesting ways to bypass root detection, and figured that if I could debug the application over JDWP I could read and make changes to the running state without needing root access.

We previously posted 1 about exploiting the protocol to create a Runtime object and start executing code, and I wanted to see what else was possible using JDWP.

Unfortunately, getting apps that aren’t marked as debuggable to act debuggable isn’t that easy, and if you want to do it without changing the app (and potentially breaking any self checks it does) then sadly it will always require root access.
There’s a few ways to manage it, firstly to run it within the emulator, which has ro.debuggable set to 1 and makes every app that runs debuggable. Several apps can detect the emulator (either via the ro properties, or by the IMEI number, etc), although now thanks to the Setpropx tool 2 it may be possible to set that on a handset (although it requires root to run) and achieve similar effects.

At the time I didn’t know about setpropx, I wanted to mess around with Cydia Substrate 3 instead and I figure the ro.debuggable change is more likely to be detected than Substrate at the moment. I knew that Substrate affected everything above the zygote, but I wasn’t sure if it could hook the zygote itself. Time to find out…

First off I had to find out how debugging got enabled. I could have approached it from the Manifest and track that attribute until it did something in code, but instead I worked back from a hint a friend gave me about what happens when JDWP is enabled. It turns out, it gets piped into adb so that the handsets can be debugged remotely, and the Dalvik VMs communicate/collect their JDWP connections through a socket (called @jdwp_control). Tracking that back in the code eventually got me nowhere, but I had learnt more about what goes on…

The Dalvik VM gets forked/started with debugging enabled (it gets forked, but there’s a wrapper that allows you to effectively set command line parameters), and that’s how it knows to fire up the JDWP thread or not. Taking a different tack, I started grepping through the zygote area of things looking for key phrases like debugging.

That helped a lot, and I whittled it down to a flag “—enable-debugger” that gets sent to the zygote process when it launches. That was in a private method, but got called from a single public method that wrapped it (which is awesome for substrate because if you need to make signifcant changes there’s little else going on to get in the way). The code lived in android.os.Process.start():

public ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
int debugFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
throw new RuntimeException(
"Starting VM process through Zygote failed", ex);
}

So what we wanted was to set debugFlags appropriately, and back in startViaZygote we can see how:

if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
argsForZygote.add("--enable-debugger");
}

So time for our substrate module! We need to hook android.os.Process, and hook the start method. Unfortunately, since Java allows you to overload methods with different arguments, to get the right one you always have to specify the parameter types as part of the method you’re looking for:

public void classLoaded(Class<?> process) {
Method start;
try {
start = process.getMethod("start", String.class, String.class, Integer.TYPE, Integer.TYPE, int[].class, Integer.TYPE, Integer.TYPE,
Integer.TYPE, String.class, String[].class);
} catch (NoSuchMethodException e) {
start = null;
}
...
}

Awesome, we’ve now got the Method, or failed trying. We can check if start is null, and if not we’re good to apply the hook (using the more concise MS.MethodAlteration rather than MS.MethodHook, since we don’t use the old MethodPointer for anything funky).

if (start != null) {
MS.hookMethod(process, start, new MS.MethodAlteration() {
public Object invoked(Object process, Object... args)
throws Throwable
{
args[5] = ((Integer) args[5] | 0x1);
return invoke(process, args);
}
});
}

Inside the invoked method (which is where we hooked the function) we get the object we were called on, here it’s process, and the arguments which are boxed as Objects because Substrate compiling against random types that might’ve been defined in the target code would be a pain.
As it is I just want to tweak an integer, so I can cast that, tweak it, and then put it back into the Object array. Finally I pass the modified flags off to the real function, and… it works!!!

Awesome, so what?

Now we can start doing some interesting bits and pieces with a little heard of tool called jdb. It’s the java debugger, and it can attach to java programs in debugging mode, and let you see what’s going on with them. So now that all apps running (whether the manifest marks them as debuggable are not) have JDWP enabled, we can get to work.

First off, identify the app you want to debug by runnning “adb jdwp”, you’ll get a list of JDWP socket numbers, one for each program. If you then run your program and try “adb jdwp” again, you’ll get one extra number, so that’s the one to remember.

Next you need to forward the JDWP socket to your machine. Remember how I said everything gets collected by adb? Well that comes in useful now, because you can do:

adb forward tcp:12345 jdwp:<socketnum>

And then attach to it with jdb as follows:

jdb -attach localhost:12345

In this example we’ll be debugging the uber-complex HelloBill app, as produced by my good friend Bob. First off we can look at the classes defined using classes (although there’s lots of classes from android in general, even though the App only had about two):

Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable Initializing jdb ...
> classes
...
com.mwr.hellobill.MainActivity
com.mwr.hellobill.MainActivity$1
...

The first class is the main one, and the second class is the anonymous one constructed to handle the clicking. We can figure out a lot of ths from decompiling the dex code, of course. However we got here, we can see the onClick event handler using the methods call:

> methods com.mwr.hellobill.MainActivity$1
...
android.view.View$OnClickListener onClick(android.view.View)

Now we can set a breakpoint, and see what goes on inside the code:

> stop in
> com.mwr.hellobill.MainActivity$1.onClick(android.view.View)
Set breakpoint com.mwr.hellobill.MainActivity$1.onClick(android.view.View)

Now just push the button.

>
Breakpoint hit: "thread=<1> main",
com.mwr.hellobill.MainActivity$1.onClick(), line=25 bci=0

<1> main[1]

- From here we can start inspecting things, so let's dump this (since in Java there should always be a this object to give you your surroundingcontext):

<1> main[1] dump this
this = {
this$0: instance of com.mwr.hellobill.MainActivity(id=830038112008)
}

Ok, so then what’s this$0?

<1> main[1] dump this$0
this$0 = {
btnPushMe: instance of android.widget.Button(id=830038214040)
strHiBill: "Hi Bill!"
...
}

So that can be helpful with watching internal variables and tracking the state of objects whilst they’re in use. So what else can we do?
Well from here, not a great deal, but if we step into the next instruction, we can ask for local variables, and these are more useful:

<1> main[1] step
>
Step completed: "thread=<1> main", android.widget.Toast.makeText(),
line=238 bci=0

<1> main[1] locals
Method arguments:
Local variables:
context = instance of com.mwr.hellobill.MainActivity(id=830038112008)
text = "Hi Bill!"
duration = 1

We can change local variables pretty easily, so with the following, we can make the toast say “Hello, you!” and stay up for 10 seconds!

<1> main[1] set text = "Hello, you!"
text = "Hello, you!" = "Hello, you!"
<1> main[1] set duration = 10
duration = 10 = 10

The output is a little odd, but basically the last value won’t be a repeat if the set failed, it’ll just say null. So that’s great, but our code’s still stuck. Don’t forget to let it run free again afterwards!

<1> main[1] run
>

One last thing. You can also evaluate expressions, and these can involve the output from functions or methods. As such, we can dynamically call methods at will. Push the button again, and this time we won’t step, we’ll stay where we are and check out the locals…

<1> main[1] locals
Method arguments:
Local variables:
v = instance of android.widget.Button(id=830038214040)

Here v is our button, so we can evaluate the following expression to change the text of the button:

<1> main[1] eval v.setText("Hello")
v.setText("Hello") = <void value>

You won’t see anything on the display, since like any normal debugger, the process is paused and so can’t redraw itself, but let it run and watch the button change!

As you can see there’s a lot of things you can do with java debugging, and when combined with reflection, there’s very little you can’t change from the code. JDB is a little cumbersome, but it comes with java, so should be on every android development system around. Have a play with it and see how you can make programs bend to your will… ;)

References

1 https://labs.mwrinfosecurity.com/blog/2011/07/07/debuggable-apps-in-android-market/

2 http://forum.xda-developers.com/showthread.php?p=21628552

3 http://www.cydiasubstrate.com/