上两贴:
https://bbs.pediy.com/thread-249163.htm
https://bbs.pediy.com/thread-249378.htm
前面有提到过 SandHook 需要手动写一个 Hook 方法作为 Hook 的入口,所以想要兼容 Xposed Callback 式 API 就必须凭空生成一个 Hook 方法来转发 Xposed Callback 逻辑。
所以我选择了 DexMaker,但是 Dexmaker 生成代码并且加载的时候还是太慢了,尽管只需要第一次生成(后面只需要加载即可),第一次 Hook 一个函数大概需要耗费 100ms 的时间。
至于 Backup 方法,则可写可不写,如果不写,则可以直接 New 一个 Method,将原 ArtMethod 中的数据填充进去,但是这里有个比较蛋疼的问题。真实存在的 ArtMethod 是在 Non-Moving 堆中的,也就是说,原版的 ArtMethod 不会 Moving GC,而 New 出来的只是一个普通对象,GC 的时候地址就会移动,每次调用 backup 方法之前都需要检查是否被移动。
当然 SandHook 预先写了一堆空方法备用。
其实优化方案很简单,依然是预先写一堆 Stub 函数,但是这些 Stub 变成了 hook 函数,并不仅仅在 Non-Moving Space 中占一个坑。他还需要接收原方法的参数,调用 XposedBridge,并且返回返回值。
Epic 是到栈中将参数一个个捞出来,而 SandHook 则将参数全部设为 long(32bit 为 int)
原因很简单:
如何转换?
对象地址使用 jweak JavaVMExt::AddWeakGlobalRef(Thread* self, ObjPtr<mirror::Object> obj) 转换成 java 对象。
可以用 dlsym/fake_dlsym 搜索到。
long 可以直接转成 int char byte short 等等,boolean 判断是不是 0 即可,float 和 double 则是存在浮点寄存器,而 long 是在通用寄存器,在参数列表和返回值中发现这两个类型直接转用 dexmaker 即可,一般这两种参数也少。
另外在 32bit 下 long/double 是 8 字节,参数列表中间夹了这两种类型参数会造成参数列表混乱,直接跳过走 Dexmaker。
最后写了个 python 脚本自动生成 stub 函数,基本 9 成以上的函数 hook 直接走 stub,耗时仅仅 1 - 3ms。
最后结合 VirtualApp 简单实现了一个类似 VXP 的免 Root Xposed 容器,目前测了 Q++, 应用变量,XPrivacyLua,MDWechat 等模块可以使用。
https://github.com/ganyao114/SandVXposed
VM 的 Inline 优化一直是我们 Hook 的最大阻碍,这里做个实验:
Android 7.1:
主要修改了原方法的返回值
前 1 s,方法 Hook 正常,输出 Hook 后的返回值:
1s 之后:
这说明不到一秒,由于被 Hook 方法过于简单,而调用该方法的方法热度较高,调用者发生了 Inline 优化并且进行了栈上替换。
研究 ART 源码发现:
当被 inline 方法的 code units 大于设置的阈值的时候,方法 Inline 失败。
这个阈值是 CompilerOptions -> inline_max_codeunits
那么想办法把这个阈值设为 0 就可以了。
经过搜索,CompilerOptions 一般与 JitCompiler 绑定:
而 ART 的 JitCompiler 为全局单例:
ok,那么我们就得到了 “static void* jit_compilerhandle” 的 C++ 符号 “_ZN3art3jit3Jit20jit_compiler_handle_E“
最后修改里面的值就可以了。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-2-25 09:57
被坑大编辑
,原因: 增加 “阻止 ART Inline 优化”