AOSP系统层集成Frida-Gadget:从编译到实战的完整指南

发布时间:2026/7/14 12:06:21

AOSP系统层集成Frida-Gadget:从编译到实战的完整指南 1. 项目概述为什么要在AOSP层面集成Frida-Gadget如果你做过安卓应用的安全分析或逆向工程Frida这个名字一定不陌生。它就像一把动态的“手术刀”能让我们在应用运行时实时地注入代码、拦截函数、修改内存从而洞察应用内部的运行逻辑或者绕过某些安全检测。通常我们使用Frida的方式是“附加”到一个已经运行的应用进程上或者通过修改APK将Frida的Gadget小工具库打包进去让应用一启动就加载它。但今天我们要聊的是一个更底层、更彻底的玩法将Frida-Gadget直接集成到Android开源项目AOSP的系统镜像里。这不再是针对单个应用的“打补丁”而是从系统层面为所有运行在该系统上的应用预先埋下这个强大的动态分析能力。想象一下你刷入自己编译的AOSP系统开机后任何一个你安装的应用理论上都可以被Frida脚本无感地“连接”和“操控”无需再对每个APK进行繁琐的脱壳、重打包、签名。这对于需要批量、自动化测试大量应用的安全研究员或者构建定制化动态分析环境的开发者来说效率的提升是颠覆性的。我最初产生这个想法是在为一个大型应用商店做自动化安全检测平台时。每天要处理成千上万个新上架的应用如果每个都走一遍“解包-注入-重打包-签名-安装”的流程资源消耗和时间成本都难以承受。于是我们把目光投向了系统底层。通过在AOSP编译阶段将libfrida-gadget.so库及其配置文件植入到系统镜像的特定目录并修改系统属性或init.rc脚本使其在系统启动时自动加载或者为所有应用提供一个默认的、可被Frida Server连接的“后门”。这样测试设备只需要刷入一次我们定制编译的系统之后安装的任何应用都天然具备了Frida插桩的能力。当然这条路并不平坦。AOSP的版本差异、系统分区只读、SELinux策略、不同架构的适配每一个环节都可能成为拦路虎。网上能找到的零散资料大多停留在“修改APK”的层面关于系统集成的实战细节少之又少。接下来我就结合自己多次在Pixel设备上刷入AOSP并集成Frida-Gadget的经验把其中的核心思路、关键步骤、踩过的坑和解决方案毫无保留地分享给你。2. 核心思路与方案选型编译时集成 vs 运行时注入在决定动手之前我们必须明确两种主流的集成思路它们决定了后续所有工作的路径和复杂度。2.1 方案一编译时集成推荐这是最彻底、最稳定的方法。核心思想是在AOSP源码编译的过程中将Frida-Gadget作为系统的一部分编译进去。具体做法获取预编译的Gadget库从Frida的官方Release页面下载对应你目标设备架构如arm64的frida-gadget-[version]-android-[arch].so文件。将其放入AOSP源码树通常我们会把它放在device/[vendor]/[device]/目录下或者创建一个自定义的prebuilts/frida-gadget/目录。关键是确保它在编译系统的扫描路径内。编写Android.bp或Android.mk这是AOSP的构建蓝图。你需要编写一个模块定义告诉编译系统“这是一个预编译的共享库请把它打包到系统镜像的/system/lib(64)/或/vendor/lib(64)/目录下”。同时你可能还需要编写一个init.rc服务在系统启动时设置一些环境变量如FRIDA_GADGET_ENABLE或直接dlopen这个库。修改系统属性或SELinux策略为了让非系统应用即普通第三方APP能够加载来自系统分区的这个库可能需要调整SELinux的neverallow规则给相关的domain如untrusted_app添加对系统库文件的execute权限。优势一次编译永久生效刷机后该能力内置于系统与系统同生命周期。对应用完全透明应用无需任何修改其进程启动后会因系统环境的配置而自动加载Gadget。稳定性高作为系统原生部分兼容性和稳定性通常优于运行时注入。劣势门槛高需要理解AOSP的构建系统Soong/Blueprint。编译耗时每次修改都需要重新编译系统镜像时间成本高。版本绑定Gadget库的版本与AOSP版本需要兼容升级任一版本可能需要重新适配。2.2 方案二运行时注入灵活但复杂这种方法不修改AOSP源码而是在系统启动后通过adb或一个常驻的系统服务将Gadget库注入到目标进程。这更像是一种“系统级”的frida -f操作。具体做法将Gadget库推入设备adb push libfrida-gadget.so /data/local/tmp/。编写一个注入脚本或服务利用ptrace、LD_PRELOAD或libdl的dlopen等机制编写一个可执行文件或守护进程。这个进程需要root权限它负责在目标应用启动时将libfrida-gadget.so加载到目标进程的内存空间。通过setprop或init.rc启动服务让这个注入服务在系统启动时自动运行。优势无需编译AOSP直接在现有系统上操作快速验证想法。灵活控制可以动态选择对哪些应用进行注入。劣势需要Root权限注入操作本身通常需要root。稳定性挑战注入时机难以精确把控容易导致应用崩溃或注入失败。对抗检测这种动态注入行为更容易被应用内的反调试、反注入机制检测到。SELinux限制更严在非permissive模式下ptrace等操作会受到严格限制。我的选择与建议对于追求稳定、长期使用的分析环境我强烈推荐方案一编译时集成。虽然前期投入大但一劳永逸后续的使用体验丝滑流畅。下文也将主要围绕这个方案展开。方案二更适合快速原型验证或针对特定封闭环境但作为生产环境方案风险较高。3. 实战准备环境、源码与工具链工欲善其事必先利其器。在开始修改AOSP之前我们需要一个稳定、高效的编译环境。3.1 硬件与基础环境准备宿主机推荐使用Linux系统Ubuntu 20.04/22.04 LTS是官方推荐且社区验证最多的。你需要一块至少500GB的空闲SSD以及16GB以上的内存。编译AOSP非常消耗磁盘和内存机械硬盘和小于16GB的内存会让你在编译的漫长等待中崩溃。AOSP源码确定你要编译的AOSP版本例如android-13.0.0_r41和目标设备例如Google Pixel 6代号oriole。通过清华大学或中科大的镜像源同步代码会快很多。repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest -b android-13.0.0_r41 repo sync -c -j$(nproc --all)设备驱动从Google开发者网站下载对应你设备型号和AOSP版本的驱动二进制文件Vendor Image解压后运行脚本将其放入源码树。Frida-Gadget库访问Frida的GitHub Releases页面下载与你设备架构匹配的版本。例如对于Pixel 6arm64下载frida-gadget-16.2.1-android-arm64.so.xz解压得到.so文件。3.2 关键工具与概念理解Soong/Blueprint构建系统AOSP现在主要使用Android.bp文件来定义模块。你需要学会编写一个简单的cc_prebuilt_library_shared模块来引入预编译的Gadget库。SELinux这是最大的“拦路虎”之一。你需要知道如何查看SELinux拒绝日志dmesg | grep avc或logcat | grep avc并编写*.te文件来添加策略规则。init.rc与系统属性了解如何通过修改init.[device].rc或添加system.prop文件来设置启动时加载库所需的环境变量。3.3 一个常见的误区直接替换系统库有些初学者会想能不能直接把libfrida-gadget.so改名为libc.so之类的系统库替换掉它这是绝对错误且危险的想法签名验证系统分区的重要库有Vendor签名验证替换后会导致系统无法启动。功能依赖系统库之间有复杂的依赖关系随意替换会导致连锁崩溃。SELinux即使替换成功SELinux也会阻止非系统域执行它。正确的思路是让Gadget作为一个独立的、新增的系统库存在并通过系统配置让应用进程在启动时主动去加载它。4. 核心步骤详解将Frida-Gadget编译进AOSP假设我们的目标设备是Pixel 6 (oriole)AOSP版本是android-13。4.1 步骤一放置Gadget库与编写构建脚本在AOSP源码根目录下创建一个自定义目录来存放我们的修改这样便于管理且不与原生代码混淆。mkdir -p vendor/frida-gadget/ cd vendor/frida-gadget/将下载好的libfrida-gadget.so请重命名为libfrida-gadget.so去掉版本号便于管理放入prebuilts/子目录。mkdir -p prebuilts/arm64/ cp ~/Downloads/libfrida-gadget.so prebuilts/arm64/编写Android.bp文件定义预编译库模块。// vendor/frida-gadget/Android.bp cc_prebuilt_library_shared { name: libfrida-gadget, vendor: true, // 或 product: true取决于你想放在/vendor还是/product分区 arch: { arm64: { srcs: [prebuilts/arm64/libfrida-gadget.so], }, // 如果需要支持其他架构可以在这里添加 }, compile_multilib: 64, // 仅64位 strip: { none: true, // 不剥离符号便于调试 }, check_elf_files: false, // 跳过ELF文件检查避免因Gadget的特殊性报错 }这个模块告诉构建系统这里有一个预编译的共享库libfrida-gadget.so请将它打包到vendor或product分区的lib64目录中。4.2 步骤二确保模块被系统镜像包含仅仅定义了模块还不够需要让构建系统在编译设备镜像时包含它。这通常通过设备特定的makefile实现。找到你设备的设备树目录例如device/google/oriole/。编辑该目录下的device.mk或oriole.mk文件具体名称可能不同寻找包含PRODUCT_PACKAGES的文件。在文件中添加一行PRODUCT_PACKAGES libfrida-gadget这行代码将我们定义的libfrida-gadget模块添加到系统镜像的软件包列表中。4.3 步骤三配置系统以加载Gadget这是最关键也最易出错的一步。我们的目标是让应用进程在启动时自动加载libfrida-gadget.so。有几种常见方法方法A通过环境变量LD_PRELOAD不推荐用于Android主空间在Android上直接全局设置LD_PRELOAD风险很高可能破坏zygote进程导致系统无法启动。更安全的方式是针对特定进程。方法B修改系统属性并让init.rc处理推荐在device/google/oriole/目录下找到或创建一个系统属性文件例如system.prop。添加属性用于控制Gadget的启用和配置路径。# 启用Frida Gadget persist.frida.gadget.enable1 # Gadget库的绝对路径 persist.frida.gadget.path/vendor/lib64/libfrida-gadget.so # 可选的Gadget配置文件路径 persist.frida.gadget.config/vendor/etc/frida-gadget.config编写一个init.frida.rc服务文件同样放在设备树目录。# init.frida.rc service frida-gadget-setup /system/bin/sh /vendor/bin/setup-frida-gadget.sh class main user root group root oneshot # 只执行一次 on property:sys.boot_completed1 persist.frida.gadget.enable1 start frida-gadget-setup这个服务在系统启动完成且Gadget启用属性为1时执行一个shell脚本。创建/vendor/bin/setup-frida-gadget.sh脚本。#!/vendor/bin/sh # 此脚本为示例实际逻辑更复杂 GADGET_PATH$(getprop persist.frida.gadget.path) CONFIG_PATH$(getprop persist.frida.gadget.config) if [ -f $GADGET_PATH ]; then # 关键为所有untrusted_app进程设置LD_PRELOAD # 注意这需要SELinux放行且可能不稳定。更优方案是使用wrap.sh属性。 setprop wrap.com.example.targetapp LD_PRELOAD$GADGET_PATH FRIDA_GADGET_CONFIG$CONFIG_PATH fi注意上面的setprop wrap.方法是Android提供的一种为特定应用包装执行环境的方式。com.example.targetapp需要替换成你真正想注入的目标应用的包名。如果你想对所有应用生效需要一个更复杂的机制比如遍历已安装应用列表并动态设置属性但这在init阶段难以实现。方法C使用wrap.sh方案针对单个应用更精准这是目前相对稳定和推荐的方式无需修改太多系统核心。在Gadget模块的Android.bp中增加一个sh脚本文件到系统。prebuilt_etc { name: frida-gadget-wrap.sh, src: scripts/wrap.sh, sub_dir: frida, filename: wrap.sh, }在scripts/wrap.sh中编写包装逻辑。#!/system/bin/sh # 这是被wrap.sh调用的脚本 export LD_PRELOAD/vendor/lib64/libfrida-gadget.so export FRIDA_GADGET_CONFIG/vendor/etc/frida-gadget.config exec $在设备树的device.mk中为你关心的应用设置wrap属性。# 为包名为com.example.targetapp的应用启用wrap PRODUCT_SYSTEM_PROPERTIES wrap.com.example.targetapp/vendor/etc/frida/wrap.sh这样当com.example.targetapp启动时系统会先执行wrap.sh脚本设置好LD_PRELOAD然后再启动应用本身。4.4 步骤四处理SELinux策略无论采用哪种加载方式只要涉及非标准路径的库加载或环境变量设置几乎一定会触发SELinux的avc: denied。编译并刷入系统后抓取SELinux拒绝日志adb shell su dmesg | grep avc | grep frida # 或 grep gadget cat /proc/kmsg | grep avc # 另一种方式日志会类似avc: denied { execute } for pidxxx name“libfrida-gadget.so” ... scontextu:r:untrusted_app:s0 ... tcontextu:object_r:vendor_file:s0 ...。这表示untrusted_app域没有对vendor_file类型的文件执行权限。在设备树目录下如device/google/oriole/sepolicy/找到或创建针对untrusted_app的TE规则文件例如vendor/frida-gadget/sepolicy/frida_gadget.te。# frida_gadget.te # 允许untrusted_app执行vendor分区中的frida-gadget库文件 allow untrusted_app vendor_file:file { execute open read map }; # 如果使用了wrap.sh还需要允许appdomain执行shell脚本 allow untrusted_app shell_exec:file { execute open read };在设备树的sepolicy目录下的file_contexts文件中为我们的库和脚本打上正确的安全标签。# file_contexts /vendor/lib(64)?/libfrida-gadget\.so u:object_r:frida_gadget_exec:s0 /vendor/etc/frida/wrap\.sh u:object_r:frida_gadget_exec:s0然后更新frida_gadget.te使用新定义的类型type frida_gadget_exec, file_type, vendor_file_type, exec_type; allow untrusted_app frida_gadget_exec:file { execute open read map };最后确保你的BoardConfig.mk或sepolicy的Makefile包含了新添加的.te文件。实操心得SELinux策略调试是个耐心活。一个黄金法则是缺什么权限就补什么但范围要尽可能小。不要简单粗暴地allow untrusted_app vendor_file:file *;。使用audit2allow工具在AOSP编译环境中可以根据avc日志自动生成策略规则但自动生成的规则可能需要手动精简以确保安全最小化。4.5 步骤五编译与刷机配置编译环境source build/envsetup.sh lunch aosp_oriolo-userdebug # 使用userdebug版本有root权限开始编译m -j$(nproc --all)这个过程视机器性能而定可能需要1到数小时。编译完成后刷入设备adb reboot bootloader fastboot flashall -w # -w 会清除用户数据注意备份5. 验证、连接与问题排查刷机完成后重启设备。如何验证集成是否成功5.1 基础验证检查库文件是否存在adb shell ls -l /vendor/lib64/libfrida-gadget.so检查系统属性adb shell getprop | grep frida启动一个目标应用然后查看日志adb logcat | grep -i frida如果成功加载你应该能看到Frida Gadget相关的初始化日志。5.2 连接Frida在电脑上启动Frida服务端通常不需要了因为Gadget已经内置。但你需要确保电脑和设备在同一个网络或者通过USB连接并做端口转发。使用Frida客户端进行连接。Gadget默认会在127.0.0.1:27042监听。USB连接adb forward tcp:27042 tcp:27042 frida -H 127.0.0.1:27042 -f com.example.targetapp --no-pause网络连接需确保设备防火墙允许frida -H [设备IP]:27042 -f com.example.targetapp --no-pause如果连接成功你会看到Frida的交互提示符。5.3 常见问题与排查实录即使步骤都对了你也可能会遇到各种问题。下面是我踩过的一些坑和解决方案问题1应用启动立即崩溃日志显示dlopen failed: library “libfrida-gadget.so” not found。排查库文件路径错误或者SELinux阻止了访问。解决确认libfrida-gadget.so确实存在于你设置的路径/vendor/lib64/。检查SELinux日志(dmesg | grep avc)看是否有对vendor_file或frida_gadget_exec的execute或open拒绝。按照第4.4节补充策略。检查库文件的权限是否正确ls -l应该是-rw-r--r--root root。问题2应用启动后Frida可以连接但一执行脚本就断连或应用崩溃。排查Gadget版本与Android系统版本或应用架构不兼容。或者Gadget的配置文件有问题。解决确保下载的Gadget库架构arm64与你的设备及应用是否为64位匹配。尝试使用不同版本的Frida-Gadget。Frida 16.x 对Android 13/14支持较好。回退到15.x或升级到最新版试试。创建一个简单的Gadget配置文件/vendor/etc/frida-gadget.config内容为{ “interaction”: { “type”: “listen”, “address”: “0.0.0.0:27042” } }并确保路径在LD_PRELOAD或wrap.sh脚本中正确指定。错误的配置会导致Gadlet初始化失败。问题3wrap.sh方式不生效应用启动时没有加载Gadget。排查wrap.sh机制可能被系统或应用本身禁用。解决确认你设置的系统属性是否正确生效adb shell getprop wrap.com.example.targetapp。在Android 10及以上版本如果应用的targetSdkVersion 28并且其android:debuggable属性不为true则wrap.sh可能被禁用。尝试在应用的AndroidManifest.xml中临时添加android:debuggable“true”仅用于测试需重新编译APK。另一种方案是使用LD_PRELOAD的全局设置但这需要更复杂的SELinux策略和可能修改zygote的启动参数风险极高不推荐新手尝试。问题4编译通过但刷机后系统无法启动卡在开机动画。排查最可能的原因是SELinux策略错误导致关键服务如zygote启动失败或者init.rc脚本有语法错误。解决进入fastboot模式刷回原来的镜像。在编译时可以尝试将SELinux模式设置为permissive来快速定位是否是策略问题。在BoardConfig.mk中添加BOARD_KERNEL_CMDLINE androidboot.selinuxpermissive。如果permissive模式下能启动那问题肯定出在SELinux上。仔细检查init.frida.rc的语法确保service和on语句块正确闭合。问题5Frida连接成功但无法拦截JAVA函数。排查Android运行时ART版本与Frida Gadget的兼容性问题。解决这是最常见的问题之一。确保你使用的Frida版本支持你的Android版本。参考输入内容中的兼容性表格例如Android 13建议使用Frida 16.2.0。尝试使用frida --version查看客户端版本并确保与设备端的Gadget版本一致或兼容。在Frida脚本中尝试先延迟几秒再执行hook因为Java运行时可能还未完全初始化。可以使用setTimeout()或Java.perform()的延迟回调。6. 进阶技巧与优化建议当基础功能跑通后可以考虑以下优化让你的定制系统更加强大和易用。6.1 自动化构建与持续集成手动操作容易出错。你可以将上述步骤编写成一系列的Shell脚本或Python脚本并集成到你的AOSP编译环境中。更进一步可以搭建一个Jenkins或GitLab CI/CD流水线实现提交代码后自动编译、打包和生成刷机包。6.2 动态配置与开关不要总是让Gadget加载。可以通过系统设置Settings应用添加一个开关或者通过一个特定的BroadcastReceiver来动态控制persist.frida.gadget.enable属性的值。这样可以在需要分析时才开启减少对日常使用的影响和潜在的性能开销。6.3 集成更多分析工具既然已经定制了AOSP何不把其他有用的工具也打包进去例如可以集成strace、tcpdump、busybox等命令行工具或者将frida-server也编译进去作为Gadget的备用连接方案。6.4 针对高版本Android的适配Android 14 (UpsideDownCake) 及更高版本对原生库的加载、JNI调用有了更严格的限制。你可能需要关注namespace隔离确保Gadget库在正确的namespace中被加载。使用JNIDebug接口高版本Android可能限制了LD_PRELOAD对某些系统库的影响需要研究通过wrap.sh结合debuggable标志或者利用profileableshell等机制。密切关注Frida更新Frida团队会持续适配新Android版本及时更新Gadget库是关键。6.5 安全与伦理提醒最后必须强调一点能力越大责任越大。你编译的包含Frida-Gadget的系统镜像是一个极其强大的分析工具但同时也可能被滥用。仅用于安全研究、渗透测试在授权范围内、应用调试或教学目的。不要将其安装在非你所有的设备上不要用于破解、修改他人受版权保护的软件以牟利。妥善保管你的定制镜像和编译环境避免泄露。这条路走下来你会发现将Frida-Gadget集成进AOSP不仅仅是完成一项技术任务更是对Android系统架构、安全机制和构建系统的一次深度之旅。每一次解决SELinux拒绝每一次成功连接上内置Gadget的应用带来的成就感都是巨大的。希望这篇长文能为你扫清障碍祝你编译顺利分析愉快。如果在实践中遇到新的问题不妨回到AOSP的源码、SELinux的官方文档和Frida的Issue页面去寻找答案社区的智慧总是无穷的。

相关新闻