1. 为什么需要Frida持久化注入
在Android应用安全分析和逆向工程中,Frida是最常用的动态分析工具之一。但传统的Frida使用方式有个明显痛点:每次都需要通过frida -U命令手动附加到目标进程,这在某些场景下非常不方便。比如:
- 分析应用启动阶段的初始化流程
- 调试那些会检测Frida注入的应用
- 需要长期监控某些关键函数调用
- 在无法连接电脑的移动端环境进行调试
这时候就需要持久化注入技术了。简单来说,就是让目标应用在启动时自动加载Frida环境,不需要我们手动干预。我最近在一个金融类App的分析中就遇到了这种情况:应用启动时会检测调试状态,如果发现被Frida附加就会立即退出。通过持久化注入,成功绕过了这个保护机制。
实现持久化注入主要有两种方式:
- 修改Dex文件添加加载代码
- 修改原生so文件添加依赖库
第一种方法需要处理Dex的复杂结构,而且容易被反编译工具发现。第二种方法通过修改so文件的动态链接依赖,把frida-gadget.so作为依赖库注入,这种方式更加隐蔽稳定。实测下来,在大多数情况下第二种方法成功率更高,这也是我们今天要重点讲解的技术方案。
2. 环境与工具准备
2.1 基础工具安装
在开始之前,我们需要准备以下工具(建议使用Linux或MacOS环境):
# 安装apktool用于反编译和重打包 sudo apt install apktool # 安装lief库用于二进制文件修改 pip3 install lief # 下载最新版uber-apk-signer wget https://github.com/patrickfav/uber-apk-signer/releases/download/v1.2.1/uber-apk-signer-1.2.1.jar # 下载对应架构的frida-gadget.so # 从https://github.com/frida/frida/releases下载2.2 目标APK处理
选择目标APK时有个小技巧:优先选择那些没有强加固保护的App。可以通过以下命令快速检查:
# 查看APK是否被加固 apktool d test.apk如果反编译顺利,没有报错,通常说明这个APK适合我们的实验。我建议新手先用一个简单的Demo App练手,比如自己写一个包含native库的测试应用。
2.3 关键文件准备
我们需要准备两个核心文件:
libgadget.so:从Frida官网下载的gadget库,需要重命名libgadget.config.so:配置文件,内容如下:
{ "interaction": { "type": "script", "path": "/data/local/tmp/myscript.js", "on_change": "reload" } }这个配置文件告诉Frida加载我们指定的JS脚本。注意路径要写绝对路径,因为不同Android版本的库加载路径可能不同。
3. 使用LIEF修改SO文件
3.1 定位目标SO文件
首先用apktool反编译APK:
apktool d target.apk -o output_dir进入lib目录,通常会有多个ABI子目录(armeabi-v7a, arm64-v8a等)。选择与你的测试设备匹配的架构,然后找出主业务so文件。有个快速定位的技巧:
- 文件名包含"main"、"core"、"app"等关键词的so
- 文件体积较大的so
- 使用readelf查看动态段依赖:
readelf -d libtarget.so | grep NEEDED3.2 使用LIEF注入依赖
找到目标so后,使用Python脚本进行修改:
import lief # 加载目标so文件 target = lief.parse("libtarget.so") # 添加frida-gadget依赖 target.add_library("libgadget.so") # 保存修改 target.write("libtarget_modified.so")这里有个坑要注意:有些so文件开启了DT_FLAGS_1的NOW标志,会导致加载顺序问题。解决方法是在write之前添加:
target[lief.ELF.DYNAMIC_TAGS.FLAGS_1].remove(lief.ELF.DYNAMIC_FLAGS_1.NOW)3.3 验证修改结果
修改完成后,使用readelf检查:
readelf -d libtarget_modified.so | grep NEEDED应该能看到新添加的libgadget.so依赖。我遇到过修改后so损坏的情况,这时可以尝试用patchelf工具作为备用方案:
patchelf --add-needed libgadget.so libtarget.so4. 重打包与签名
4.1 替换SO文件并重打包
将修改后的so文件复制回原目录,覆盖原文件。同时把libgadget.so和配置文件也放入同一目录:
lib/ └── arm64-v8a/ ├── libtarget_modified.so ├── libgadget.so └── libgadget.config.so然后使用apktool重打包:
apktool b output_dir -o unsigned.apk4.2 APK签名
使用uber-apk-signer进行签名:
java -jar uber-apk-signer-1.2.1.jar --apks unsigned.apk签名后会生成unsigned-aligned-debugSigned.apk文件。建议每次修改后都用新签名,避免缓存问题。
4.3 安装测试
安装前先卸载原应用:
adb uninstall com.target.package adb install unsigned-aligned-debugSigned.apk5. 脚本准备与调试
5.1 编写Frida脚本
创建myscript.js,内容示例:
'use strict'; console.log("Script loaded!"); Java.perform(function() { var Log = Java.use("android.util.Log"); Log.d("Frida", "Injected successfully!"); // Hook示例 var Crypto = Java.use("com.target.Crypto"); Crypto.encrypt.implementation = function(data) { console.log("Encrypt called with: " + data); return this.encrypt(data); }; });5.2 推送脚本到设备
adb push myscript.js /data/local/tmp/ adb shell chmod 644 /data/local/tmp/myscript.js5.3 日志监控
启动应用并监控日志:
adb logcat | grep -E "Frida|frida"如果一切正常,应该能看到我们的日志输出。我在实际项目中遇到过这些常见问题:
- 脚本路径错误:检查配置文件中的路径是否与push路径一致
- 权限问题:确保
/data/local/tmp目录可读 - 架构不匹配:确认so文件架构与设备CPU架构一致
6. 不同Android版本的适配
6.1 库加载路径差异
Android 7.0以下和以上版本的库加载路径有明显区别:
- Android <7.0:
/data/app-lib/<package>-<random> - Android 7.0-8.1:
/data/app/<package>-<random>/lib - Android 9.0+:
/data/app/<package>-<random>/lib/<abi>
这会导致配置文件中的脚本路径需要相应调整。一个兼容性解决方案是使用环境变量:
{ "interaction": { "type": "script", "path": "@/data/local/tmp/myscript.js", "on_change": "reload" } }6.2 SELinux策略限制
高版本Android的SELinux会限制非标准路径的库加载。解决方法有两种:
- 将gadget库打包到apk的lib目录
- 修改SELinux策略(需要root)
对于大多数情况,第一种方法就足够了。我在MIUI 12上测试时发现,即使路径正确也会加载失败,这时需要在配置中添加:
"allow": { "dlopen": true, "dlsym": true }7. 进阶技巧与问题排查
7.1 隐藏Frida特征
有些应用会检测frida-gadget的存在。可以通过以下方式隐藏:
- 重命名gadget库为常见名称如
libssl.so - 修改gadget的导出符号:
strip -x libgadget.so- 使用配置禁用某些特征:
"frida": { "disable_teardown": true, "discoverable": false }7.2 多进程应用处理
对于多进程应用,需要在配置中指定注入策略:
"session": { "persist_timeout": 30, "threads": { "spawn": { "action": "allow" } } }7.3 常见错误排查
- 加载失败:检查
logcat中的linker错误 - 脚本不执行:确认配置文件路径和权限
- 应用崩溃:可能是so文件损坏或架构不匹配
- 注入成功但无输出:检查脚本语法和hook的类名是否正确
我在实际项目中总结了一个排查清单:
- 确认apktool反编译/重打包过程无报错
- 检查修改后的so文件是否包含正确的依赖
- 验证所有文件都在正确的ABI目录
- 检查配置文件路径与设备实际路径一致
- 确认脚本语法正确且无运行时错误
8. 安全与伦理考量
虽然这项技术非常强大,但在实际使用时需要注意:
- 只对你有合法权限分析的应用使用
- 不要用于绕过正常付费机制
- 企业应用分析要遵守保密协议
- 公开研究成果时注意脱敏处理
我曾经在一个银行App的分析中发现了一个严重漏洞,通过正规渠道报告后获得了他们的致谢。技术本身是中性的,关键在于如何使用它。