ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Frida入门:三种工作模式与Hook实战

Frida入门:三种工作模式与Hook实战 很多人第一次接触Frida上来就搜“frida下载”“frida安卓版”“frida gadget”结果下载了一堆文件却不知道哪个该用在哪个场景尤其是看到frida-gadget-16.5.6-android-arm64.so.xz、frida-gadget-17.17.0-android-arm64.so.xz这类文件名时更是一头雾水。还有人问iOS上用TrollStore怎么配合Frida、怎么用Python去调用问题五花八门但根子上都是同一个坎Frida基本概念没捋顺。这篇是Frida学习系列的第一篇先把“Frida家族里每个工具是干嘛的”“三种连接模式有什么区别”“一次完整的Hook是怎么跑起来的”讲清楚。读完你能自己判断我到底需要frida-server还是frida-gadget为什么版本必须对齐脚本里的Java.use到底在做什么。1. Frida到底是什么Frida是一个动态插桩工具英文叫Dynamic Instrumentation。用大白话说它能让一个正在运行的程序按你写的脚本临时改行为、打印内部数据、调用内部函数。你不用重新编译App不用改源码只要在设备上注入一段代码就能实时观察甚至修改程序的执行过程。它最常出现在这几个场景里移动App安全测试合规授权下分析某个App的加密逻辑、签名校验、接口参数构造方式。恶意样本分析追踪恶意软件运行时的敏感行为比如读取了哪些文件、调用了哪些系统接口。协议逆向通过Hooksend/write/SSL_read等函数观察程序的网络通信内容。功能调试App本身不提供的信息你通过Hook把它打印出来辅助排查问题特别是灰度环境或线上疑难问题。Frida和Xposed这类框架很容易被新手搞混。Xposed是改框架后重启生效Hook点相对固定Frida是运行期间注入不需要重启手机、不需要发布新版本改脚本立刻生效。代价是它需要一个能运行的环境不管是root设备还是模拟器总得有个载体让你把代码注入进去。这个载体就是后面要说的frida-server或frida-gadget。它的第二个特点是用JavaScript写脚本逻辑用Python或命令行控制连接底层是C/C实现的高性能注入引擎。你不需要先学会C语言再去搞逆向JS会一点就能上手。脚本发送到目标进程后由Frida内置的JavaScript引擎解释执行执行时能访问目标进程里的类、对象、内存就像在目标进程里开了一个后门终端只不过这个终端是通过正规API打开的。如果你是一点基础都没有的新手记住一句话就够了Frida等于“目标进程里的临时调试器 可热更新脚本 Python/命令行控制端”后面所有概念都围绕这个核心展开。1.1 Frida家族工具与各自定位Frida不是一个单文件而是一整套工具链新手经常下载错文件就是因为没分清楚这些名字。名称作用跑在哪里典型形态frida-tools命令行工具集合frida、frida-ps、frida-trace等你的电脑Python包frida / frida-core核心库负责进程注入、通信、脚本管理电脑端与设备端都有Python绑定、C库frida-server设备端守护进程以独立进程运行接收电脑端命令Android/iOS设备ELF可执行文件frida-gadget动态库可被App加载让App自身变成可注入主体目标进程内部.so/.dylibfrida CLI交互式命令行入口你的电脑frida命令你电脑上通过pip install frida-tools安装的东西本质是“控制端”。控制端不能直接对手机里的App生效它必须通过USB或网络去连接手机上的一个入口这个入口在root设备上通常是frida-server在非root重打包场景下通常是frida-gadget。从版本号角度说frida --version输出的是控制端版本frida-server --version输出的是设备端版本。很多新人报错“protocol mismatch”或“unable to communicate”十有八九是控制端和设备端版本不同。所以下载frida-server时务必去看frida官方Releases页里与电脑端完全一致的版本号比如电脑端是17.17.0设备端也尽量用17.17.0不要拿一个16.x的server去连一个17.x的客户端。1.2 Frida能Hook哪些层面Frida中最常被提起的是“Hook”但Hook分几个层面新手往往不知道自己要Hook哪一层。第一层是Java层也就是Android App里用Java/Kotlin写的业务代码比如登录校验函数、加密函数、接口请求函数。这一类是最容易上手的通过Java.use就能类名定位改完即时生效。日常遇到“App有个按钮点进去总是提示版本过低想看看那个判断逻辑”就属于这个范畴。第二层是Native层也就是so库里的C/C代码常与加解密、协议解析、反调试相关。这一层需要你先把so拖进IDA或Ghidra里定位偏移再在Frida里用Module.findBaseAddress、Interceptor.attach在Native函数入口下断点。难度会陡增因为你不仅要懂ARM汇编还得理解函数调用约定。第三层是更底层的系统调用或内存读写比如追踪某段Buffer、搜内存中的特征字符串、主动调用Native导出函数。这个层面的对手是最复杂的恶意软件和商业防护方案但对搞安全的人来说也是最有价值的部分。概念阶段不用急着全部掌握先具备一个图谱Java.use管Java层Interceptor.attach管Native层Memory.scan管内存查找Java.choose管已存活对象枚举。后续实操都是在这些API上做组合。2. 三种工作模式attach、spawn与gadgetFrida一共有三种经典工作模式每种模式的启动时机和适用场景完全不同。我在带新人时发现80%的困惑都来自这三种模式没区分清楚。2.1 Attach模式注入一个已经在跑的进程attach是最直观的模式。你先打开目标AppApp处于运行状态然后Frida通过frida-server去附着到这个进程里注入脚本。好处是简单、快、不需要重启App适合大多数运行中的状态分析坏处是进程早期的初始化代码已经执行过了有些发生在启动阶段的敏感判断你看不到。另外有一些App做了反调试检测会在启动后定期检查自身是否被附加attach模式在长期挂机时比较容易被反检测逻辑发现。命令行执行方式frida -U -p 12345 -l hook.js这里的-p 12345是指进程PID-U表示通过USB连接的设备。如果不知道PID先用frida-ps -U列出进程列表找到目标App的包名或进程名再填入PID。也可以用frida -U -n 应用名 -l hook.js-n表示按进程名匹配省得每次查PID。2.2 Spawn模式从冷启动阶段就开始Hookspawn模式是Frida帮你把目标App启动起来但启动过程中会先挂起然后注入脚本最后恢复运行。这样你可以观察到App从入口点开始的所有行为很多在attach模式下看不到的早期初始化逻辑用spawn都能抓到。命令行执行方式frida -U -f com.example.fridademo -l hook.js-f后面跟包名Frida会自己启动这个App。注意在某些交互式CLI或特定版本里spawn后目标进程会保持暂停状态等待你在Frida控制台手动敲%resume恢复如果发现执行命令后App一直停在启动页不动就不要傻等在Frida的交互控制台里输入%resume再按回车。如果使用Python脚本APIspawn之后也要显式调用device.resume(pid)否则App不会继续跑。spawn模式是逆向分析里更常用的模式因为它覆盖了启动全生命周期。缺点是如果目标App本身被加固或者有较严格的反调试措施spawn过程可能触发检测有些场景下反而要用attach。2.3 Gadget模式免root设备或重打包场景的“内嵌”方案frida-server运行在root设备上时很方便但大量真实测试环境是非root手机这时候就需要另一种思路把Frida的能力做成一个动态库塞进目标App进程里让App一启动就主动加载Frida的代码。这个动态库就是frida-gadget。用它最常见的做法是重打包解包目标APK把对应架构的libfrida-gadget.so放进lib/abi/目录然后在Manifest里让App启动时加载这个库或者替换原入口soname再把APK重新签名、安装。App运行后gadget会在进程内部建立监听你的电脑端连接过去就能注入脚本。还有一种情况是iOS上的TrollStore环境。TrollStore本身是一个签名/安装工具能让没有越狱的设备安装带有独立签名的IPA很多人把frida-gadget打包成带独立签名的IPA装进设备从而拿到一个可调试的入口。这个方法的关键不是“找到某个专门给iOS巨魔用的Frida”而是理解gadget是动态库、需要被目标App加载TrollStore解决的是签名和安装问题Frida解决的是注入和调试问题两者是配合关系。Gadget模式可以不需要root但因为要改APK再重签很多App有签名校验和完整性校验直接重打包后可能闪退或提示篡改。在合规测试环境里优先选择frida-server如果必须非root真机才考虑gadget。3. 安装与部署先把地基打对很多人买设备、下了一堆工具最后卡在“frida-ps连不上设备”或“版本协议不匹配”。这一节把标准流程完整走一遍并指出哪里容易出错。3.1 电脑端环境电脑端需要Python 3.8以上执行pip install frida-tools这个命令会把frida、frida-tools、相关依赖一起装好。装完先验证版本frida --version再验证Python绑定python -c import frida; print(frida.__version__)两个版本号应该一致或非常接近。如果只看frida-tools版本而不看底层frida版本后续对不上时会很迷惑所以这个验证习惯从一开始就养成。在Windows上要注意如果你同时装了多个Python版本pip可能装到旧版本里。建议在虚拟环境中操作或者使用python -m pip install frida-tools确保装进当前Python环境。3.2 Android设备端frida-server的部署Android设备上需要先确认几点设备已root或者使用的是已root的模拟器。确认CPU架构。查看CPU架构adb shell getprop ro.product.cpu.abi通常输出是arm64-v8a对应的下载文件是frida-server-版本-android-arm64armeabi-v7a对应32位ARM模拟器常见的是x86_64。不要只看主版本架构错了启动时会直接报Exec format error。下载frida-server的地方是Frida官方Releases页面文件名里的版本号要和你电脑端frida --version一致。举个例子你电脑端是17.17.0那就找frida-server-17.17.0-android-arm64。如果下载的是16.5.6你的工具箱是17.x大概率连接时会报协议不匹配。部署命令adb push frida-server-17.17.0-android-arm64 /data/local/tmp/frida-server adb shell chmod 755 /data/local/tmp/frida-server adb shell su -c /data/local/tmp/frida-server 注意frida-server需要root权限运行普通用户执行会提示权限不足。如果设备root方式是Magisk先打开授权如果是在模拟器里一般自带的root账户直接可用。启动后不要关闭那个终端另外开一个电脑终端验证frida-ps -U如果能看到Android设备上正在运行的进程列表说明通道已经通了。执行不了时先检查frida-server是否还在运行、设备的USB调试是否授权、adb devices里是否能看到设备。有时需要手动做一次端口转发adb forward tcp:27042 tcp:27042frida默认通过27042端口与设备端通信做了转发后即使某些版本的USB通道异常也经常能恢复。3.3 frida-gadget下载与解压的坑如果你走的是gadget路线下载时会遇到一个很典型的问题文件后缀不是.so而是.so.xz比如frida-gadget-16.5.6-android-arm64.so.xz、frida-gadget-17.17.0-android-arm64.so.xz。.xz是一种压缩格式不能直接把.so.xz放进App的lib目录必须先解压得到真正的动态库文件。在Linux/macOS上解压xz -d frida-gadget-17.17.0-android-arm64.so.xzWindows下可以用7-Zip解压或者用tar -xf也能处理部分xz文件。解压后得到一个名字类似frida-gadget-17.17.0-android-arm64.so的文件在重打包时通常把它重命名为libfrida-gadget.so再放进lib/arm64-v8a/目录。在Android上如果通过System.loadLibrary(frida-gadget)加载Frida还会自动寻找同名前缀的配置文件配置名一般是libfrida-gadget.config.so文件内容实际是JSON格式里面可以指定gadget的交互方式、监听端口、要自动加载的脚本路径等。一个最简单的监听配置长这样{ interaction: { type: listen, address: 127.0.0.1, port: 27042, on_port_conflict: fail } }假设你的应用运行在Android设备本地gadget在27042端口建立一个监听服务电脑端需要先做端口转发adb forward tcp:27042 tcp:27042然后用远程方式连接frida -H 127.0.0.1:27042 -l hook.js注意这里用的不再是-U因为你是通过TCP端口访问远端设备里的gadget而不是走USB抽象设备通道。很多人这一步用错参数导致怎么都连不上。4. 第一次实操从一个最简单的Hook开始概念说了那么多不上手等于白看。下面用一个可复现的最小示例把Frida的完整工作流程跑通。目标App假设是你自己写的一个测试项目包名叫com.example.fridademo里面有一个方法public int add(int a, int b) { return a b; }我们在不知道源码细节的前提下去Hook它让它被调用时打印参数并且把返回值改成100。4.1 编写JavaScript脚本新建一个文件叫hook.js内容如下Java.perform(function () { var MainActivity Java.use(com.example.fridademo.MainActivity); var add MainActivity.add.overload(int, int); add.implementation function (a, b) { console.log([add] called, a a , b b); var result add.call(this, a, b); console.log([add] original result result); return 100; }; });为什么最外层要套Java.perform因为Frida的脚本线程不天然运行在Java虚拟机上下文里你在脚本里要访问Java类、操作Java对象时必须先把执行环境切换到Java的Runtime线程中。这是Java层Hook的固定套路新手容易漏。Java.use是获得一个Java类的WrapperMainActivity.add.overload(int, int)则是明确指定要Hook的是两个int参数的add方法因为Java允许重载同名方法可以有多个参数列表你不写清楚参数类型Frida不知道该Hook哪一个。如果只有一个方法且无重载可以用MainActivity.add.implementation直接写但现实里几乎都有重载风险建议从一开始就养成用overload的习惯。有一个极其容易踩的坑在implementation里直接调用this.add(a, b)会无限递归导致栈溢出。原因是你改写的就是add本身再调用this.add等于反复进到你新实现的这个函数里。正确做法是先把原始方法引用保存下来如上面代码里的var add MainActivity.add.overload(...)再用add.call(this, a, b)调原始逻辑。我见过很多新手在这里迷惑日志刷了几万行最后才发现是递归。4.2 用命令行加载脚本启动frida-server后在电脑终端执行frida -U -f com.example.fridademo -l hook.js-f表示spawn冷启动Frida会先把App挂起、注入脚本、再恢复。如果App停在启动界面不动在Frida交互控制台输入%resume然后操作App触发add方法控制台应该会看到类似输出[add] called, a 1, b 2 [add] original result 3返回值被改成了100说明Hook生效。注意你返回100后App真正拿到的是100而不只是日志里的数字所以这个Hook会直接影响业务逻辑。如果不想新起App而是Hook一个已经打开两次的进程可以把-f改成-p PID或者-n 进程名。判断什么时候用spawn、什么时候用attach有一个简单标准如果你需要从Application或入口Activity刚创建就开始观察用spawn如果App已经运行到你感兴趣的页面用attach通常更快。4.3 用Python脚本代替命令行命令行适合快速测试但真正的自动化分析几乎都用Python。下面这段Python代码完成同样的事import frida import sys def on_message(message, data): if message[type] send: print([message], message[payload]) else: print([error], message) device frida.get_usb_device() pid device.spawn([com.example.fridademo]) session device.attach(pid) with open(hook.js, r, encodingutf-8) as f: js_code f.read() script session.create_script(js_code) script.on(message, on_message) script.load() device.resume(pid) input(Press Enter to detach...\n)脚本里的顺序是固定的先spawn拿到PID再attach然后创建并加载脚本最后resume。你把JavaScript脚本内容通过create_script发给目标进程目标进程内嵌的JS引擎会执行它。on_message是回调函数。JavaScript侧如果调用send(...)消息会通过这个回调传到Python侧如果只用了console.log信息会直接打到控制台。在复杂项目里我通常会建议统一用send把结构化数据传回Python端再在Python侧做保存和分析这样比在终端里看日志更利于后续处理脚本。5. 常见问题与排查技巧实录下面这些问题是Frida社区里出现频率最高的也是我自己带人和实操中反复遇到的。整理成速查表方便直接对号入座。现象常见原因处理思路Unable to connect to remote frida-server: timed outfrida-server没启动/端口不通检查进程是否在跑执行adb shell su -c /data/local/tmp/frida-server 必要时adb forward tcp:27042 tcp:27042Failed to enumerate processes: not permittedfrida-server没有root权限确认以root账户启动并检查SELinux策略unable to start process/ spawn失败包名错误、多进程App、反调试先用frida-ps -U确认进程名换attach模式试ClassNotFoundException类还没被加载或类名写错改用spawn模式提前注入检查完整类名内部类用$连接ReferenceError: Java is not defined在非Android环境执行Java API确认脚本运行在Android目标里或者审查是否漏了环境判断方法Hook了但没一点日志多进程/目标类由另一个进程加载/方法根本没被调用用frida-ps -U查看进程名找到真正运行逻辑的那个进程再attachtypeerror: ... is not a function方法有重载没指定参数列表用overload(参数类型)指定再HookApp启动直接闪退可能是完整性校验/反调试检测回到attach模式观察优先在自己写的Demo上练习5.1 几个不容易注意的坑第一Frida脚本并不会在App启动一瞬间就保证类已经加载。Android的类加载是有惰性的如果你用attach模式去Hook一个还没被加载的类就会报ClassNotFoundException。这时候可以用spawn模式在更早的阶段注入让Frida等类被加载后再Hook。spawn模式下如果还是比较早可以在脚本里用一个延迟循环去轮询类是否可用这不是正规解法但新手阶段很实用。更自然的方式是触发你想分析的那个页面等页面初始化完成再Hook类基本就加载了。第二同名方法重载怎么区分。我在上面的代码里已经写了overload(int, int)但在真实项目里看到一个方法名后需要去反编译看它的完整签名。比如public void setData(String data) public void setData(int index, String data)这两个都叫setData你要Hook第二个就得写var setData cls.setData.overload(int, java.lang.String);基础类型如int写int引用类型要写完整类名如java.lang.String。这里没有捷径耐心对照反编译结果填。第三打印调用栈帮我快速定位。有时候你不知道某个函数是被谁调用的光打印参数不够可以把当前调用栈打出来console.log(Java.use(android.util.Log).getStackTraceString(Java.use(java.lang.Throwable).$new()));打印出来的堆栈里能直接看到调用链对理解业务逻辑非常有用。我每次分析一个未知App时第一件事往往不是去Hook目标方法而是先打印这个方法的调用栈看它是从哪个页面、哪个回调链路上触发的。5.2 从日志到RPC把数据回传到电脑端上面示例里console.log只是把日志打到终端如果你想在Python侧拿到数据做判断可以用send。JavaScript侧这样写send({ type: result, value: result, a: a, b: b });Python侧的on_message里就能拿到比如存到列表、写文件、或者触发后续操作。这个方法在批量提取数据时非常有用。比如分析一个App对多个接口的请求参数你就等着脚本把参数全部send回来Python侧统一落盘最后做成一份报告。只用console.log的话日志一多就筛不过来了。Frida还支持RPC调用也就是Python侧主动调用JavaScript里暴露的函数。做法是在JS脚本里用rpc.exports定义rpc.exports { getResult: function () { return hello from target; } };Python侧result script.exports_sync.get_result() print(result)更常用的API还有script.exports_sync和异步的script.exports。RPC适合那种“进程已经跑起来隔段时间要问一下结果”的场景比如让目标App在后台自己跑业务流程你用RPC定时去拉状态而不是每次都重连重注入。6. 部署到具体场景时需要注意的版本问题版本对齐是Frida世界里最磨人的问题单独拿出来说都不为过。很多人下载时只看到了“frida官网下载”之类的关键词却没有意识到版本里藏了三个变量平台、架构、版本号。平台Android还是iOSLinux还是Windows平台错了一切白搭。架构arm64、arm、x86_64。Android真机用arm64模拟器用x86_64居多用UIAutomator的自动化框架尤其明显。版本号客户端、Python绑定、设备端要尽量一致。用真实的下载场景举例。你在Frida官方Releases页看到frida-gadget-17.17.0-android-arm64.so.xz这里的含义是版本17.17.0平台android架构arm64压缩xz压缩需要解压又比如frida-gadget-16.5.6-android-arm64.so.x如果看到了.so.x有可能是某种归档压缩或者社区魔改后的命名先尝试用file命令或解压工具看一眼文件头到底是什么格式别盲目改名使用。检查桌面控制端版本和是否配套最可靠的方式frida --version pip show frida再去查设备端frida-server的版本adb shell /data/local/tmp/frida-server --version或者frida-ps -U如果连接成功一般版本就不会有大问题。连接失败时第一步永远先查版本再查端口不要上来就怀疑脚本写错了。在重打包App时还有一个容易踩的残留问题如果目标App是32位与64位多架构的你要把所有ABI目录都放上对应的gadget动态库否则在某些机型上会直接因找不到so崩溃。你可以在APK里看到lib/arm64-v8a、lib/armeabi-v7a这些目录哪个存在哪个目录里就得放同名库哪怕只支持arm64-v8a也不能只放一个目录敷衍因为安装时系统会按主ABI去查找。7. 写给初学者的练习路线与避坑清单如果你是刚接触Frida真心不建议一上来就找一个商业App去练手。我的经验是先走下面这个路线相对平滑**第一阶段自己写一个最小Demo App。**Android Studio新建项目随便弄一个按钮或Activity里面放几个Java方法包括带重载的方法、返回boolean的校验方法、调用一下文件或网络的方法。然后部署到模拟器用Frida逐一Hook。这个阶段的价值是把attach、spawn、Java.use、overload、implementation这些基础动作练到肌肉记忆里。**第二阶段找一个开源App或自己常开发的应用来调试。**这时候可以尝试打印调用栈、用Java.choose枚举已经存在的对象、修改对象字段值。你会开始理解Hook并不是简单把返回值改了就行还要处理多实例、多线程、延迟加载、缓存等现实问题。**第三阶段再进入Native层。**用Cusom so样例或者开源SDK做实验把Interceptor.attach和符号解析跑通。在此之前不要硬啃去破解什么加固壳那会把你的学习信心彻底击碎。避坑清单是长期踩坑总结出来的每一条背后都有具体教训**下载前先确认电脑端版本再下载设备端对应版本。**别看到最新版就追Frida新版对旧手机可能有兼容性问题。**模拟器优先不要一开始就在真机上折腾root。**Android模拟器自带root部署frida-server非常简单真机反而会有各种厂商定制、SELinux、root授权弹窗干扰。**Hook方法前先去反编译确认类名、方法名、方法签名不要凭记忆猜。**猜错一次浪费的时间够你仔细看十分钟反编译结果。**避免直接修改某个重要函数的返回值为固定值。**很多时候你改了返回值App会走一个你没见过的分支然后崩溃。先打印、观察、统计确认逻辑后再动手改。**修改原逻辑之前先保存原方法引用并调用原实现。**上面提到的递归问题在真实项目里会刷爆内存直接把目标App卡死。**日志能结构化就不要纯文本。**用send传到Python侧统一处理格式化和归档都方便。个人情绪上我学Frida时最大的感受是这东西学习曲线不在于“工具怎么装”而在于“你有没有能力读懂App在做什么”。Frida只是把一扇门打开了门后面的类和函数、调用链和逻辑关系才是你真正要花时间的地方。所以建议每个新手都准备一个反编译工具比如jadx、JEB、IDA配合使用脚本越写越熟练但对业务代码的理解能力才是拉开差距的地方。
返回列表