Frida API基本使用

最近在补安卓安全相关的知识,其中Frida动态Hook是一个重要的部分,于是写一篇文章来总结面对不同情况下Frida API的使用方法。

脚本开发环境搭建

首先下载官方提供的nodejs项目

1
2
3
git clone https://github.com/oleavr/frida-agent-example.git
cd frida-agent-example/
npm install

使用webstorm/vscode打开项目,之后就是nodejs开发的步骤。编写好脚本后,可以

Frida API的使用

这一部分使用Frida-Labs帮助我总结在使用Frida Hook不同方法的情况

Hook一个不带参数的成员方法

  • Frida 0x1中修改com.ad2001.frida0x1.MainActivity#get_random方法,固定返回值为0
1
2
3
4
5
6
7
8
9
10
11
Java.perform(function (){
// 指定要Hook的Java类
Java.use("com.ad2001.frida0x1.MainActivity")
// 指定要Hook的成员方法
.get_random
// 重新实现方法
.implementation = function (){
// 修改逻辑: 此处为固定返回值
return 0;
}
})

Hook一个带参数成员方法

  • Frida 0x1中修改com.ad2001.frida0x1.MainActivity#check方法,固定第一个参数为0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 指定Java上下文
Java.perform(function (){
// 指定要Hook的Java类
Java.use("com.ad2001.frida0x1.MainActivity")
// 指定要Hook的成员方法的重载
.check.overload("int","int")
// 重新实现方法
.implementation = function (i,i2){
// 此处是修改的方法逻辑(将方法的第一个传参固定为0)
i = 0
// 其余方法逻辑不变, 直接调用原来的方法逻辑
this.check(i,i2)
}
})

调用一个类的静态方法

  • Frida 0x2中调用com.ad2001.frida0x1.MainActivity#get_flag方法
1
2
3
4
5
6
Java.perform(function (){
// 指定要调用的Java类
Java.use("com.ad2001.frida0x2.MainActivity")
// 调用方法
.get_flag(4919);
})

修改一个类的静态变量

  • Frida 0x3中修改com.ad2001.frida0x3.Checker中code的值
1
2
3
4
5
6
Java.perform(function (){
// 指定要修改的Java类
Java.use("com.ad2001.frida0x3.Checker")
// 指定要修改的静态变量的值
.code.value = 512;
})

创建一个类的实例并调用他的成员方法

  • Frida 0x4中创建com.ad2001.frida0x4.Check中的示例并调用com.ad2001.frida0x4.Check#get_flag方法
1
2
3
4
5
6
7
8
9
Java.perform(function (){
// 指定要创建对象的Java类
var flag = Java.use("com.ad2001.frida0x4.Check")
// 创建实例
.$new()
// 调用成员方法
.get_flag(1337)
console.log(flag)
})

调用一个已存在的类的实例的成员方法

  • Frida 0x5中获取已经存在的实例com.ad2001.frida0x5.MainActivity,并调用com.ad2001.frida0x5.MainActivity#flag方法
1
2
3
4
5
6
7
8
9
10
11
Java.perform(function (){
// 选择对应类
Java.choose("com.ad2001.frida0x5.MainActivity",{
// 匹配成功后回调函数, 参数为该类实例
onMatch: function (instance){
console.log("found instance")
instance.flag(1337)
},
onComplete: function (){}
})
})

调用一个以对象作为参数的方法

  • Frida 0x6中获取已经存在的实例com.ad2001.frida0x6.MainActivity,并调用com.ad2001.frida0x6.MainActivity#flag方法,以一个Checker对象作为参数传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Java.performNow(function() {
// 选择实例对应类
Java.choose('com.ad2001.frida0x6.MainActivity', {
// 匹配成功后回调函数
onMatch: function(instance) {
console.log("found instance");
// 创建一个新的Checker实例
var checker = Java.use("com.ad2001.frida0x6.Checker")
.$new;
// 给Checker对象赋值
checker.num1.value = 1234; // num1
checker.num2.value = 4321; // num2
// 将Checker对象作为参数传递
instance.get_flag(checker); // 调用实例的成员方法
},
onComplete: function() {}
});
});

Hook构造函数

  • Frida 0x7中获取已经存在的实例com.ad2001.frida0x7.MainActivity,并hook Checker类的构造函数创建Checker示例,然后调用com.ad2001.frida0x7.MainActivity#flag方法,以Checker对象作为参数传递。这里需要注意Hook的时机,如果以-l参数传递脚本会报错:Didn't find class "com.ad2001.frida0x7.MainActivity" on path: DexPathList,这是因为MainActivity还未加载完成,有两种方式解决,一种是使用frida -f先启动程序,然后在控制台中输入hook脚本;第二种是在脚本中指定延迟hook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function hook(){
Java.performNow(function() {
// 选择对应类
Java.choose('com.ad2001.frida0x7.MainActivity', {
// 匹配成功后回调函数
onMatch: function(instance) {
console.log("Instance found");
// 指定类并调用构造函数创建实例
var checker = Java.use("com.ad2001.frida0x7.Checker")
.$new(600,600);
instance.flag(checker); // invoking the get_flag method
},
onComplete: function() {}
});
});
}

// 延迟5s开始hook
setTimeout(hook,5000)

Hook Native层方法修改传参

image-20250322183445509image-20250322183423626

Hook Native方法的关键是获取导出函数的地址,Firda提供了以下4种API(同样可能会存在Hook时机的问题,可以使用延迟函数或者使用相应的APP的功能后确保so加载):

  1. Module.enumerateExports():获取so文件种所有导出函数的信息,再遍历结果,获取指定函数地址

    1
    2
    3
    4
    5
    6
    7
    8
    Java.performNow(function() {
    var modules = Module.enumerateExports('libfrida0x8.so')
    for (var i in modules){
    if (modules[i].name.indexOf("cmpstr") > -1){
    console.log(modules[i].address)
    }
    }
    });
  2. Module.getExportByName():根据名称获取导出函数的地址

1
2
3
4
5
Java.performNow(function() {
// 注意这里要写导出表中的函数名
var addr = Module.getExportByName("libfrida0x8.so","Java_com_ad2001_frida0x8_MainActivity_cmpstr")
console.log(addr)
});
  1. Module.findExportByName():根据名称获取导出函数的地址,没找到抛出异常并返回null

    1
    2
    3
    4
    5
    Java.performNow(function() {
    // 注意这里要写导出表中的函数名
    var addr = Module.findExportByName("libfrida0x8.so","Java_com_ad2001_frida0x8_MainActivity_cmpstr")
    console.log(addr)
    });
  2. Module.getBaseAddress():如果以上API都无法获取函数地址,那么可以利用此API先获取so文件的基址,然后加上函数偏移的方式获取函数地址

    1
    2
    3
    4
    5
    6
    7
    8
    Java.performNow(function() {
    // 注意这里要写导出表中的函数名,0x770是在IDA Pro等静态逆向工具中查看的函数偏移地址
    var addr = Module.getBaseAddress("libfrida0x8.so").add(0x770)
    console.log("base + offset: " + addr)

    addr = Module.findExportByName("libfrida0x8.so","Java_com_ad2001_frida0x8_MainActivity_cmpstr")
    console.log(addr)
    });
  • Frida 0x8中通过hook libc中的strcmp函数来使得通过输入校验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Java.performNow(function () {
var strcmp_adr = Module.findExportByName("libc.so", "strcmp");
// hook native层函数
Interceptor.attach(strcmp_adr, {
onEnter: function (args) {
var arg0 = Memory.readUtf8String(args[0]); // first argument
var flag = Memory.readUtf8String(args[1]); // second argument
// 只hook指定的函数, 此处表示只对我们输入为Hello的情况进行处理
if (arg0.includes("Hello")) {
console.log("Hookin the strcmp function");
console.log("Input " + arg0);
console.log("The flag is "+ flag);
}
},
// Modify or log return value if needed
onLeave: function (retval) {
}
});
})

Hook Native方法修改返回值

1
2
3
4
5
6
7
8
9
10
11
12
Java.performNow(function () {
var check_flag_addr = Module.findExportByName("liba0x9.so", "Java_com_ad2001_a0x9_MainActivity_check_1flag");
// hook native层函数
Interceptor.attach(check_flag_addr, {
onEnter: function (args) {
},
onLeave: function (retval) {
// 修改返回值
retval.replace(1337)
}
});
})

总结

  • 在进行hook时,由于代码加载时机的问题可能会导致hook不到原本应该能hook到的内容,此时解决思路通常有几种:
    1. 使用setTimeoutAPI延时加载;
    2. 在控制台使用APP一段时间后再贴入脚本;
    3. APP启动时指定脚本路径;
  • Hook Native层方法相关的API
    1. Module.enumerateExports():获取so文件中所有导出函数的信息,再遍历结果,获取指定函数地
    2. Module.getExportByName():根据名称获取导出函数的地址
    3. Module.findExportByName():根据名称获取导出函数的地址,没找到抛出异常并返回null
    4. Module.getBaseAddress():如果以上API都无法获取函数地址,那么可以利用此API先获取so文件的基址,然后加上函数偏移的方式获取函数地址

参考