Frida使用笔记

Frida使用笔记

mmj Lv2

另外提供了使用失败时适配一加系列手机的一些解决方法

操作模式:

操作模式 描述 优点 主要用途
CLI(命令行)模式 通过命令行直接将JavaScript脚本注入进程中,对进程进行操作 便于直接注入和操作 在较小规模的操作或者需求比较简单的场景中使用
RPC模式 使用Python进行JavaScript脚本的注入工作,实际对进程进行操作的还是JavaScript脚本,可以通过RPC传输给Python脚本来进行复杂数据的处理 在对复杂数据的处理上可以通过RPC传输给Python脚本来进行,有利于减少被注入进程的性能损耗 在大规模调用中更加普遍,特别是对于复杂数据处理的需求

注入模式与启动命令:

注入模式 描述 命令或参数 优点 主要用途
Spawn模式 将启动App的权利交由Frida来控制,即使目标App已经启动,在使用Frida注入程序时还是会重新启动App 在CLI模式中,Frida通过加上 -f 参数指定包名以spawn模式操作App 适合于需要在App启动时即进行注入的场景,可以在App启动时即捕获其行为 当需要监控App从启动开始的所有行为时使用
Attach模式 在目标App已经启动的情况下,Frida通过ptrace注入程序从而执行Hook的操作 在CLI模式中,如果不添加 -f 参数,则默认会通过attach模式注入App 适合于已经运行的App,不会重新启动App,对用户体验影响较小 在App已经启动,或者我们只关心特定时刻或特定功能的行为时使用
1
frida -U -f 进程名 -l hook.js

attach模式 :

1
frida -U 进程名 -l hook.js

如何开始?

1
2
3
4
5
6
7
8
9
10
11
12
13
adb shell

su

cd /data/local/tmp/

./frida-server

另开终端

frida-ps -Uai

frida -U -f <package_name>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#一加12# #frida#
记个小坑,使用frida spawn模式时会time out,提示PID 1442进程超时,
使用frida-ps查询发现,该进程是zygote,遂想到一加12不支持32位应用,
于是使用***adb shell su -c stop zygote_secondary***结束该进程,再次spawn,运行成功。

---------------------------------------------------------------------------------------

对于运行 ColorOS 15 的一加用户,请使用以下命令终止 zygote32 进程。
否则,Frida 将等待来自 zygote32 进程的信号响应(显然已超时),而不是预期的 zygote64 进程。

***setprop persist.sys.tango_zygote32.start 0***
在此之后,将停止。zygote_tango

OP5CFBL1:/data/local/tmp # getprop | grep "zygote"
[init.svc.zygote]: [running]
[init.svc.zygote_ocomp]: [running]
[init.svc.zygote_tango]: [stopped]
[init.svc_debug_pid.zygote]: [26502]
[init.svc_debug_pid.zygote_ocomp]: [1611]
[init.svc_debug_pid.zygote_tango]: []
[persist.oplus.zygote_ocomp]: [true]
[persist.sys.system.abnormalboot]: [2025-01-15 07:44:49:zygote reboot by netd]
[persist.sys.tango_zygote32.start]: [0]
[ro.boottime.zygote]: [6197081507]
[ro.boottime.zygote_ocomp]: [6380248329]
[ro.boottime.zygote_tango]: [6382967496]
[ro.zygote]: [zygote64]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
其他:可考虑禁用magisk中Zygisk

SElinux状态保持宽容:***setenforce 0***

禁用usap:***setprop persist.device_config.runtime_native.usap_pool_enabled false***
您可以尝试以下作:
getprop|grep usap
如果 getprop xxx.usap 返回 true
setprop xxxx.usap false
然后 getprop xxxx.usap 将返回 false
以自己的方式使用 spawn
olives:/ # ***getprop|grep usap***
[persist.device_config.runtime_native.usap_pool_enabled]: [false]
[persist.device_config.runtime_native.usap_pool_refill_delay_ms]: [3000]
[persist.device_config.runtime_native.usap_pool_size_max]: [10]
[persist.device_config.runtime_native.usap_pool_size_min]: [1]
[persist.device_config.runtime_native.usap_refill_threshold]: [5]
olives:/ #

Frida Java层 hook

1.Hook普通方法、打印参数和修改返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//定义一个名为hookTest1的函数
function hookTest1(){
//获取一个名为"类名"的Java类,并将其实例赋值给JavaScript变量utils
var utils = Java.use("类名");
//修改"类名"的"method"方法的实现。这个新的实现会接收两个参数(a和b)
utils.method.implementation = function(a, b){
//将参数a和b的值改为123和456。
a = 123;
b = 456;
//调用修改过的"method"方法,并将返回值存储在`retval`变量中
var retval = this.method(a, b);
//在控制台上打印参数a,b的值以及"method"方法的返回值
console.log(a, b, retval);
//返回"method"方法的返回值
return retval;
}
}

2.Hook重载参数

1
2
3
4
5
6
7
8
9
10
11
12
13
// .overload()
// .overload('自定义参数')
// .overload('int')
function hookTest2(){
var utils = Java.use("com.zj.wuaipojie.Demo");
//overload定义重载函数,根据函数的参数类型填
utils.Inner.overload('com.zj.wuaipojie.Demo$Animal','java.lang.String').implementation = function(a,b){
b = "aaaaaaaaaa";
this.Inner(a,b);
console.log(b);
}
}

3.Hook构造函数

1
2
3
4
5
6
7
8
9
10
function hookTest3(){
var utils = Java.use("com.zj.wuaipojie.Demo");
//修改类的构造函数的实现,$init表示构造函数
utils.$init.overload('java.lang.String').implementation = function(str){
console.log(str);
str = "52";
this.$init(str);
}
}

4.Hook字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function hookTest5(){
Java.perform(function(){
//静态字段的修改
var utils = Java.use("com.zj.wuaipojie.Demo");
//修改类的静态字段"flag"的值
utils.staticField.value = "我是被修改的静态变量";
console.log(utils.staticField.value);
//非静态字段的修改
//使用`Java.choose()`枚举类的所有实例
Java.choose("com.zj.wuaipojie.Demo", {
onMatch: function(obj){
//修改实例的非静态字段"_privateInt"的值为"123456",并修改非静态字段"privateInt"的值为9999。
obj._privateInt.value = "123456"; //字段名与函数名相同 前面加个下划线
obj.privateInt.value = 9999;
},
onComplete: function(){

}
});
});

}

5.Hook内部类

1
2
3
4
5
6
7
8
9
10
11
12
function hookTest6(){
Java.perform(function(){
//内部类
var innerClass = Java.use("com.zj.wuaipojie.Demo$innerClass");
console.log(innerClass);
innerClass.$init.implementation = function(){
console.log("eeeeeeee");
}

});
}

6.枚举所有的类与类的所有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function hookTest7(){
Java.perform(function(){
//枚举所有的类与类的所有方法,异步枚举
Java.enumerateLoadedClasses({
onMatch: function(name,handle){
//过滤类名
if(name.indexOf("com.zj.wuaipojie.Demo") !=-1){
console.log(name);
var clazz =Java.use(name);
console.log(clazz);
var methods = clazz.class.getDeclaredMethods();
console.log(methods);
}
},
onComplete: function(){}
})
})
}

7.枚举所有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function hookTest8(){
Java.perform(function(){
var Demo = Java.use("com.zj.wuaipojie.Demo");
//getDeclaredMethods枚举所有方法
var methods =Demo.class.getDeclaredMethods();
for(var j=0; j < methods.length; j++){
var methodName = methods[j].getName();
console.log(methodName);
for(var k=0; k<Demo[methodName].overloads.length;k++){
Demo[methodName].overloads[k].implementation = function(){
for(var i=0;i<arguments.length;i++){
console.log(arguments[i]);
}
return this[methodName].apply(this,arguments);
}
}
}
})
}

8.主动调用

静态方法

1
2
3
var ClassName=Java.use("com.zj.wuaipojie.Demo");
ClassName.privateFunc();

非静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
var ret = null;
Java.perform(function () {
Java.choose("com.zj.wuaipojie.Demo",{ //要hook的类
onMatch:function(instance){
ret=instance.privateFunc("aaaaaaa"); //要hook的方法
},
onComplete:function(){
//console.log("result: " + ret);
}
});
})
//return ret;

Frida native 层 hook

1.Process

API 说明
Process.id 返回附加目标进程的 id
Process.isDebuggerAttached 检查当前是否对目标程序已附加
Process.enumerateModules 枚举当前进程的所有模块,返回模块对象的数组
Process.enumerateThreads 枚举当前进程的所有线程,返回包含 idstatecontext 等属性的对象数组

2.Module

API 说明
Module.load 加载指定 so 文件,返回一个 Module 对象
enumerateImports() 枚举所有 Import 库函数,返回 Module 数组对象
enumerateExports() 枚举所有 Export 库函数,返回 Module 数组对象
enumerateSymbols() 枚举所有 Symbol 库函数,返回 Module 数组对象
findExportByName()findBaseAddress() 寻找指定 so 库中 export 库中的函数地址
Module.findBaseAddress(name)Module.getBaseAddress(name) 返回 so 的基地址

3.Memory

API 说明
Memory.copy() 复制内存
Memory.scan() 搜索内存中特定模式的数据
Memory.scanSync() 同步搜索内存中特定模式的数据,返回多个匹配的数据
Memory.alloc() 在目标进程的堆上申请指定大小的内存,返回一个 NativePointer
Memory.writeByteArray() 将字节数组写入指定内存地址
Memory.readByteArray() 从指定内存地址读取字节数组

4. hook 打印

(int 型、bool 型、char 型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Native 层 hook 打印
// int 型、 bool 型、 cahr 型
function hookTest() {
Java.perform(function () {
//根据导出函数名打印地址
var addr = Module.findExportByName("<so_name>", "<function_name>");
//var addr=Module.findExportByName("libnative-lib.so","Java_com_example_native_1lib_MainActivity_check");
console.log(addr);
if (addr != null) {
Interceptor.attach(addr, {
//onEnter 里打印和修改参数
onEnter: function (args) { //args 传入参数
console.log(args[0]);//打印第一个参数的值
console.log(this.context.x1);//打印寄存器的内容
console.log(args[1].toInt32()); //toInt32() 转十进制值
console.log(args[2].readCString());//读取字符串 char型

console.log(hexdump(args[2]));//dump 内存

},
//onLeave 里打印和修改返回值
onLeave: function (retval) { //retval 为返回值
console.log(retval);
console.log("retval", retval.toInt32());
}

})
}

})
}

(string 型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Native 层 hook 打印
// string 类型
function hookTest() {
Java.perform(function () {
//根据导出函数名打印地址
var addr = Module.findExportByName("<so_name>", "<function_name>");
//var addr=Module.findExportByName("libnative-lib.so","Java_com_example_native_1lib_MainActivity_check");
console.log(addr);
if (addr != null) {
Interceptor.attach(addr, {
//onEnter 里打印和修改参数
onEnter: function (args) { //args 传入参数
//方法1:
var jString = Java.cast(args[1].Jave.use("Java.lang.String"));
console.log("args:", jString.toString());

//方法2:
var JNIEnv = Java.vm.getEnv();
var originalStrPtr = Java.getStringUtfChars(args[1], null).readCString();
console.log(originalStrPtr);
var modifiedContent = "str1";
var newJSring = JNIEnv.newSringUtf(modifiedContent);
args[1] = newJString;

},
//onLeave 里打印和修改返回值
onLeave: function (retval) { //retval 为返回值
//方法1:
var jString = Java.cast(retval.Jave.use("Java.lang.String"));
console.log("retval:", jString.toString());
//方法2:
var JNIEnv = Java.vm.getEnv();
var originalStrPtr = Java.getStringUtfChars(retval, null).readCString();
console.log(originalStrPtr);
var modifiedContent = "str1";
var newJSring = JNIEnv.newSringUtf(modifiedContent);
retval.replace(newJString);

}

})
}

})
}

5. 枚举导入导出表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//枚举导入导出表
//导入表(Import Table):列出了库需要从其他库中调用的函数和符号名称 --需要的
//导出表(Export Table):列出了库中可以被其他程序或库访问的所有公开函数和符号的名称 --提供的
function hookTest1() {
Java.perform(function () {
//打印导入表
var imports = Module.enumerateImports("libxxx.so");
for (var i = 0; i < imports.length; i++) {
if (imports[i].name == "some_name") {
console.log(JSON.stringify(imports[i]));
//通过 JSON.stringify() 打印 object 数据
console.log(imports[i].address);

}
}
//打印导出表
var exports = Module.ennumerateExports("libxxx.so");
for (var i = 0; i < exports.length; i++) {
console.log(JSON.stringify(exports[i]));
}
})
}

6.获取 so 基地址

1
2
3
4
5
6
7
8
9
10
11
//获取 so 基地址的方式
function get_to_so_addr() {
Java.perform(function () {
var moduleAddr1 = Process.findModuleByName("libxxx.so").base;
var moduleAddr2 = Process.getModuleByName("libxxx.so").base;
var moduleAddr3 = Module.findBaseAddress("libxxx.so");
console.log(moduleAddr1);
console.log(moduleAddr2);
console.log(moduleAddr3);
})
}

7.hook 未导出函数 函数基地址计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//hook 未导出函数与函数基地址的计算
function hookTest5(){
Java.perform(function(){
//根据导出函数名打印及地址
var soAddr= findBaseAddress("libxxx.so");
console.log(aoAddr);
var funcAddr=soAddr.add(0x1234);//函数基地址+偏移地址
console.log(funcAddr);
if(funcAddr!=null){
Interceptor.attach(funcAddr,{
onEnter:function(args){

},
onLeave:function(retval){
console.log(retval.toInt2());

}
})
}
})
}

//函数的地址计算
//安卓里面一般 32 位的 so 中用的是 thumb 指令,64 位的 so 中用的是 arm 指令
//ida 中 opcode bytes 判断,arm 指令为四个字节(options->general->Number of opcode bytes(non-graph)输入4)
//thumb 指令的函数指令计算方式:so 基地址+函数在 so 中的偏移 +1
//arm 指令的函数指令计算方式:so 基地址+函数在 so 中的偏移

8.hook dlopen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//hook dlopen
//android_dlopen_ext()、dlopen()、do_dlopen()主要用于加载库文件
function hook_dlopen(){
var dlopen=Module.findExportByName(null,"dlopen");
Interceptor.attach(dlopen,{
onEnter:function(args){
var so_name=args[0].readCString();
if (so_name.indexOf("libxxx.so")>=0) this.call_hook=true;
},
onLeave:function(retval){
if(this,call_hook) hookTest2();
}
});
//高版本 android 系统使用 android_dlopen_ext
var android_dlopen_ext=Module.findExportByName(null,"android_dlopen_ext");
Interceptor.attach(android_dlopen_ext,{
onEnter:function(args){
var so_name=args[0].readCString();
if(so_name.indexOf("libxxx.so")>=0) this.call_hook=true;
},onLeave:function(retval){
if (this.call_hook) hookTest2();
}
});
}

9.写数据

1
2
3
4
5
6
7
8
9
10
//frida 写数据
//一般写在 app 的私有目录里,不然会报错(Permission denied)
var file_path="data/user/0/<package_name>/xxx.txt";
var file_handle= new File(file_path,"wb");
if(file_handle&&file_handle!=null){
file_handle.write(data);//写入数据
console.log("write data success!");
file_handle.flush();//刷新
file_handle.close();//关闭
}

10.内联Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//inline_hook
function inline_hook(){
var soAddr=Module.findBaseAddress("libxxx.so");
if(soAddr){
var funcAddr=soAddr.add(0x1234);//函数基地址+偏移地址
Java.perform(function(){
Interceptor.attach(funcAddr,{
onEnter:function(args){
console.log(this.context);
console.log(this.context.x1);//打印寄存器的内容
this.context.x1=ptr(1);
console.log(this.context.x1);

}
})
})
}
}

//将地址的指令解析成汇编
var soAddr=Module.findBaseAddress("libxxx.so");
var codeAddr=Instruction.parse(soAddr.add(0x1234));
console.log(codeAddr.toString());

11. 普通函数与 JNI 函数的主动调用

1
2
3
4
5
6
7
//普通函数与 jni 函数的主动调用
var funcAddr=Module.findBaseAddress("libxxx.so").add(0x1234);
//NativeFunction 的第一个参数是地址 第二个参数是返回值类型 第三个[]里面是传入的参数的类型(有几个就填几个)
var aesAddr= new NativeFunction(funcAddr,'pointer',['pointer','pointer']);
var encry_test=Memory.allocUtf8String("cyphertext");
var key=Memory.allocUtf8String('key');
console.log(aesAddr(encry_test,key).readCString());

常见模板?

Frida

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function() {

var <class_reference> = Java.use("<package_name>.<class>");
<class_reference>.<method_to_hook>.implementation = function(<args>) {

/*
OUR OWN IMPLEMENTATION OF THE METHOD
*/

}

})
  • Java.perform是 Frida 中的一个函数,用于为脚本创建特殊上下文,以便与 Android 应用程序中的 Java 代码进行交互。这就像打开一扇门来访问和作应用程序内部运行的 Java 代码。进入此上下文后,您可以执行挂钩方法或访问 Java 类等作,以控制或观察应用程序的行为。

  • var <class_reference> = Java.use("<package_name>.<class>");

    在这里,您声明了一个变量来表示目标 Android 应用程序中的 Java 类。您指定要与函数一起使用的类,该函数将类名作为参数。表示 Android 应用程序的包名称,并表示要与之交互的类。<class_reference>Java.use<package_name><class>

  • <class_reference>.<method_to_hook>.implementation = function(<args>) {}

    在选定的类中,通过使用表示法访问它来指定要挂接的方法。在这里,您可以定义自己的逻辑,以便在调用挂接的方法时执行。<class_reference>.<method_to_hook><args>

调用静态方法

1
2
3
4
5
6
7

Java.perform(function() {

var <class_reference> = Java.use("<package_name>.<class>");
<class_reference>.<static_method>();

})

such as…

1
2
3
4
5
6
7

Java.perform(function() {

var a = Java.use("com.ad2001.frida0x2.MainActivity");
a.get_flag(4919); // method name

})

使用 Frida 更改变量的值

1
2
3
4
5
6
Java.perform(function (){

var <class_reference> = Java.use("<package_name>.<class>");
<class_reference>.<variable>.value = <value>;

})

such as…

1
2
3
4
5
6
7
Java.perform(function (){

var a = Java.use("com.ad2001.frida0x3.Checker"); // class reference
a.code.value = 512;


})

调用非静态方法

在 Frida 中,要创建 Java 类的实例,可以使用$new()方法。

1
2
3
4
5
6
7
Java.perform(function() {

var <class_reference> = Java.use("<package_name>.<class>");
var <class_instance> = <class_reference>.$new(); // Class Object
<class_instance>.<method>(); // Calling the method

})

such as…

1
2
3
4
5
6
7
8
Java.perform(function() {

var check = Java.use("com.ad2001.frida0x4.Check");
var check_obj = check.$new(); // Class Object
var res = check_obj.get_flag(1337); // Calling the method
console.log("FLAG " + res);

})

调用现有实例的方法

因为 frida 可以很容易地在现有实例上调用方法。为此,我们将使用两个 API。

  • Java.performNow:用于在 Java 运行时的上下文中执行代码的函数。
  • Java.choose:在运行时枚举指定 Java 类(作为第一个参数提供)的实例。
1
2
3
4
5
6
7
8
Java.performNow(function() {
Java.choose('<Package>.<class_Name>', {
onMatch: function(instance) {
// TODO
},
onComplete: function() {}
});
});

这里有两个回调:

  • onMatch 上

    • 将针对在作期间找到的指定类的每个实例执行回调函数。onMatchJava.choose

    • 该回调函数接收当前实例作为其参数。

    • 您可以在回调中定义要对每个实例执行的自定义作。onMatch

    • function(instance) {}instance

      ,该参数表示目标类的每个匹配实例。您可以使用所需的任何其他名称。

  • onComplete

    • 回调在作完成后执行作或清理任务。此数据块是可选的,如果您在搜索完成后不需要执行任何特定作,则可以选择将其留空。onCompleteJava.choose

such as…

1
2
3
4
5
6
7
8
9
Java.performNow(function() {
Java.choose('com.ad2001.frida0x5.MainActivity', {
onMatch: function(instance) { // "instance" is the instance for the MainActivity
console.log("Instance found");
instance.flag(1337); // Calling the function
},
onComplete: function() {}
});
});
  • 标题: Frida使用笔记
  • 作者: mmj
  • 创建于 : 2025-05-09 23:26:21
  • 更新于 : 2025-05-11 22:49:09
  • 链接: https://samzhaohx.github.io/2025/05/09/Frida使用笔记/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论