刚学 frida,从网上找了一个题库练练手。

Challenge 0x1

image-20240605084236391

逻辑很简单,就是输入一个数然后判断是不是等于 get_random 这个函数的返回值 * 2 + 4,直接对该返回值 hook

1
2
3
4
5
6
7
8
function hook_1() {
var utils = Java.use("com.ad2001.frida0x1.MainActivity");
utils.get_random.implementation = function() {
var res = this.get_random();
console.log(res);
return res;
}
}

Challenge 0x2

直接主动调用这个函数即可

image-20240605090206156

1
2
3
4
function hook_2() {
var MainActivity = Java.use("com.ad2001.frida0x2.MainActivity");
MainActivity.get_flag(4919);
}

Challenge 0x3

image-20240605091048304

把这个 code 变量的值改成 512 即可

1
2
3
4
function hook_3() {
var Checker = Java.use("com.ad2001.frida0x3.Checker");
Checker.code.value = 512;
}

Challenge 0x4

主动调用非静态态方法,注意这不是一个 MainActivity 或者其他安卓组件。

image-20240605092006544

主动调用有两种写法 Java.choose$new()

Java.choose 适用场景:

  • 当你想要对现有的实例进行操作而不是创建新实例时使用。
  • 应用中可能已经存在多个你需要操作的实例时,这种方法尤为有效。

$new() 适用场景:

  • 当你希望主动创建并控制一个对象实例时使用。
  • 当你需要确保使用的实例是全新的,而不是应用中已有的实例时,这种方法更合适。

那么这道题显然应该用 $new() ,因为我们没有创建对应的实例,不能直接拿来用。

1
2
3
4
5
6
function hook_4() {
let check = Java.use("com.ad2001.frida0x4.Check");
let check_obj = check.$new();
let res = check_obj.get_flag(1337);
console.log("FLAG " + res);
}

Challenge 0x5

这道题需要主动调用 MainActivity 中的 flag 方法,但是如果使用 Challenge 0x4 中的脚本就会报错,原因是像 Activity 这样的 Android 组件依赖于应用程序的上下文(context)来正常运行。而在 Frida 中,你可能缺少所需的上下文。Android 的 UI 组件通常需要一个特定的线程,并且该线程需要关联一个 Looper。如果你在处理 UI 任务,必须确保你是在主线程上并且有一个活跃的 LooperActivity 是 Android 应用程序生命周期的一部分。创建 MainActivity 的实例可能需要应用程序处于特定的状态,通过 Frida 来管理整个生命周期可能并不容易。

image-20240812213336364

1
2
3
4
5
6
7
8
9
function hook_5() {
Java.choose("com.ad2001.frida0x5.MainActivity", {
onMatch:function(instance) {
instance.flag(1337);
},
onComplete:function() {
}
});
}

运行以后UI界面就会出现 flag

Challenge 0x6

也是主动调用 MainActivity 中的 get_flag 方法,不同的是参数是一个 Class,我们的思路是先用 Java.use 找到对应的类,然后新建实例,并给实例中的两个成员变量num1,num2 赋值,最后再主动调用函数。

image-20240812220141673

image-20240812220101283

1
2
3
4
5
6
7
8
9
10
11
12
13
function hook_6() {
Java.choose("com.ad2001.frida0x6.MainActivity", {
onMatch:function(instance) {
let Checker = Java.use("com.ad2001.frida0x6.Checker");
let Checker_obj = Checker.$new();
Checker_obj.num1.value = 1234;
Checker_obj.num2.value = 4321;
instance.get_flag(Checker_obj);
},
onComplete:function() {
}
});
}

Challenge 0x7

也是要 hook flag,这个方法在onCreate方法中被调用了,而且Checker 类里面有构造器了,之前的 choose 方法直接拿来用即可,注意 $new 加参数

image-20240812230805569

image-20240812230506088

1
2
3
4
5
6
7
8
9
10
11
function hook_7() {
Java.choose("com.ad2001.frida0x7.MainActivity", {
onMatch:function(instance) {
let Checker = Java.use("com.ad2001.frida0x7.Checker");
let Checker_obj = Checker.$new(1234, 1234);
instance.flag(Checker_obj);
},
onComplete:function() {
}
});
}

Challenge 0x8

逻辑都在 SO 里面

image-20240813000722948

IDA 看一下

image-20240813000754790

可以看到 s2 就是我们要找的 enc 也即是 flag,同时可以看到有日志信息打印了 s2,那直接 logcat就行了

image-20240813001703476

此外我们也可以 hook strcmp 函数,其第二个参数就是 enc,为了避免把所有的 strcmp 执行结果都输出,我们可以对其第一个参数进行限制,因为第一个参数就是我们输入的 text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function hook_8() {
var strcmpAddr = Module.findExportByName("libfrida0x8.so","strcmp");
if(strcmpAddr != null){
Interceptor.attach(strcmpAddr,{
onEnter: function(args){
let arg0 = Memory.readUtf8String(args[0]);
let arg1 = Memory.readUtf8String(args[1]);
if (arg0.includes("flag")) {
console.log("Input: " + arg0);
console.log("enc: "+ arg1);
}
},
onLeave: function(retval){
}
})
}
}

Challenge 0x9

hook native 方法返回值

image-20240813004053543

image-20240813004044888

1
2
3
4
5
6
7
8
9
10
11
12
function hook_9() {
let checkAddr = Module.findExportByName("liba0x9.so","Java_com_ad2001_a0x9_MainActivity_check_1flag");
if(checkAddr != null){
Interceptor.attach(checkAddr,{
onEnter: function(args){
},
onLeave: function(retval){
retval.replace(1337);
}
})
}
}

Challenge 0xA

JAVA层啥也看不出来,只知道调用了一个 stringFromJNI

image-20240813005744874

但是该函数只是返回了一个字符串,这里 F5 看不到字符串,有点怪,看汇编可以。

image-20240813005855376

注意到还有一个函数 get_flag ,但是没有交叉引用,看来是 native 主动调用,该函数为 void 型,有两个参数,注意两个参数相加需要为 3,查一下日志就可以获得 flag

image-20240813005950239

1
2
3
4
5
6
function hook_A() {
var funcAddr = Module.findBaseAddress("libfrida0xa.so").add(0x1DD60);
var get_flag = new NativeFunction(funcAddr , 'void', ['long', 'int']);
console.log("ok");
get_flag(2, 1);
}

challenge 0xB

逻辑全在SO层,但是 get_flag 啥都没有

image-20240821133430515

看一下汇编

image-20240821134139018

0xDEADBEEF 赋值给了 W8 寄存器,然后又把该值存到了 X29 帧指针的 var_24 位置,然后又赋值给了 W8 ,在减去 0x539 ,如果结果不是 0 就跳转到 loc_1532C,否则到 0x15250,而这个条件永远是到 loc_1532C,所以 ida 会自动优化掉该函数

用 Ghidra 打开 so 文件,把这个选项的勾给去掉,就能看到 false 后面的逻辑了

image-20240821144919262

image-20240821145000577

看起来是需要通过 inline hook 修改函数的执行流,然后在日志中找 flag

我们直接给 nop 掉

image-20240821143530887

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
function hook_B() {
let codeAddr = Module.findBaseAddress("libfrida0xb.so").add(0x15248);

// 读取和打印当前地址处的汇编指令
let instruction = Instruction.parse(codeAddr);
console.log("Before patching:", instruction.toString());

Memory.patchCode(codeAddr, 8, function(code) {
const writer = new Arm64Writer(code, { pc: codeAddr });
writer.putBytes(hexToBytes("1F2003D51F2003D5"));
writer.flush();
});

// 读取和打印修改后的汇编指令
instruction = Instruction.parse(codeAddr);
console.log("After patching:", instruction.toString());

function hexToBytes(hex) {
let bytes = [];
for (let i = 0; i < hex.length; i += 2) {
bytes.push(parseInt(hex.substr(i, 2), 16));
}
return bytes;
}
}

还有第二种方法

image-20240821144146276

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var codeAddr = Module.findBaseAddress("libfrida0xb.so").add(0x15248);  // Addres of the b.ne instruction
Memory.protect(codeAddr, 0x1000, "rwx");
var writer = new Arm64Writer(codeAddr); // ARM64 writer object
var target = Module.findBaseAddress("libfrida0xb.so").add(0x1524c); // Address of the next instruction b LAB_00115250

try {

writer.putBImm(target); // Inserts the <b target> instruction in the place of b.ne instruction
writer.flush();
} finally {

writer.dispose();

}