67、Android逆向(三):Frida框架使用、动态Hook Java层
好,咱们继续。前两章我们把Android逆向的基础环境、静态分析、Smali语法都过了一遍。说实话,静态分析就像看一本没有目录的书——你能读到内容,但很难快速定位关键逻辑。这时候就需要动态分析登场了。
Frida,就是动态分析领域里的一把瑞士军刀。我个人习惯把它叫做「逆向界的Chrome DevTools」——你可以在运行时注入JavaScript代码,实时观察、修改App的行为。今天我们就来聊聊怎么用Frida Hook Java层的方法。
Frida是什么?为什么选它?
Frida是一个动态插桩框架。说白了,它允许你往正在运行的进程里注入一段JavaScript代码,然后这段代码可以拦截函数调用、修改返回值、打印调用栈……几乎无所不能。
我当年第一次接触Frida时,心里想的是:「这玩意儿合法吗?」后来发现,它确实是安全研究的标准工具。相比Xposed需要重启设备、修改系统分区,Frida完全在用户态工作,灵活得多。
核心优势:
- 无需root?其实还是需要,但可以通过frida-gadget注入到非root设备
- 支持热插拔——改代码不用重启App
- JavaScript编写脚本,上手门槛低
- 同时支持Java层和Native层Hook
环境搭建
嗯,这里要注意。Frida的环境分两部分:PC端和手机端。
PC端安装很简单:
pip install frida-tools
手机端需要运行一个frida-server。记得版本一定要匹配!我曾经因为版本不一致,折腾了整整一个下午才发现问题。
# 查看手机架构
adb shell getprop ro.product.cpu.abi
# 下载对应版本的frida-server
# 解压后push到手机
adb push frida-server /data/local/tmp/
adb shell chmod 755 /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server &
避坑指南: 我曾经遇到过frida-server启动后,手机端显示运行正常,但PC端就是连不上。后来发现是端口被占用了。建议用 frida-server -l 0.0.0.0:27042 指定端口。
第一个Hook脚本:Hello World
咱们先来个最简单的——Hook一个Java类的静态方法。假设目标App里有这样一个类:
public class Utils {
public static String getSecret() {
return "this_is_secret";
}
}
我们的目标是让 getSecret() 永远返回 "hooked!"。Frida脚本如下:
// hook.js
Java.perform(function() {
var Utils = Java.use('com.example.Utils');
Utils.getSecret.implementation = function() {
console.log('[+] getSecret() was called!');
return 'hooked!';
};
});
运行方式:
frida -U -l hook.js com.example.app
你想想看,这有多强大?不需要修改APK,不需要重新编译,一行代码就能改变App的行为。
Hook实例方法
静态方法搞定了,实例方法呢?其实差不多,但要注意实例的获取方式。
Java.perform(function() {
var MainActivity = Java.use('com.example.MainActivity');
// Hook实例方法
MainActivity.onClick.implementation = function(view) {
console.log('[+] onClick called with view: ' + view);
// 调用原方法
return this.onClick(view);
};
// 也可以直接创建实例来调用
var instance = MainActivity.$new();
instance.onClick(null);
});
这里有个小技巧:this 关键字在implementation里指向当前实例。如果你想调用原方法,直接用 this.原方法名() 就行。
构造函数的Hook
构造函数怎么Hook?用 $init:
Java.perform(function() {
var MyClass = Java.use('com.example.MyClass');
MyClass.$init.overload('int', 'java.lang.String').implementation = function(num, str) {
console.log('[+] Constructor called with: ' + num + ', ' + str);
// 可以修改参数
return this.$init(999, 'modified');
};
});
个人经验: 遇到重载方法时,用 overload() 指定参数类型。如果不确定参数类型,可以用 overload('*') 匹配所有重载,但建议还是精确指定。
动态修改字段值
有时候我们不光要Hook方法,还要修改对象的字段值。比如一个登录状态检查:
Java.perform(function() {
var UserManager = Java.use('com.example.UserManager');
UserManager.isLoggedIn.implementation = function() {
// 直接修改字段
this.mIsLoggedIn.value = true;
return true;
};
// 或者通过实例修改
var instance = Java.choose('com.example.UserManager', {
onMatch: function(inst) {
inst.mIsLoggedIn.value = true;
console.log('[+] Found instance, set logged in');
},
onComplete: function() {}
});
});
Java.choose() 是个好东西——它会在堆上搜索指定类的所有实例。我经常用它来定位关键对象。
实战案例:绕过SSL Pinning
讲个真实案例。有一次我分析一个金融类App,它做了SSL Pinning,抓包工具死活抓不到HTTPS流量。静态分析发现它用了OkHttp的CertificatePinner。
Hook方案很简单:
Java.perform(function() {
var CertificatePinner = Java.use('okhttp3.CertificatePinner$Builder');
CertificatePinner.add.overload('java.lang.String', 'java.lang.String[]').implementation = function(hostname, pins) {
console.log('[+] SSL Pinning bypassed for: ' + hostname);
// 不添加任何证书锁定
return this;
};
});
就这么几行代码,SSL Pinning就绕过去了。你想想看,如果没有动态Hook,你得反编译、改Smali、重新打包签名……费时费力。
Frida脚本的调试技巧
写Frida脚本时,难免会遇到问题。我分享几个调试技巧:
- 打印调用栈:
Java.perform(function() { console.log(Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Exception').$new())); }); - 查看所有已加载的类:
Java.enumerateLoadedClasses({ onMatch: function(c) { console.log(c); }, onComplete: function() {} }); - 动态加载脚本: 用
%load hook.js在Frida控制台里热加载
核心要点: Frida的JavaScript环境是独立的,不能直接访问App的变量。所有操作都要通过 Java.use()、Java.choose() 等API来桥接。
本章知识体系
下面这张图总结了Frida Hook Java层的核心流程:
常见问题与避坑
最后,我总结几个新手容易踩的坑:
- 类名写错: 一定要用完整的包名路径,比如
com.example.MyClass,不是MyClass - 忘记Java.perform: 所有Java相关操作必须在
Java.perform()回调里执行 - 异步问题: Frida的JavaScript是异步的,但
Java.perform()内部是同步的。不要在回调里做耗时操作 - App崩溃: 如果Hook后App闪退,多半是返回值类型不匹配。检查一下原方法的签名
我的习惯: 每次写Hook脚本前,先用 frida -U -f com.example.app --no-pause 启动App,然后在控制台里手动测试API。确认没问题了再写成脚本文件。
好了,这一章的内容就到这里。Frida的Java层Hook是Android逆向的必修课,掌握了它,你就能在运行时随心所欲地操控App逻辑。下一章我们会深入Native层,看看怎么用Frida Hook so文件里的C/C++函数。
公众号:蓝海资料掘金营,微信deep3321