(Teaser) Lightly poking Dalvik with a stick

This post is more of a teaser to an upcoming, larger post on messing around with Dalvik.

I think the technique I briefly discuss here is really powerful (think Cydia Substrate but for your own app, i.e. without the root) and would be interesting to my fellow developers.

Also, I’ve spent way too long on this on-and-off and I want to have something to show for it, even if it’s just this post!

There will be a follow-up post with lots of explanations and other techniques (once I develop them) but for now there’s only the one type of change that I’ve bothered to implement.

The goal

Hook arbitrary methods (for now Java only) in the framework and override their behaviour inside your app.

The setup

A simple app in Java, calling into a custom JNI library, replacing the Dalvik in-memory representation of framework classes, so that they execute different code.

The entry point

Overwriting Dalvik in-memory structures is hard enough but actually finding them is equally hard. Or would’ve been, if most of them weren’t exposed in the shape of DvmGlobals and the extern DvmGlobals gDvm in Globals.h.

This symbol is exposed by a library called libdvm.so. The thing is, that library is in your app_proccess on the phone, so you already have that symbol!

Modifying classes

Then, it’s the fun part.

How do we modify classes in the simplest way, though? We don’t want to mess with bytecode (not yet at least), so can we do something by just changing pointers?

Well, the easy part is that we can get all loaded classes from the helpfully named loadedClasses field in DvmGlobals. The downside is that it’s a HashTable, so we can only traverse it linearly or do a lookup on it.

Also, here’s an excerpt of ClassObject, the struct representing classes in the VM:

struct ClassObject: Object {
    ...
    
    /* superclass, or NULL if this is java.lang.Object */
    ClassObject* super;
    
    ...

    /* array of interfaces this class implements directly */
    int interfaceCount;
    ClassObject** interfaces;

    /* static, private, and <init> methods */
    int directMethodCount;
    Method* directMethods;

    /* virtual methods defined in this class; invoked through vtable */
    int virtualMethodCount;
    Method* virtualMethods;

    /*
     * Virtual method table (vtable), for use by "invoke-virtual".  The
     * vtable from the superclass is copied in, and virtual methods from
     * our class either replace those from the super or are appended.
     */
    int vtableCount;
    Method** vtable;
}

Lots of pointers to play with there!

The plan

Instead of modifying bytecode, just change the inheritance chain of framework classes. For example, make everything that inherits from View inherit from MyView instead, so that we can hook into those super.whatever calls.

Let’s do it!

static int forEachReparent(void* data, void* arg) {
	ReparentInfo* opInfo = (ReparentInfo*) arg;
	ClassObject* cls = (ClassObject*) data;

	if(cls->super == opInfo->oldClass && cls != opInfo->newClass) {
		ALOGI("Reparenting %s", cls->descriptor);

		cls->super = opInfo->newClass;
	
		ALOGI("[%s] Switched super", cls->descriptor);
	}

	return 0;
}

That’s not enough though. Due to the way the vtable works (it copies the methods from the superclass, see comment in excerpt above), we may need to fix up the vtables for other classes (not just the direct descendants but down the chain too).

static int forEachFixVtable(void* data, void* arg) {
    ReparentInfo* opInfo = (ReparentInfo*) arg;
    ClassObject* cls = (ClassObject*) data;

    if(isSubclass(cls, opInfo->newClass)){    
        for(int i = 0; i < opInfo->methodInfo.from.size(); i++){
            Method* oldMethod = opInfo->methodInfo.from[i],
                  * newMethod = opInfo->methodInfo.to[i];

            ALOGI("[%s] Check if vtable refers to the %s from %s", cls->descriptor, oldMethod->name, opInfo->oldClass->descriptor);

            for(int j = 0; j < cls->vtableCount; j++){
                Method* method = cls->vtable[j];
                if(cmpMethod(oldMethod, method) && oldMethod->clazz == method->clazz) {
                    cls->vtable[j] = newMethod;
                    ALOGI("[%s] Changed vtable record for %s", cls->descriptor, method->name);
                }
            }
        }
    }
}


The Java code

With the plumbing in place, the rest is even simpler:

public class MyView extends View {
    ... <ctors> ...
    @Override
	public void draw(Canvas canvas) {
		long start = android.os.SystemClock.uptimeMillis();
		canvas.drawColor(0x77996633);
		super.draw(canvas);
		long end = android.os.SystemClock.uptimeMillis();
	
		Log.i("libdalvikplay", "View " + this.getClass().getSimpleName() + " drew in " + (end-start) + "ms");
	}
}

public class MyViewGroup extends ViewGroup {
    ... <ctors> ...
	@Override
	protected void dispatchDraw(Canvas canvas) {
		long start = android.os.SystemClock.uptimeMillis();
		super.dispatchDraw(canvas);
		long end = android.os.SystemClock.uptimeMillis();
	
		Log.i("libdalvikplay", "ViewGroup " + this.getClass().getSimpleName() + " dispatchDrew in " + (end-start) + "ms");
	}
}


The result


Screenshot of test app

 I/libdalvikplay(23017): ViewGroup RelativeLayout dispatchDrew in 1ms
 I/libdalvikplay(23017): ViewGroup FrameLayout dispatchDrew in 2ms
 I/libdalvikplay(23017): View ImageView drew in 1ms
 I/libdalvikplay(23017): ViewGroup HomeView dispatchDrew in 1ms
 I/libdalvikplay(23017): View TextView drew in 0ms
 I/libdalvikplay(23017): ViewGroup LinearLayout dispatchDrew in 1ms
 I/libdalvikplay(23017): ViewGroup LinearLayout dispatchDrew in 1ms
 I/libdalvikplay(23017): ViewGroup LinearLayout dispatchDrew in 2ms
 I/libdalvikplay(23017): View LinearLayout drew in 2ms
 I/libdalvikplay(23017): View OverflowMenuButton drew in 0ms
 I/libdalvikplay(23017): ViewGroup ActionMenuView dispatchDrew in 0ms
 I/libdalvikplay(23017): View ActionMenuView drew in 1ms
 I/libdalvikplay(23017): ViewGroup ActionBarView dispatchDrew in 3ms
 I/libdalvikplay(23017): ViewGroup ActionBarContainer dispatchDrew in 3ms
 I/libdalvikplay(23017): View ActionBarContainer drew in 3ms
 I/libdalvikplay(23017): ViewGroup ActionBarContainer drew in 3ms
 I/libdalvikplay(23017): ViewGroup LinearLayout dispatchDrew in 3ms
 I/libdalvikplay(23017): ViewGroup ActionBarOverlayLayout dispatchDrew in 5ms
 I/libdalvikplay(23017): ViewGroup DecorView dispatchDrew in 5ms
 I/libdalvikplay(23017): View DecorView drew in 5ms
 I/libdalvikplay(23017): ViewGroup DecorView drew in 5ms

It’s not much but it’s pretty cool, right?


← The emulator is dead, long live the emulator! Views, camera, action!_→