Skip to content

Android: obfuscating the JNI surface layer

Proguard is a popular Java obfuscator. It makes the compiled bytecode (either JVM or Dalvik) harder to understand when decompiled (obfuscation), and also smaller by class/method renaming and optimization.

There are two aspects of the Java source that are not affected by Proguard. One are the classes that interface with Android, basically the Activities, Views, Services, etc, which are referenced by name in the AndroidManifest or in the resource files. Because they are referenced by class name and use special method names (e.g. onCreate), Proguard won’t obfuscate such classes.

The other part that is not touched by Proguard are the native method names. The JNI methods keep their names unchanged, and these names are visible, upon decompilation, in two places: in the Dalvik bytecode, and in the native library (.so).

The fact that this JNI layer interfacing Java with native code is not obfuscated is a significant weakness — for example it allows an attacker to extract and reuse the native library (.so) unchanged, as the JNI interface is clearly exposed.

I present here a simple trick which allows obfuscation of the JNI layer, renaming the method names to meaningless names on both the Java and native side, while keeping the source code relatively readable and maintainable and without affecting performance.

Let’s consider an example, initial situation:

class Native {
    native static int rotateRGBA(int rgb, int w, int h);
}

extern "C" int Java_pakage_Native_rotateRGBA(JNIEnv *env, jclass, int rgb, int w, int h);

In the example above Proguard can’t obfuscate the method name rotateRGBA, which remains visible on the Java side and on the native side.

The solution is to use directly a meaningless method name in the source, while taking care to minimally disrupt the readability and maintainability of the code.

class Native {
    private native static int a(int rgb, int w, int h); //rotateRGBA

    static int rotateRGBA(int rgb, int w, int h) {
        return a(rgb, w, h);
    }
}

// rotateRGBA
extern "C" int Java_pakage_Native_a(JNIEnv *env, jclass, int rgb, int w, int h);

The JNI method is renamed to a meaningless a. But the call on the Java side is wrapped by the meaningfully named method rotateRGBA. The Java clients continue to invoke Native.rotateRGBA() as before, without being affected at all by the rename.

What is interesting is that the new Native.rotateRGBA method is not native anymore, and thus can be renamed by Proguard at will. The result is that the name rotateRGBA completely disappears from the obfuscated code, on both Dalvik and native side. What’s more, Proguard optimizes away the wrapper method, thus removing the (negligible) performance impact of wrapping the native call.

Conclusion: eliminated the JNI method name from the obfuscated code (both Dalvik bytecode and native library), with minimal impact to readability and no performance impact.

Post a Comment

Your email is never published nor shared. Required fields are marked *