JNI 动态注册与静态注册详解


一、JNI 基础概念

JNI(Java Native Interface)是 Java 与 Native 代码(C/C++)交互的桥梁。当 Java 调用 Native 方法时,需要将 Java 方法与 Native 函数绑定,这种绑定分为 静态注册动态注册


二、静态注册

1. 定义

静态注册是 通过固定命名规则 自动将 Java 方法与 Native 函数绑定。开发者只需按照规则命名 Native 函数,系统自动完成映射。

2. 命名规则

Java 方法 com.example.MyClass.myMethod 对应的 Native 函数名:

1
Java_com_example_MyClass_myMethod(JNIEnv* env, jobject obj, ...)
  • 包名中的 . 替换为 _
  • 方法名直接拼接。

3. 示例

(1) Java 代码

1
2
3
4
package com.example;
public class NativeLib {
public native static String staticMethod(String input);
}

(2) C/C++ 代码

1
2
3
4
5
6
7
8
9
#include <jni.h>

// 静态注册的函数
JNIEXPORT jstring JNICALL
Java_com_example_NativeLib_staticMethod(JNIEnv* env, jclass clazz, jstring input) {
const char* str = env->GetStringUTFChars(input, nullptr);
env->ReleaseStringUTFChars(input, str);
return env->NewStringUTF("Static Registration: Hello from C++");
}

4. 特点

  • 优点:简单易用,无需手动注册。
  • 缺点
    • 函数名冗长,可读性差。
    • 无法隐藏函数符号(逆向时易被发现)。

三、动态注册

1. 定义

动态注册是 通过 JNI_OnLoad 函数手动绑定 Java 方法与 Native 函数。开发者需调用 RegisterNatives 方法,明确指定 Java 方法与 Native 函数的对应关系。

2. 核心步骤

  1. 实现 JNI_OnLoad 函数,在库加载时执行注册。
  2. 定义 JNINativeMethod 结构体数组,描述 Java 方法与 Native 函数的映射。
  3. 调用 env->RegisterNatives 完成注册。

3. 示例

(1) Java 代码

1
2
3
4
package com.example;
public class NativeLib {
public native static String dynamicMethod(String input);
}

(2) C/C++ 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <jni.h>

// 动态注册的函数
jstring dynamic_method(JNIEnv* env, jclass clazz, jstring input) {
const char* str = env->GetStringUTFChars(input, nullptr);
env->ReleaseStringUTFChars(input, str);
return env->NewStringUTF("Dynamic Registration: Hello from C++");
}

// 定义方法映射表
static JNINativeMethod methods[] = {
{"dynamicMethod", "(Ljava/lang/String;)Ljava/lang/String;", (void*)dynamic_method}
};

// JNI_OnLoad 入口
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}

// 注册 Native 方法
jclass clazz = env->FindClass("com/example/NativeLib");
if (env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof(methods[0])) < 0) {
return JNI_ERR;
}

return JNI_VERSION_1_6;
}

4. 关键数据结构

1
2
3
4
5
typedef struct {
const char* name; // Java 方法名
const char* signature; // 方法签名
void* fnPtr; // Native 函数指针
} JNINativeMethod;

5. 特点

  • 优点
    • 函数名自由定义,隐藏实现细节。
    • 灵活性高,适合大型项目。
  • 缺点
    • 需要手动维护映射关系。
    • 逆向时更难定位 Native 函数。

四、对比总结

特性 静态注册 动态注册
绑定方式 自动通过命名规则绑定 手动通过 RegisterNatives 绑定
函数名 冗长(包名+类名+方法名) 可自定义
逆向难度 易(函数名暴露) 难(需分析 JNI_OnLoad
灵活性
适用场景 简单场景 复杂项目或需隐藏实现的场景

五、实战技巧

1. 静态注册逆向分析

  • 特征:函数名类似 Java_com_example_Class_method
  • IDA 操作:直接搜索函数名定位逻辑。

2. 动态注册逆向分析

  • 步骤

    1. 查找 JNI_OnLoad 函数(入口点)。
    2. 分析 RegisterNatives 调用,找到 JNINativeMethod 数组。
    3. 根据数组中的方法签名定位 Native 函数。
  • IDA 示例

    1
    2
    3
    4
    5
    6
    7
    // 反编译 JNI_OnLoad 后可能看到的代码
    v6 = (*a1)->FindClass(a1, "com/example/NativeLib");
    v7 = (struct JNINativeMethod *)malloc(0x18uLL);
    v7->name = "dynamicMethod";
    v7->signature = "(Ljava/lang/String;)Ljava/lang/String;";
    v7->fnPtr = (void *)dynamic_method;
    (*a1)->RegisterNatives(a1, v6, v7, 1LL);

3. 动态注册的 Hook 方法(Frida)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Hook RegisterNatives 函数,打印动态注册信息
Interceptor.attach(
Module.findExportByName("libart.so", "_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi"),
{
onEnter: function (args) {
var env = args[0];
var clazz = args[1];
var methods = args[2];
var methodCount = args[3].toInt32();

// 遍历方法表
for (var i = 0; i < methodCount; i++) {
var method = methods.add(i * Process.pointerSize * 3);
var name = method.readPointer().readCString();
var signature = method.add(Process.pointerSize).readPointer().readCString();
var fnPtr = method.add(Process.pointerSize * 2).readPointer();
console.log(`[+] 动态注册方法: ${name}, 签名: ${signature}, 函数地址: ${fnPtr}`);
}
}
}
);

六、开发建议

  1. 选择注册方式
    • 小型项目或快速原型:静态注册。
    • 大型项目或需隐藏实现:动态注册。
  2. 安全加固
    • 动态注册 + 代码混淆 + 符号表剥离,增加逆向难度。
  3. 调试技巧
    • 使用 adb logcat 查看 JNI 错误日志(如 JNI_OnLoad 失败)。

总结

  • 静态注册:简单直接,适合快速开发,但安全性低。
  • 动态注册:灵活隐蔽,适合复杂项目,需额外维护成本。
    掌握两者的区别和使用场景,能更好地设计 Native 模块并应对逆向分析。

参考链接

Android JNI静态注册和动态注册方法详解_jni动态注册-CSDN博客