Frida
基本原理
Labs
0x1 Hook方法
实验1主要是环境搭建,任务是使用frida hook一个静态方法
题目的关键代码大致如下:
public class MainActivity extends AppCompatActivity {
public void onCreate(Bundle bundle) {
// ...
final int i = get_random();
// ...
public void onClick() {
String user_input = editText.getText().toString();
if (TextUtils.isDigitsOnly(user_input)) {
MainActivity.this.check(i, Integer.parseInt(user_input));
} else {
Toast.makeText(MainActivity.this, "Invalid input", 1).show();
}
}
// ...
}
public static bool check(int i, int i2) {
if (((i * 2) + 4) == i2) {
// ...
}
}
}
简单地说,就是先生成随机数,然后让用户输入并判断是否相同。
所以有三个思路:
- hook
get_random
,替换这个函数,并返回一个自定义的数字,
Java.perfrom(function() {
var MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity.get_random.implementation = function() {
console.log("get_random(): Returning 5");
return 5;
}
});
- hook
get_random
,并打印出返回值
Java.perform(function() {
var MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity.get_random.implementation = function() {
var ret = this.get_random();
console.log("get_random(): Returning " + ret);
console.log("Check value: " + ((ret * 2) + 4));
return ret;
}
});
- hook
check
,并打印出参数,随后继续执行check
hook第一次调用的时候,会打印出参数,这样就可以看到随机数的值,随后在第二次输入时手动输入正确结果即可
Java.perform(function() {
var MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity.check.overload("int", "int").implementation = function(a, b) {
console.log("check(" + a + ", " + b + ")");
this.check(a, b);
}
});
- 直接hook
check
,自己构建一个正确的输入
Java.perform(function() {
var MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity.check.overload("int", "int").implementation = function(a, b) {
this.check(5, 14);
}
});
0x2 调用静态函数
实验2任务是手动调用一个静态函数,关键代码如下:
public class MainActivity extends AppCompatActivity {
public static void get_flag(int a) {
if (a == 4919) {
// ...
}
}
}
直接调用即可
Java.perform(function() {
var MainActivity = Java.use("com.ad2001.frida0x2.MainActivity");
MainActivity.get_flag(4919);
});
0x3 修改变量的值
实验3任务是修改一个变量的值,关键代码如下:
public class MainActivity extends AppCompatActivity {
public void onCreate(Bundle bundle) {
public void onClick() {
if (Checker.code == 512) {
// ...
}
}
}
}
public class Checker {
static int code = 0;
public static void increase() {
code += 2;
}
}
由于题目提供了increase()
静态方法,所以可以用实验2中用到的技巧直接调用这个方法
Java.perform(function() {
var Checker = Java.use("com.ad2001.frida0x3.Checker");
for (var i = 0; i < 256; i++) {
Checker.increase();
}
});
此外,也可以直接修改code
的值
Java.perform(function() {
var Checker = Java.use("com.ad2001.frida0x3.Checker");
Checker.code.value = 512;
});
0x4 创建一个类的实例
实验4任务是创建一个类的实例,关键代码如下:
public class Check {
public String get_flag(int a) {
if (a == 1337) {
// ...
}
}
}
由于get_flag
是一个非静态方法,所以需要先创建一个实例,然后调用这个方法
Java.perform(function() {
var Check = Java.use("com.ad2001.frida0x4.Check");
var check = Check.$new();
var flag = check.get_flag(1337);
console.log(flag);
});
0x5 在已有实例中调用方法
关键代码如下:
public class MainActivity extends AppCompatActivity {
public void flag(int code) {
if (code == 1337) {
// ...
}
}
}
由于安卓组建的生命周期,无法像实验4一样,创建一个MainActivity
的实例,但由于安卓软件在启动后已经创建了MainActivity
,所以可以直接使用该实例进行调用(在虚拟机中可能存在问题,最好使用实体机进行)
Java.perform(function() {
Java.choose('com.ad2001.frida0x5.MainActivity', {
onMatch: function(instance) {
instance.flag(1337);
},
onComplete: function() {}
});
});
0x6 调用成员参数的方法
关键代码如下:
public class MainActivity extends AppCompatActivity {
public void flag(Checker A) {
if (A.num1 == 1234 && A.num2 == 4321) {
// ...
}
}
}
需要先创建一个Checker
的实例,然后调用flag
方法
Java.perform(function() {
Java.choose('com.ad2001.frida0x6.MainActivity', {
onMatch: function(instance) {
var Checker = Java.use("com.ad2001.frida0x6.Checker");
var A = Checker.$new();
A.num1.value = 1234;
A.num2.value = 4321;
instance.flag(A);
},
onComplete: function() {}
});
});
0x7 Hook 构造函数
关键代码如下:
public class MainActivity extends AppCompatActivity {
public void flag(Checker A) {
if (A.num1 > 512 && A.num2 > 512) {
// ...
}
}
}
public class Checker {
int num1;
int num2;
public Checker(int a, int b) {
num1 = a;
num2 = b;
}
}
此时,Checker只有一个构造函数,所以可以直接按照实验6的思路来调用这个构造函数
Java.perform(function() {
var Checker = Java.use("com.ad2001.frida0x7.Checker");
Checker.$init.overload("int", "int").implementation = function(a, b) {
console.log("Checker($1, $2) is called");
console.log("a: " + a + ", b: " + b);
return this.$init(600, 600);
}
});
另一种方法是对构造函数进行hook,这一步和实验1中的hook类似,但函数名使用的是$init
注意:这个方法无法用在ARM64的设备上
Java.perform(function() {
var Checker = Java.use("com.ad2001.frida0x7.Checker");
Checker.$init.iplementation = function(a, b) {
this.$init(600, 600);
}
});
0x8 native层hook
关键代码如下:
public class MainActivity extends AppCompatActivity {
public native int cmpstr(String str);
static {
System.loadLibrary("frida0x8");
}
public void flag() {
// onClick
if (MainActivity.this.cmpstr(user_input) == 1) {
// ...
}
}
}
这里调用了native层的函数(so文件中实现),由于cmpstr
调用了底层的strcmp
函数,所以可以直接hook这个函数,把strcmp的参数打印出来,就可以得到正确的输入
var strcmp_addr = Module.findExportByName("libc.so", "strcmp");
Interceptor.attach(strcmp_addr, {
onEnter: function(args) {
console.log("Enter strcmp");
console.log("arg0: " + Memory.readUtf8String(args[0]));
console.log("arg1: " + Memory.readUtf8String(args[1]));
},
onLeave: function(retval) {
// modify retval
}
});
0x9 修改native层函数返回值
关键代码如下:
public class MainActivity extends AppCompatActivity {
public native int check_flag();
static {
System.loadLibrary("a0x9");
}
public void onCreate() {
// onClick
if (MainActivity.this.check_flag() == 1337) {
// ...
}
}
}
这里直接hookcheck_flag
函数,然后返回正确的值即可,但在hook前,需要先知道函数的地址
思路是先使用enumerateExports("liba0x9.so")
输出所有函数,从中找到这个函数,然后在脚本中获取地址
var check_flag_addr = Module.enumerateExports("liba0x9.so")[0]['address'];
// Can also use Module.findExportByName("liba0x9.so", "Java_com_ad2001_a0x9_MainActivity_check_1flag");
Interceptor.attach(check_flag_addr, {
onEnter: function(args) {
console.log("Enter check_flag");
},
onLeave: function(retval) {
retval.replace(1337);
}
});
0xA 调用native层函数
关键代码如下:
public class MainActivity extends AppCompatActivity {
public final native String stringFromJNI();
public void onCreate() {
// onClick
if (MainActivity.this.get_flag() == 1337) {
// ...
}
}
static {
System.loadLibrary("frida0xa");
}
}
native层中包括了两个函数,一个是stringFromJNI
,另一个是get_flag
,这里get_flag
没有被任何函数调用到,我们的目标就是调用这个函数
void get_flag(int a, int b) {
if (a + b == 3) {
// ...
}
}
这里根据基地址和偏移来算出函数的地址,随后根据地址获取函数指针(NativePointer
),再获取函数的NativeFunction
,最后调用这个函数即可
var get_flag_addr = Module.getBaseAddress("libfrida0xa.so").add(0x18BB0);
var get_flag_ptr = new NativeFunction(get_flag_addr);
const get_flag_func = new NativeFunction(get_flag_ptr, 'void', ['int', 'int']);
get_flag_func(1, 2);
0xB 使用X86Writer和ARM64Writer来Patch指令
关键代码如下:
public class MainActivity extends AppCompatActivity {
public native int getFlag();
public void onCreate() {
// onClick
MainActivity.onCreate();
}
public static final void onCreate() {
this$0.getFlag();
}
static {
System.loadLibrary("frida0xb");
}
}
这里的getFlag
函数是一个无法正常执行的函数,大致逻辑如下:
if (0xdeadbeef == 0x539) {
// ...
}
因此,需要对这个函数中的if跳转进行patch,使其返回正确的值
Writer中提供了putNop()
的方法,可以用来替换指令,这里需要先找到jnz
指令的地址,然后替换为nop
ARM64中可以使用
putBImm()
方法,该指令相当于x64里的jmp $+5
,作用与nop
相同 在patch前需要修改内存保护,使其可写可执行
var jnz_addr = Module.getBaseAddress("libfrida0xb.so").add(0x20e2a - 0x10000);
Memory.protect(jnz, 0x1000, "rwx");
var writer = new X86Writer(jnz);
try {
writer.putNop();
writer.putNop();
writer.putNop();
writer.putNop();
writer.putNop();
writer.putNop();
writer.flush();
} finally {
writer.dispose();
}