frida
1 frida安装与使用
如果安卓版本比较低的话,最新版frida不是很稳定。推荐安卓7、8安装frida 12.8.0版本,安卓10/frida14,安卓12/frida16。
1.1、安装手机的frida
adb shell getprop ro.product.cpu.abi
PS C:\Users\Administrator> adb shell getprop ro.product.cpu.abi // 查看Androld手机设备设置
arm64-v8a // CPU架构
1.2 下载frida
官网地址:https:/github.com/frida/frida/releases
一定要对应的CPU架构

目前用的最多的是frida,frida-ps,frida-trace
- frida 是hook命令,
- frida-ps 是查看进程的,
- frida-trace 是跟踪函数调用。
一般来讲模拟器使用的是x86或x86_64的架构,而真机就是 arm或arm64架构。这里返回的是x86_64,那么我们下载x86_64的。
1.3 安装与使用
下载后解压出来只有一个文件,需要把它推送到模拟器中/data/loacl/tmp下
# 确保设备已通过USB连接并启用调试模式,执行:adb devices 验证连接。
# 若提示`permission denied`,尝试:adb root 后再执行后续步骤。
1. adb push frida-server-17.0.0 /data/local/tmp/ # 推送文件到设备
> e:\frida-server-17.0.0: 1 file pushed, 0 skipped. 66.2 MB/s (52018928 bytes in 0.749s)
# 表示成功
2. adb shell # 进入ADB Shell
3. su #切换管理员权限
> flame:/ # 执行后会出现一下效果表示root成功了
4. cd /data/local/tmp/ # 进入目标目录
5. chmod 777 /data/local/tmp/frida-server-17.0.0 # 修改文件权限
# 注意:chmod 777 会开放全部权限,生产环境建议使用最小权限原则(如755)。
6. ./frida-server-17.0.0 运行程序
# 避免检测,可以不使用这个端口启动,默认启动责使用 adb forward tcp:27042 tcp:27042
7. ./frida-server -10.0.0.0:6666
# 端口转发,默认 adb forward tcp:27042 tcp:27042
8. adb forward tcp:6666 tcp:6666
在PC上安装Python的运行环境,安装完成后执行下面的命令安装frida
frida-tools==XXX
pip install frida==XXX
注意: server与client版本必须保持一致
2 Frida 命令列表
Frida 提供了一系列命令行工具,用于与目标进程交互、脚本注入和管理。以下是常用的 Frida 命令及其功能:
| 英文选项 | 中文翻译 |
|---|---|
--version | 显示程序版本 |
-h, --help | 显示帮助信息 |
-D ID, --device=ID | 连接到指定设备 |
-U, --usb | 连接到USB设备 |
-R, --remote | 连接到远程服务器 |
-H HOST, --host=HOST | 连接到指定主机的远程服务器 |
-f FILE, --file=FILE | 启动指定文件 |
-n NAME | 附加到指定名称的进程 |
-p PID | 附加到指定PID的进程 |
--debug | 启用调试器 |
--enable-jit | 启用即时编译 |
-l SCRIPT | 加载本地脚本 |
-c URI | 加载CodeShare脚本 |
-e CODE | 执行单行代码 |
-q | 安静模式 |
--no-pause | 不暂停主线程 |
-o LOGFILE | 输出到日志文件 |
2.1 基本命令
frida --help
显示 Frida 的帮助信息,列出所有可用的命令和选项。
frida -l <script.js> -n <process_name>
将指定的 JavaScript 脚本注入到目标进程(通过进程名指定)。
frida -l <script.js> -p <pid>
将指定的 JavaScript 脚本注入到目标进程(通过进程 ID 指定)。
frida -U -l <script.js> -n <process_name>
在已连接的 USB 设备上注入脚本(-U 表示 USB 设备)。
frida-trace -n <process_name> -i <function_name>
动态跟踪目标进程中的函数调用(通过函数名指定)。
2.2 设备管理
frida-ls-devices
列出所有可连接的设备(USB、本地、远程)。
frida-ps
列出当前设备上运行的进程。
frida-ps -U
列出 USB 设备上运行的进程。
frida-ps -a
列出所有进程,包括系统进程。
2.3 脚本调试
frida --runtime=duk -l <script.js> -n <process_name>
使用 Duktape JavaScript 引擎运行脚本(默认引擎)。
frida --runtime=v8 -l <script.js> -n <process_name>
使用 V8 JavaScript 引擎运行脚本(性能更高)。
frida -f <executable> -l <script.js>
启动目标可执行文件并注入脚本。
2.4 高级功能
frida-discover
发现目标进程中的模块和函数。
frida-kill <pid>
终止目标进程(通过进程 ID 指定)。
frida-repl -n <process_name>
启动交互式 REPL(Read-Eval-Print Loop)与目标进程交互。
2.5 附加选项
--debug
启用调试模式,输出更多日志信息。
--no-pause
注入脚本后不暂停目标进程。
--enable-jit
启用 JavaScript JIT 编译(提升性能)。
3 常见用法示例
动态跟踪函数调用:
frida-trace -n Telegram -i "recv*"
跟踪 Telegram 进程中所有以 recv 开头的函数调用。
注入脚本到 Android 应用:
frida -U -l hook.js -n com.example.app
在 USB 连接的 Android 设备上注入 hook.js 到 com.example.app 进程。
启动 REPL 交互:
frida-repl -n Calculator
与 Windows 计算器进程交互并动态执行脚本。
3.1 Frida两种操作模式
| 操作模式 | 说明 |
|---|---|
| CLI命令行 | Javascript脚本注入进程 |
| RPC | Python进行Javascript脚本注入 |
3.2 Frida 操作 APP 的两种方式
| 方式名称 | 方式说明 | CLI下启动方式 | 使用举例 |
|---|---|---|---|
| spwan | 将启动APP的权利交由Frida来控制。不管APP是否启动,都会重新启动APP。 | -f参数指定包名 | frida -U -l myhook.js -f 包名 --no-pause |
| attach | 建立在目标APP已经启动的情况下,Frida通过ptrace注入程序从而执行Hook的操作 | 不加-f参数 | frida -U -l myhook.js 包名 |
spawn模式详解
原理:
启动一个新的进程并将其挂起,同时注入 Frida 的 JavaScript 代码,注入完成后调用resume恢复进程的正常运行。
命令解析:
frida -U -l myhook.js -f com.xxx.xxxx --no-pause。-U用于连接 USB 设备;-l用于指定要加载的 Frida 脚本(这里是myhook.js);-f用于指定要重启并注入脚本的进程(通过应用包名指定,这里是com.xxx.xxxx) ;--no-pause表示自动运行程序,即注入脚本后不暂停应用启动,直接让应用继续执行。
myhook.js 脚本
Java.perform(function () {
// 假设应用启动时会调用某个类的初始化函数来加载配置
var targetClass = Java.use('com.example.demoapp.utils.ConfigLoader');
targetClass.init.implementation = function () {
console.log('Hooked: ConfigLoader.init is called');
// 执行原函数逻辑
this.init();
};
});
python脚本
import frida
import sys
def on_message(message, data):
print("message", message)
print("data", data)
# 通过Spawn模式启动一个新的应用程序进程,并在该进程中加载Frida脚本
device = frida.get_usb_device()
pid = device.spawn(["com.luoge.com"])
device.resume(pid) # 恢复应用程序的执行
session = device.attach(pid)
with open("myhook.js") as f:
script = session.create_script(f.read())
script.on("message", on_message)
script.load()
sys.stdin.read() # 阻塞主线程,以保持脚本运行
attach模式详解
原理:
attach模式下,Frida利用ptrace机制修改进程内存,将JavaScript脚本注入到处于启动状态的Android目标进程中。ptrace是Linux系统提供的一种进程调试机制,允许一个进程(调试器)控制另一个进程(被调试进程),能读写被调试进程的内存和寄存器等 ,这使得Frida可注入脚本并执行Hook等操作。
命令解析:
frida -U -l myhook.js com.xxx.xxxx这条命令用于在attach模式下注入脚本。-U:指定操作对象为USB连接的设备。在Frida操作中,当设备通过USB与开发机相连时,使用该参数可准确连接到对应的设备进行后续操作 。-l:用于指定要加载的JavaScript脚本文件,这里myhook.js就是具体的脚本文件,该脚本中可编写各种Hook函数、逻辑代码来实现对目标进程的监控、修改等功能。com.xxx.xxxx:指定目标进程名。若想指定进程的PID,可使用-p选项。例如frida -U -l myhook.js -p <pid>。可通过frida-ps -U命令查看当前通过USB连接设备上正在运行的进程列表及其PID信息,方便准确指定目标进程。
myhook.js 脚本
Java.perform(function () {
// 假设应用启动时会调用某个类的初始化函数来加载配置
var targetClass = Java.use('com.example.demoapp.utils.ConfigLoader');
targetClass.init.implementation = function () {
console.log('Hooked: ConfigLoader.init is called');
// 执行原函数逻辑
this.init();
};
});
python脚本
import frida
import sys
def on_message(message, data):
print("message", message)
print("data", data)
# 通过attach模式启动一个新的应用程序进程,并在该进程中加载Frida脚本
device = frida.get_usb_device()
pid = device.get_process("com.jjj.com").pid # 获取目标应用程序的PID
session = device.attach(pid) # 附加到目标进程
with open("myhook.js") as f:
script = session.create_script(f.read())
script.on("message", on_message)
script.load()
sys.stdin.read() # 阻塞主线程,以保持脚本运行
4 Hook Java方法
这段内容是对使用Frida进行Java方法Hook时关键概念的讲解,以下是结合示例的进一步说明:
| 名称 | 说明 |
|---|---|
| Java.use() | 作用是获取Java类对象,获取后就能在Frida的JavaScript环境中对该类进行操作。 |
| Java.perform() | 用于在Frida的JavaScript环境中创建一个执行上下文,在这个上下文中可以安全地对Java类和方法进行操作。因为Frida注入的脚本执行环境与Java虚拟机环境交互需要特定的上下文设置,Java.perform() 就提供了这样的环境。 |
| implementation | 该属性用于重新定义被Hook方法的实现逻辑。通过它可以在原方法执行前后添加自定义代码,甚至完全替换原方法的行为。 |
| this 和 参数 | 在自定义的implementation函数中,this指向原方法所属的对象实例,可以用来调用原方法或访问对象属性;函数的参数则是原方法被调用时传入的参数,可用于修改参数值等操作。 |
| 调用原始方法 | 在自定义实现中,通过this.原方法名.apply(this, arguments) 形式调用原始方法,确保原方法的逻辑也能执行,避免完全覆盖原方法功能。arguments是 JavaScript 中保存函数调用时传入参数的类数组对象。 |
示例:
Java.perform(() => {
// 获取java.util.Date类对象
const Date = Java.use('java.util.Date');
// 调用Date类的方法,比如获取当前时间戳
const now = Date.now();
console.log('Current timestamp:', now);
});
// 不使用Java.perform() 直接操作会报错
// const Date = Java.use('java.util.Date'); 这样写是错误的
Java.perform(() => {
const Date = Java.use('java.util.Date');
// 正确在上下文中操作Java类
});
// 示例:假设要 Hook java.util.Date 的 getTime 方法,在获取时间戳前后添加日志:
Java.perform(() => {
const Date = Java.use('java.util.Date');
Date.getTime.implementation = function () {
console.log('Before getting time');
const result = this.getTime();
console.log('After getting time');
return result;
};
const date = new Date();
date.getTime();
});
// 示例:假设Hook一个带参数的方法`add` (比如在某个自定义的`Calculator`类中) :
Java.perform(() => {
const Calculator = Java.use('com.example.Calculator');
Calculator.add.implementation = function (a, b) {
// 可以修改传入参数
a = a + 1;
b = b + 1;
// 使用this调用原始方法
const result = this.add(a, b);
return result;
};
const calculator = new Calculator();
const sum = calculator.add(1, 2);
console.log('Sum:', sum);
});
// 示例:还是以`Calculator`类的`add`方法为例:
```javascript
Java.perform(() => {
const Calculator = Java.use('com.example.Calculator');
Calculator.add.implementation = function (a, b) {
// 这里也可以不修改参数直接调用原始方法
const result = this.add.apply(this, arguments);
return result;
};
const calculator = new Calculator();
const sum = calculator.add(1, 2);
console.log('Sum:', sum);
});
4.1 重载函数常用的类型
- 提示:如果出现以上没有的类型,则app源码文件.class.对象类型
| java中的类型 | frida里面的类型 |
|---|---|
| int | int |
| float | float |
| boolean | boolean |
| string | java.lang.String |
| char | [C |
| byte | [B |
| list | java.util.List |
| HashMap | java.util.HashMap |
| ArrayList | java.util.ArrayList |
| JavaObject | java.lang.Object |
| String[] | [Ljava.lang.String |
4.2 载入类Java.use(类名位置.类名)
Java.use方法用于加载一个Java类,相当于Java中的Class.forName()。比如要加载一个string类:
var stringclass =ava.use("java.lang.string");
加载内部类:
var MyClass_InnerClass = Java.use("com.luoyesigiu.Myclass$InnerClass");
4.3 重载方法overload()
示例XXXX包下面的 Test.java
public class Test{
public int a;
private static int b = 200;
public Test(int a){
this.a=a
};
public int a(){
return 10
};
public static void seta(int a){
this.a = a
};
public void seta1(int a){
this.a = a
};
public InnerTest(){
public int a;
public InnerTest(int a){
this.a = a
};
};
}
示例Java类(Calculator.java)
import XXXX.Test
public class Calculator {
public int add(int a, int b) {
return a + b;
};
public float add(float a, float b) {
return a + b;
};
public String add(String a, String b) {
return a + b;
};
public String add(Test test) {
return test.a;
};
}
Hook方法重载的JavaScript脚本
Java.perform():创建一个Frida的JavaScript执行上下文,用于安全地操作Java对象和方法。Calculator = Java.use('Calculator');:获取Calculator类的代理对象,以便在JavaScript中操作该类。Calculator.add.overload('int', 'int').implementation = function() {... }:使用overload方法指定要Hook的特定重载方法,这里通过传入参数类型来区分不同的重载版本,并重新定义其实现,在原方法执行前后添加自定义的日志打印逻辑。
通过这种方式,就可以针对Java中重载的方法,使用Frida进行分别Hook和自定义处理。
指定单个重载调用
Java.perform(() => {
// 获取Calculator类
const Calculator = Java.use('Calculator');
// Hook int add(int a, int b) 方法
Calculator.add.overload('int', 'int').implementation = function (a, b) {
console.log('传递的参数 a:', a, ', b:', b);
const result = this.add(a, b);
console.log('执行后的结果 result:', result);
return result;
};
// Hook float add(float a, float b) 方法
Calculator.add.overload('float', 'float').implementation = function (a, b) {
console.log('传递的参数, a:', a, ', b:', b);
const result = this.add(a, b);
console.log('执行后的结果, result:', result);
return result;
};
// Hook String add(String a, String b) 方法
Calculator.add.overload('java.lang.String', 'java.lang.String').implementation = function (a, b) {
console.log('传递的参数, a:', a, ', b:', b);
const result = this.add(a, b);
console.log('执行后的结果:', result);
return result;
};
});
// Hook int add(Test test) 方法
Calculator.add.overload('XXXX.Test').implementation = function (arg) {
console.log('传递的参数, arg:', arg);
const result = this.add(arg);
console.log('执行后的结果:', result);
return result;
};
});
指定全部重载调用 overloads,apply()
// 指定某个方法
Java.perform(() => {
const Calculator = Java.use('Calculator');
Calculator.add.overloads.forEach(overload => {
overload.implementation = function() {
console.log(JSON.stringify(Array.from(arguments))); // 打印每个参数
const originalResult = overload.apply(this, arguments); // 传递参数调用结果
console.log('Return value:', originalResult);
return originalResult;
};
});
});
// 模糊匹配某个方法
Java.perform(() => {
const Calculator = Java.use('Calculator');
// 使用overloads函数获取所有重载方法
var methods = targetClass['add'].overloads;
console.log(methods);
console.log(methods.length, '多少个重载方法');
// 遍历所有的重载方法并进行钩子
methods.forEach(function (method) {
method.implementation = function () {
console.log('方法被调用:', method);
// 在这里可以添加你的自定义逻辑
// 调用原始方法
return method.apply(this, arguments);
};
});
}
4.4 hook构造函数TestClass.$init
Java.perform(function() {
// 获取目标类
var TestClass = Java.use('Test');
// Hook构造函数
TestClass.$init.overload('int').implementation = function(a) {
// 在构造函数执行前打印参数
console.log('[+] 正在创建Test对象,参数a:', a);
// 调用原始构造函数
this.$init(a);
// 在构造函数执行后打印对象状态
console.log('[+] Test对象创建完成,成员变量a的值:', this.a.value);
// 可以选择修改成员变量的值
this.a.value = a * 2;
console.log('[+] 修改后的成员变量a的值:', this.a.value);
};
});
4.5 Hook主动调用
主动调用分2中情况,静态方法、实例方法,如果是Hook,是不区分静态和实例的。
// 静态方法调用
Java.perform(function() {
// 获取目标类
var TestClass = Java.use('Test');
// 直接调用
TestClass.seta(12)
});
// 实例方法调用
Java.perform(function() {
// 获取目标类
var TestClass = Java.use('Test');
Java.choose("Test", {
// 当找到匹配的实例时调用
onMatch: function (obj) {
int a = 11;
// 调用实例的 getInfo 方法并打印结果
console.log("找到 Test 实例,其信息为:", obj.seta1(a));
},
// 搜索完成时调用
onComplete: function () {
console.log("内存中 Test 类实例搜索操作完毕");
}
});
})
- 获取类引用:使用
Java.use("Test")获取Test类的引用,以便后续操作。 - 搜索实例:通过
Java.choose("Test", { ... })在 Java 堆中搜索Test类的实例。 - 处理找到的实例:在
onMatch回调函数中,对找到的每个Test实例调用seta1方法,并将结果打印到控制台。 - 搜索完成处理:在
onComplete回调函数中,打印一条消息表示搜索操作已经完成。
通过这个示例,我们可以在运行时动态地对Test类的实例进行监控和分析,获取其相关信息,这对于逆向工程和应用程序调试非常有帮助。
5 Hook Java类
获取和修改类的字段、hook内部类、枚举所有加载的类
5.1 修改类字段
// 静态字段的修改
Java.perform(function() {
var TestClass = Java.use('Test');
TestClass.b.value = '100';
});
// 非静态字段的修改
Java.perform(function() {
Java.choose("Test", {
onMatch: function (obj) {
obj._a.value = 100; // 字段名与方法名相同时,前面需要加下划线区分
},
// 搜索完成时调用
onComplete: function () {
console.log("内存中 Test 类实例搜索操作完毕");
}
});
})
5.2 hook内部类
需要再类和内部类之间加一个$符号,innerTestClass.$init进行内部构造函数的重载调用
Java.perform(function() {
var innerTestClass = Java.use('Test$InnerClass');
innerTestClass.$init.overload('int').implementation = function(a){
this.$init(a) // 调用构造函数
return this.$init(a)
};
});
5.3 枚举所有加载类和方法
枚举所有加载类——无法获取构造方法
getDeclaredMethods() 获取该类的所有方法,返回一个对象数组
getName 获取当前方法的名称
Java.perform(function() {
var TestClass = Java.use('Test');
var methods = TestClass.class.getDeclaredMethods();
for (var i=0; i<methods.length; i++ ){
console.log(methods[i].getName);
};
});
5.4 枚举所有加载类
首先枚举类的所有方法和HOOK类的所有重载方法写出来
Java.perform(function() {
var TestClass = Java.use('Test');
var methods = TestClass.class.getDeclaredMethods();
for (var i=0; i<methods.length; i++ ){
var methodName = methods[i].getName;
console.log("方法名:",methodName )
for (var j=0; j<TestClass[methodName].overloads.length; j++ ){
TestClass[methodName].overloads[k].implementation = function(a){
for (var k=0; k<arguments.length; k++){
console.log(arguments[k])
}:
return this[methodName].apply(this, arguments)
};
};
};
});
apk下载网站
https://www.liqucn.com/
https://www.wandoujia.com/
http://www.2265.com/
8464

被折叠的 条评论
为什么被折叠?



