一、最标准 & 官方的动态注册方式(JNI_RegisterNatives)
1️⃣ JNI_OnLoad + RegisterNatives(最常见)
static JNINativeMethod gMethods[] = {
{"nativeAdd", "(II)I", (void *)native_add},
{"nativeInit", "()V", (void *)native_init},
};
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
vm->GetEnv((void **)&env, JNI_VERSION_1_6);
jclass clazz = env->FindClass("com/example/Test");
env->RegisterNatives(clazz, gMethods,
sizeof(gMethods)/sizeof(gMethods[0]));
return JNI_VERSION_1_6;
}
✅ 特点
-
方法名、签名、函数指针三元组
-
字符串 + 函数指针同时存在
-
非常适合静态分析 & Frida/Unidbg
🔍 逆向切入点
-
JNI_OnLoad -
RegisterNatives -
JNINativeMethod[]表
2️⃣ 多 Class / 分模块注册
registerClass(env, "com/a/A", methodsA);
registerClass(env, "com/b/B", methodsB);
✅ 特点
-
一个 so 注册几十上百个类
-
常见于大型 SDK(腾讯、阿里)
🔍 逆向技巧
-
跟
FindClass -
跟字符串池里的 Java 类路径
二、半隐藏型:JNI 函数指针二次封装
3️⃣ 封装 RegisterNatives(初级混淆)
int my_register(JNIEnv *env, const char *cls, JNINativeMethod *m, int n) { jclass c = env->FindClass(cls); return env->RegisterNatives(c, m, n); }
甚至:
(*(env->functions + 215))(env, clazz, methods, n);
✅ 特点
-
IDA 看不到
RegisterNatives符号 -
靠 JNI function table 调用
🔍 逆向
-
识别
JNIEnv->functions -
Frida hook
RegisterNatives的 真实地址
三、反编译最烦的:字符串 / 表结构混淆
4️⃣ JNINativeMethod 表动态生成
JNINativeMethod *m = malloc(sizeof(JNINativeMethod) * n);
m[i].name = decrypt("xxx");
m[i].signature = decrypt("xxx");
m[i].fnPtr = calc_func(i);
特点
-
没有静态表
-
name/signature 运行时解密
🔍 逆向
-
跟 解密函数
-
Hook
RegisterNatives直接 dump
5️⃣ 函数指针运行时计算(offset / hash)
m[i].fnPtr = base + offset[i];
或者:
fn = (void *)((char *)so_base + hash_table[name]);
✅ 特点
-
函数地址无法静态交叉引用
-
常见于加固 SDK
🔍
-
先拿 so base
-
动态调试时下断点
四、进阶:完全不走 RegisterNatives 的骚操作
6️⃣ 利用 JNI 函数表劫持(少见但狠)
env->functions->CallVoidMethod = my_call;
或者修改 ART 内部结构
⚠️ 极少见
-
强依赖 Android 版本
-
高风险但极难分析
7️⃣ 利用 dlsym + 手动调用 Java 方法
void *handle = dlopen("libart.so", RTLD_NOW);
void *sym = dlsym(handle, "_ZN3art...");
直接调用 ART 内部接口
⚠️
-
非 JNI 官方
-
加固厂常用(某些 Dex2C / VMProtect)
五、结合 Java 层的"假动态注册"
8️⃣ Java 层 native 方法名动态生成
static {
String m = decrypt("native_" + x);
System.loadLibrary("xxx");
register(m);
}
Java 层看不到真实 native 名
🔍
-
反 Java 混淆
-
或直接 Hook JNI 层
六、现代加固常见组合拳(真实项目)
实战中经常是 多种方式叠加
| 技术 | 是否常见 |
|---|---|
| JNI_OnLoad + RegisterNatives | ⭐⭐⭐⭐⭐ |
| 字符串加密 | ⭐⭐⭐⭐ |
| 函数指针偏移 | ⭐⭐⭐ |
| 多 so 分散注册 | ⭐⭐⭐ |
| Dex2C + Native 调度 | ⭐⭐⭐⭐ |
| ART 私有 API | ⭐⭐ |