Back
Featured image of post Learning Frida with Labs

Learning Frida with Labs

学习一下Frida的基本用法

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) {
            // ...
        }
    }
}

简单地说,就是先生成随机数,然后让用户输入并判断是否相同。

所以有三个思路:

  1. 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;
    }
});
  1. 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;
    }
});
  1. 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);
    }
});
  1. 直接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();
}
Built with Hugo
Theme Stack designed by Jimmy