文章目录
- [1 基本概念](#1 基本概念)
- [2 Hook 语法](#2 Hook 语法)
- [3 案例讲解](#3 案例讲解)
- [4 技术总结](#4 技术总结)
⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。
1 基本概念
在Java中,静态方法是属于类本身的方法,而非类的实例对象。它可以直接通过类名调用(如ClassName.methodName()
),无需创建类的实例。这种特性使得静态方法在工具类、工具函数等场景中被广泛使用。
在Hook技术中,静态方法的Hook与实例方法存在一定差异:
- 定位方式:需通过类本身而非实例对象定位方法
- 调用方式:Hook后的静态方法仍可通过类名直接调用
- 参数处理:静态方法没有隐含的
this
参数,仅处理显式声明的参数
对于逆向分析而言,静态方法常作为核心逻辑的载体(如验证逻辑、加密解密等),因此掌握静态方法的Hook技巧是Java层逆向的重要基础。
2 Hook 语法
使用Frida对Java静态方法进行Hook的核心语法如下:
-
定位目标类
通过
Java.use('完整类名')
获取目标类的引用(静态方法属于类,无需实例化):javascriptvar TargetClass = Java.use('com.example.TargetClass');
-
Hook静态方法
通过
类名.方法名.implementation
重写静态方法实现:javascriptTargetClass.staticMethod.implementation = function(参数列表) { // 自定义逻辑(如打印参数、修改参数、跳过原逻辑等) console.log("原始参数:", 参数列表); // 打印原始参数 var newParams = [修改后的参数]; // 构造新参数 var result = this.staticMethod(...newParams); // 调用原始方法(this指向类本身) return result; // 返回结果(若有返回值) };
-
主动调用静态方法
若需手动触发静态方法,可直接通过类名调用:
javascriptTargetClass.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 核心源码分析
java
/* 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.Activity
protected 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 脚本
javascript
import Java from 'frida-java-bridge';
Java.perform(function () {
// 定位目标类(静态方法属于类,直接通过类名获取)
var MainActivity = Java.use('com.ad2001.frida0x2.MainActivity');
// Hook静态方法get_flag
MainActivity.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"
与你的本地代码路径一致,后续不再赘述。
python
import frida
import sys
import time
def 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 注入。

shell
npm run watch


效果
执行脚本后,get_flag
方法的验证被绕过,实现了"任意条件下显示结果"的目标。

4 技术总结
-
静态方法Hook核心要点
- 定位方式:通过
Java.use('类名')
直接获取类引用,无需实例化 - 方法重写:使用
类名.静态方法名.implementation
修改实现逻辑 - 调用特性:
this
在静态方法Hook中指向类本身,调用原始方法需用this.方法名(参数)
- 定位方式:通过
-
参数篡改技巧
对于带验证逻辑的静态方法,可通过在Hook中替换参数(如案例中强制传入
4919
)绕过条件判断,直接执行核心逻辑。 -
主动调用的必要性
当目标静态方法无自然触发入口时,需在Hook脚本中主动调用(
类名.方法名(参数)
),强制触发Hook逻辑并执行目标代码。 -
Frida Java层Hook流程
定位目标类 → 重写目标方法 → 自定义Hook逻辑(参数/返回值处理) → 触发目标方法(自然触发或主动调用),该流程适用于绝大多数Java层静态方法Hook场景。