Anti-Debugging Fun With Android ART

Messing with JDWP-related memory structures makes for some nice and stealthy anti-debugging tricks. The first mention I saw of this method was in a 2013 presentation by Bluebox Security, who were granted a software patent on the same trick a year later. The patented method overwrites certain pointers in the Dalvik DvmGlobals structure. Their method however works only on Dalvik, so I decided to look into how one could achieve similar things with ART. Hopefully, not too late before someone patents ART anti-debugging as well (personally, I find the whole idea of patenting anti-debugging tricks laughable, but that's a discussion for another time).

First, let's have a look at the method described by Bluebox. In Dalvik, there's a global variable called gDvm that holds a pointer to the DvmGlobals structureThis makes it easy to directly access global JDWP-related data. For example, gDvm.jdwpState points to a struct that contains global debugging data and function pointers. Manipulating that data causes the JDWP thread to malfunction or crash. Example from the patent application:

JNIEXPORT jboolean JNICALL Java_com_example_disable(JNIENV* env, jobject dontuse ){

  // gDvm==struct DvmGlobals

  gDvm.jdwpState = NULL;

  return JNI_TRUE; 

}

At first glance, this isn't as easy to accomplish in ART. No global symbols pointing to important data structures exist (see source code here and here). We have a global variable named gJdwpState pointing to the main struct JdwpState instance, but unfortunately gJdwpState is a local symbol, so, the linker won't resolve it for us.

Interestingly however, libart.so exports some of the vtables of JDWP-related classes as global symbols. I don't know what's the reason for that and whether it's a normal thing to do (maybe a computer scientist could comment on that?), but it does give us some nice ways of tampering with the behavior of the JDWP thread. Interesting classes include JdwpSocketState and JdwpAdbState - these two handle JDWP connections via network sockets and ADB, respectively.


We can overwrite the method pointers in various ways. Simply nulling them is not a good idea however, as it crashes the process. A good way I found is overwriting the address of "jdwpAdbState::ProcessIncoming()" with the address of "JdwpAdbState::Shutdown()". Note: I suppose that something similar is done in one of the soft tokens I hacked last year, so I am certainly not the first one to come up with that idea (I could probably still patent it though, just to troll everyone else later).

My native implementation of this trick looks as follows:

#include <jni.h>
#include <string>
#include <android/log.h>
#include <dlfcn.h>
#include <sys/mman.h>
#include <jdwp/jdwp.h>

#define log(FMT, ...) __android_log_print(ANDROID_LOG_VERBOSE, "JDWPFun", FMT, ##__VA_ARGS__)

// Vtable structure. Just to make messing around with it more intuitive

struct VT_JdwpAdbState {
    unsigned long x;
    unsigned long y;
    void * JdwpSocketState_destructor;
    void * _JdwpSocketState_destructor;
    void * Accept;
    void * showmanyc;
    void * ShutDown;
    void * ProcessIncoming;
};

extern "C"

JNIEXPORT void JNICALL Java_sg_vantagepoint_jdwptest_MainActivity_JDWPfun(
        JNIEnv *env,
        jobject /* this */) {

    void* lib = dlopen("libart.so", RTLD_NOW);

    if (lib == NULL) {
        log("Error loading libart.so");
        dlerror();
    }else{

        struct VT_JdwpAdbState *vtable = ( struct VT_JdwpAdbState *)dlsym(lib, "_ZTVN3art4JDWP12JdwpAdbStateE");

        if (vtable == 0) {
            log("Couldn't resolve symbol '_ZTVN3art4JDWP12JdwpAdbStateE'.\n");
        }else {

            log("Vtable for JdwpAdbState at: %08x\n", vtable);

            // Let the fun begin!

            unsigned long pagesize = sysconf(_SC_PAGE_SIZE);
            unsigned long page = (unsigned long)vtable & ~(pagesize-1);

            mprotect((void *)page, pagesize, PROT_READ | PROT_WRITE);

            vtable->ProcessIncoming = vtable->ShutDown;

            // Reset permissions & flush cache

            mprotect((void *)page, pagesize, PROT_READ);

        }
    }
}

Once this function has run, any connected Java debugger is disconnected, and any further connection attempts fail. Amazingly, it all happens quietly and without any helpful explanations in the log:

Pyramidal Neuron:~ berndt$ adb jdwp

2926

Pyramidal Neuron:~ berndt$ adb forward tcp:7777 jdwp:2926

Pyramidal Neuron:~ berndt$ jdb -attach localhost:7777

java.io.IOException: handshake failed - connection prematurally closed

    at com.sun.tools.jdi.SocketTransportService.handshake(SocketTransportService.java:136)

    at com.sun.tools.jdi.SocketTransportService.attach(SocketTransportService.java:232)

    at com.sun.tools.jdi.GenericAttachingConnector.attach(GenericAttachingConnector.java:116)

    at com.sun.tools.jdi.SocketAttachingConnector.attach(SocketAttachingConnector.java:90)

    at com.sun.tools.example.debug.tty.VMConnection.attachTarget(VMConnection.java:519)

    at com.sun.tools.example.debug.tty.VMConnection.open(VMConnection.java:328)

    at com.sun.tools.example.debug.tty.Env.init(Env.java:63)

    at com.sun.tools.example.debug.tty.TTY.main(TTY.java:1066)

The trick is quite stealthy - by obuscating and hiding the implementation, you can make it quite a pain to discover (the calls to dlopen and dlsym are the main giveaways). Note that we only handle ADB connections - you'll likely need to patch JdwpSocketState as well to fully prevent Java debugging.

How to Defeat this Trick

There are many ways to bypass this defense. Most likely you'll want to patch the app to prevent the vtable tampering or - if you can't easily do that for whatever reason - repair the vtable after it has been manipulated. We're documenting all the necessary techniques in the OWASP Mobile Testing Guide - plus, you'll also find a lot more anti-debugging tricks there:

About this Article

This article is part of the Mobile Reverse Engineering Unleashed series. Click the blue label on the top of this page to list orther articles in this series.

About the OWASP Mobile Security Testing Guide

I wrote this how-to for the OWASP Mobile Security Testing Guide (MSTG), a manual for testing the security of mobile apps. The MSTG is an open source effort and we welcome contributions and feedback. To discuss and contribute, join the OWASP Mobile Security Project Slack Channel. You can sign up here:

http://owasp.herokuapp.com/

Also, check out the mobile crackmes we developed for the guide!

About the Author

Bernhard Mueller is a full-stack hacker, security researcher, and winner of BlackHat's Pwnie Award.