android native 中的函数动态注册方式总结

一、最标准 & 官方的动态注册方式(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 ⭐⭐

七、逆向/Hook 统一破局思路(很重要)

下一篇分析

相关推荐
侠客行03172 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪2 小时前
深入浅出LangChain4J
java·langchain·llm
较劲男子汉4 小时前
CANN Runtime零拷贝传输技术源码实战 彻底打通Host与Device的数据传输壁垒
运维·服务器·数据库·cann
老毛肚4 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
wypywyp4 小时前
8. ubuntu 虚拟机 linux 服务器 TCP/IP 概念辨析
linux·服务器·ubuntu
风流倜傥唐伯虎5 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
Doro再努力5 小时前
【Linux操作系统10】Makefile深度解析:从依赖推导到有效编译
android·linux·运维·服务器·编辑器·vim
senijusene5 小时前
Linux软件编程:IO编程,标准IO(1)
linux·运维·服务器
不像程序员的程序媛5 小时前
Nginx日志切分
服务器·前端·nginx
Yvonne爱编码5 小时前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python