在ART中,每一个Java方法在虚拟机内部都对应着一个C++对象:Artmethod。对native方法来说在Artmethod中有两个很关键的指针:
一个是entry_point_from_jni:专门用于JNI调用的入口点。当Java层不论是静态注册还是动态注册最终都是这个入口。
再一个就是entry_point_from_quick_compiled_code_:这个就是ART 执行代码的通用入口
我们重点关注entry_point_from_jni

Android 11 起,ART 把 JNI ID 从裸指针改造成了可切换的间接寻址体系,Android 12 沿用了这套设计
kindices是Debuggable App 默认的模式 在这个模式下entry_point_from_jni存放的就不是函数地址 而是一个索引。真正的函数地址被放在了一个全局的间接寻址表中。
kPointer则是Release App 默认的模式 在这里就是直接存储的是函数的内存地址

在一个Native方法被实际调用之前,ART并不知道它对应的C/C++函数在哪里。为了处理这种未决的状态,ART使用了跳板桩。
当类被加载的时候,ArtMethod被创建,他的Native入口点被统一设置为一个名为GetJniDlsymLookupStub的跳板地址。当Java代码第一次调用这个native方法时,执行流跳到了这个Stub。这个Stub的内部逻辑是拿着方法名和签名,去通过dlsym在已加载的.so库中寻找真实的C++函数。找到真实的函数后,Stub会修改ArtMethod的内部指针,将入口点更新为真实的函数地址。这样在下一次再调用这个方法的时候,就直接跳入真实的函数了 不再去走Stub。这被称为延迟绑定。
根据上面的内容不难看出 要实现目标需要解决kindices模式 懒加载 还需要向外提供一个接口 方便随时frida查看绑定状态
Patch 1 关掉 kIndices,保证 data_ 里存的是真实函数指针;Patch 2 在 SetDataPtrSize 这个唯一写入口下钩,抓绑定发生的瞬间;Patch 3 和 Patch 4 提供 dumpNativeBindings 接口,任意时刻把 data_ 读出来并用 dladdr 解析成 so!symbol+offset。
art/runtime/runtime.cc 原本根据 App 是否 debuggable 切换 JNI ID 模式:
Debuggable App 走 kIndices,直接改成一律 kPointer:
这样 data_ 里一定是真实函数指针。
ArtMethod::data_ 的写入路径很多,但最后都会走到 ArtMethod::SetDataPtrSize。在这里做处理,所有绑定行为都能抓到。
[内核课程]《Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。