文章目录
-
- [1. Hook 语法](#1. Hook 语法)
-
- [1.1 构造函数与实例创建核心语法](#1.1 构造函数与实例创建核心语法)
- [1.2 Hook 构造函数逻辑](#1.2 Hook 构造函数逻辑)
- [1.3 主动创建类实例](#1.3 主动创建类实例)
- [1.4 init 与 new 的关系](#1.4 init 与 new 的关系)
- [1.5 示例:结合`init\`和\`new`的用法](#1.5 示例:结合
$init
和$new
的用法)
- [2. 案例解析](#2. 案例解析)
-
- [2.1 核心代码分析](#2.1 核心代码分析)
-
- [2.1.1 Checker类](#2.1.1 Checker类)
- [2.1.2 MainActivity类](#2.1.2 MainActivity类)
- [3. Hook 思路](#3. Hook 思路)
-
- [3.1 问题定位](#3.1 问题定位)
- [3.2 解决方案](#3.2 解决方案)
- [3.3 执行步骤](#3.3 执行步骤)
-
- [3.3.1 步骤1:获取活跃的MainActivity实例](#3.3.1 步骤1:获取活跃的MainActivity实例)
- [3.3.2 步骤2:创建符合条件的Checker实例](#3.3.2 步骤2:创建符合条件的Checker实例)
- [3.3.3 步骤3:调用flag方法触发解密](#3.3.3 步骤3:调用flag方法触发解密)
- [4. Hook 脚本](#4. Hook 脚本)
-
- [4.1 JS 脚本](#4.1 JS 脚本)
- [4.2 Python 脚本](#4.2 Python 脚本)
- [4.3 成功显示 Flag](#4.3 成功显示 Flag)
- [5. 技术总结](#5. 技术总结)
-
- [5.1 两种构造干预方式的核心差异](#5.1 两种构造干预方式的核心差异)
- [5.2 案例中选择`new\`而非\`init`的关键原因](#5.2 案例中选择
$new
而非$init
的关键原因) - [5.3 Frida工具特性的场景适配](#5.3 Frida工具特性的场景适配)
- [5.4 适用场景总结](#5.4 适用场景总结)
⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。
1. Hook 语法
在Frida中,操作Java类的实例化过程主要涉及两个核心方法:$init
和$new
。其中$init
对应Java中的构造函数逻辑,用于Hook或修改实例初始化过程;$new
则对应Java中new
关键字的操作,用于主动创建类的实例。
1.1 构造函数与实例创建核心语法
- 获取目标类引用 :通过
Java.use("类全名")
获取目标类的JavaScript包装对象。 - Hook构造函数(
$init
) :通过类引用.$init.implementation
重写构造函数逻辑,干预实例初始化参数或过程。 - 主动创建实例(
$new
) :通过类引用.$new(参数)
主动调用构造函数创建实例,无需依赖原程序的实例创建逻辑。
1.2 Hook 构造函数逻辑
$init
是Java构造函数在Frida中的映射,用于定义实例初始化时的逻辑。通过重写$init.implementation
,可以修改传入的参数或在初始化后调整实例属性。
示例 :Hook Checker
构造函数并修改初始化参数
js
Java.perform(function(){
// 获取Checker类引用
var Checker = Java.use("com.xxx.xxx.Checker");
// Hook构造函数($init对应Java中的构造方法)
Checker.$init.implementation = function(a, b) {
// 修改参数为520, 520后调用原始构造逻辑
this.$init(520, 520);
};
});
1.3 主动创建类实例
$new
对应Java中new
关键字的操作,用于主动创建类的实例。它在内部会调用$init
方法完成初始化,因此使用时需传入构造函数所需的参数。当需要绕开原程序的实例创建逻辑,直接生成符合条件的实例时,$new
是核心工具。
示例 :主动创建Checker
实例
js
Java.perform(function(){
// 获取Checker类引用
var Checker = Java.use("com.xxx.xxx.Checker");
// 调用$new创建实例,传入参数600, 600(等价于Java中的new Checker(600, 600))
var validChecker = Checker.$new(600, 600);
});
1.4 init 与 new 的关系
$init
是构造函数的具体实现逻辑,负责初始化实例的属性和状态;$new
是创建实例的入口,调用$new
时会自动触发$init
来完成实例初始化;- 若已Hook
$init
,则通过$new
创建实例时,会执行重写后的$init
逻辑。
1.5 示例:结合$init
和$new
的用法
以下示例先Hook构造函数打印参数,再主动创建符合条件的实例:
js
Java.perform(function(){
var Checker = Java.use("com.xxx.xxx.Checker");
// Hook构造函数,打印原始参数
Checker.$init.implementation = function(a, b) {
this.$init(a, b); // 保留原始逻辑
};
// 主动创建实例
var customChecker = Checker.$new(1000, 1000);
console.log("主动创建的实例参数:num1=" + customChecker.num1.value + ", num2=" + customChecker.num2.value);
});
2. 案例解析
本章示例应用的链接:
https://pan.baidu.com/s/16EE2XE-OZS_xBRPlWUODbw?pwd=n2vb
提取码: n2vb
使用APK:Challenge 0x7.apk

2.1 核心代码分析
案例的核心逻辑围绕MainActivity
和Checker
两个类展开,关键代码解析如下:
2.1.1 Checker类
简单的实体类,构造函数接收两个int参数a
和b
,并赋值给实例变量num1
和num2
。其作用是存储判断条件所需的参数,无其他复杂逻辑。
java
public class Checker {
int num1; // 需满足 >512
int num2; // 需满足 >512
// 构造函数:初始化num1和num2
Checker(int a, int b) {
this.num1 = a;
this.num2 = b;
}
}
2.1.2 MainActivity类
onCreate
方法中创建Checker
实例时使用了固定参数(123, 321)
,这两个值均不满足num1>512
和num2>512
的条件,导致flag
方法的解密逻辑无法触发。flag
方法是核心判断逻辑:仅当传入的Checker
实例满足num1>512且num2>512
时,才会执行AES解密并显示flag。
java
// MainActivity中关键逻辑
public void flag(Checker A) throws ... {
// 核心条件:只有参数满足时才解密flag
if (A.num1 > 512 && 512 < A.num2) {
// AES解密并显示flag
Cipher cipher = Cipher.getInstance("AES");
...
this.t1.setText(decrypted);
}
}
简言之,案例的核心矛盾是:原程序创建的Checker实例参数不满足条件,导致flag无法显示,而Hook的目标就是解决这一矛盾。
3. Hook 思路
要触发flag显示,需满足flag
方法的参数条件。由于原程序中Checker
实例的参数固定为(123, 321)
,因此需要通过Hook技术绕开这一限制。
3.1 问题定位
- 限制来源:
MainActivity.onCreate
中创建的Checker
实例参数(123, 321)
不满足num1>512且num2>512
。 - 目标:使
flag
方法接收到符合条件的Checker
实例,触发解密逻辑。
3.2 解决方案
无需修改原程序的构造函数逻辑,而是通过主动创建符合条件的Checker
实例 ,并手动调用flag
方法,直接绕开原程序的参数限制。
3.3 执行步骤
3.3.1 步骤1:获取活跃的MainActivity实例
- 原因:
flag
方法是MainActivity
的成员方法,且需要其内部的TextView t1
已初始化(否则设置文本会失败)。 - 实现:通过
Java.choose("com.ad2001.frida0x7.MainActivity")
枚举进程中已加载的MainActivity
实例,确保获取的是onCreate
执行完毕后的活跃实例(此时t1
已绑定布局)。
3.3.2 步骤2:创建符合条件的Checker实例
- 关键:直接调用
Checker
的构造函数,传入满足条件的参数(如600, 600
)。 - 实现:通过
Java.use("com.ad2001.frida0x7.Checker").$new(600, 600)
主动创建实例,无需依赖原程序的构造调用。
3.3.3 步骤3:调用flag方法触发解密
- 操作:使用步骤1获取的
MainActivity
实例,调用其flag
方法并传入步骤2创建的Checker
实例。 - 结果:由于参数满足条件,
flag
方法执行AES解密,最终通过t1.setText
显示flag。
4. Hook 脚本
4.1 JS 脚本
js
import Java from 'frida-java-bridge';
Java.perform(function () {
Java.choose('com.ad2001.frida0x7.MainActivity', {
// 当找到MainActivity实例时触发的回调
onMatch: function (instance) {
console.log("找到MainActivity实例,准备执行操作");
// 获取Checker类的引用
var checkerClass = Java.use("com.ad2001.frida0x7.Checker");
// 手动创建Checker实例,传入参数600和600(满足flag方法中"num1>512且num2>512"的条件)
var validChecker = checkerClass.$new(600, 600);
// 调用MainActivity实例的flag方法,并传入符合条件的Checker对象,触发解密逻辑
instance.flag(validChecker);
},
// 枚举完成(所有实例都已处理)时的回调,此处无需额外操作
onComplete: function () {
}
});
});
4.2 Python 脚本
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.frida0x7"
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()
4.3 成功显示 Flag

5. 技术总结
5.1 两种构造干预方式的核心差异
在Frida中,Java构造函数可通过$init
方法映射进行Hook(重写$init.implementation
修改参数),但这种方式强依赖Hook时机 ------必须在目标实例创建前完成Hook,否则无法拦截已创建的实例。而通过Java.use(类名).$new(参数)
主动创建实例,则完全绕开了原程序的实例创建逻辑,无需依赖Hook时机,直接构造符合条件的对象。
5.2 案例中选择$new
而非$init
的关键原因
- 时机问题规避 :原程序中
Checker
实例在MainActivity.onCreate
中被快速创建(new Checker(123, 321)
),若使用$init
Hook,需确保注入脚本在onCreate
执行前生效(如spawn
模式提前注入),否则会因实例已创建而Hook失效。而$new
主动创建实例无需关心原实例的创建时机,直接构造符合条件的对象(600, 600),稳定性更高。 - 逻辑简化 :原程序的
flag
方法仅依赖Checker
实例的参数是否满足条件,无需修改原实例的其他逻辑。通过$new
直接创建"合格"实例,比Hook$init
更直接,避免了因构造函数逻辑复杂(如额外初始化操作)导致的Hook风险。 - 依赖资源就绪 :
flag
方法需操作MainActivity
的TextView
(UI组件),必须在onCreate
执行完毕后调用(否则TextView
未初始化)。Java.choose
枚举MainActivity
实例,确保了调用flag
时依赖的UI资源已就绪,而$init
Hook无法直接保证这一点。
5.3 Frida工具特性的场景适配
Java.use
不仅用于Hook类,更支持主动调用构造函数($new
),这在需要"正向创建符合条件的对象"时尤为高效。Java.choose
的核心价值是获取"已初始化的活跃实例",对于依赖生命周期的组件(如Activity
、UI控件),它能确保操作在实例就绪后执行,避免空指针等错误。
5.4 适用场景总结
当目标逻辑依赖特定对象的属性/参数,且原程序的实例创建时机早、流程简单时,主动创建符合条件的实例($new
)+ 枚举活跃目标实例(Java.choose
) 是更可靠的方案。它规避了Hook时机的限制,直接触发目标逻辑,尤其适合UI相关场景或需要快速验证的场景。而$init
Hook更适合需要修改"所有新创建实例"的通用场景,需额外注意注入时机的把控。