移动App逆向实战:Frida动态Hook与协议分析全流程解析

移动App逆向实战:Frida动态Hook与协议分析全流程解析 1. 项目概述与核心目标最近在分析一些移动应用的数据交互逻辑时我选择了一个典型的阅读类App——“某点小说”作为实战对象。这个项目的目的很明确完整地走一遍从网络数据抓取到应用层代码Hook的逆向分析流程。这不仅仅是破解某个功能更重要的是理解一个商业化App是如何构建其通信、加密和业务逻辑的这对于安全研究、竞品分析或是理解移动端架构都大有裨益。整个过程涉及静态分析、动态调试、协议逆向等多个环节我会把每一步踩过的坑和总结的技巧都分享出来。选择“某点小说”是因为它用户基数大其客户端与服务器之间的通信机制、内容加密方式、用户认证流程都具有一定的代表性。通过这次实战你不仅能学会如何使用Fiddler、Charles、BurpSuite等工具进行抓包还能掌握如何利用Frida这个强大的动态插桩工具去Hook关键的函数解密数据甚至修改应用行为。无论你是移动安全的新手还是想深化逆向技能的开发者这篇记录都能提供一个清晰的、可复现的路径。2. 逆向分析环境与工具链搭建工欲善其事必先利其器。一个稳定、高效的逆向分析环境是成功的一半。这个环境需要兼顾网络抓包、动态调试和代码分析。2.1 核心工具选型与配置网络抓包工具是逆向的“眼睛”。我通常会准备两套方案Fiddler/Charles和BurpSuite。Fiddler和Charles在HTTPS解密和移动端代理设置上非常直观友好适合快速查看和修改HTTP/HTTPS请求。而BurpSuite更偏向安全测试其Repeater、Intruder等功能在参数爆破和漏洞探测时无可替代。对于“某点小说”这类App我建议先用Fiddler或Charles进行初步的协议分析。Android测试环境的选择上真机和模拟器各有优劣。真机尤其是Root过的能提供最真实的环境但操作不便。我更多使用雷电模拟器它兼容性好自带Root权限需要手动在设置中开启方便安装Xposed、Frida等框架。在模拟器中安装目标App后别忘了配置Wi-Fi代理指向你运行抓包工具的主机IP和端口如192.168.1.100:8888并在主机上安装抓包工具的CA证书到模拟器中以解密HTTPS流量。Frida是整个动态分析的核心。它分为两部分在PC上运行的客户端frida-tools和在移动设备或模拟器上运行的服务端frida-server。你需要根据模拟器或手机的架构通常是x86或arm下载对应版本的frida-server通过adb push推送到设备并赋予可执行权限后运行。PC端通过pip install frida-tools安装。一个常见的坑是版本不匹配务必保持frida和frida-server版本一致。2.2 辅助工具与脚本准备除了核心工具一些辅助脚本能极大提升效率。对于Frida可以提前准备一些通用脚本比如枚举所有类和方法、跟踪某个类的所有方法调用、以及拦截加解密函数如javax.crypto.Cipher的doFinal方法的脚本。网络上有很多开源的一体化Frida脚本它们集成了常用功能但理解其原理并自己修改以适应特定App更重要。静态分析工具如JADX-GUI或APKTool也是必备的。在开始动态Hook之前先用它们反编译APK浏览一下Java代码搜索关键词如“encrypt”、“decode”、“sign”、“token”可以快速定位到可能的关键函数为后续的Frida Hook提供精准目标。有时候关键的逻辑可能写在Native层.so文件这就需要用到IDA Pro或Ghidra进行更底层的分析本次实战以Java层为主。注意在模拟器或真机上运行Frida-server时可能会遇到App检测Frida的情况。一些加固后的App会检测进程名、端口或内存中的特征。应对方法包括重命名Frida-server二进制文件、使用定制编译的Frida、或者先挂起App进程再注入。在本次对“某点小说”的分析中其社区版未遇到强检测但这是实际工作中必须考虑的环节。3. 网络协议抓包与初步分析一切就绪后第一步就是观察App是如何与服务器“对话”的。这是理解其业务逻辑和数据流的起点。3.1 HTTPS流量捕获与解密启动雷电模拟器设置好代理并安装CA证书。然后打开Fiddler确保“Capture HTTPS CONNECTs”和“Decrypt HTTPS traffic”选项已勾选。接着在模拟器中启动“某点小说”App。很快Fiddler的会话列表中就会刷出大量请求。我们需要关注的是那些与核心业务相关的接口例如获取书籍目录、章节内容、用户信息、书架同步等。通过观察URL路径如包含/chapter/、/book/、/user/和响应内容类型application/json可以快速筛选出目标接口。一个关键步骤是解密HTTPS。如果配置正确你应该能看到请求和响应的明文JSON数据。如果遇到“Tunnel to...443”或者响应乱码说明证书安装有问题或App使用了证书绑定SSL Pinning。对于后者就需要动用Frida来绕过。幸运的是在初步测试中“某点小说”的基础内容接口没有启用强证书绑定我们可以直接看到类似以下的请求和响应请求示例GET /api/v3/book/123456/chapters?page1 HTTP/1.1 Host: api.xxx.com User-Agent: .../Android Authorization: Bearer eyJhbGciOiJIUzI1NiIs... X-Sign: a1b2c3d4e5f6...响应示例明文{ code: 0, message: success, data: { chapterList: [ {id: 1, title: 第一章, content: 5pWw5o2u5LqG..., isVip: false}, {id: 2, title: 第二章, content: 5LiN5Yv5LqG..., isVip: true} ] } }3.2 关键接口与参数识别通过观察多个请求我们可以总结出该App接口的一些共性认证方式通常在请求头中使用Authorization: Bearer token这个token在登录后获得是维持会话的关键。签名机制几乎每个请求都带有一个X-Sign或类似名称的头部。这是客户端生成的、用于防止请求被篡改的签名。签名算法通常是服务端和客户端约定的将请求参数可能包括时间戳、设备信息等按特定规则拼接后再进行某种哈希如MD5、SHA256或HMAC计算。逆向签名算法是本次分析的核心难点之一。数据加密注意响应中content字段的值它看起来像Base64编码的字符串字符集符合Base64特征。解码后可能仍然是乱码这说明服务器返回的章节内容很可能在Base64编码之上还进行了额外的加密可能是对称加密如AES。VIP章节的content字段甚至可能直接返回null或一个提示需要购买后才能解密。分页与参数列表类接口通常包含page、size等分页参数。此时我们的目标变得清晰一是找到生成X-Sign签名的方法二是找到解密content内容的方法。这需要我们从动态运行的App中“钩取”这些关键函数。4. 静态代码分析与关键函数定位在盲目使用Frida进行大面积Hook之前先通过静态分析缩小目标范围是高效的做法。使用JADX-GUI打开“某点小说”的APK文件。4.1 关键词搜索与代码浏览首先进行全局搜索。搜索关键词包括“sign” 用于寻找签名相关的类和方法。“encrypt”/“decrypt”/“cipher” 用于寻找加解密相关代码。“AES”/“DES”/“RSA”/“MD5”/“SHA” 常见的算法名。“Base64” 用于定位编码解码处。关键接口的路径片段 如“/api/v3/book/”有时代码中会硬编码或拼接URL。搜索“sign”后我们可能会发现多个类例如SignUtil、SecurityHelper、ApiSign等。点进去查看重点关注那些包含MapString, String参数、返回一个String类型签名、并且方法内部有MessageDigest用于MD5/SHA或Mac用于HMAC初始化代码的函数。例如我们可能找到一个疑似的方法public class SecurityUtils { public static String generateSign(MapString, String params, String secretKey) { // 1. 对参数按key排序并拼接成字符串 // 2. 拼接上secretKey // 3. 进行MD5哈希 // 4. 返回哈希值的十六进制字符串可能转为大写 } }同样搜索“decrypt”或查看content字段的处理逻辑可能会定位到一个DecryptUtil类其中包含使用Cipher类进行AES解密的方法。4.2 确认与记录目标通过静态分析我们假设了两个关键目标签名函数com.xxx.reader.utils.SecurityHelper.generateApiSign(Map, String)解密函数com.xxx.reader.utils.CryptoUtil.aesDecrypt(String, String)记下这些类的完整路径和方法签名。静态分析的结果是推测性的最终需要通过动态Hook来验证这些函数是否确实被调用以及它们的输入输出是否符合我们的观察。实操心得大型App的代码往往经过混淆。类名和方法名可能变成a、b、c。这时关键词搜索可能失效。我们需要转变思路通过搜索字符串常量来定位。例如在抓包中看到的接口路径/api/v3/book/或者错误信息“sign error”在代码中搜索这些字符串就能找到引用它们的方法从而顺藤摸瓜找到关键逻辑。JADX的“查找用例”功能非常好用。5. Frida动态Hook实战这是整个流程中最精彩的部分。我们将编写Frida JavaScript脚本将“钩子”注入到目标App进程中拦截并监视关键函数的执行。5.1 Frida脚本基础与函数Hook首先我们编写一个基础的脚本框架用于Hook上面定位到的疑似签名函数。// hook_sign.js Java.perform(function() { console.log([*] Starting Frida Hook Script...); // 定位目标类 var SecurityHelper Java.use(com.xxx.reader.utils.SecurityHelper); // Hook 目标方法 SecurityHelper.generateApiSign.implementation function(paramsMap, secretKey) { console.log([] generateApiSign called!); // 打印传入的参数 console.log( |- paramsMap: JSON.stringify(paramsMap)); console.log( |- secretKey: secretKey); // 调用原方法获取真实的返回值 var originalResult this.generateApiSign(paramsMap, secretKey); console.log( |- originalResult: originalResult); // 返回原结果不影响App正常运行 return originalResult; }; console.log([*] generateApiSign Hook installed.); });保存脚本后在命令行中运行frida -U -l hook_sign.js -f com.xxx.reader --no-pause-U: 连接到USB设备模拟器。-l: 加载脚本。-f: 启动目标App包名。--no-pause: 立即启动。如果Hook成功当App发起网络请求时我们将在Frida控制台看到该函数被调用的日志并清晰地看到传入的参数和计算出的签名值。通过对比多次请求的输入输出我们可以逆向推导出签名算法例如是否所有参数都参与签名参数拼接顺序是什么使用的哈希算法是MD5还是SHA256secretKey是固定的还是动态的5.2 复杂参数处理与算法验证在实际Hook时参数可能不是简单的MapString, String可能是JSONObject或自定义对象。我们需要熟悉Frida的API来正确处理这些类型。// 如果参数是JSONObject var JsonObject Java.use(org.json.JSONObject); SecurityHelper.generateApiSign.implementation function(jsonParams, secretKey) { console.log([] generateApiSign called!); // 将JSONObject转为字符串查看 var paramsString jsonParams.toString(); console.log( |- jsonParams: paramsString); // ... 后续操作 }; // 遍历Map的另一种方式 if (paramsMap) { var iterator paramsMap.keySet().iterator(); while (iterator.hasNext()) { var key iterator.next(); var value paramsMap.get(key); console.log( |- ${key} : ${value}); } }为了验证我们对算法的猜测可以在Hook函数中模拟计算。例如我们怀疑它是MD5(排序后的参数字符串 secretKey)。那么可以在脚本中引入JavaScript的Crypto库或使用Frida的Crypto模块进行实时计算并将计算结果与原结果对比。如果一致则算法破解成功。5.3 Hook加解密函数与内容解密接下来Hook疑似的内容解密函数。方法类似但这里我们更关心解密后的明文。// hook_decrypt.js Java.perform(function() { var CryptoUtil Java.use(com.xxx.reader.utils.CryptoUtil); CryptoUtil.aesDecrypt.implementation function(encryptedBase64, key) { console.log([] aesDecrypt called!); console.log( |- encryptedBase64: encryptedBase64); console.log( |- key: key); var originalResult this.aesDecrypt(encryptedBase64, key); console.log( |- decryptedResult: originalResult); // 这里应该是解密后的章节明文 return originalResult; }; });运行此脚本触发一个章节内容的请求。如果Hook点正确你将在日志中看到传入的encryptedBase64就是响应中content字段的值而originalResult就是解密后的、可读的小说文本。key的来历也需要关注它可能是固定的字符串也可能是从服务器另一个接口动态获取的。有时解密函数可能不是直接暴露的静态方法而是在某个回调或网络库的拦截器中被调用。这就需要我们扩大Hook范围或者去Hook更底层的Cipher.getInstance()和Cipher.doFinal()方法。5.4 主动调用与算法复现当我们确认了签名和加解密的算法细节后最终目标是在脱离App环境的情况下用Python或其他语言复现整个流程。Frida的另一个强大功能是主动调用RPC。我们可以修改脚本将关键函数暴露给外部调用// hook_rpc.js Java.perform(function() { var SecurityHelper Java.use(com.xxx.reader.utils.SecurityHelper); rpc.exports { generatesign: function(paramsJson, secretKey) { var result ; Java.perform(function() { // 将JSON字符串转为Java的HashMap var HashMap Java.use(java.util.HashMap); var map HashMap.$new(); var params JSON.parse(paramsJson); for (var key in params) { map.put(key, params[key]); } // 主动调用 result SecurityHelper.generateApiSign(map, secretKey); }); return result; }, decryptcontent: function(cipherText, key) { var result ; Java.perform(function() { var CryptoUtil Java.use(com.xxx.reader.utils.CryptoUtil); result CryptoUtil.aesDecrypt(cipherText, key); }); return result; } }; });在Python端我们可以这样调用import frida import json with open(hook_rpc.js, r) as f: js_code f.read() session frida.get_usb_device().attach(某点小说) script session.create_script(js_code) script.load() # 调用远程导出的函数 params {bookId: 123456, page: 1} secret 固定的或动态获取的Secret sign script.exports.generatesign(json.dumps(params), secret) print(计算出的签名:, sign)这样我们就拥有了一个可以独立运行的签名/解密服务可以用于编写爬虫或进行其他自动化测试。6. 问题排查与进阶对抗在实际操作中几乎不会一帆风顺。下面记录一些常见问题及其解决思路。6.1 常见问题速查表问题现象可能原因排查与解决思路Frida连接被拒绝或进程崩溃App检测到Frida注入1. 使用-f参数在App启动前注入。2. 使用frida-server的隐藏版本如frida-server-xx.x.x-android-xxx-hidden。3. 修改frida-server文件名和进程名。4. 使用objection等工具的anti-frida绕过脚本。Hook函数后无任何日志输出1. 类名/方法名错误混淆导致。2. 方法签名不匹配参数类型、数量。3. 该方法从未被调用。1. 使用Java.choose()或Java.enumerateMethods()动态枚举已加载的类和方法。2. 检查方法重载使用overload()指定正确的参数类型。3. 扩大Hook范围Hook其父类或接口方法。抓包工具看不到HTTPS明文1. CA证书未正确安装到系统信任区。2. App使用了SSL Pinning证书绑定。1. 确认证书已安装并开启“信任用户证书”。2. 使用Frida Hook SSL验证相关方法如TrustManager、OkHttp的CertificatePinner使其总是返回成功。签名算法看似随机每次不同签名包含时间戳timestamp或随机数nonce。在Hook中仔细检查传入参数寻找ts、timestamp、nonce等字段。算法通常是MD5/SHA(排序参数 secretKey)时间戳也作为参数之一参与计算。解密函数Hook到但key为空或错误key可能从本地存储如SharedPreferences或另一个网络接口获取。1. HookSharedPreferences的getString方法查看相关key。2. 搜索与“key”、“token”、“secret”相关的网络请求并分析其响应。6.2 应对代码混淆与加固对于混淆严重的App静态分析几乎失效。此时必须依赖动态分析。堆栈跟踪法 Hook一个你确定会被调用的基础方法例如okhttp3.Request.Builder.build()在Hook函数中打印当前的调用堆栈Java.use(android.util.Log).getStackTraceString(Java.use(java.lang.Exception).$new())。从堆栈信息中可以找到App自定义的类和方法名即使它们是a.a.a.b这样的形式。模糊Hook 如果知道目标方法的大致特征如参数数量、返回值类型可以尝试枚举所有类的方法进行模糊匹配和Hook。这虽然效率低但在没有头绪时是最后的手段。关注系统API 无论业务逻辑如何混淆最终调用系统API如MessageDigest.getInstance()、Cipher.init()的代码是无法混淆的。直接Hook这些系统API然后反向追溯调用栈是定位关键逻辑的利器。6.3 协议复现与自动化当成功逆向出签名和加解密算法后就可以用Python的requests库等工具完全模拟客户端的行为。构建请求 按照规则组装请求参数包括设备信息、时间戳等。生成签名 用Python复现签名算法计算X-Sign。发送请求 携带签名和Token如果需要发送请求。处理响应 对返回的加密内容用Python复现的解密算法进行解密。整个过程可以封装成一个爬虫或API客户端。但务必注意法律和道德边界仅将技术用于学习、安全研究或经授权的测试不要用于侵犯版权、干扰服务等非法用途。逆向分析是一个需要耐心、细心和发散思维的过程。从抓包看到现象到静态分析猜测可能再到动态Hook验证猜想最后复现算法完成自动化每一步都环环相扣。这次对“某点小说”的实战涵盖了从入门到进阶的典型路径。最大的收获不是破解了某个App而是建立起一套应对未知App的分析方法论。工具在变对抗技术在升级但“观察-假设-验证”的核心逻辑不会变。