未使用Java_命名规范时如何查找JNI对应函数指针
核心原理
JNI提供两种native方法绑定机制,未找到符合静态注册规则的导出符号、但方法可正常调用,是因为该so使用了动态注册方案:
- 静态注册依赖导出符号名匹配,要求导出函数严格遵循
Java_包名_类名_方法名的命名规则,虚拟机首次调用native方法时会主动在so导出表中检索对应符号完成绑定。 - 动态注册不需要遵循固定命名规则,也不要求绑定的原生函数对外导出。so被
System.loadLibrary加载时,虚拟机会自动调用so内实现的JNI_OnLoad函数,开发可在该函数中调用JNIEnv提供的RegisterNatives接口,手动传入Java方法名、方法签名、原生函数指针的映射关系,直接完成绑定。被绑定的原生函数甚至可以标记为文件内可见的static函数,导出表中自然不会出现符合静态注册命名规则的符号。
定位目标函数实现的实操方法
- 从
JNI_OnLoad入口跟踪注册流程:在IDA导出表中直接定位JNI_OnLoad函数,这是所有标准动态注册逻辑的固定执行入口,顺着函数调用流找到RegisterNatives调用点即可。 - 解析
RegisterNatives参数获取函数地址:RegisterNatives的函数签名为jint RegisterNatives(JNIEnv* env, jclass clazz, const JNINativeMethod* methods, jint nMethods),其中第三个参数是JNINativeMethod类型的结构体数组,每个结构体依次存储三个字段:Java侧方法名字符串指针、方法签名字符串指针、绑定的原生函数指针。目标方法nativeInitialize是无参void返回的静态方法,对应方法签名为()V,找到方法名字段匹配nativeInitialize、签名字段匹配()V的数组条目,第三个字段存储的值就是目标函数的地址,在IDA中跳转至对应地址即可查看完整实现。 - 混淆/加密场景快速定位:如果so做了字符串加密、控制流混淆,直接梳理注册逻辑效率极低,可以在Java层给
nativeInitialize方法打调试断点,方法触发调用时直接读取当前PC寄存器的值,计算该值相对于so加载基址的偏移,在IDA中跳转至对应偏移即可直接定位到目标函数,无需逆向混淆逻辑。 - 自定义加固场景处理:部分加固壳会绕过标准
RegisterNatives流程,直接修改JNI内部方法表完成绑定,这种场景可以在动态调试环境下hookRegisterNatives函数,打印所有注册的方法名与对应函数指针即可,无需手动逆向壳的绑定逻辑。
提示:静态注册与动态注册支持在同一个so内混合使用,不存在必须统一使用某一种注册方式的限制。只要Java层确认native方法可正常调用、导出表中找不到对应静态符号,就一定走了动态注册或自定义绑定路径,无需反复排查IDA导出表解析是否存在错误。
内容的提问来源于stack exchange,提问作者bhgfas
相关产品推荐
相关产品推荐

