Frida Hooker移动安全分析实战:从原理到对抗反调试
1. 项目概述为什么Frida Hooker是移动安全分析的“瑞士军刀”如果你正在阅读这篇文章大概率是刚接触移动安全逆向或者听说过Frida这个强大的动态插桩框架但面对网上零散的教程和复杂的命令行感到无从下手。别担心我完全理解这种感觉。几年前我第一次接触Frida时面对那些看似“黑魔法”的脚本也是一头雾水。但经过大量实战项目的锤炼我可以说掌握Frida尤其是其核心的Hook挂钩能力是打开移动应用内部世界大门的万能钥匙。它不像静态分析那样需要你面对海量的汇编代码也不像传统调试器那样步骤繁琐。Frida允许你像在应用运行时“注入”自己的逻辑实时查看、修改函数参数和返回值这种能力在漏洞挖掘、协议分析、安全评估甚至自动化测试中都是无价之宝。2024年移动应用生态更加复杂加固、混淆、反调试技术层出不穷但Frida及其活跃的社区也在不断进化。所谓的“Frida工具包hooker”并不是一个单一的软件而是指围绕Frida核心的Hook功能构建的一整套工具链、脚本集合与最佳实践。它包括了Frida核心引擎、Python绑定、各种实用脚本如 objection、frida-trace以及我们自定义的Hook脚本。本指南的目标就是带你从零开始搭建这套工具链理解其工作原理并最终能够针对真实场景编写有效的Hook脚本实现从“看教程”到“解决问题”的质变。无论你是安全研究员、逆向工程师还是对应用内部机制感到好奇的开发者这套指南都将为你提供一条清晰的路径。2. 环境搭建与工具链全解析工欲善其事必先利其器。一个稳定、高效的环境是后续所有操作的基础。这里我会详细拆解每个环节的选择与配置理由帮你避开初期最常见的坑。2.1 核心组件选型与安装Frida的生态主要由两部分组成服务端Server和客户端Client。服务端是一个需要运行在目标设备通常是Android手机或iOS设备上的守护进程客户端则是我们在电脑上运行的Python脚本或命令行工具用于与服务端通信并注入我们的JavaScript代码。第一步安装Python客户端这是我们的主控端。强烈建议使用Python 3.7及以上版本并通过pip安装。这里有一个关键细节Frida的Python包版本最好与后续安装的服务端版本保持一致以避免潜在的兼容性问题。pip install frida-tools这条命令会同时安装fridaPython绑定库和frida-tools一套实用的命令行工具如frida-psfrida-trace。为什么不直接pip install frida因为frida-tools包含了那些开箱即用的工具对于新手入门和日常使用来说方便得多。第二步准备目标设备与环境Android真机推荐这是最理想的测试环境。你需要一台已经解锁Bootloader并刷入了Magisk用于Root的手机。为什么推荐Root因为Root权限允许Frida以更高的权限运行能够Hook更多受保护的系统进程和函数绕过一些基础的反调试检测。对于新手我建议使用Google Pixel系列或小米的部分型号其社区支持好刷机资源丰富。Android模拟器如果暂时没有真机可以使用模拟器。推荐Google官方推出的Android Studio自带模拟器或者经过修改支持Root的Genymotion。在模拟器上安装Frida服务端相对简单但需要注意一些强力的加固方案在模拟器上可能行为与真机不同。iOS设备这需要一台已越狱的iPhone/iPad。在Cydia或Sileo等越狱商店中直接搜索并安装Frida即可。iOS上的逆向环境门槛更高本指南会以Android为主进行讲解但核心的Hook逻辑是相通的。注意使用真机进行逆向操作存在一定风险可能导致应用无法运行或设备不稳定。务必在专门的测试机上进行操作避免使用主力机。2.2 Frida Server的部署与连接这是将Frida能力“注入”目标设备的关键一步。你需要根据你的设备CPU架构通常是arm或arm64去Frida的GitHub Release页面下载对应的frida-server二进制文件。下载与推送将下载的frida-server-xx.x.x-android-arm64.xz解压得到frida-server文件。通过ADBAndroid Debug Bridge将其推送到设备的/data/local/tmp/目录这个目录通常具有可执行权限。adb push frida-server /data/local/tmp/ adb shell chmod 755 /data/local/tmp/frida-server运行服务端在设备上启动这个服务。adb shell su # 获取Root权限 cd /data/local/tmp ./frida-server 末尾的是让它在后台运行。你会看到命令提示符返回没有报错即表示启动成功。端口转发与验证为了让电脑上的客户端能连接到手机上的服务端需要建立一个ADB端口转发默认端口27042。adb forward tcp:27042 tcp:27042随后在电脑终端运行frida-ps -U如果它能列出你手机当前运行的进程列表那么恭喜你整个Frida通道已经打通了-U参数代表连接到USB设备。实操心得很多时候连接失败问题出在端口转发或权限上。确保adb devices能看到你的设备确保手机上的frida-server进程正在运行可以用ps | grep frida查看。如果frida-ps -U报错尝试重启frida-server并重新进行端口转发。2.3 辅助工具生态介绍单独使用Frida的核心库虽然强大但有些工具能极大提升效率它们构成了“工具包”的重要部分。Objection这是一个基于Frida的“运行时移动安全评估”工具它封装了很多常用操作。比如一键禁用SSL证书绑定SSL Pinning、一键列出Activity、快速搜索内存等。安装很简单pip install objection。它的优势在于命令直观对于常见任务可以免写脚本。objection -g 包名 explore # 进入交互模式后可以运行如 # android sslpinning disableFrida-trace这是一个用于快速跟踪函数调用的神器。你不需要写完整的JS脚本只需指定库名和函数名甚至函数名的部分字符串它就能自动生成Hook脚本并实时输出调用日志。这对于快速探索一个陌生应用的行为流程非常有帮助。frida-trace -U -i open 包名 # 这会跟踪目标应用中所有对open函数的调用编辑器与代码提示虽然你可以用任何文本编辑器写JS脚本但配置一个支持JavaScript语法高亮和Frida API提示的环境会舒服很多。VS Code加上合适的插件是不错的选择。关键是要习惯查阅 Frida JavaScript API文档 这是你Hook脚本的“字典”。3. Hook原理深度剖析与JavaScript API精讲在开始写脚本之前我们必须理解Frida是如何工作的以及它能做什么、不能做什么。这能帮助你在遇到问题时知道该朝哪个方向思考。3.1 Frida的核心工作原理动态插桩你可以把目标应用想象成一列正在轨道上运行的火车每一节车厢代表一个函数或一块代码。静态分析是等你把火车完全拆解后去研究每一节车厢的结构。而Frida的动态插桩则是在火车飞驰的过程中在特定的轨道岔口函数入口/出口设立一个“工作站”。当火车经过这个岔口时会被引导进你的工作站在这里你可以检查查看这节车厢里装了什么函数参数。记录记下车厢的编号和货物信息打印日志。修改偷偷换掉一部分货物修改参数或返回值。拦截甚至让火车完全停下来或者改变它的目的地阻止函数执行或改变执行流程。完成这些操作后火车会继续驶向原定目的地或你指定的新目的地。这一切对火车本身应用程序来说在理想情况下是毫无感知的。实现这一切的“魔法”在Android上主要依赖于ptrace系统调用和LD_PRELOAD等技术在iOS上则依赖于Cydia Substrate旧版或自己实现的注入框架。作为使用者我们不需要深究这些底层细节但理解这个“拦截并处理”的模型至关重要。3.2 JavaScript API 核心四件套我们的Hook脚本主要用JavaScript来写Frida提供了丰富的API。掌握下面这四个核心你就能解决80%的问题。Java.perform()这是所有Hook脚本的起点。你的主要Hook代码必须包裹在这个函数调用内部。这是因为Frida需要在正确的线程上下文中执行Java相关的操作。忘记它你的Java层Hook几乎肯定会失败。Java.perform(function () { // 所有的Hook代码写在这里面 console.log(脚本已加载); });Java.use()这是Hook Java类的钥匙。它用于获取一个Java类的包装对象通过这个对象你可以访问类的静态方法、Hook实例方法等。var StringClass Java.use(java.lang.String); // 现在StringClass就代表了java.lang.String类方法Hookimplementation这是最常用的操作。通过替换方法的implementation属性你可以在方法执行前后插入自己的逻辑。var MyClass Java.use(com.example.MyClass); MyClass.myMethod.implementation function (arg1, arg2) { // 1. 方法执行前可以打印参数 console.log(myMethod called with: arg1${arg1}, arg2${arg2}); // 2. 可以修改参数 var modifiedArg arg1 _hooked; // 3. 调用原方法可选。如果不调用则完全拦截此方法。 var retVal this.myMethod(modifiedArg, arg2); // 4. 方法执行后可以打印或修改返回值 console.log(myMethod returned: ${retVal}); retVal retVal _modified; // 5. 返回修改后的值 return retVal; };关键点this.myMethod在这里指的是原方法通过它调用原逻辑。this上下文在实例方法中指向当前对象。对象枚举与搜索Java.choose()很多时候我们需要Hook已经存在于内存中的对象实例而不是类。Java.choose()会在堆上遍历指定类的所有活实例并对每个实例执行你的回调函数。Java.choose(com.example.MyClass, { onMatch: function(instance) { // 对每一个找到的MyClass实例执行 console.log(Found instance: instance); console.log(Instance field value: instance.secretField.value); }, onComplete: function() { console.log(Search complete.); } });这在需要与应用程序的当前状态进行交互时非常有用比如直接调用某个实例的方法。3.3 数据类型转换与内存操作在JavaScript中处理Java对象和基本类型时需要一些转换。基本类型数字、布尔值在JS和Java间可以直接传递。字符串Java字符串是对象但在Frida的JS中你可以直接用JS字符串Frida会自动转换。不过当需要调用接收java.lang.String参数的原生方法时确保传递的是正确的类型。数组与列表Java数组在JS中对应类似数组的对象。你可以使用Java.array来创建Java数组。var intArray Java.array(int, [1, 2, 3, 4, 5]);内存操作Native层对于Hook NativeC/C函数你需要使用Interceptor.attach。这涉及更底层的指针操作需要一定的C语言和内存布局知识。// 假设我们已经找到了一个native函数的地址nativeFuncPtr Interceptor.attach(nativeFuncPtr, { onEnter: function(args) { // args[0], args[1]... 是参数 console.log(Native function called. First arg: args[0]); }, onLeave: function(retVal) { // retVal 是返回值 console.log(Native function returned: retVal); } });4. 从零编写你的第一个Hook脚本实战演练理论讲得再多不如动手一试。让我们从一个最简单的目标开始Hook一个Android应用中的java.lang.String的equals方法看看哪些字符串在被比较。4.1 目标分析与脚本结构我们的目标是在任何App中只要调用了String.equals()就打印出参与比较的两个字符串。这能帮助我们快速定位一些关键字符串判断逻辑比如密码验证、权限检查等。一个完整的Frida脚本通常包含以下部分引导部分Java.perform包裹。类获取使用Java.use获取目标类。方法Hook替换目标方法的implementation。逻辑实现在Hook函数里编写我们的逻辑打印、修改等。错误处理可选但重要使用try-catch包裹可能出错的代码。4.2 代码实现与逐行解读将以下代码保存为hook_string.js。Java.perform(function () { console.log([*] 开始Hook java.lang.String.equals); // 获取java.lang.String类的引用 var String Java.use(java.lang.String); // Hook equals 方法 String.equals.implementation function (obj) { // 1. 打印调用栈可选用于定位哪里调用了equals // console.log(Java.use(android.util.Log).getStackTraceString(Java.use(java.lang.Exception).$new())); // 2. 获取当前字符串对象的内容this代表调用equals的String对象 var thisString this.toString(); // 3. 获取传入的对比对象的内容。obj可能是null也可能不是String类型。 var compareString ; try { // 先判断是否为null再判断是否是String实例最后转换 if (obj ! null Java.cast(obj, Java.use(java.lang.Object)).getClass().getName().indexOf(String) ! -1) { compareString Java.cast(obj, Java.use(java.lang.String)).toString(); } else { compareString String(obj); // 尝试通用转换 } } catch (e) { compareString [Not a String or conversion failed]; } // 4. 打印关键信息 console.log([*] String.equals被调用:); console.log( this (当前字符串): ${thisString}); console.log( obj (比较对象): ${compareString}); // 5. 调用原方法执行实际的比较逻辑并获取结果 var result this.equals(obj); // 6. 打印比较结果 console.log( 返回值: ${result}); // 7. 返回原方法的返回值确保应用行为正常 return result; }; console.log([*] java.lang.String.equals Hook 完成); });逐行解读与避坑指南Java.perform脚本的必须入口。Java.use获取类引用。注意这里将类赋值给了变量String这会覆盖JS原本的String全局对象但在这个局部作用域内没有问题。更稳妥的做法是使用其他变量名如JString。implementation替换这是Hook的核心动作。this关键字在implementation函数内this指向调用该方法的Java对象实例一个String对象。参数处理equals方法接收一个Object类型参数。我们不能直接假设它是String。代码中使用了Java.cast进行安全转换和类型检查这是非常重要的健壮性处理。直接调用obj.toString()如果obj不是String或为null会导致脚本崩溃退出。调用原方法var result this.equals(obj);这里再次调用equals会不会导致无限递归不会。因为此时this.equals指向的是我们替换了的implementation函数吗不在这个implementation函数内部this.equals神奇地仍然指向原始的方法。这是Frida内部处理的机制让我们能方便地调用原逻辑。返回值务必返回原方法的返回值或你修改后的值除非你明确想改变程序流程。4.3 脚本注入与执行启动目标应用在手机上打开你想分析的应用。附加进程并注入脚本在电脑终端执行frida -U -l hook_string.js -f com.example.targetapp --no-pause-U: 连接到USB设备。-l: 加载指定的JavaScript脚本文件。-f: 启动一个新的进程指定包名。如果应用已运行你可以用-n指定应用名称或直接使用PID来附加。--no-pause: 启动后立即恢复进程运行否则进程会暂停。观察输出操作手机上的应用触发字符串比较逻辑比如登录操作。你会在终端看到大量的String.equals调用日志其中可能就包含你感兴趣的用户名、密码哈希、关键标识等。实操心得第一次运行可能会打印出海量日志因为系统内部也在频繁使用equals。这时候你需要结合你对目标应用的理解过滤日志。比如你怀疑登录密码是跟一个固定字符串比较那你就可以在脚本的console.log前加一个判断if (thisString.indexOf(“password”) ! -1 || compareString.indexOf(“password”) ! -1)只打印包含特定关键词的比较。这就是从“漫无目的”到“精准打击”的进化。5. 进阶Hook技巧与复杂场景应对掌握了基础Hook后我们会遇到更复杂的情况构造函数、重载方法、内部类、匿名类、资源ID和枚举等等。5.1 构造函数的Hook与对象创建监控Hook构造函数可以让你在对象被创建的第一时间就知道有时还能修改初始化的参数。var MyClass Java.use(com.example.MyClass); // Hook构造函数构造函数名固定为$init MyClass.$init.overload(java.lang.String, int).implementation function (name, count) { console.log([*] MyClass对象被创建: name${name}, count${count}); // 可以修改传入的参数 count count * 2; // 调用原构造函数必须调用否则对象初始化不完整 this.$init(name, count); // 构造函数没有返回值 };注意构造函数名是$init使用.overload(...)来指定你要Hook的特定参数列表的构造函数因为构造函数可以重载。5.2 重载方法的精确打击当一个类有多个同名但参数不同的方法时你需要使用.overload来指定具体是哪一个。var Utils Java.use(com.example.Utils); // Hook 参数为String的方法 Utils.doSomething.overload(java.lang.String).implementation function (str) { console.log(doSomething(String) called with: ${str}); return this.doSomething(str); }; // Hook 参数为int的方法 Utils.doSomething.overload(int).implementation function (num) { console.log(doSomething(int) called with: ${num}); return this.doSomething(num); };如果你不确定有哪些重载可以在脚本中先打印console.log(Utils.doSomething.overloads);它会列出所有重载签名。5.3 内部类与匿名类的Hook内部类的类名格式为OuterClass$InnerClass。匿名类则包含数字如OuterClass$1、OuterClass$2。找到它们的确切名称是难点。方法一使用frida-trace探索frida-trace -U -j * 包名可以跟踪所有Java方法观察日志输出从中找到你关心的类和方法名。方法二使用Java.enumerateLoadedClasses()这个API可以枚举所有已加载的类通过过滤类名来寻找。Java.perform(function () { Java.enumerateLoadedClasses({ onMatch: function (className) { if (className.includes(OuterClass) className.includes($)) { console.log(发现内部/匿名类: ${className}); } }, onComplete: function () {} }); });找到类名后Hook方式与普通类无异Java.use(com.example.OuterClass$1)。5.4 字段的读取与修改除了方法你也可以直接操作对象的字段。var MyClass Java.use(com.example.MyClass); // 读取/修改静态字段 console.log(静态字段值: MyClass.staticField.value); MyClass.staticField.value new value; // 对于实例字段你需要先有一个实例通过Java.choose或Hook方法获取 Java.choose(com.example.MyClass, { onMatch: function(instance) { console.log(实例字段值: instance.instanceField.value); instance.instanceField.value 100; }, onComplete: function() {} });5.5 主动调用Java方法有时我们不仅想观察还想主动“问”应用一些信息或者“让”它执行一些操作。Java.perform(function () { // 1. 主动调用静态方法 var System Java.use(java.lang.System); var currentTime System.currentTimeMillis(); console.log(当前时间戳: ${currentTime}); // 2. 主动调用实例方法 (需要先获取实例) Java.choose(android.app.ActivityThread, { onMatch: function(instance) { // 假设这个实例有一个getApplication方法 var app instance.getApplication(); console.log(Application: app); }, onComplete: function() {} }); // 3. 创建新对象并调用其方法 var ArrayList Java.use(java.util.ArrayList); var newList ArrayList.$new(); // 调用构造函数 newList.add(Hello from Frida); console.log(List size: newList.size()); });关键点主动调用构造函数使用$new()调用普通实例方法使用获取到的实例直接调用。6. 反调试、反Hook对抗与绕过思路在分析一些安全性较高的应用如金融、游戏类App时你可能会遇到各种防护措施导致Frida脚本失效、应用崩溃或检测到调试状态。这里分享一些常见的对抗点与绕过思路。6.1 常见检测手段检测Frida进程与服务检查/proc/self/status中的TracerPid不为0表示被调试遍历进程列表查找frida-server、frida-helper等进程名或检测27042等默认端口是否开放。检测内存特征Frida注入的库如libfrida-gadget.so或JavaScript引擎如libv8在内存中有特定特征字符串或代码片段。检测线程名Frida会创建一些特征线程如“pool-frida-”。检测文件系统检查设备是否已Root如/su、/magisk文件是否存在或是否存在Frida相关文件。完整性校验对自身的DEX文件、SO库进行校验防止被修改或注入。环境检测检测是否运行在模拟器中检测调试器属性android:debuggable。6.2 绕过方案与实战策略策略一隐身术 - 修改Frida特征这是最直接的方法。既然对方检测特征我们就改变特征。修改服务端名称与端口重新编译frida-server修改源码中的默认端口和进程名。对于新手可以使用社区修改好的版本但要注意安全。使用定制化的Gadget模式不采用frida-server独立进程模式而是将Frida的gadget库直接打包进目标APK或动态加载使其与应用进程融为一体更难被检测。脚本层面隐藏在Hook脚本一开始就主动移除或修改可能暴露的特征。例如Hookandroid.os.Process.myPid和读取/proc/self/status的函数返回伪造的“干净”信息。// 示例Hook读取status的函数过滤TracerPid var FileInputStream Java.use(java.io.FileInputStream); FileInputStream.$init.overload(java.io.File).implementation function(file) { if (file.getPath().includes(/proc/self/status)) { // 这里可以返回一个我们伪造的File对象比较复杂 console.log([*] 检测到对/proc/self/status的读取可能需要拦截); } // 继续原逻辑 return this.$init(file); }; // 更底层的做法是Hook libc的open函数策略二先发制人 - 提前Hook检测函数在应用的检测代码执行前就Hook掉关键的检测函数使其永远返回“安全”的结果。这需要你通过逆向分析找到检测逻辑的具体位置。定位关键类和方法使用frida-trace或逆向工具如JADX搜索TracerPid、frida、debug、root等关键词找到检测代码所在的类和方法。编写对抗Hook脚本// 假设检测函数在com.example.SecurityChecker.checkEnvironment() var SecurityChecker Java.use(com.example.SecurityChecker); SecurityChecker.checkEnvironment.implementation function() { console.log([*] 环境检测函数被调用直接返回安全); return false; // 假设返回false表示安全 }; // Hook检查文件是否存在的方法 var File Java.use(java.io.File); File.exists.implementation function() { var path this.getPath(); if (path.includes(frida) || path.includes(/su)) { console.log([*] 拦截对敏感路径的检查: ${path}); return false; // 告诉应用文件不存在 } return this.exists(); };策略三降维打击 - 使用更底层的工具组合当Frida被针对性地防御时可以考虑结合其他工具。使用objection的隐藏功能objection启动时有一些参数可以尝试隐藏自身如--startup-command “android hooking set return_value”需具体研究。基于内核的调试对于极度顽固的应用可能需要动用更底层的动态分析工具如ptrace直接附加或使用GDB但这需要极高的系统权限和专业知识。静态分析与动态结合先通过静态分析JADX, IDA彻底理解反调试逻辑再设计精确的Hook点而不是盲目尝试。实操心得对抗是一个持续的过程。没有一劳永逸的方案。我的经验是对于大多数中等级别的防护“修改特征关键函数Hook”的组合拳通常有效。首先尝试使用修改版的frida-server然后在脚本最开始就注入针对性的反反调试Hook。如果应用崩溃需要分析日志logcat看崩溃在哪个环节再针对性地解决。保持耐心逆向工程本身就是一场与开发者之间的智力博弈。7. 高效问题排查与脚本调试心法即使经验丰富写Hook脚本也难免遇到问题脚本不生效、应用崩溃、输出不符合预期。建立一套高效的排查流程至关重要。7.1 问题排查金字塔当脚本无效时按照从下到上的顺序排查基础连接层Frida服务端真的运行了吗frida-ps -U能列出进程吗设备是否掉线端口转发是否正常这是最常见也最容易被忽视的问题。脚本加载层脚本语法有错误吗可以在脚本开头加一句console.log(“脚本开始执行”);来验证脚本是否被成功加载和解析。如果这行都没打印说明脚本加载失败检查JS语法。Java上下文层你的Hook代码写在Java.perform里面了吗没有它所有Java.use都会失败。确保没有因为异常导致脚本提前退出用try-catch包裹可能出错的代码块。目标定位层你Hook的类名和方法名完全正确吗大小写包名是否是内部类使用frida-trace或Java.enumerateLoadedClasses()来确认类是否被加载方法签名是否正确。逻辑错误层脚本逻辑本身是否有问题比如在implementation函数内递归调用了自己导致死循环对null值没有判断仔细审查代码逻辑。7.2 实用调试技巧善用console.log这是最直接的调试工具。不仅打印参数返回值还可以打印调用栈来定位代码执行路径。var Log Java.use(android.util.Log); var Exception Java.use(java.lang.Exception); console.log(Log.getStackTraceString(Exception.$new()));使用send()和recv()进行异步通信console.log输出在Frida CLI中有时不方便处理。你可以用send()将数据发回Python端用recv()接收Python端的指令实现更复杂的交互调试。// JS端 send({type: data, payload: importantValue}); // Python端 (在on_message回调中处理)利用setTimeout延迟执行有些Hook需要在应用初始化完成后进行过早注入可能找不到类。可以用setTimeout将Hook代码包裹延迟执行。setTimeout(function() { Java.perform(function () { // 延迟执行的Hook代码 }); }, 3000); // 延迟3秒分模块测试不要一次性写一个庞大的脚本。先写一个小脚本测试能否成功Hook一个简单的系统类如java.lang.String。成功后再逐步添加对目标类的Hook。7.3 常见错误与解决方案速查表错误现象可能原因解决方案TypeError: cannot read property ‘implementation’ of undefined1. 类名错误类不存在。2. 方法名错误或方法不存在。3. 类尚未被加载。1. 使用Java.enumerateLoadedClasses确认类名。2. 使用frida-trace或反射(class.getMethods)确认方法。3. 使用setTimeout延迟Hook或Hook类加载器。脚本执行无任何输出进程退出1. 脚本中存在未捕获的异常。2.Java.perform外使用了Java API。1. 在关键代码块外包裹try-catch打印错误。2. 确保所有Java相关操作都在Java.perform回调内。应用在Hook后立即崩溃1. Hook函数逻辑错误导致内存破坏或无限递归。2. 修改了关键数据导致应用状态异常。3. 反调试机制触发。1. 检查Hook函数逻辑确保调用原方法(this.originalMethod)时参数正确。2. 先注释掉修改参数/返回值的代码只打印日志看是否还崩溃。3. 查看logcat崩溃日志定位崩溃点并实施反反调试。frida-ps -U无法列出进程1.frida-server未运行或已退出。2. ADB连接断开或端口转发失败。3. 设备未授权调试。1. 重新运行frida-server。2. 执行adb kill-server adb start-server重新adb forward。3. 检查手机弹出的是否授权了电脑调试。Hook系统类成功但Hook应用自定义类失败应用使用了自定义类加载器如插件化、热修复框架类不在系统ClassLoader中。使用Java.ClassFactory或遍历所有ClassLoader来获取类引用。Java.enumerateClassLoaders可以帮助找到正确的加载器。掌握这些排查方法你就能像侦探一样逐步缩小问题范围最终定位并解决它。每一次解决问题的过程都是对Frida和目标应用理解加深的过程。