6

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

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

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

4

3 回答 3

10

在研究这个完全相同的问题时,我遇到了一个我认为合理的解决方案。不幸的是,该解决方案不会按照要求自动混淆原生 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 方法名称,对可读性的影响最小,对性能没有影响。

来源:混淆 JNI 表层

我仍在寻找一种可以自动混淆原生 Java 代码和相关 JNI 的工具。

于 2014-01-20T20:10:05.377 回答
4

基于 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 中调用你的 JNI 方法,Native.rotateRGBA(0, 200, 400)但是编译后,Kotlin 会自动将你的方法调用重命名为: Native.a

于 2020-04-05T16:16:53.617 回答
3

JNI 支持两种绑定本地方法的方式。简单的是按名称完成的,不能用于混淆的类和方法名称。

另一种方法是从您的库中调用RegisterNatives 。JNI_OnLoad()您必须为此技术为每个类准备一个本地方法表:

爪哇:

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 构建结束后从映射文件中手动复制名称。自动化可能需要调整 gradle 构建,因为通常 C++ 在 Java 之前编译,但这是可能的。

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

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

于 2018-11-06T11:18:52.210 回答