资讯动态

Frida+Objection+Wallbreaker实战:环境搭建与Hook技巧全解析

发布时间:2026/10/19 10:08:42 来源:尧图企业网站定制
搞移动端安全测试或者做应用逆向分析的朋友应该都绕不开 Frida 这套动态插桩工具而 Objection 则是在 Frida 基础上进一步封装的“懒人神器”。这个组合最吸引人的地方是你不用从零手写大量 JavaScript 注入脚本也能在几分钟内完成对目标 App 的类枚举、内存搜索、绕过 Root 检测、SSL Pinning 等一系列操作。这篇保姆级教程我就从环境搭建开始讲一直做到实战 Hook再把 Wallbreaker 插件怎么用来分析类结构说清楚。内容适合刚入门移动逆向的测试工程师也适合已会用 Frida 但没系统性梳理过 Objection 和 Wallbreaker 的同学参考。整个过程中你会接触到 frida-server、frida-tools、objection、wallbreaker 这些核心组件还会看到各种常见的启动报错、版本不匹配、加固对抗问题。我会按照实际操作的顺序来写先把环境彻底打通再讲 Hook 思路最后把坑一个一个列出来方便你排查。1. 环境搭建一套能稳定复现的 Frida 工作台1.1 为什么版本匹配是头等大事在动手之前最容易被忽视也最坑的一点是版本匹配。Frida 由运行在 PC 端的 frida-tools、frida-python 以及运行在目标设备上的 frida-server 组成。这三个东西的版本核心版本号必须一致。之前我带过一个新人电脑上装的是 16.x 的 frida-tools设备上却放了 15.x 的 frida-server结果 attach 的时候报错信息五花八门有提示无法握手、有提示找不到端口、有提示协议错误。单独看每一条报错真不容易想到是版本问题。建议的做法很简单先在你的电脑终端执行pip install frida-tools装完以后执行frida --version查看你当前的版本号然后再去下载对应版本的 frida-server。这里我特别建议用虚拟环境来装不要直接装到系统 Python 里否则不同项目之间很容易互相污染依赖。我一般这样操作python3 -m venv ~/frida_env source ~/frida_env/bin/activate pip install frida-tools frida --version看到版本号之后再去下载对应版本的 frida-server 文件。下载的时候你要注意设备架构真机一般是 arm64很多 Windows 模拟器是 x86_64部分较早的模拟器镜像可能是 x86。选错架构的 frida-server 在设备上根本跑不起来会提示无法执行二进制文件。这个坑非常经典排查方法是在 adb shell 里执行uname -m看真实架构信息。1.2 frida-server 的部署与启动细节frida-server 拿到手之后部署过程看起来很简单但细节也不少。我先给出一套完整的步骤adb push frida-server /data/local/tmp/ adb shell su chmod 755 /data/local/tmp/frida-server /data/local/tmp/frida-server 用su切换到 root 原因是 frida-server 需要足够权限去 ptrace 目标进程。很多 App 本身就做了反调试检测如果你的 frida-server 没有 root 权限可能连目标进程都看不到。部署完成后在 PC 端执行frida-ps -U如果能够列出设备上的进程列表就说明环境基本打通了。这里有几个细节需要多提一句如果你的设备是真机且开启的是 USB 调试连接直接使用-U参数即可。如果你使用的是模拟器某些模拟器需要先执行adb connect 127.0.0.1:端口号连接到自定义端口再使用-U操作。如果你当前用的是无线 adb或者设备通过远程方式连接frida 同样支持通过-H 主机:端口指定远程 frida-server 地址但调试体验不如 USB 模式稳定。实际操作中我还发现 frida-server 默认监听 27042 端口这个信息在后面对抗检测的时候会用到。有些 App 会扫描设备上有没有监听 27042 端口的进程一旦发现就直接闪退。应对方法有两条路一条是给 frida-server 二进制文件改名另一条是修改它的默认监听端口。最粗暴的方式是下载源码自己改端口重编但对新手来说不太友好。提示frida-server 只是负责注入和通信真正执行注入脚本逻辑的是 PC 端。所以调试过程中PC 端终端会实时显示脚本里的 console.log 输出这一点理解之后你在排查“脚本没生效”这类问题时思路会清晰很多。1.3 连接测试与第一个 Demo Hook环境准备好以后建议先用一个 Demo App 做连接验证。某测试项目里我们用的包名是com.example.demo.target连上设备后启动它然后到 PC 端执行frida -U -f com.example.demo.target --no-pause -l hook_demo.js-f表示启动一个新的进程实例--no-pause表示不要在启动瞬间卡住主线程等待脚本加载完成-l指定脚本路径。这样就能在 App 刚启动时注入脚本适合 hook 那些只在启动阶段调用一次的逻辑。我自己平时写的 hook_demo.js 第一版一般都会先验证注入是否生效比如枚举一下当前加载的类或者简单 hook 一个系统方法Java.perform(function () { console.log([*] Java layer ready.); Java.enumerateLoadedClasses({ onMatch: function (cls) { if (cls.indexOf(demo.target) ! -1) { console.log([*] found: cls); } }, onComplete: function () { console.log([*] enumerate done.); } }); });执行 frida 命令之后如果终端能打印出匹配的类名就说明 frida-server 正常工作、注入链路也完整。这个验证步骤不要跳过后面所有复杂 hook 都建立在“注入能成功”这个前提上。2. Objection把常用操作变成一句话命令2.1 为什么有了 Frida 还要用 Objection很多人一开始只写 Frida 脚本也能完成很多操作但每次想分析一个没见过的 App都要重新写一遍枚举类、内存搜索、Activity 列举之类的代码效率就低了。Objection 的价值在于它把这类高频操作封装成一条条命令让你能在交互式 Shell 里快速检验一个方向到底可不可行。比如你想看目标 App 有哪些 Activity用 Frida 写脚本需要枚举并过滤类名在 Objection 里直接输入objection -g com.example.demo.target explore回车进入 objection 交互环境输入android hooking list activities所有导出的 Activity 就列出来了。又比如你想看这个 App 当前加载了哪些类输入android hooking list classes这些命令背后跑的其实还是 frida 注入脚本只是对象替你处理了繁琐的筛选和格式化输出。对于快速摸清一个 App 的结构来说Objection 比手写脚本省太多时间了。我常说 Frida 是步枪Objection 就是给步枪装上了一个瞄准镜打得准的前提还是 Frida 本身工作正常。2.2 安装 Objection 与连接常见问题Objection 的安装同样是 pippip install objection装完之后直接执行objection -g 包名 exploreObjection 会尝试 spawn 这个包并加载默认的 Frida 脚本。如果 spawn 失败这时候可以退而求其次先手动启动 App再使用objection -g 包名 --startup-command或者其他方式 attach。不过说实话Objection 平时最稳定的连接方式就是 spawn 模式因为它需要尽早介入来 hook 启动阶段。这里我遇到的比较多的报错是“Unable to start ... due to timeout”。这种问题通常是目标 App 在启动阶段就做了反调试、反 Frida 检测导致注入过程被中断。解决思路是先手动把 App 跑起来然后用objection -g 包名 explore对已经运行的进程做 attach某些检测逻辑只在启动早期生效错过那个窗口就相对宽松。Objection 交互环境的体验和 SQLite 命令行很像输入命令回车就执行输入help可以看到所有命令分类。不需要过多记忆命令先记住最常用的几条android hooking list classes、android hooking list activities、memory search、android root disable、android sslpinning disable这五条基本能覆盖大部分快速验证场景。2.3 内存搜索与绕过校验的典型操作举个例子某测试 App 在登录成功后会往内存里放一个明文 token 字符串你想快速验证这个字符串是否存在就可以用 memory search。Objection 交互里执行memory search demo_token执行之后会返回命中的内存地址。你可以结合 dump 内存、查看堆栈等方式确认这个 token 出现在哪个对象或者哪个方法附近。这种动态搜索能力在定位加密数据、关键字符串时非常高效也是在逆向分析里最朴素却最实用的场景之一。绕过 Root 检测的操作用一条命令就能完成android root disableObjection 的原理是替换掉 Java 层常用的 Root 检测 API比如System.getProperty、File.exists等让 App 认为当前环境没有 Root。不过它只对基于 Java 层判断的 Root 检测有效如果 App 在 native 层做了检测那还是需要配合 Frida 脚本去 hook native 函数。这也提醒我们任何工具都不是万能的关键还是要理解 App 做了哪些检测才能选对绕过方式。3. Frida 实战 Hook从 Java 层到 Native 层3.1 Java 层 Hook 的完整套路Objection 适合快速验证但做精准的漏洞验证和逻辑分析时还是要写 Frida 脚本。Java 层 Hook 的核心套路可以拆成四步找到目标类、找到目标方法、改写方法实现、验证结果。假设目标 App 有一个登录验证函数签名类似public boolean checkLogin(String username, String password)我想做的事情是固定密码无论用户输入什么只要用户名正确就返回 true。脚本可以这样写Java.perform(function () { var target Java.use(com.example.demo.target.LoginManager); target.checkLogin.implementation function (username, password) { console.log([*] checkLogin called, user username , pwd password); var result this.checkLogin(username, fixed_password); console.log([*] result result); return result; }; });这段脚本有几个关键点Java.perform是必须的包装层确保代码在 Java VM 的线程里执行。implementation function (...) {}替换了原方法。想在替换后的函数里调用原逻辑就用this.checkLogin(...)这是最典型的方法链调用方式。不想调用原逻辑时可以自由构造返回值这在绕过某些校验时非常好用。使用时注意函数重载问题。同一个类里可能存在多个同名方法比如checkLogin(String, String)和checkLogin(String)同时存在。直接写target.checkLogin.implementation ...可能会因为重载版本无法确定而失败或作用到错误的版本上。解决办法是显式指定重载var target Java.use(com.example.demo.target.LoginManager); target.checkLogin.overload(java.lang.String, java.lang.String).implementation function (username, password) { ... };3.2 批量 Hook 与构造参数问题有时候不知道 App 内部有哪些方法值得 hook这时候可以先把目标类里所有方法枚举出来看看有没有重要的敏感操作。比如我想看com.example.demo.target.ApiClient里所有方法的名字和参数类型Java.perform(function () { var clazz Java.use(com.example.demo.target.ApiClient); var methods clazz.class.getDeclaredMethods(); methods.forEach(function (m) { console.log([*] method: m.toString()); }); });不要小看这一步很多关键逻辑藏在一些不显眼的方法里直接看方法列表往往比猜函数名更高效。当然枚举完还要抓重点可以进一步查看某个类具体有哪些字段尤其是静态字段。静态字段往往保存了全局配置、加密密钥等敏感信息。构造参数这一点也容易踩坑。Hook 构造函数时Java.use出来的类直接.$init.overload(...).implementation来改写构造逻辑。需要创建对象实例时用$new方法。比如我想在 Hook 脚本里手动创建一个com.example.demo.target.RequestBuilder实例并调用它的某个方法写法是var requestBuilder Java.use(com.example.demo.target.RequestBuilder); var newInstance requestBuilder.$new(fake_request_id); newInstance.buildHeader();这种写法在日常分析中非常常见相当于你直接在目标进程里充当“调用者”模拟 App 内部业务逻辑从而观察返回值或堆栈。3.3 Native 层 Hook 与调用约定不少 App 把核心算法放到 so 文件里Java 层只是做了 JNI 调用。这种情况下 Java 层 Hook 就看不到具体的加解密逻辑了。Frida 同样可以对 native 函数做插桩核心是用Module.findExportByName找到 so 中导出函数的地址再用Interceptor.replace或Interceptor.attach挂钩。举个例子假设目标 so 是libdemo_core.so导出函数名是Java_com_example_demo_target_NativeBridge_encryptvar encryptPtr Module.findExportByName(libdemo_core.so, Java_com_example_demo_target_NativeBridge_encrypt); if (encryptPtr) { Interceptor.attach(encryptPtr, { onEnter: function (args) { console.log([*] encrypt called); }, onLeave: function (retval) { console.log([*] encrypt result ptr: retval); } }); }native hook 比 Java hook 繁琐的点在于参数和返回值都是指针你需要结合 JNI 规范来解析。比如 JNI 函数参数里第一个是JNIEnv*第二个是jobject从第三个参数开始才是真正的 Java 层参数。如果你的目标函数是 JNI 导出函数解析参数时就要把这些偏移量算清楚否则拿到的数据完全是错的。这里我需要补充一个经验看到Module.findExportByName找不到导出函数时不要慌。很多 so 里的函数被 strip 掉了符号导出表里没有。这种情况下可以先 dump so 文件用静态分析工具查看字符串引用、交叉引用定位到函数偏移后再用 Frida 的Module.getBaseAddress加上偏移量进行 hook这才是通用的 native hook 思路。4. Wallbreaker 插件让类结构分析不再靠猜4.1 什么时候需要 WallbreakerObjection 本身有很多实用功能但它的类 dump 能力相对基础尤其当你面对的是加固 App 时Java 层类很多是在运行时动态生成或者解密出来的静态反编译工具根本看不到完整的类结构。这时候 Wallbreaker 插件就显得非常有用。它的核心功能是直接从内存里读取类信息把当前进程中加载的所有类、类的字段、方法、继承关系等以可读的格式导出。我通常的使用场景是先打开 Objection对目标 App 做基本检测和绕过然后遇到了一个具体类想知道它的内部结构到底长什么样直接用 Wallbreaker 导出这个类的完整定义用文本形式看里面的字段名、方法名以及父类关系。这种能力在分析动态加载的类、自研混淆框架的类时是降维打击级别的。4.2 Wallbreaker 的安装与常用命令Wallbreaker 本质上是一个 Objection 插件安装时分为两部分先安装 objection再把 wallbreaker 扩展复制到 objection 的插件目录。比较新的 objection 版本也支持通过插件管理器来加载我自己习惯用的是把仓库克隆下来后直接复制插件文件夹的方式。克隆步骤大致是git clone https://github.com/xxxx/wallbreaker.git cd wallbreaker cp -r wallbreaker ~/.objection/plugins/执行完以后重启 objection进入交互环境后输入plugin wallbreaker应该就能看到插件加载成功的提示。不同版本的命令可能略有差别实在拿不准就先输入plugin wallbreaker help查看当前版本的说明。常用命令我整理在下面命令作用plugin wallbreaker list列出当前可用的类信息或已加载的类名列表plugin wallbreaker dump 全类名导出指定类的完整结构包括方法、字段、继承关系plugin wallbreaker analyse 全类名分析指定类的内部结构并展示关键字段和方法-f 保存路径把导出结果保存到文件方便后续分析注意这里说的“包含继承关系”是从运行时内存里面读取的所以即使 App 本身做了一层自定义加载只要类已经被 ClassLoader 加载进内存Wallbreaker 就能看到。4.3 结合实际案例分析一个动态加载的业务类有一次我在测试一个模拟项目 X 时发现它的核心逻辑不在主 DEX 里而是在启动过程中从服务器下载并动态加载了一个加密 DEX之后再用自定义 ClassLoader 加载某个关键类。静态分析工具完全看不到这个类我只能靠运行时分析。流程是这样的先正常打开 App 完成初始化然后进入 Objection 交互用plugin wallbreaker list配合过滤关键词找到相关的类名比如里面带core或者loader的类名。找到之后再用plugin wallbreaker dump com.xxx.dynamic.CoreLoader导出这个类的结构直接就能看到它的字段里有没有保存解密密钥、方法里有哪些关键调用点。基于导出的信息再返回去写 Frida 脚本 hook 这些方法整个过程就变得清晰了。没有 Wallbreaker 之前你需要靠猜测去枚举成千上万个类再手动检查每个类的方法表耗时且容易漏掉关键点。用上插件之后分析效率提升得非常明显。提示Wallbreaker 依赖的是运行时类信息所以使用它之前一定要确保目标 App 已经完整启动并加载了你要分析的类。如果类还没有被加载dump 会提示找不到对应类这时候先触发相关业务逻辑再回来 dump。5. 折腾实录常见问题与排查套路5.1 我在实战里踩过的 6 个典型坑第一步永远是确认环境但很多问题恰恰出在环境上。我把这些年踩过的坑整理成一张表方便你按图索骥。现象大概率原因解决方法提示unable to connect to remote frida-serverfrida-server 没有启动或设备端端口被占用检查/data/local/tmp/frida-server进程是否存活杀掉重启确认frida-ps -U能否列出进程frida-server 启动后马上消失设备 root 权限不足或二进制与设备架构不匹配确认是su环境下执行的用uname -m检查架构后重新下载spawn 模式注入崩溃缺少--no-pause或脚本里有未捕获异常加上--no-pause脚本里用try/catch包裹所有主要逻辑hook 方法不生效无任何日志输出类名错误、方法名错误、进程未启动完整先用 Objection 枚举类名比对再确认代码里是否抛了异常但被 console 吞掉App 闪退检测到 FridaApp 有反 Frida 检测逻辑尝试绕过检测、延迟 attach、或者换用更隐蔽的注入方式版本不一致导致协议错误PC 端 frida-tools 与设备端 frida-server 版本号不一致统一版本号重新下载设备端 frida-server重启设备端服务先给每个人我的核心建议当你觉得“为什么我搞不定”的时候先别急着怀疑技术的天花板检查一下环境。这个建议我给过很多人几乎每次都能帮他们定位到问题。尤其是在设备端和 PC 端的版本一致性、进程权限、注入时机这三个点它们构成了 90% 的 Frida 使用障碍。另外加固 App 是一个比较大的门类很多新手遇到加固之后就卡住了。常见方案是先脱壳再分析或者直接用 Frida 在运行时 dump 解密后的 DEX。Wallbreaker 在这个阶段的价值又会凸显出来因为它不从磁盘上读取 DEX而是直接从内存里拿类结构。就算壳没有完全脱掉只要类加载了它就能分析。5.2 真机选择与系统定制的个人建议做动态调试时用模拟器和用真机的体验差异非常大。模拟器方便快捷但很多安全检测 App 会检测模拟器特征比如 Build 信息、传感器数据、CPU 指令集等。如果你测试的 App 对模拟器有强检测模拟器里连启动都过不去。这时候一台能解锁 Bootloader、能 root 的真机会让你少走很多弯路。我的个人偏好是使用公版 Android 设备不推荐某些深度定制系统因为定制系统可能阉割底层调试接口或者自带一些检测逻辑干扰测试。拿到设备后第一件事是解锁 Bootloader然后刷入带 root 管理的系统最后确保adb root或者su可用。这里的细节比较多但总体上就是个标准流程网上有很多针对特定设备的教程可以参照。系统版本方面我建议优先用 Android 10 及以下的版本做动态测试因为高版本 Android 对 ptrace、文件系统访问的限制更严格Frida 注入的成功率和稳定性都会受到影响。当然这也不是绝对的部分新版本系统配合新版 frida 一样可以跑只不过坑会多一些。如果你手头有多个设备就选一个系统版本低、root 稳定的设备当成专用测试机。5.3 把 Frida 脚本整理成模板库最后分享一个能明显提升效率的习惯把自己常用的 Frida 脚本整理成模板库。别每次都从零开始写把反复用到的代码块抽出来比如function findClass(className) { Java.perform(function () { var classes Java.enumerateLoadedClassesSync(); classes.forEach(function (cls) { if (cls.includes(className)) { console.log([*] cls); } }); }); }再比如一个自动处理重载赋值的 helperfunction hookAllOverloads(targetClass, targetMethod, impl) { Java.perform(function () { var target Java.use(targetClass); var overloads target[targetMethod].overloads; overloads.forEach(function (overload) { overload.implementation impl; }); }); }有了这类模板接到一个新 App 之后我的工作流会变成这样先 Objection 看类名、看 Activity再 Wallbreaker 看目标类的结构然后从模板库里找到合适的 hook 片段改一下类名和方法名运行脚本观察输出。整套流程下来大部分测试目标在半小时内就能完成初步的动态分析。提示hook 一个方法时尽量用overload(...)显式指定参数类型。自动遍历所有重载虽然方便但可能出现不同重载逻辑差异很大统一返回固定值反而影响分析结果的情况。最后分享一点实操体会做过多年测试之后我最大的感受是逆向分析的工具本身并不复杂真正拉开差距的是对运行时机制的理解和对坑位的预判能力。Frida、Objection、Wallbreaker 这些工具都只是帮助你更快地看到内存里的真实状态而“看什么、为什么看、看完怎么做”才是核心。比如说你知道了 Frida 的工作原理是 ptrace 注入就能理解为什么有些 App 要检测 27042 端口也就能理解为什么修改监听端口、改二进制名可以对部分检测生效。这种从原理出发的排查方式比死记一堆命令要靠谱得多。我经常会建议身边的朋友先从模拟器加一个简单的 Demo App 练手把环境搭建跑通把第一个 hook 脚本跑通再用 Wallbreaker 导出一个真实类的结构这样就算正式入坑了。之后再慢慢扩展到加固对抗、Native 分析、协议还原这些高级方向就有底气了。这个组合的扩展空间还很大除了本篇文章提到的内容还可以尝试把 Frida 接入自己的自动化测试平台用 Python 调 frida 库批量跑脚本也可以把 Wallbreaker 的导出结果接进静态分析流程让动态信息和静态信息互相印证。希望这篇保姆级教程能帮你把基础打扎实。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑