论坛里偶尔能看到「SO 在 PC 模拟、JNI 丢真机跑」的说法,当时没看太懂中间怎么接。后来手写补环境补烦了,自己撸了一版。下面先简单说下整体思路,再写实现细节和踩坑。分工:PC 跑 SO,手机跑该真算的那部分 Java对象跨进程:只传编号,不传指针举个短例子(SO 遍历 TreeMap):要点:PC 的 DvmObject 多半是「指向手机的遥控器」;手机 Handle 表里才是 TreeMap、Iterator 本体。同一条链里编号对得上就行,PC 的 1003 和手机的 1003 不要求是同一个内存对象。手机算完之后怎么传回来?按返回值类型来一次 JNI = 一次 JSON 往返。手机 invoke 完,按 JNI 返回值是什么类型,在 JSON 里带不同的 result,不是固定只传 Handle:对象还要链式调用(next 再 getKey),就靠 Handle 来回指;字符串、字节数组 把值传回来就行,SO 在 Native 里直接用,通常不必再登记成 Handle。分流:不是全部 JNI 都上网本质都是:PC 截 JNI → 发请求 → 真机 ART 执行 → 结果回传。差别在执行端放哪、要少写多少补环境。Router 在架构里干什么(和上面对应)Router 本身不算 JNI、也不连手机,只做三件事:分拣之后是两条执行链,互斥、只走一条:所以架构图里「代理 → Router → 分叉」就是:先进 Router 再决定本地算还是手机算;控制台 [local] / [remote] 就是这次走了哪条叉。Native 指令只在 PC;需要真 JVM 的 JNI 才走右边那条叉。可以把它想成快递分拣中心:SO 每次调 JNI,都先到 Router,Router 不自己算,只负责「这单该本地送还是发手机」。Unidbg 原来直接调 AbstractJni。我在外面包了一层动态代理:任何 callObjectMethod、findClass……先进代理,代理只做一件事:这样不用改 Unidbg 源码,用户还是 vm.setJni(this)。Router 收到「方法名 + 参数」,压进一个小结构:op 比如 "callObjectMethodV",args 里是 [vm, dvmObject, signature, varArg]。
为什么要打包? 后面发 JSON 时,整包 JniCall 转 args 就行,Router 本身不关心本地还是远程。真正「分发」发生在 HybridJniExecutor:路由策略怎么判? 从参数里找出 JNI 的 signature 字符串(形如 java/util/Iterator->next()Ljava/lang/Object;):判完以后,两条路:本地路:LocalJniExecutor 用反射找到 Jni 接口上对应方法,调原来的实现类——和没加转发前一模一样,补环境 switch 还写在这。远程路:RemoteJniExecutor 把 JniCall 编成 JSON,Socket 发到 8765,等手机回一行 JSON,再解码塞回 Unidbg。外面再包一层「打印执行器」,控制台就会看到:[remote] / [local] 就是 Router 分发结果的直观体现。一句话:Router = 统一进门 → 打包成 JniCall → 按类名前缀分拣 → 本地反射 or 远程 JSON。第 1 步:先想直接改 AbstractJni —— 不行最开始很直接的想法,把 Unidbg 里每个 JNI override 改成一行转发,例如:findClass、callBooleanMethod 等几十个方法都要这么改。能跑,但:后来改成 不动 AbstractJni,外面用动态代理包 Jni 接口,再只做 Router + 本地反射,跑通 SO 确认和改之前结果一致。第 2 步:定 JSON 协议 + PC 端假 Agent 在 PC 再起一个监听 8765 的小程序,用真 Java 处理 TreeMap/Iterator,跟未来手机 Agent 同一套 JSON。协议定成 一行 JSON 一问一答,方便 log 里直接 grep。遇到的问题:Unidbg 回调名带 V,Agent 只认不带 V 的 opUnidbg 的 Jni 接口里,处理可变参数的方法名末尾会多一个 **V**(表示 VarArg),比如 callObjectMethodV、callBooleanMethodV。我在 PC 侧用动态代理转发时,method.getName() 拿到的就是这个带 V 的名字,原样写进 JSON 的 op 字段。假 Agent 分发器是按「不带 V 的标准名」写的 switch,两边对不上:表现就是:Socket 通了、JSON 也 parse 成功,但 Agent 回 ok:false,或直接抛 unknown op: callObjectMethodV。跟参数翻译无关,纯粹是 op 名字不一致。PC 发 JSON 前,把末尾的 V 剥掉即可:这样 PC 和 Agent 只维护一套 op 分支,不用为每个 xxxV 再复制一份。第 3 步:参数翻译(最难,具体代码 AI 帮着改了几轮) Unidbg 里 SO 调 JNI 时,参数不是普通 Java 对象,而是一堆框架类型:DvmObject、VarArg、VaList、StringObject……PC 要把这些「翻译成 JSON 能发的形式」再发出去,手机算完还要「翻译回来」塞给 Unidbg。我主要理规则(什么发 HANDLE、什么发 MAP),具体反射和编码 AI 帮着改了几轮。第 4 步:分流策略 —— 为什么「全扔手机」不行 跑通 PC 假 Agent 之后,很自然地想:既然真机 ART 算得准,干脆所有 JNI 都发手机,PC 一个 switch 都不用写。试下来不行,原因是:独立 Agent 是一个自己安装的 APK,不在目标 App 进程里。所以分流不能是「能发就发」,而是 按类名前缀划边界:第 5 步:真机 Agent APK 前面 PC 假 Agent 就是在电脑上开了一个「小型 Java 服务」,监听 8765,收到 JSON 就调 TreeMap、回 JSON。第五步做的事很简单:把这段逻辑原样搬进手机里。可以把它想成在手机上装了一个「专职接电话的 App」:第 6 步:删 java/ 补环境
TreeMap/Iterator 的 override 全删,只留 vm.setJni(this) 和 android/* 伪装,签名和纯 PC 跑一致才算通。本地处理的是「Router 分拣错了或不该上网的」,最终仍进本地 switch 补环境。壳的类型不能设成占位符类本身,必须按 signature 推断真实 Java 类型,否则后面 JNI 类型全乱。手机 Agent 与 PC MockAgent 的 JniOpDispatcher 采用 按 JNI signature 硬编码 的 if/switch:收到 JSON 后根据 op + signature 在真机执
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-291421.htm
[原创]手搓 JniForward:Unidbg JNI 转发真实 Android ART 的探索
494 浏览
5 回复
看了学习一下
tql
别玩unidbg了,直接真机trace一遍完事了
为啥一定要在电脑上跑
感谢分享