JNI Hook
Frida
动态分析
JNI 动态 Hook 路径:别只看 Java.perform,要盯桥两端的数据变化
做 Android 动态分析时,很多人一看到 JNI,第一反应还是停留在 Java 层:Java.perform、改参数、看返回值。这个入口当然有用,但如果你的目标已经跨到 native 层,光盯 Java 往往信息不够。真正高价值的动态 hook,通常要把桥两端一起看:Java 侧传了什么,native 侧又把它变成了什么。
先说结论:JNI hook 的核心不是“在哪层 hook”,而是“哪一层最能减少未知”
同一个目标,可能有三种不同的动态切入点:
- Java 调用点
- JNI 桥接层
- native 核心函数
关键不是三层都上,而是先判断:你现在到底缺哪块信息。
动态 hook 不是为了“我能挂上”,而是为了最小代价回答最关键的问题。
第一层:Java 调用点适合回答什么?
- 谁在什么时候调用 native
- Java 语义上的参数是什么
- 调用前有没有业务层拼装
- 返回值如何影响上层流程
如果你还没搞清调用场景,Java 层通常是最便宜的入口。
第二层:JNI 桥接层适合回答什么?
- jstring/jbyteArray 最终变成了什么
- Java 对象字段有没有被拆出来重组
- 参数边界、长度、编码如何变化
这层最大的价值,在于它能帮你把“Java 世界的语义”和“native 世界的数据结构”对上。
第三层:native 核心层适合回答什么?
- 真正参与算法/校验/签名的输入是什么
- 关键分支怎么走
- 输出是在哪里被产出的
如果你的目标是加密、签名、校验放行、设备指纹这类核心逻辑,最终往往还是要落到这里。
什么时候只挂 Java 不够?
典型信号包括:
- Java 参数看起来正常,但结果就是解释不通
- native 内部还有二次拼装或派生
- 返回值只是结果的一部分,真正副作用发生在 native 内部
- Java 层只是一层包装壳
这种时候如果还只盯 Java,很容易一直在表层打转。
最值钱的动态问题通常是什么?
- Java 传下来的原始值是什么
- 桥接后 native 实际拿到的值是什么
- 真正进入核心处理函数的又是哪一份
把这三个问题串起来,JNI 动态链路通常就会清楚很多。
最容易犯的错误
- 挂到 Java 层就以为自己已经拿到真相
- 直接冲 native 核心,结果丢了业务上下文
- 看到 bridge 层代码就嫌烦,不做参数对齐
- 日志很多,但没有把三层关系串起来
我更推荐的 JNI 动态 hook 顺序
- 先挂 Java 调用点,确认场景和参数语义
- 再挂 JNI 桥接层,对齐参数转换
- 最后挂 native 核心层,抓真实输入输出
这条路线的底层逻辑是:先保住语义,再下钻到实现。
结尾
JNI 动态 hook 最怕的不是复杂,而是断层。只盯 Java,你容易看不到真实处理;只盯 native,你又容易失去业务语义。真正稳的做法,是把桥两端的数据变化连起来。只要这条线连通,很多本来像黑盒的 native 逻辑,都会开始变得可解释。