在开发一款APP的功能扩展时,通常会使用Frida编写大量逻辑。为了摆脱每次都需要通过Frida Server动态注入的繁琐流程,并规避Root环境的要求,将Frida脚本直接固化到目标应用中成为一个极具吸引力的方案。
最初的尝试指向了frida-gadget,但在Android 16等高版本系统上,它可能无声无息地失效,脚本无法执行且没有任何错误日志,这迫使我们寻找更可靠的替代方案。
由于网络上缺乏成熟的一键化工具,我们决定自行实现一个完整方案。最终目标是:仅需一行命令,即可自动化打包出包含固化脚本的Xposed模块、已修改的APP安装包,以及可独立注入运行的.so(Linux/Android)和.dll(Windows)文件。
如果你只想快速实现脚本打包,可以直接使用我们已经实现的开源工具:https://github.com/std-microblock/fripack/。
1. 自编译 Frida-GumJS 核心
为了实现高度定制化和隐蔽性,我们需要自行编译Frida的核心脚本引擎——Frida-GumJS。这不仅可以抹去一些明显的特征,还能根据需求裁剪功能以减小最终二进制文件的体积。
Frida-GumJS 默认支持V8和QuickJS两个引擎。V8性能强劲但体积庞大,QuickJS则更加轻量。为了追求极致的精简,我们在编译时选择关闭V8引擎及一些非必需的内置模块(如Database)。编译过程通过编写GitHub CI流程来复现和自动化,确保了环境的可复现性。具体实现可参考:https://github.com/FriRebuild/fripack-inject。
获得编译好的GumJS库后,我们使用xmake建立项目,创建一个能够调用GumJS执行JavaScript代码的简易程序。为了将脚本数据直接嵌入二进制文件内部而非依赖外部文件,我们设计了一个带有特殊魔数(Magic)标记的配置结构体。这样,最终的.so或.dll文件可以通过扫描自身二进制内容,定位到这个结构体,并根据其中指定的偏移量和大小找到内嵌的JS脚本数据。
2. 将脚本数据嵌入二进制文件
为了让自制的“gadget”能读取内嵌脚本,我们需要将JS脚本数据写入ELF(Linux/Android)或PE(Windows)文件的新增段(Segment)中,并正确计算其在内存中的地址。
ELF 文件编辑
ELF文件通过程序头(Program Header)中的PT_LOAD段将数据映射到内存。我们的任务是:
- 在ELF文件中新增一个PT_LOAD段,用于存放脚本数据。
- 确保该段的虚拟地址(vaddr)按页(如4K)对齐,否则动态链接器(dl)可能无法正确加载。
- 由于新增段可能导致文件头扩大,需要调整后续所有受影响的节(Section)的文件偏移(sh_offset),避免重叠。
- 特别处理PT_PHDR段,确保它被某个PT_LOAD段所覆盖,否则加载会失败。
- 最后,计算内嵌的配置结构体与脚本数据在内存中的偏移,并回填到结构体的相应字段中。
这个过程涉及对ELF格式的精细操作,需要处理诸多边界情况。
PE 文件编辑
相比之下,PE(Portable Executable)文件格式的编辑逻辑类似但更为直观。其节(Section)的概念与ELF的段(Segment)功能相似。我们只需在文件末尾新增一个节,将脚本数据放入,并设置好节的内存属性。随后,同样计算并回填内存偏移量即可。从实践来看,PE格式的设计使得这一过程比ELF更少遇到隐晦的坑。
完成以上步骤后,我们便得到了一个自包含、无外部依赖、加载后即可自动执行内嵌脚本的二进制文件。
3. 将二进制文件加载到目标进程
拥有了可自执行的二进制文件后,下一步是将其加载到目标应用程序的进程空间中。根据不同的平台和场景,有多种注入技术可供选择:
- Android APK 重打包:解包APK,修改其原生库依赖或添加初始化代码,在应用启动时自动加载我们的.so。
- Xposed 模块:利用Xposed框架在目标应用进程初始化时执行代码的特性,加载模块APK中携带的.so文件。
- Zygisk 模块:基于Magisk的Zygisk框架,实现更底层和隐蔽的注入。
- Linux LD_PRELOAD:通过环境变量劫持动态链接过程。
- Windows DLL 注入:使用远程线程创建等经典DLL注入技术。
目前,我们已实现了前五种方式。下面重点介绍通过Xposed模块加载的实现,这对于Android应用逆向分析和功能扩展来说是一个非常实用的场景。
4. 实现 Xposed 模块自动打包
Xposed模块的本质是一个特殊的Android APK,其AndroidManifest.xml中包含特定元数据(meta-data),并且assets目录下存在声明入口点的文件。
我们的目标是不依赖复杂的Android Studio/Gradle构建体系,快速生成这样的APK。方案是直接构建一个标准的apktool工程目录结构:
- 编写必要的
AndroidManifest.xml。
- 在
assets目录下创建xposed_init文件,指定入口类。
- 编写Smali代码(或Java代码反编译为Smali),实现
IXposedHookZygoteInit或IXposedHookLoadPackage接口。在该类的初始化方法中,获取模块自身APK文件的路径,然后使用System.load()方法加载内嵌了我们Frida脚本的.so库。
- 将编译好的.so文件放入APK的
lib目录对应架构子文件夹下。
- 使用
apktool b命令将整个目录打包成APK,并对其进行签名和对齐。
通过将上述ELF/PE编辑、Xposed模块组装、APK打包等步骤串联,并封装成命令行工具,我们最终实现了从Frida JS脚本到可一键部署的持久化模块的全自动化构建流程。
这套方案不仅适用于Android安全研究,也为Windows/Linux平台下的软件调试与功能增强提供了一种高效的脚本固化思路。