Frida 17.6.0在Android端的Zygote注入机制上进行了一次值得关注的重构。它展示了一种更加稳定的注入设计思路传统的ptrace注入方案虽然在功能上强大,但在实际应用中常常面临稳定性挑战:在子进程中残留痕迹容易被检测、与其他工具的兼容性也时有冲突等。而新引入的“zymbiote”机制采用了完全不同的实现路径——通过外部内存操作和轻量级通信,在几乎不留下痕迹的情况下完成了进程监控。理解这套机制不仅能帮助我们更好地使用新版工具,更重要的是,它为我们思考Android系统层级的动态分析技术提供了新的视角。本文将详细解析zymbiote的技术原理和实现细节去年我在这篇文章中介绍了spawn模式注入so的实现原理:spawn模式注入so它的核心思想是先向zygote进程注入一个mon.so,这样它运行在zygote进程,就可以轻而易举的实现对zygote进程中函数的hook,通过hook fork系列函数,在fork触发之后再安装对setArgV0的hook,在setArgV0函数触发时判断是否是目标app,从而确定是否dlopen需要注入的so,现在看来这套设计是有问题的,因为setArgV0函数是用于设置进程名的,在app启动时它会被稳定触发,所以完全可以跳过对fork函数的hook,直接hook setArgV0。其次就是zygote里驻留的so不是很好清理,以及selinux的问题处理的也很粗糙当然,Frida之前的注入也有这些问题,server一启动,对libc的hook就安装了,这会导致很多app打开就闪退,我在校时吃饭需要用到完美校园这个app,经常付钱的时候才想起来刚用过frida,手机还没重启,就会很尴尬可以看到之前的注入方式(开源的,公开的)大多都是通过ptrace注入zygote,其中ptrace负责实现远程读写+远程调用,再通过hook监控fork相关的函数实现的,而这次Frida更新,带来了一种新的思路但是核心逻辑是不变的,仍然需要远程读写+hook,但是这里它两个模块都做了优化远程读写:当前frida采用直接读写/proc/<zygote>/mem的形式进行远程读写(这个操作挂圈好像用的很多)。在我的理解中/proc/[pid]/mem是通过文件系统接口暴露的进程虚拟内存空间的直接读写通道,它像一个窗户,窗户内部是进程的完整虚拟地址空间,那么要如何精准的在这块空间里找到我们想要的东西呢?那就需要先去读/proc/[pid]/maps,map翻译过来是地图的意思,事实也的确如此,maps就像是这块空间的一张地图,通过它就可以定位到我们想要的位置hook再次回顾一下之前,我们的安装hook是通过运行在zygote进程内部的代码 修改目标函数开始处的指令为跳转指令,从而使运行到该函数时自动跳转到我们自己的hook函数的frida选择的是setArgV0函数作为触发点,这个函数之前介绍过的,它会在app启动时稳定被调用值得注意的是,这是一个JNI函数,对于JNI函数,它在java层就会有一个对应的java函数,那我们直接hook对应的java函数就好了。这个就很熟悉了,直接修改对应java函数的ArtMethod的entry_point即可,完全不需要inline hook了,frida就是这样做的,当然对于JNI函数,还有别的hook方法,但是frida在这个注入场景,修改entry_point是最简洁的做法那么hook如何安装呢?回顾刚刚的hook方案,核心是修改目标函数的entry_point,而目标函数是系统库里的函数,那他在开机之后,app启动之前,就存在zygote进程的内存里面了。那么我们只需要在zygote的内存里找到目标函数的entry_point字段,然后把它的值修改到我们的hook函数的地址就行了,那么现在就需要解决两个问题Frida 采用了一个巧妙的方法来定位 android_os_Process_setArgV0 对应的 ArtMethod 的 entry_point:首先,通过解析 libandroid_runtime.so 这个ELF 文件,找到 JNI 函数的符号地址:这里得到的 set_argv0_address 是函数在内存中的实际地址Frida 通过读取 zygote 进程的堆内存,搜索包含这个地址的位置:这样就拿到了art_method_slot的地址,即存储了指向android_os_Process_setArgV0地址的字段的地址,接下来通过上面提到的,读写/proc/<zygote>/mem来实现修改该字段,但在修改之前,需要备份一下原始指向的函数地址,后面注入成功/失败之后需要unhook,会用到原本的值那么应该把art_method_slot里存储的值改成什么呢?这就该解决第二个问题了传统的做法是通过 ptrace 注入一个 .so 文件到 zygote 进程,hook 函数就在这个 .so 里。但 Frida 的 zymbiote 机制采用了不同的思路:不注入 .so,而是注入一小段精心设计的机器码(payload)这个payload该如何设计我们放到后面再来讨论,目前先解决该把它放在哪的问题payload 应该放在哪里?这个位置需要满足几个条件:Frida 选择了 libstagefright.so 的最后一页:选择libstagefright.so的原因很简单,因为它的最后一页往往有未使用的空间,足够我们写入payload先看一下frida的zymbiote吧,源代码 zymbiote.c这里定义了一些libc里的函数,运行时会被用到。但是由于这段代码并不是像linker加载so那样被加载进内存的,所以它缺少重定位步骤,后续需要自己手动重定位这个就是android_os_Process_setArgV0的hook函数,流程总结下来就是 恢复 hook → 调用原始函数 → 连接 Server → 发送信息 → 等待确认 → 暂停进程这个是暂停函数,用于发送SIGSTOP暂停进程。其他的辅助函数就不介绍了,感兴趣的自己去看前面提到,_FridaApi 结构体里定义了一些 libc 函数指针,但这些指针在编译时都是空的。因为 payload 是纯机器码,不会经过动态链接器的重定位过程,所以需要 Frida Server 手动填充这些函数地址。重定位过程:填充完数据后,就可以注入了:这里直接指向payload是因为链接脚本将hook函数所在的段放在了payload的起始位置(偏移 0)。通过在函数定义时指定section,再配合链接脚本控制段的顺序,就能确保hook函数位于payload开头。至此就完成了payload的注入与android_os_Process_setArgV0函数的hook做完上面的工作,setArgV0就被替换了。在app启动之后,setArgV0被调用,然后走到frida_zymbiote_replacement_set_argv0里,执行里面的逻辑。这个函数上面介绍过了,Frida并没有选择直接在这里进行注入,而是仅仅获取了这个“时机”,拿到了这个时机之后,就启动Socket与外部通信,
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-289866.htm
[推荐]新版Frida-Zymbiote注入机制
96 浏览
12 回复
看雪的markdown解析器好像不支持vala的,,,
tql
感谢分享
感谢分享
感谢分享
为你点赞!
为你点赞!
犇
看看
学习
大侠强啊
蜕无痕
大侠强啊
还是造哥更吊
大侠强啊
还是造哥更吊