记录一次有趣的 Frida Hook 调试
Created by: N1rv0us Zhang
Created time: May 10, 2024 11:48 PM
Tags: android, debugger, essays
起初
最近需要调试一个比较特殊的 app 子进程。虽然我一直感觉frida对Android app的子进程调试有些缺陷,但是由于这次需要调试 Native 的代码,我还是习惯的把直接把frida attach到进程上;瞬间调试的目标进程崩溃了 …
哦?什么原因呢?被反调干掉了?还是有其他问题导致程序崩溃呢 ….
通常遇到 Frida 挂不上去的情况,我都会选择另辟蹊径,比如在主进程上找找进程间通信把问题解决了,不会正面硬刚。但这次要求必须调试这个该死的进程,于是,很多在平常看 blog 学到的技术第一次组合应用到了实践中。非常值得记录一番。
失败的尝试
虽然是失败了,但是也有很多不错的内容喔 ~
我先是想到可能是 frida-server
触发了一些异常。因为这算是最傻瓜式的 frida 调试方法了,随便一个最基本的反调试都会识别,同时 frida-server
的超高可用性也会向调试目标进程加入一堆不太必要的操作,这些操作也可能会让一个脆弱的子进程崩溃;
那么,我们尝试简化下 frida-server
注入进程的逻辑;改为直接把 frida-gadget 注入到目标进程里面后连接 PC 上的 frida-client
操作方法可以选择重打包目标 app 就和在 nonRoot 环境中使用 frida 的操作一致。我这次选择了另一种方案,直接通过一个小工具把 frida-gadget 注入到目标进程的内存中,免去重打包的操作:
我在平常乱翻 blog 的时候发现的一个不错的工具,用 Rust 编写的;无论是内存注入的思路还是代码实现都十分的优雅。Nice
https://github.com/erfur/linjector-rs
第一次使用 frida-gadget 遇到了个小坑,其使用和 frida-server 还是有些区别的,在 Frida 官方文档中也有使用说明:
frida-gadget 的运行需要两个文件,一个从官网下载的对应架构的 frida-gadget.so,另一个为配置文件:
1 | lib |
内存注入时只需注入 libgadget.so
即可,它会在当前目录下加载对应的config;因此 frida-gadget.so 与其config的名称必须是按照格式匹配的;
config 按照如下的格式编写, 参数解析看官方文档即可:
1 | { |
然而,一顿操作把 frida-gadget 注入到目标进程之后,进程还是崩溃了;
Shit 怀疑人生开始了,我到底注入成功了没有?难不成真的遇到了顶级反 Frida ?
什么触发了 Crash
到这里我还是没搞明白,是什么导致了挂在 frida 的过程中必然 crash。但实际上无非两种大的思路:
- 被反调试之类的安全机制发现了,直接 kill 掉了进程;
- frida 自己把进程搞崩了;
于是,我向大哥咨询,大哥给了我一个很重要的 Hint :
这个进程不允许有 I/O 操作,只要有 I/O 类型的操作,就会马上 crash !
有了这个 hint 之后,我补充了一些测试,发现这个进程实际上是没有反调试的。崩溃的原因就是 frida 的运行导致了进程 crash .
那么反思一下,frida的注入过程中会触发 I/O 类型的操作么?答案是肯定的:
- frida-server 也需要把 frida-gadget 注入到目标进程,虽然不记得具体流程,但也很容易触发到crash;
- 直接注入 frida-gadget 的尝试,其功能的实现必须要读取当前目录下的文件,必然会有 I/O
此时,我需要一个不会有任何 I/O 行为的 Frida.
柳暗花明
那么,我要使用一个最小化运行 frida 的方式 —— 打包一个 frida-gum 到一个 Native so 文件中,然后使用前文的 linjector-rs 注入到目标进程中;
这里同样是使用以前玩过的一个不错的开源项目 FGum :
https://github.com/SeeFlowerX/FGum
直接将需要执行的 frida-script 也编入Native so中,这样 frida-script 会一起直接加载到内存中,执行结果重定向到 logcat 中,就避免了 I/O 操作;
当然,需要在 .init_array
或者 constructor
中加载 frida-script,以保证注入的脚本能正常跑起来。
虽然这里写的很轻松,但是,实现代码的时候还是提笔忘字Google了半天,所以呀,趁这个机会在强化下记忆吧 …
1 |
|
函数间触发顺序为:
1 | # ./test |
测试中发现,在 .init_array session 中触发函数还是会莫名的崩溃,于是使用了 constructor ;
- Final Code Below :
1 | void my_init_func(void) __attribute__((constructor)); |
编译完成之后,把 Native so 用 linjector-rs 注入到目标进程。正常输出了 frida-script 打印的日志;
Ok, 大功告成,搞定,下班儿 ~