JNI proguard混淆

8
我有混淆问题。为了更好地想象:
JAVA代码
class JniTest...

public void test()
{
    //some code
}

public void runJniCode()
{
    //here I call native code
}

本地代码
JNIEXPORT void JNICALL
Java_path_to_class_test(JNIEnv* env, jobject  obj)
{
    //here I call test method from Java

}

一切都很好,直到我想发布混淆版本。Java类的名称(例如JniTest)和此类中的方法test被Proguard重命名为“a”和“a()”(这可能不总是相同的),但在本地代码中,该方法和类的原始名称保持不变,因为它们被硬编码为字符串,例如:
jmethodID mid = env->GetMethodID(cls, "test", "someSignature");

有没有办法动态设置方法名?

不,我不得不在Proguard中更改设置以保留这个方法 :( - cecan89
3个回答

12

在研究这个确切的问题时,我发现了一个我认为是合理的解决方案。不幸的是,该解决方案不能自动混淆所请求的本机Java代码和JNI方法,但我仍然认为值得分享。

来自源的引用:

我在这里介绍一个简单的诀窍,允许混淆JNI层,在Java和本地端重命名方法名称为无意义的名称,同时保持源代码相对可读和可维护,而不影响性能。

让我们考虑一个例子,初始情况:

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);
在上面的示例中,Proguard无法混淆方法名称rotateRGBA,在Java端和本机端仍然可见。解决方案是在源代码中直接使用一个无意义的方法名称,同时要最小限度地破坏代码的可读性和可维护性。 -> 在上述示例中,Proguard无法混淆方法名rotateRGBA,在Java端和本地端仍然可见。解决方案是直接在源代码中使用一个无意义的方法名,同时尽可能地减少对代码的可读性和可维护性的影响。
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);
JNI方法被重命名为一个无意义的a。但Java端的调用被有意义地命名为rotateRGBA方法。Java客户端继续像以前一样调用Native.rotateRGBA(),不会受到重命名的影响。 有趣的是,新的Native.rotateRGBA方法不再是本地方法,因此可以任意由Proguard重命名。这导致在Dalvik和本地端的混淆代码中,rotateRGBA的名称完全消失了。更重要的是,Proguard优化掉了包装方法,从而消除了包装本地调用的(可忽略的)性能影响。 结论:从混淆代码(包括Dalvik字节码和本地库)中消除了JNI方法名,对可读性的影响极小,且没有性能影响。
资料来源:Obfuscating the JNI surface layer 我仍在寻找一种工具,可以自动混淆本地Java代码及其关联的JNI。

2
请在回答中引用链接页面,以防目标页面发生更改。 - mhlester
啊,我在创建原始帖子时考虑过这个问题,但出于尊重原作者和简洁性的考虑,我决定不这样做。然而,如果链接失效,这个答案将变得毫无意义,这个理由是完全有道理的。感谢您的提示! - AndrewJC

6

在AndrewJC的回答基础上,如果你的应用程序是用Kotlin编写的,你也可以使用@JvmName注释来代替使用包装器方法,例如:

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);

现在您可以使用 Native.rotateRGBA(0, 200, 400) 在 Kotlin 中调用 JNI 方法,但是当编译时,Kotlin 将自动将您的方法调用重命名为:Native.a

4
JNI支持两种绑定本地方法的方式。简单的方法是通过名称完成的,不能用于混淆的类和方法名。
另一种方式涉及在库中从JNI_OnLoad()调用RegisterNatives。您必须为每个类准备一个本地方法表来使用此技术: 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;
}

这个小片段展示了你可以用由ProGuard生成的实际混淆名称替换名称。简单的方法是相信ProGuard混淆是确定性的,并在发布Java构建后手动从mapfile复制名称。自动化可能需要调整gradle构建,因为通常是在Java之前编译C ++,但这是可能的。
如果混淆方法引用了一些经过混淆的类-任务变得更具挑战性。
RegisterNatives()用于混淆的最大优势不仅在于它允许您更改类和方法名称,而且本地方法实现也无法从外部库中看到。例如,在上面的示例中,cnm是静态的并且不容易发现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接