Frida

pre

  1. 已经 root 了的安卓真机 (我的是 Android9.0)
    frida 版本 16.1.0 frida-server 版本 16.1.0
  2. 启动 frida-server
1
2
3
4
5
6
adb shell
su
cd /data/local/tmp
./frida-server
# 要换端口的话 可以使用 -l 参数
./frida-server -l 0.0.0.0:12123

ps : 端口转发

1
adb forward tcp:12123 tcp:12123
  1. frida 常用指令
指令 说明
frida-ps 用于列出设备上的进程
-U 连接到 USB 设备
-a 显示进程的详细信息,包括进程的名称、PID(进程 ID)、包名等
-i 显示进程的架构信息
-H 连接到远程设备
-l 加载指定的脚本
-f 启动指定的应用程序

eg:

1
2
3
4
5
6
7
frida-ps -Uai
# 列出当前通过 USB 连接的设备上所有运行的进程。
frida-ps -Uai | grep com.nobody.zunjia

frida -U -f <package_name> -l <script.js>
frida -H 127.0.0.1:12123 -f <package_name> -l <script.js>
# 启动指定的应用程序,并加载指定的脚本

获取日志

1
adb logcat | busybox grep <PID>

frida java 层 hook

  1. 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
//定义一个名为“hooktest1”的函数
function hooktest1() {
//获取一个名为“类名”的 java 类,并将其实例赋值给 js 变量 utils
var utils = Java.use("类名");
//var utils =Java.use("com.nobody.zunjia.DexCall");

//修改"类名"的"method"方法实现,这个新的实现会接受两个参数
utils.method.implementation = function (a, b) {
//更改参数的值
a = 123;
b = 456;
//调用修改过的“method”方法,并将返回值存储在变量 retval 中
var retval = this.method(a, b);

//打印参数 a b 以及返回值
console.log(a, b, retval);

return retval;
}
}

function main() {
Java.perform(function () {
hooktest1();
})
};
setImmediate(main);
  1. overload 使用
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
//.overload
function hooktest2() {

var utils = Java.use("类名");
//var utils =Java.use("com.nobody.zunjia.DexCall");

utils.method.overload('int', 'java.lang.String').implementation = function (i, str) {
//如果有自定义参数类型在 smali 中找
//实在不知道可以在报错中找

//调用修改过的“method”方法,并将返回值存储在变量 retval 中
var retval = this.method(i, str);


console.log(i, str);

// return retval;
// return "retval";
}
}

function main() {
Java.perform(function () {
hooktest1();
})
};
setImmediate(main);

/*
当hook的目标方法或构造函数存在多个重载时,需要使用 overload 来指定具体的参数类型
例如:
public class MyClass {
public MyClass() { }
public MyClass(String str) { }
public MyClass(String str, int num) { }
}
*/
  1. hook 构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//hook 构造函数
function hooktest3() {
var utils = Java.use("<package_name>.<class_name>");
//var utils=Java.use("com.nobody.zunjia.DexCall");

//修改类的构造函数的实现
utils.$init.overload('java.lang.String').implementation = function (str) {
console.log(str);
str = "hello";
this.$init(str);
}
}

/*构造函数 :名称与类名相同;没有返回值;自动调用;可以重载
public class MyClass{
public MyClass(String str){
//构造函数
}
}*/
  1. hook 字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//hook 字段
function hooktest4() {
//静态字段修改
var utils = Java.use("<package_name>.<class_name>");
//修改类的静态字段的值
utils.staticField.value = "hello world";
console.log(utils.staticField.value);

//非静态字段的修改
//使用 Java.choose() 枚举类的所有实例
Java.choose("<package_name>.<class_name>", {
onMatch: function (obj) {
//修改实例的非静态字段 "_privateInt"和非静态字段 "privateInt"的值
obj._privateInt.value = 123;//如果字段名与函数名相同,需要在前面加个下划线
obj.privateInt.value = 456;
},
onComplete: function () { }
});
}
  1. hook 内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//hook 内部类
function hooktest5() {
Java.perform(function () {
//内部类
var innerClass = Java.use.perform("<package_name>.<class_name>$<inner_class_name>");
//var innerClass = Java.use("com.example.OuterClass$InnerClass");
console.log(innerClass);
innerClass.$init.implementation = function () {
console.log("hook innerClass");
//...


}

});
}
  1. 枚举所有方法
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
//枚举出所有方法
function hooktest7() {
Java.perform(function () {
var a = Java.use("<package_name>.<class_name>");
//getDeclaredMethods 枚举所有方法
var methods = a.class.getDeclaredMethods();
for (var i = 0; i < methods.length; i++) {
var methodName = methods[i].getName();
console.log(methodName);
for (var j = 0; j < a[methodName].overloads.length; j++) {
a[methodName].overloads[j].implementation = function () {
for (var k = 0; k < arguments.length; k++) {
//arguments.length 表示当前方法调用时传入的参数个数
console.log(arguments[k]);
//打印每个参数的值

}
return this[methodName].apply(this, arguments);
//将原始方法的返回值返回给调用方
}
}
}


})


}
  1. 主动调用
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
//主动调用

//静态方法的主动调用
function hooktest_1(){
Java.perform(function(){
var ClassName=Java.use("<>package_name>.<class_name>");
ClassName.privateFunc();
})
}

//非静态方法的主动调用
function hooktest_2(){
Java.perform(function(){
var ret =null;
Java.choose("<package_name>.<class_name>",{ //要 hook 的类
onMatch:function(instance){
ret =instance.privateFunc("arg"); //要 hook 的方法
},
onComplete:function(){
//console.log("ret: "+ret);
}
}

)
})
}

frida native 层 hook

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

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

})
}

})
}
    1. hook 打印(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
46
// 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);


}

})
}

})
}
  1. 枚举导入导出表
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]));
}
})
}
  1. 获取 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);
})
}
  1. 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 中的偏移
  1. 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();
}
});
}
  1. 写数据
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();//关闭
}
  1. 内联 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());
  1. 普通函数与 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
http://example.com/2025/04/18/6/
作者
Eleven
发布于
2025年4月18日
许可协议