【Frida Android】基础篇4:Java层Hook基础——调用静态方法
文章目录
- 1 基本概念
- 2 Hook 语法
- 3 案例讲解
- 3.1 核心源码分析
- 3.2 代码逻辑分析
- 1. 核心组件与流程
- 2. 验证逻辑(核心)
- 3. 解密细节(辅助理解)
- 3.3 绕过思路
- 3.4 Frida 脚本
- 脚本说明
- 使用方法
- 效果
- 4 技术总结
⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。
1 基本概念
在Java中,静态方法是属于类本身的方法,而非类的实例对象。它可以直接通过类名调用(如ClassName.methodName()
),无需创建类的实例。这种特性使得静态方法在工具类、工具函数等场景中被广泛使用。
在Hook技术中,静态方法的Hook与实例方法存在一定差异:
- 定位方式:需通过类本身而非实例对象定位方法
- 调用方式:Hook后的静态方法仍可通过类名直接调用
- 参数处理:静态方法没有隐含的
this
参数,仅处理显式声明的参数
对于逆向分析而言,静态方法常作为核心逻辑的载体(如验证逻辑、加密解密等),因此掌握静态方法的Hook技巧是Java层逆向的重要基础。
2 Hook 语法
使用Frida对Java静态方法进行Hook的核心语法如下:
-
定位目标类
通过Java.use('完整类名')
获取目标类的引用(静态方法属于类,无需实例化):var TargetClass = Java.use('com.example.TargetClass');
-
Hook静态方法
通过类名.方法名.implementation
重写静态方法实现:TargetClass.staticMethod.implementation = function(参数列表) {// 自定义逻辑(如打印参数、修改参数、跳过原逻辑等)console.log("原始参数:", 参数列表); // 打印原始参数var newParams = [修改后的参数]; // 构造新参数var result = this.staticMethod(...newParams); // 调用原始方法(this指向类本身)return result; // 返回结果(若有返回值) };
-
主动调用静态方法
若需手动触发静态方法,可直接通过类名调用:TargetClass.staticMethod(参数); // 调用静态方法(会触发Hook逻辑)
核心特点:静态方法的Hook无需依赖类的实例,直接通过类引用操作,且this
在静态方法Hook中指向类本身。
3 案例讲解
本章示例应用的链接:
https://pan.baidu.com/s/16EE2XE-OZS_xBRPlWUODbw?pwd=n2vb
提取码: n2vb
使用APK:Challenge 0x2.apk
打开示例应用:Challenge 0x2.apk,没有操作组件。
使用 JADX-GUI 反编译 APK,查看应用的核心类 MainActivity
3.1 核心源码分析
/* loaded from: classes3.dex */
public class MainActivity extends AppCompatActivity {static TextView t1;@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activityprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);t1 = (TextView) findViewById(R.id.textview);}public static void get_flag(int a) throws BadPaddingException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {if (a == 4919) {try {SecretKeySpec secretKeySpec = new SecretKeySpec("HILLBILLWILLBINN".getBytes(), "AES");Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");IvParameterSpec iv = new IvParameterSpec(new byte[16]);cipher.init(2, secretKeySpec, iv);byte[] decryptedBytes = cipher.doFinal(Base64.decode("q7mBQegjhpfIAr0OgfLvH0t/D0Xi0ieG0vd+8ZVW+b4=", 0));String decryptedText = new String(decryptedBytes);t1.setText(decryptedText);} catch (Exception e) {e.printStackTrace();}}}
}
3.2 代码逻辑分析
这个APK的核心功能是通过验证后解密并显示flag,我们先拆解其逻辑:
1. 核心组件与流程
MainActivity
:主界面活动,初始化时绑定了一个TextView (t1)
用于显示结果。- 核心方法
get_flag(int a)
:静态方法,接收一个整数参数a
,是验证的关键。
2. 验证逻辑(核心)
get_flag
方法的逻辑非常明确:
- 只有当传入的参数
a == 4919
时,才会执行后续的AES解密操作; - 解密成功后,将结果显示在
TextView t1
上; - 若
a != 4919
,则不执行解密,自然也不会显示flag。
3. 解密细节(辅助理解)
当验证通过(a=4919
)时,会执行AES解密:
- 密钥:
"HILLBILLWILLBINN".getBytes()
(固定密钥); - 加密模式:
AES/CBC/PKCS5Padding
; - IV向量:
new byte[16]
(16个0字节); - 待解密数据:Base64编码的字符串
"q7mBQegjhpfIAr0OgfLvH0t/D0Xi0ieG0vd+8ZVW+b4="
。
3.3 绕过思路
要让任意输入(或无需满足a=4919
)都能显示flag,核心是让get_flag
方法中的验证条件a == 4919
永远成立。
具体方案:
-
通过Frida Hook
get_flag
方法,忽略原始参数a
,强制传入4919
,让解密逻辑必然执行。 -
原 APP 没有提供触发
get_flag
的入口,导致即使 Hook 了参数替换逻辑,也没机会执行。因此需要通过MainActivity.get_flag(xxx)
,直接 “手动触发” 这个方法,让 Hook 逻辑有机会生效。
3.4 Frida 脚本
import Java from 'frida-java-bridge';Java.perform(function () {// 定位目标类(静态方法属于类,直接通过类名获取)var MainActivity = Java.use('com.ad2001.frida0x2.MainActivity');// Hook静态方法get_flagMainActivity.get_flag.implementation = function (a) {console.log("原始参数a: " + a);// 忽略原始参数,强制传入4919,使验证通过this.get_flag(4919); // this指向MainActivity类本身};// 主动调用静态方法,传入任意参数(这里传1111)触发Hook逻辑MainActivity.get_flag(1111);
});
脚本说明
- 定位类与方法:通过
Java.use('com.ad2001.frida0x2.MainActivity')
获取目标类,由于get_flag
是静态方法,直接通过类名调用get_flag.implementation
进行Hook。 - 修改参数:Hook后,不管外部传入的
a
是什么值,都强制用4919
调用原始get_flag
方法,此时验证条件a == 4919
必然成立,解密逻辑会执行并显示flag。 - 主动调用触发 Hook:当脚本执行
MainActivity.get_flag(1111)
时,因为我们已经 Hook 了get_flag
的实现,所以会先进入 Hook 后的函数(而不是原始函数)。
启动脚本:仅修改PACKAGE_NAME
包名变量的值
注意"./js/compiled_hook.js"
与你的本地代码路径一致,后续不再赘述。
import frida
import sys
import timedef on_message(message, data):if message['type'] == 'send':print(f"[Hook 日志] {message['payload']}")elif message['type'] == 'error':print(f"[错误] {message['stack']}")# 目标应用包名
PACKAGE_NAME = "com.ad2001.frida0x3"def main():try:device = frida.get_usb_device(timeout=10)print(f"已连接设备:{device.name}")print(f"启动进程 {PACKAGE_NAME}...")pid = device.spawn([PACKAGE_NAME])device.resume(pid)time.sleep(2)process = device.attach(pid)print(f"已附加到进程 PID: {pid}")with open("./js/compiled_hook.js", "r", encoding="utf-8") as f:js_code = f.read()script = process.create_script(js_code)script.on('message', on_message)script.load()print("JS 脚本注入成功,开始监控...(按 Ctrl+C 退出)")sys.stdin.read()except frida.TimedOutError:print("未找到USB设备")except frida.ProcessNotFoundError:print(f"应用 {PACKAGE_NAME} 未安装")except FileNotFoundError:print("未找到 js 脚本,请检查路径")except Exception as e:print(f"异常:{str(e)}")finally:if 'process' in locals():process.detach()print("程序退出")if __name__ == "__main__":main()
使用方法
代码结构和启动方式与上一章完全一致(本系列案例基本保持一致):启动 frida_server → 编译 hook.js 脚本 → 执行 run.py 启动 hook 注入。
npm run watch
效果
执行脚本后,get_flag
方法的验证被绕过,实现了“任意条件下显示结果”的目标。
4 技术总结
-
静态方法Hook核心要点
- 定位方式:通过
Java.use('类名')
直接获取类引用,无需实例化 - 方法重写:使用
类名.静态方法名.implementation
修改实现逻辑 - 调用特性:
this
在静态方法Hook中指向类本身,调用原始方法需用this.方法名(参数)
- 定位方式:通过
-
参数篡改技巧
对于带验证逻辑的静态方法,可通过在Hook中替换参数(如案例中强制传入4919
)绕过条件判断,直接执行核心逻辑。 -
主动调用的必要性
当目标静态方法无自然触发入口时,需在Hook脚本中主动调用(类名.方法名(参数)
),强制触发Hook逻辑并执行目标代码。 -
Frida Java层Hook流程
定位目标类 → 重写目标方法 → 自定义Hook逻辑(参数/返回值处理) → 触发目标方法(自然触发或主动调用),该流程适用于绝大多数Java层静态方法Hook场景。