根据官方文档,Frida 17之后的版本中, GumJs 运行时不再捆绑 bridges(例如 frida-java-bridge、frida-objc-bridge, frida-swift-bridge)。因此这篇Java Hook原理分析参考的项目源码在frida-java-bridge中。
以如下例子进行原理分析
Adapter["doAdapter"]获取到的是methodPrototype对象,然后将自定义的hook函数赋值给implementation字段,使用到的是implementation的set()方法来安装Hook(对应android.js 1697行处)。
set() 首先校验目标方法是否是构造方法,此方法不通过当前路径实现。然后检查目标方法是否已经被hook,如果被hook过了,则撤销之前的hook逻辑。最后,如果存在新的hook逻辑,则调用implement()方法将用户定义的js hook逻辑封装成native函数,并调用mangler.replace()方法对目标方法进行hook。
该函数主要是对用户定义的hook逻辑封装成native函数。其中makeMethodImplementation() 的作用是把一堆上下文参数封进闭包,生成一个符合 NativeCallback 签名的入口函数,其内部调用了handleMethodInvocation()方法。
当 Java 调用某个被 Hook 的方法时,负责将 JNI 的数据结构转换成 js 对象,执行用户定义的 js hook 代码,然后再将结果转换成回 JNI 的变量类型。
这里分析andriod平台下的java hook,因此我们分析lib\android.js下的ArtMethodMangler,该类的初始化函数如下:
对应ArtMethodMangler.replace()方法,位置在lib\android.js3707行处。接下来进行拆解分析。
这里主要是获取原函数的ArtMethod结构体中部分字段的值,具体为jniCode()、accessFlags()、quickCode()、interpreterCode(方法解释执行入口),对于fetchArtMethod方法的详细分析见fetchArtMethod。
这部分主要检测是否进行了xposed hook,如果进行了,则借助xposed hook的信息获取原方法的ArtMethod指针,并重新调用fetchArtMethod获取原函数的ArtMethod结构体中部分字段的值。
调用cloneArtMethod()复制原函数的ArtMethod,后续的修改都在这个副本上进行的。之后调用patchArtMethod()方法对复制出来的ArtMethod中的jniCode、accessFlags、quickCode、interpreterCode进行修复,具体见patchArtMethod。这里分析一下入参:
accessFlags:
总结一下就是 把方法标记为普通的 native 方法,同时禁用 ART 的特殊快速路径优化。
quickCode
原本是quick模式入口,现修改成api.artClassLinker.quickGenericJniTrampoline,代表的是ClassLinker的quick_generic_jni_trampoline_字段。也就是说,从 quick 路径进入这个方法时,不直接执行原来的 quick compiled code,而是进入 ART 的通用 JNI 调用桥,再由 JNI 桥读取 jniCode 并调用native化的hook代码。
interpreterCode
原本是解释器模式入口,现修改成api.artInterpreterToCompiledCodeBridge,是从解释器模式转成机器码模式的入口。这样解释器执行该方法时,会桥接到 compiled/JNI 路径,最终进入 generic JNI trampoline 和 native化的hook代码。
这里对原方法的accessFlags进行了处理,禁用了快速调用路径标志,如果原方法不是 native,还需要移除访问检查跳过标志,最后加上kAccCompileDontBother,告诉 ART 不要尝试 JIT/AOT 编译这个方法。
如果原方法是解释执行,且使用了Nterp,那么quickCode就会替换成art_quick_to_interpreter_bridge,强制跳出解释器模式,并改用机器码模式。
如果原方法的quickCode是 ART Quick 编译路径的入口,也就是说原方法已经是机器码模式了,那么就需要通过ArtQuickCodeInterceptor对机器码进行patch,这部分见ArtQuickCodeInterceptor.activate。
最后是收尾工作
getArtMethodSpec返回的是ArtMethod结构体布局描述,返回的结构是
由于Android版本的差异,有些字段就不存在(例如interpreterCode),于是借助reduce过滤出存在的字段并获取对应字段的值。
获取jniCode、accessFlags、quickCode、interpreterCode字段,并进行修正,设置为相应的入参值。
该函数首先调用_allocateTrampoline()方法分片trampoline的内存空间、确定用于重定向的字节大小,以及获取空闲寄存器。以Arm64为例,接下来调用writeArtQuickCodeReplacementTrampolineArm64()函数编写trampoline,返回用于重定向的字节大小,然后保存原函数quickCode入口处相应字节大小的指令,用于后续恢复。最后调用writeArtQuickCodePrologueArm64()函数修改quickCode入口使其跳转到编写好的trampoline处。
首先保存部分寄存器,然后调用findReplacementFromQuickCode方法查找是否存在replacement实现,如果有,则返回相应地址,否则为NULL(0),这里分情况进行讨论分析:
位置在lib\android.js 1925行处。
主要是根据传入的原函数methodId 查找对应的replacement methodId,返回 NULL 表示应调用原始方法,否则返回指向replacement ArtMethod 的指针。
这里做了栈检查,这样来自hook逻辑中的原函数调用就不会是无限递归,而是返回NULL,执行原函数逻辑。这就是我们在hook A函数,并能够在hook逻辑内部调用原始A函数的原因。
这里则是根据可用重定位空间为8字节或16字节,设置不同的跳转指令。
Frida 在进行 Java Hook时,首先会将用户编写的 JS 函数包装成 NativeCallback,使其具备 JNI native 函数的调用形式。之后的hook工作都围绕 ArtMethod 做修改。它会读取目标方法的 jniCode、accessFlags、quickCode、interpreterCode 字段,复制出一个 ArtMethod副本,用于replacement method,并把这个副本改造成 native 方法:jniCode 指向前面生成的 NativeCallback,quickCode 指向 ART 的通用 JNI trampoline,interpreterCode 指向解释器到编译代码的桥接入口。这样无论方法从解释器路径还是 quick compiled code 路径进入,最终都能转到 Frida 的 replacement 实现。需要额外注意的是,对于已经编译成机器码的方法,Frida 还会 patch 原始机器码入口,即修改原始机器码前 8/16 字节为跳转到 trampoline 的指令,trampoline 中再通过 findReplacementFromQuickCode() 查询当前方法是否存在 replacement。如果存在,就跳转到 replacement method 入口;如果不存在,或者当前调用来自 Hook 逻辑内部对原函数的调用,则恢复执行原方法。
参考:
7daK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6h3g2H3N6$3W2C8K9g2)9J5k6h3y4G2L8g2)9J5c8X3k6J5K9h3c8S2i4K6u0r3k6Y4u0A6k6r3q4Q4x3X3c8B7j5i4k6S2i4K6u0V1j5Y4u0A6k6r3N6W2i4K6u0r3y4q4)9J5k6e0q4Q4x3X3c8E0k6i4c8Z5L8$3c8Q4x3X3c8Z5L8$3!0C8K9h3&6Y4i4K6u0V1j5X3q4K6K9h3y4K6
[原创]源码简析之ArtMethod结构与涉及技术介绍-Android安全-看雪安全社区|专业技术交流与安全研究论坛
Frida Internal - Part 3: Java Bridge 与 ART hook - 有价值炮灰
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2026-5-9 15:08
被gal2xy编辑
,原因: