
1. 项目概述为什么我们需要告别零散教程如果你在搜索引擎里输入“Frida Hook Android”大概率会得到一堆零散的博客、论坛帖子和代码片段。有的教你如何安装Frida有的给你一段Hook某个API的脚本还有的告诉你Objection怎么启动。但当你真正上手想把一个完整的应用分析流程跑通时会发现这些碎片化的知识像一堆散落的拼图你很难把它们拼成一个完整的画面。不是环境报错就是脚本失效或者根本不知道下一步该做什么。这正是“告别零散教程”这个标题想解决的问题。它不是一个简单的工具介绍而是一套从环境搭建到实战应用再到问题排查的完整作战手册。核心目标是让你掌握如何将Frida和Objection这两个强大的动态分析工具组合使用形成一套高效、可复现的分析工作流并聚焦于两个最具威力的实战场景——内存搜索与Hook挂钩。Frida是一个动态代码插桩工具简单说它允许你在应用运行时像做手术一样向目标进程注入你自己的JavaScript代码从而监视、修改甚至替换应用的原生函数和逻辑。而Objection则是建立在Frida之上的一个“瑞士军刀”它通过命令行提供了大量开箱即用的功能让你无需编写复杂的脚本就能快速进行内存枚举、类搜索、方法Hook等操作。为什么是“动态分析”在安全研究和逆向工程中静态分析直接看反编译的代码就像看地图而动态分析在应用运行时观察则是亲自开车上路。很多关键逻辑如加密算法、网络请求构造、授权验证只有在运行时才会暴露动态分析是触及这些核心的必经之路。通过这个项目你将学会的不是几个孤立的命令而是如何像一位熟练的侦探在应用的“活体”中寻找线索、设置监视点并最终理解其行为逻辑。2. 核心工具链解析Frida与Objection的定位与协同在开始实战前我们必须厘清Frida和Objection各自扮演的角色以及它们如何协同工作。这就像理解狙击手和观察手的关系各自独立强大但组合起来才能发挥最大效能。2.1 Frida底层的代码插桩引擎Frida是这一切的基石。它的核心是一个注入到目标进程中的“小精灵”称为frida-server这个“小精灵”在目标进程内部创建了一个JavaScript运行时环境。你编写的JavaScript脚本可以通过Frida的Python绑定或命令行工具frida发送给这个“小精灵”执行。Frida的核心价值在于其灵活性语言无关可以Hook JavaAndroid、Objective-C/SwiftiOS、C/CNative层代码。脚本化使用JavaScript编写Hook逻辑开发调试速度快。动态性无需重启应用随时注入、修改、卸载脚本。一个最基础的Frida脚本结构如下Java.perform(function() { // 确保在Java虚拟机上下文中执行 var TargetClass Java.use(com.example.app.TargetClass); TargetClass.targetMethod.implementation function(arg1, arg2) { // 调用原方法前执行的操作 console.log([*] targetMethod called! arg1: arg1); // 调用原方法并获取返回值 var retval this.targetMethod(arg1, arg2); // 调用原方法后执行的操作 console.log([*] targetMethod returned: retval); // 可以修改返回值 return retval; }; });这段脚本Hook了com.example.app.TargetClass类的targetMethod方法在方法调用前后打印参数和返回值。这就是Frida工作的基本范式。2.2 Objection基于Frida的自动化渗透测试运行时如果说Frida给了你一套精密的机床和原材料那么Objection就是一套已经组装好的、针对移动应用特别是Android和iOS的常用工具套装。它内置了数十个命令覆盖了从基础信息收集到高级Hook的常见需求。Objection的核心价值在于其便捷性和自动化无需编码通过命令行直接执行常见任务如列出Activity、搜索类、Hook方法等。REPL环境提供一个交互式环境可以边探索边测试。内存操作内置强大的内存搜索和修改功能这是本项目的重点之一。例如在Objection的REPL中你只需要输入android hooking list activities它就会自动枚举出当前应用的所有Activity而无需你编写任何脚本去遍历PackageManager。协同工作模式 典型的流程是使用Objection进行快速的侦查和初步探索如找类、找方法、测试简单Hook。当Objection的内置命令无法满足复杂需求时如需要复杂的逻辑判断、修改算法、批量处理再切换到编写自定义的Frida脚本利用Frida的完整API实现精细控制。Objection本身也支持加载并运行你写好的Frida脚本import命令。两者是互补而非替代的关系。注意很多新手会混淆frida-toolsFrida的Python CLI工具如fridafrida-ps和objection。请记住objection是一个独立的工具它内部使用了Frida的Python绑定。安装Objection时通常会连带安装Frida的Python包但两者的命令行接口是分开的。3. 环境搭建与配置打造稳定的分析工作站一个稳定、隔离的分析环境是成功的第一步。这里我推荐使用Android模拟器而非真机进行初学练习因为它更容易重置、快照和进行一些底层操作。3.1 模拟器选择与系统镜像准备首选方案Android Studio 自带的模拟器安装Android Studio从官网下载并安装。安装时确保勾选“Android Virtual Device (AVD)”组件。创建AVD打开AVD Manager点击“Create Virtual Device”。设备型号选择Pixel 4或Pixel 6等主流型号即可。系统镜像这是关键强烈推荐选择 x86_64 架构的镜像并且API等级最好在28 (Android 9) 到 33 (Android 13) 之间。原因如下Frida-server兼容性Frida为x86_64架构提供的server版本最稳定问题最少。ARM架构的模拟器或真机可能需要处理ARM到x86的二进制翻译有时会引入奇怪的问题。Root权限在创建AVD的最后一步点击“Show Advanced Settings”找到“AVD Name”下方的选项将Boot option从Standard改为Cold boot。然后在启动后更容易获取root权限对于模拟器通常可以通过adb root命令直接获取。启动并配置启动AVD。第一次启动后建议进入Settings - System - Advanced - Developer options开启“USB debugging”。虽然模拟器通过ADB直接连接但开启这个选项是个好习惯。备选方案GenymotionGenymotion是另一个优秀的模拟器性能通常更好且预装了Google Play服务。配置思路类似选择x86_64架构的系统镜像。3.2 Frida生态安装客户端与服务器端Frida分为客户端Client和服务器端Server。客户端运行在你的电脑分析机上服务器端运行在目标设备手机/模拟器上。步骤1在电脑上安装Frida客户端和Objection打开你的电脑终端Windows用PowerShell或CMDmacOS/Linux用Terminal。# 安装Frida的Python工具包客户端 pip install frida-tools # 安装Objection pip install objection # 安装用于脚本开发的Node.js环境可选但推荐便于管理复杂的JS脚本 # 可以从Node.js官网下载安装包安装完成后运行frida --version和objection --version验证安装。步骤2在模拟器中安装Frida-server确定架构和版本在模拟器的终端可以通过adb shell进入运行getprop ro.product.cpu.abi确认输出是x86_64。然后运行frida --version查看你刚安装的客户端版本号例如16.1.4。下载匹配的server访问Frida的GitHub Releases页面找到与你客户端版本号相同的发布包。下载名为frida-server-16.1.4-android-x86_64.xz的文件。推送并启动# 解压下载的.xz文件得到frida-server-16.1.4-android-x86_64文件 # 将文件推送到模拟器的临时目录并赋予执行权限 adb push frida-server-16.1.4-android-x86_64 /data/local/tmp/frida-server adb shell chmod 755 /data/local/tmp/frida-server # 进入模拟器shell并以root身份启动server注意前面的让它在后台运行 adb shell su cd /data/local/tmp ./frida-server 验证连接保持模拟器shell开启server在电脑的新终端运行frida-ps -U如果成功列出模拟器中运行的进程列表恭喜你环境搭建成功实操心得经常有人卡在frida-ps -U报错“Unable to connect to remote frida-server”。99%的原因是两个1.版本不匹配客户端和server版本必须严格一致2.server没有以root权限运行在模拟器adb shell后必须先执行su。务必仔细检查这两点。4. 实战核心一使用Objection进行高效内存搜索内存搜索是动态分析的“雷达”它能帮助我们在应用的运行时内存中快速定位敏感数据如字符串、类实例、加密密钥等。Objection的memory命令集让这个过程变得极其简单。4.1 启动Objection并附加目标应用假设我们要分析一个名为com.vulnerable.app的应用。# 方法1如果应用已启动直接附加 objection -g com.vulnerable.app explore # 方法2如果应用未启动让Objection启动它并附加 objection -g com.vulnerable.app explore --startup-command android hooking list activities-g参数指定目标包名explore命令会启动一个REPL交互式解释器环境。成功进入后提示符会变成[usb] #。4.2 内存搜索的四大常用命令在Objection的REPL中我们可以执行以下搜索1. 搜索所有可读字符串[usb] # memory search --string password这个命令会在整个进程内存中扫描所有包含子串“password”的可读字符串区域。结果会显示该字符串所在的内存地址。这对于寻找硬编码的密钥、URL、调试信息非常有效。2. 搜索并转储Dump内存区域找到地址后我们可能需要查看它周围的内存内容。[usb] # memory dump --address 0x7a12c4d000 --size 512--address指定起始地址--size指定要转储的字节数。输出会是十六进制和ASCII格式帮助你分析数据结构。3. 搜索所有对特定类的引用实例这是Objection非常强大的功能。假设我们通过反编译或猜测知道有一个类com.vulnerable.app.SecretManager。[usb] # memory search --class-name SecretManagerObjection会列出内存中所有SecretManager类的实例对象的引用地址。拿到这个地址我们就可以进一步去检查或修改这个对象的状态。4. 搜索并列出所有类当你对应用结构一无所知时可以先进行“广域扫描”。[usb] # android hooking list classes这个命令会列出所有已加载的Java类。输出可能非常长通常我们会配合grep在Objection外或者使用--filter参数来过滤。[usb] # android hooking list classes --filter Secret4.3 内存搜索实战案例定位登录令牌让我们模拟一个真实场景。一个应用登录后会将令牌Token保存在内存的某个对象里。附加应用objection -g com.vulnerable.app explore。触发登录在模拟器上手动完成登录操作。搜索可疑字符串在Objection中搜索“token”、“session”、“auth”等关键词。[usb] # memory search --string token假设在地址0x7a12c4d100处找到了一个字符串eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...看起来像JWT Token。寻找持有者光有字符串地址还不够我们需要知道是哪个对象持有这个字符串。我们可以尝试搜索这个字符串的引用或者更常见的是结合下一步的Hook去拦截返回或设置这个Token的方法。注意事项内存搜索是“暴力”的可能会搜到很多无关结果如库文件中的字符串。关键在于结合上下文和多次搜索。在登录前搜索一次作为基线登录后再搜索一次对比新增的结果往往就是你要找的目标。此外对于复杂对象直接搜索字符串可能找不到这时就需要用到“搜索类实例”的功能。5. 实战核心二编写Frida脚本实现精准Hook当Objection的内置Hook命令如android hooking watch class无法满足复杂需求时我们就需要祭出自定义Frida脚本这个“大杀器”。我们将通过一个完整的例子讲解如何Hook一个假设的登录验证方法。5.1 目标分析与脚本规划假设通过静态分析或Objection的类列表我们发现了目标类和方法类名com.vulnerable.app.auth.LoginManager方法public boolean doLogin(String username, String password)我们的Hook目标打印每次登录调用的用户名和密码明文或处理后的。强制让某次特定的登录尝试成功绕过验证。修改方法的返回值。5.2 脚本编写详解创建一个名为hook_login.js的文件。console.log([*] Starting login hook script...); Java.perform(function() { // 1. 获取目标类的引用 var LoginManager Java.use(com.vulnerable.app.auth.LoginManager); // 2. Hook 目标方法 LoginManager.doLogin.implementation function(username, password) { // 2.1 打印传入参数 console.log(\n[] Login attempt intercepted!); console.log( Username: username); console.log( Password: password); // 注意实际可能是加密后的 // 2.2 可选动态修改参数 // 例如将所有尝试的用户名改为admin // username admin; // console.log([*] Username modified to: admin); // 2.3 调用原方法并获取其结果 var originalResult this.doLogin(username, password); console.log([*] Original login result: originalResult); // 2.4 可选强制修改返回值 var forcedResult true; // 强制登录成功 console.log([!] Forcing login result to: forcedResult); // 2.5 返回修改后的值 return forcedResult; // 如果不想修改直接返回原结果 // return originalResult; }; console.log([*] LoginManager.doLogin() hook installed successfully.); });5.3 脚本注入与执行有几种方式运行这个脚本方法A使用Frida CLI直接注入frida -U -l hook_login.js -f com.vulnerable.app --no-pause-U: 连接到USB设备。-l: 加载脚本文件。-f: 启动目标应用如果已运行则用-F附加前台应用。--no-pause: 启动后不暂停应用。方法B在Objection REPL中导入首先进入Objection环境然后[usb] # import hook_login.jsObjection会自动加载并执行脚本。这种方式的好处是你可以在Objection环境中继续使用其他命令同时脚本在后台生效。方法C使用Python脚本控制适合自动化创建一个runner.pyimport frida import sys def on_message(message, data): if message[type] send: print(f[*] {message[payload]}) else: print(message) # 连接到设备 device frida.get_usb_device() # 附加到目标进程 pid device.spawn([com.vulnerable.app]) # 如果重启应用 # pid device.get_frontmost_application().pid # 如果附加到前台应用 session device.attach(pid) # 读取JS脚本 with open(hook_login.js, r) as f: jscode f.read() # 创建脚本并加载 script session.create_script(jscode) script.on(message, on_message) script.load() # 如果用了spawn需要恢复进程运行 device.resume(pid) # 保持脚本运行 sys.stdin.read()运行python runner.py。这种方式提供了最大的灵活性可以集成到更复杂的自动化流程中。5.4 高级Hook技巧重载方法与参数处理目标方法可能有多个重载Overload。上面的脚本Hook了所有doLogin方法。如果你想针对特定参数类型进行Hook需要更精确的定位。// Hook 参数为 (String, String) 的重载 LoginManager.doLogin.overload(java.lang.String, java.lang.String).implementation function(...){ // ... }; // Hook 参数为 (String, String, Context) 的重载 LoginManager.doLogin.overload(java.lang.String, java.lang.String, android.content.Context).implementation function(...){ // ... };对于参数和返回值为复杂对象非基本类型的情况Frida可以自动处理。你可以直接访问其字段或调用其方法。var UserObject Java.use(com.vulnerable.app.model.User); LoginManager.getCurrentUser.implementation function() { var user this.getCurrentUser(); // 调用原方法 console.log([*] Current user ID: user.uid.value); console.log([*] Current user name: user.getName()); // 甚至可以修改这个对象 user.uid.value 9999; return user; };实操心得Hook脚本最常遇到的错误是“Class not found”或“Method not found”。这通常是因为类尚未被加载。Android应用有很多类是在特定时机如进入某个界面才被ClassLoader加载的。解决方法有两种1.延迟Hook将Hook逻辑包裹在setTimeout或Java.choose中等待类出现。2.在合适的时机注入确保在触发目标功能如点击登录按钮前脚本已经注入并执行了Java.perform。使用-f参数让Frida在应用启动时就注入可以解决大部分类加载问题。6. 组合技实战内存搜索定位 Frida脚本Hook现在我们将第四章和第五章的技巧结合起来完成一个更贴近真实的挑战找到一个在内存中生成的、用于API签名的临时密钥并Hook其生成函数。场景应用每次启动会生成一个随机的secretKey用于给网络请求签名。这个key不存在于代码中只存在于内存。步骤1使用Objection进行侦查启动应用并附加Objection。使用android hooking list classes搜索包含“Key”、“Secret”、“Sign”等关键词的类。假设我们发现一个可疑类com.vulnerable.app.network.SignatureHelper。列出这个类的方法android hooking list class_methods com.vulnerable.app.network.SignatureHelper。发现一个方法public String generateSecretKey()。步骤2编写Hook脚本捕获密钥创建脚本hook_secret.jsHook这个生成方法。Java.perform(function() { var SignatureHelper Java.use(com.vulnerable.app.network.SignatureHelper); SignatureHelper.generateSecretKey.implementation function() { var key this.generateSecretKey(); // 先调用原方法获取密钥 console.log(\n[CRITICAL] Secret Key Generated: key); // 将密钥发送到我们的服务器或保存到文件此处仅打印 send(key); // 使用send函数可以在Python端通过on_message回调接收 return key; // 返回原密钥避免影响应用正常逻辑 }; });步骤3结合内存搜索验证运行Hook脚本触发一次网络请求让应用调用generateSecretKey。在日志中记下这个Key例如a1b2c3d4e5f6。在Objection中立即搜索这个字符串memory search --string a1b2c3d4e5f6。如果找到Objection会输出其内存地址。这证实了我们的Hook是有效的并且密钥确实存在于进程内存中。进阶我们可以进一步搜索是谁持有了这个字符串的引用。虽然Objection没有直接命令但我们可以通过Hook其他可能使用这个key的方法如签名方法来追溯调用链。步骤4持久化监控与篡改可选如果我们想每次启动都捕获密钥或者甚至想替换成我们自己的固定密钥以进行中间人攻击测试可以修改脚本Java.perform(function() { var SignatureHelper Java.use(com.vulnerable.app.network.SignatureHelper); SignatureHelper.generateSecretKey.implementation function() { // 不再调用原方法直接返回我们伪造的固定密钥 var fakeKey MY_STATIC_KEY_12345; console.log([!] Bypass: Returning fake secret key: fakeKey); return fakeKey; }; });这样所有依赖这个密钥的签名都会失效或者如果我们知道服务器验证逻辑可以构造对应的请求从而完全绕过客户端的签名校验。7. 常见问题、排查技巧与高级话题实录动态分析之路不可能一帆风顺。下面是我在无数次实战中踩坑后总结的“生存指南”。7.1 连接与注入问题问题1frida-ps -U报错 “Failed to enumerate processes: unable to connect to remote frida-server”排查检查设备连接adb devices确保设备已列出。检查server进程在设备shell中执行ps | grep frida-server查看进程是否存在。检查版本frida --version(电脑) 和 设备上运行的frida-server版本必须完全一致。检查权限确保在模拟器或已root的真机上frida-server是以root权限运行的在adb shell后先执行su。检查端口冲突极少数情况27042端口被占用。可以尝试在启动server时指定其他端口./frida-server -l 0.0.0.0:9999并在电脑端连接时加上-H 设备IP:9999。问题2注入脚本时出现 “TypeError: cannot read property ‘implementation’ of undefined”原因最常见的错误意味着Java.use()没有找到指定的类。解决类名拼写错误仔细检查包名和类名大小写敏感。类未加载这是主因。将Hook代码包裹在setTimeout中延迟执行。setTimeout(function() { Java.perform(function() { var MyClass Java.use(com.example.MyClass); // ... hook logic }); }, 3000); // 延迟3秒执行使用Java.choose在堆上查找已存在的实例如果你知道这个类已经有实例了。Java.choose(com.example.MyClass, { onMatch: function(instance) { console.log(Found instance: instance); // 可以Hook这个实例的方法但注意是实例方法不是类方法 }, onComplete: function() {} });确保脚本注入时机正确在应用启动后、目标功能被调用前注入。使用-f参数自动启动并注入是最可靠的方式。7.2 脚本调试与性能问题3脚本导致应用崩溃或无响应原因Hook的函数可能被高频调用如在循环中你的脚本逻辑太慢或存在错误。解决精简脚本逻辑在Hook函数中避免复杂的计算、同步网络请求或无限循环。使用send()异步处理将需要耗时处理的数据通过send()发送到Python端处理避免阻塞目标线程。条件Hook不是每次调用都需要处理可以加判断条件。TargetClass.targetMethod.implementation function(arg) { if (arg specific_value) { // 只处理特定参数 console.log(Intercepted!); } return this.targetMethod(arg); // 尽快返回 };使用try-catch包裹你的Hook逻辑避免未处理的异常导致进程崩溃。问题4如何查看Frida和Objection的详细日志Frida运行Frida命令时添加-D参数可以输出更详细的设备通信日志。Objection启动时添加-d参数开启调试模式会打印底层的Frida通信信息。在脚本中除了console.log还可以使用console.warn,console.error。使用send()传递的数据可以在Python端的on_message回调中更结构化地处理。7.3 对抗与反调试问题5应用检测到Frida或Objection并崩溃一些加固或安全意识强的应用会检测Frida的典型特征如特定端口、进程名、文件、映射内存等。基础绕过重命名frida-server将推送到设备的frida-server文件改一个不起眼的名字如/data/local/tmp/fs。修改监听端口如前所述启动server时用-l 0.0.0.0:随机端口。使用非标准模式尝试使用frida的--stdio模式需要修改注入方式。高级绕过这涉及更深的攻防可能需要定制Frida的编译版本、修改特征字符串或者使用其他Hook框架如Dobby Substrate进行互补。这超出了入门范畴但需要意识到这种对抗的存在。7.4 资源管理与清理问题6如何卸载或更新已注入的脚本在Frida CLI或Python脚本中按CtrlC通常会终止会话并卸载所有脚本。在Objection REPL中输入jobs list查看正在运行的脚本任务然后使用jobs kill [任务ID]来停止特定脚本。更干净的做法是在脚本中为重要的Hook保留引用并在需要时主动恢复。var hook ImplementationWrapper; // 保存原方法的引用 TargetClass.method.implementation function(...) { ... }; // 在某个条件下恢复原方法 // someCondition true; if (someCondition) { TargetClass.method.implementation hook; }我个人在实际操作中的体会是动态分析的成功30%靠工具熟练度70%靠对目标应用的耐心观察和逻辑推理。不要指望一次Hook就能拿到所有答案。它更像是一个交互式的调试过程先做一个假设“这个按钮点击后会调用A方法”然后用Objection或Frida去验证搜索类、Hook方法根据结果修正你的假设再继续探索。养成随时使用console.log()输出关键变量状态的习惯这些日志是你理解应用运行逻辑的“导航仪”。最后记得将你验证有效的脚本和命令整理成文档这就是你告别零散教程后为自己构建的、可重复使用的知识体系。