第二章:Hook的艺术 —— 使用Frida篡改运行时内存
1. 什么是 Hook?为什么需要它?
在上一章中,我们通过反编译、修改代码、重打包来篡改App。这种方法虽然有效,但缺点很明显:
麻烦:每次修改都要重新签名安装。
容易被发现:App可以检测自己的签名是否被篡改。
无法动态变化:代码是死的,而App运行时的数据是活的。
Hook(钩子)技术 解决了这些问题。
想象一下,App运行就像一辆在公路上行驶的汽车。普通攻击是“拦路抢劫”(改代码),而 Hook 是“中途换司机”(改逻辑)。我们不用修改App的身体(APK文件),而是直接在它运行时,拦截它的函数调用,并篡改返回值。
核心工具:Frida
Frida 是一个强大的动态代码插桩工具。它允许你将 JavaScript 代码片段注入到正在运行的进程中,就像给正在飞行的飞机更换引擎零件。
2. 环境准备:注入“灵魂”
2.1 架构原理
Frida Server:一个运行在安卓手机(或模拟器)后台的小程序,拥有 Root 权限,负责“抓住”目标App。
Frida Client:你电脑上的命令行工具或Python脚本,负责编写“指令”发送给 Server。
2.2 快速启动
在手机上启动 Frida Server(需Root):
./frida-server &电脑端连接测试:
frida-ps -U(列出手机上正在运行的进程)。
3. 实战一:篡改金币(入门级)
这是最经典的 Hook 案例,用于理解“修改返回值”。
场景:
某游戏App中,你的金币余额显示为100。你想把它改成999999。
3.1 定位目标
使用 JADX(第一章工具)打开 App,搜索关键词gold、coin、balance。
我们找到了获取金币的方法:
public class UserManager { public static int getGoldCoins() { // 这里通常是从数据库或服务器读取 return 100; } }3.2 编写 Frida 脚本
我们不需要修改 APK,只需要写一个hack.js脚本:
// 1. 找到目标类(UserManager) var UserManager = Java.use("com.target.game.UserManager"); // 2. 替换(Hook)getGoldCoins 方法 UserManager.getGoldCoins.implementation = function() { console.log("[*] Hook成功!原方法被调用了。"); // 3. 篡改返回值 // 原本返回 100,我们强制返回 999999 return 999999; };3.3 执行与结果
运行脚本:frida -U -l hack.js com.target.game
App界面:原本显示“金币:100”的地方,瞬间变成了“金币:999999”。
关键点:App 本身没有变,只是在内存中,我们欺骗了它自己。
4. 实战二:绕过指纹登录(进阶级)
很多App有生物识别(指纹/人脸)功能。作为测试员,我们需要绕过它,以测试登录逻辑是否健壮。
场景:
App 弹出指纹验证,只要按一下指纹,就调用onAuthenticationSucceeded()方法进入系统。
4.1 分析逻辑
在代码中,指纹验证通常有一个回调接口:
BiometricPrompt.AuthenticationCallback callback = new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationSucceeded(...) { // 验证成功,跳转主页 gotoMainPage(); } @Override public void onAuthenticationFailed() { // 验证失败,提示错误 showError(); } };4.2 Frida 攻击脚本
我们的目标不是去破解指纹算法(那太难了),而是直接调用那个“成功”的方法。
// 等待Java虚拟机加载完毕 Java.perform(function() { // 找到回调类 var AuthCallback = Java.use("androidx.biometric.BiometricPrompt$AuthenticationCallback"); // Hook 失败的方法 AuthCallback.onAuthenticationFailed.implementation = function() { console.log("[*] 指纹失败了?没关系,我们直接调用成功的方法!"); // 直接调用成功的方法(模拟指纹通过) this.onAuthenticationSucceeded(null); }; });结果:
你在手机上随便按一下,或者甚至不按,App 都会误以为指纹验证通过了。这证明了该App的生物识别逻辑仅依赖本地回调,未结合服务器端挑战(Challenge)验证。
5. 实战三:解密 HTTPS 流量(高阶级)
这是渗透测试中最实用的场景。即使你设置了代理(Burp Suite),很多App的数据包依然是“乱码”(加密的)。
原因:
App 使用了SSL Pinning(证书锁定)。它不信任系统自带的代理证书,只信任特定的证书。
5.1 绕过原理
在安卓底层,有一个关键函数负责验证证书:checkServerTrusted。我们只需要 Hook 这个函数,让它永远返回“信任”,不做任何检查。
5.2 绕过脚本(通用版)
Java.perform(function() { // 定位到证书检查的类 var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager"); var SSLContext = Java.use("javax.net.ssl.SSLContext"); // 创建一个“假的”信任管理器 var TrustManager = Java.registerClass({ name: "com.frida.FakeTrustManager", implements: [X509TrustManager], methods: { checkServerTrusted: function(chain, authType) { // 啥也不干,直接放行(不抛异常就是信任) console.log("[*] 绕过证书校验!"); }, getAcceptedIssuers: function() { return []; } } }); // 替换系统默认的SSL上下文 // ... (此处省略部分复杂代码,实际常用现成脚本) });结果:
原本无法解密的 HTTPS 流量,现在在 Burp Suite 中变得清晰可见,你可以看到明文密码、Token 等敏感信息。
6. 防御与对抗
作为开发者,如何防御 Frida?
检测 Frida 端口:Frida 默认监听 27042 端口。App 可以扫描本地端口,如果发现 Frida,直接退出。
检测 Frida 特征:扫描
/proc/self/maps内存映射,检查是否存在frida-agent字符串。混淆 Native 代码:将核心逻辑(如签名校验)写在 C/C++(NDK)里。虽然 Frida 也能 Hook Native 层,但难度呈指数级上升。
下一章预告:
《第三章:数据窃听与中间人攻击 —— 深入网络通信》
我们将不再只盯着App本身,而是把目光投向App与服务器之间的“电线”,利用第二章绕过的HTTPS流量,挖掘API接口的逻辑漏洞(如越权、短信轰炸)。
