【问题标题】:JNI proguard obfuscationJNI proguard 混淆
【发布时间】:2013-09-30 23:56:29
【问题描述】:

我有混淆的问题。为了更好的想象:

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 中的设置才能保留此方法:(

标签: android java-native-interface native obfuscation


【解决方案1】:

在研究这个完全相同的问题时,我遇到了一个我认为合理的解决方案。不幸的是,该解决方案不会按照要求自动混淆原生 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 端和本机端仍然可见。

解决方案是在源代码中直接使用无意义的方法名称,同时注意尽量减少代码的可读性和可维护性。

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】:

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

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

【讨论】:

    【解决方案3】:

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

    这个小 sn-p 表明您实际上可以将名称替换为由 ProGuard 生成的实际混淆名称。简单的方法是相信 ProGuard 混淆是确定性的,并在发布 Java 构建结束后从映射文件中手动复制名称。自动化可能需要调整 gradle 构建,因为通常 C++ 在 Java 之前编译,但这是可能的。

    如果混淆的方法也引用了一些经过混淆的类 - 任务变得更具挑战性。

    RegisterNatives() 用于混淆的最大优势在于,它不仅可以让您更改类和方法名称,而且本地方法的实现在您的本地库之外是不可见的。例如。在上面的例子中,cnmstatic,不容易被发现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多