文章目录
-
- 一、基础概念
-
- [1. 创建类实例](#1. 创建类实例)
- [2. 方法重载](#2. 方法重载)
- [3. 搜索运行时实例](#3. 搜索运行时实例)
- 二、Hook语法
-
- [1. 定位目标类](#1. 定位目标类)
- [2. 替换方法实现](#2. 替换方法实现)
- [3. 创建类实例](#3. 创建类实例)
- [4. 搜索运行时实例](#4. 搜索运行时实例)
- [5. 处理方法重载](#5. 处理方法重载)
- 三、应用源码分析
-
- [1. MainActivity类](#1. MainActivity类)
- [2. Check类](#2. Check类)
- 四、案例解析
-
- [1. 界面说明](#1. 界面说明)
- [2. hook.js脚本(核心Hook逻辑)](#2. hook.js脚本(核心Hook逻辑))
- [3. run.py脚本(Frida注入工具)](#3. run.py脚本(Frida注入工具))
- [4. 执行效果](#4. 执行效果)
- 五、技术总结
⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。
一、基础概念
1. 创建类实例
指在Hook过程中,通过动态方式生成目标类的对象实例,用于调用类中的非静态方法(非静态方法需依赖实例才能执行)。在本案例中,需创建Check
类的实例以调用其get_flag
方法(非静态方法),从而获取flag数据。
2. 方法重载
当类中存在多个同名但参数列表(参数类型、数量、顺序)不同的方法时,称为方法重载。Hook时需通过指定参数类型精确匹配目标重载版本,否则可能调用错误的方法。本案例中TextView
的setText
方法存在多个重载版本,需明确指定CharSequence
参数类型的版本以设置文本。
3. 搜索运行时实例
指在应用运行过程中,查找已被实例化的目标类对象(如Activity
、View
等动态创建的实例)。由于部分对象(如MainActivity
实例)并非通过静态方式创建,需通过运行时搜索获取其实例,进而操作其成员变量(如本案例中的TextView
控件t1
)。
二、Hook语法
1. 定位目标类
通过Java.use(className)
获取目标类的JavaScript包装对象,用于后续操作:
javascript
// 定位Check类和String类
var Check = Java.use('com.ad2001.frida0x4.Check');
var JavaString = Java.use('java.lang.String');
2. 替换方法实现
通过类名.方法名.implementation
重写目标方法的逻辑,可在其中调用原方法或修改行为:
javascript
// 重写Check类的get_flag方法
Check.get_flag.implementation = function (a) {
// 强制传入参数1337,忽略原始输入
return this.get_flag(1337);
};
3. 创建类实例
通过类名.$new()
动态创建类的实例,用于调用非静态方法:
javascript
// 创建Check类实例并调用get_flag方法
var flag = Check.$new().get_flag(0);
4. 搜索运行时实例
通过Java.choose(className, options)
搜索应用中已实例化的目标类对象,常用于获取Activity、View等动态创建的实例:
javascript
// 搜索MainActivity的运行时实例
Java.choose('com.ad2001.frida0x4.MainActivity', {
onMatch: function (activity) { // 找到实例时触发
console.log("找到MainActivity实例");
},
onComplete: function () { // 搜索完成时触发
console.log("搜索结束");
}
});
5. 处理方法重载
当方法存在多个重载版本(参数不同)时,通过方法名.overload(参数类型列表)
指定目标重载版本:
javascript
// 调用TextView的setText(CharSequence)重载方法
// 通过call执行这个重载方法,第一个参数是 "谁来调用",第二个参数是方法的输入参数。
tv.setText.overload('java.lang.CharSequence').call(tv, JavaString.$new(flag));
三、应用源码分析
本章示例应用的链接:
https://pan.baidu.com/s/16EE2XE-OZS_xBRPlWUODbw?pwd=n2vb
提取码: n2vb
使用APK:Challenge 0x4.apk
1. MainActivity类
功能:应用主界面逻辑,负责初始化UI组件(TextView):
java
public class MainActivity extends AppCompatActivity {
TextView t1; // 声明TextView成员变量
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 加载布局
this.t1 = (TextView) findViewById(R.id.txtview); // 绑定布局中的TextView
}
}
关键分析:t1
是显示文本的核心控件,Hook的目标是通过修改其内容展示flag。
2. Check类
功能:包含get_flag
方法,根据输入参数返回flag(核心逻辑):
java
public class Check {
public String get_flag(int a) {
if (a == 1337) { // 仅当参数为1337时返回flag
byte[] decoded = new byte["I]FKNtW@]JKPFA\\[NALJr".getBytes().length];
// 对字符串每个字节异或15解密
for (int i = 0; i < "I]FKNtW@]JKPFA\\[NALJr".getBytes().length; i++) {
decoded[i] = (byte) ("I]FKNtW@]JKPFA\\[NALJr".getBytes()[i] ^ 15);
}
return new String(decoded); // 返回解密后的flag
}
return ""; // 参数不正确时返回空
}
}
关键分析:get_flag
是flag的生成逻辑,仅当输入a=1337
时通过异或运算解密字符串,否则返回空。这是Hook的核心目标方法。
四、案例解析
1. 界面说明
应用默认界面无主动触发逻辑,仅初始化TextView但未设置内容,需通过Hook强制展示flag:
2. hook.js脚本(核心Hook逻辑)
代码结构与之前保持一致,参考"基础篇2"内容 4.1 章节:【Frida Android】基础篇2:Frida基础操作模式详解
javascript
import Java from 'frida-java-bridge';
Java.perform(function () { // 进入Java环境执行Hook逻辑
// 定位目标类
var Check = Java.use('com.ad2001.frida0x4.Check');
var JavaString = Java.use('java.lang.String');
var MainActivityClassName = 'com.ad2001.frida0x4.MainActivity';
// Hook get_flag方法:强制返回正确flag
Check.get_flag.implementation = function (a) {
return this.get_flag(1337); // 忽略原始参数,强制传入1337
};
// 创建Check实例并获取flag
var flag = Check.$new().get_flag(0); // 此时调用已被Hook,实际传入1337
console.log("获取到flag:" + flag);
// 延迟搜索MainActivity实例(等待界面初始化完成)
setTimeout(function () {
console.log("开始搜索MainActivity实例...");
Java.choose(MainActivityClassName, {
onMatch: function (activity) { // 找到实例后操作TextView
console.log("找到MainActivity实例");
var tv = activity.t1.value; // 获取t1成员变量(TextView实例)
if (tv) {
// 调用TextView的setText(CharSequence)重载方法设置flag
tv.setText.overload('java.lang.CharSequence').call(tv, JavaString.$new(flag));
console.log("Flag已显示");
} else {
console.log("未找到TextView(变量名可能错误)");
}
},
onComplete: function () {
console.log("MainActivity实例搜索完成");
}
});
}, 1000); // 延迟1秒,确保Activity已初始化
});
这里说明下为什么调用TextView
的setText
方法时必须指定CharSequence
类型的重载版本。
Android 中的TextView
类为了支持不同类型的输入(如字符串、资源 ID、字符序列等),设计了多个setText
重载方法,例如:
setText(String text)
:直接接收String
类型参数setText(CharSequence text)
:接收CharSequence
接口类型参数(String
是CharSequence
的实现类)setText(int resId)
:接收资源 ID(如R.string.app_name
)- 其他重载(如带参数
BufferType
的版本)
当通过 Frida 调用方法时,必须明确指定重载版本:
- 本案例中用于设置文本的
flag
是通过JavaString.$new(flag)
创建的,其中JavaString
是java.lang.String
的包装类。 - 而
String
类本质上是CharSequence
接口的实现类(public final class String implements CharSequence
),因此String
对象可以直接作为CharSequence
类型的参数传入。 - 通过
overload('java.lang.CharSequence')
明确指定重载版本后,Frida 能精确匹配到setText(CharSequence)
方法,确保传入的String
对象被正确接收,避免因类型匹配错误导致的调用失败。
3. run.py脚本(Frida注入工具)
python
import frida
import sys
import time
def on_message(message, data):
# 处理Hook脚本发送的日志
if message['type'] == 'send':
print(f"[Hook 日志] {message['payload']}")
elif message['type'] == 'error':
print(f"[错误] {message['stack']}")
# 目标应用包名
PACKAGE_NAME = "com.ad2001.frida0x4"
def main():
try:
# 连接USB设备
device = frida.get_usb_device(timeout=10)
print(f"已连接设备:{device.name}")
# 启动目标应用并获取进程ID
print(f"启动进程 {PACKAGE_NAME}...")
pid = device.spawn([PACKAGE_NAME])
device.resume(pid)
time.sleep(2) # 等待应用启动
# 附加到进程并注入Hook脚本
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. 执行效果
-
执行Python脚本注入Hook代码:
-
应用界面成功显示flag:
五、技术总结
-
核心流程:本案例通过Frida实现Java层Hook的完整流程为:定位目标类→修改关键方法逻辑→创建实例获取数据→搜索运行时对象→操作UI组件展示结果,全程围绕"创建类实例""方法重载处理""搜索运行时实例"三个核心技术点展开。
-
技术点应用:
- 创建类实例:通过
Check.$new()
生成Check
对象,解决非静态方法get_flag
的调用依赖; - 方法重载处理:用
overload('java.lang.CharSequence')
指定setText
的目标版本,避免调用错误重载方法; - 搜索运行时实例:通过
Java.choose
找到动态创建的MainActivity
实例,获取其成员变量t1
(TextView)进行操作。
- 创建类实例:通过
-
实践技巧:
- 针对非静态方法,需先创建实例或获取运行时实例才能Hook;
- 处理重载方法时,参数类型需严格匹配(如
CharSequence
而非String
,因String
是其子类); - 搜索运行时实例需考虑对象初始化时机,可通过
setTimeout
延迟执行避免实例未创建的问题。
-
应用扩展:该技术组合可用于逆向分析中操作动态生成的对象、调用特定重载方法、获取非静态成员变量等场景,是Java层Hook的核心实用技巧。