JNI proguard obfuscation

While researching this exact same problem I came across a solution which I think is reasonable. Unfortunately, the solution does not automatically obfuscate the native Java code and JNI methods as requested, but I still thought it would be worth sharing.

Quote from the source:

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.

Source: Obfuscating the JNI surface layer

I'm still on the hunt for a tool which can obfuscate native Java code and the associated JNI automatically.


JNI supports two ways of binding native methods. The simple one is done by name, and cannot be used for obfuscated class and method names.

The other way involves calling RegisterNatives from JNI_OnLoad() in your library. You msut prepare a table of native methdos for each class for this technique:

java:

package a.b;

public class C {
    public native int nativeMethod();
}

c++:

static jint cnm(JavaEnv*, jobject) {
    return 42;
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env; 
    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_FALSE;
    }

    std::string className = "a/b/C"; // may be changed by ProGuard
    std::string methodName = "nativeMethod"; // may be changed by ProGuard

    jclass cls = env->FindClass(className.c_str());

    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        return JNI_ERR;
    }
    assert(cls);

    JNINativeMethod C_methods[] = {
      { methodName.c_str(), "()I", reinterpret_cast<void*>(&cnm) }
    };

    env->RegisterNatives(cls, C_methods, sizeof(C_methods)/sizeof(C_methods[0]);
    return JNI_VERSION_1_6;
}

This small snippet shows that you can actually replace the names with actual obfuscated ones, generated by ProGuard. The easy way is to trust that ProGuard obfuscation is deterministic, and manually copy the names from the mapfile after the release Java build is over. Automation may require tweaking the gradle build, because normally C++ is compiled before Java, but this is possible.

If the obfuscated method refers to some classes that undergo obfuscation, too - the task becomes more challenging.

The big advantage of RegisterNatives() for obfuscation is that not only it lets you change the class and method names, but also the native method implementations are not visible from outside your native library. E.g. in the example above, cnm is static and is not easily discoverable.


Building upon AndrewJC's answer, if your application is in Kotlin, you can also use the @JvmName annotation instead of using a wrapper method, example:

object Native {
    @JvmName("a")
    private external fun rotateRGBA(rgb: Int, w: Int, h: Int): Int
}

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

Now you can call your JNI method in Kotlin with Native.rotateRGBA(0, 200, 400) but when compiled, Kotlin will automatically rename your method call to: Native.a.