准备-所有练习apk下载
1.一个简单固定的Frida写法
//定义一个名为fridaTest1的函数
function fridaTest1(){
Java.perform(function(){
console.log("测试-为所欲为区-这里执行所有的js逻辑");
});
}
//延迟多少秒执行fridaTest1()函数-这里有个新手非常容易弄混的地方,就是其实setTimeout可有可无
//如果有就是把js注入手机的时候,马上执行fridaTest1()函数,如果没有,需要我们自己在命令行主动执行
//还有一个需要注意的是在命令行主动执行js脚本内的函数的时候,一定要有后面的括号()
setTimeout(fridaTest1,1000);//1000 = 1秒
2.拦截普通方法,修改值并返回
//正向关键代码
//按钮1执行 我们需要用frida中hook这个函数
private void add(String num1, String num2) {
double sum = number1 + number2;
}
//1.先通过Java.use函数得到MainActivity类
var Jfrida01 = Java.use("com.zero.frida01.MainActivity");
//2.通过类名定位到add函数,通过implementation关键字访问和修改方法的具体实现
//3.其中function(a,b)可以看作成add(a,b),function()括号里面参数是可变的,
//具体个数和我们要修改的方法有关,如果add函数需要传3个参数,那么就是function(a,b,c)
Jfrida01.add.implementation = function(a,b){
//4.为所欲为区,我们可以在这里做任何我们想做的事情,劫持,修改,打印数据为明文
console.log("打印传入的a的值 = " + a);
console.log("打印传入的b的值 = " + b);
//5.修改传入的数据
a = "3"; //add('java.lang.String', 'java.lang.String')
b = "9";
//6.使用原add方法,重新计算,相当于不管用户传入什么数据,我只执行3+9的命令
this.add(a,b);//此时apk内,不管输入框输入什么数据都是12
//7.根据hook的函数是否有返回值,决定是否写return,如果有返回值不写会报错
//return res;
}
3.拦截构造函数
//正向代码
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
Student student = new Student("小翼", 12);
//1.通过包名+类名获取User类对象
var JStudent = Java.use("com.zero.frida02.Student");
//2.和普通方法差不多,唯一区别就是用$init代替了方法名,注意$init格式固定,统一代指构造函数
//3.大家注意到没,下面的function(name,age),其实用function(a,b)也行,不影响,其他的也一样
JStudent.$init.implementation = function(name,age){
console.log("打印原始传入的name的值 = " + name);
console.log("打印原始传入的age的值 = " + age);
//4.修改值的时候要注意格式一样,不然会出错
name = "小翼-hook修改";
age = 18;
//5.最终要执行原本的init方法否则运行时会报异常导致原程序无法正常运行。
this.$init(name,age);
}
4.拦截方法重载
当原程序有几个相同方法名,但形参个数不同时,使用overload,如果不用会报错
//正向代码: printInfo方法名相同,传入的形参不同
public class StudentInfo {
private String Sname;
private int Iage;
// 设置学生姓名和年龄
public void setInfo(String name, int age) {
Sname = name;
Iage = age;
}
// 设置年龄
public void setInfo(int age) {
Sname = "无名者";
Iage = age;
}
}
//1.通过包名+类名获取User类对象
var JStudent = Java.use("com.zero.frida04.StudentInfo");
//2.通过overload关键字重载定位 setInfo 方法
JStudent.setInfo.overload('java.lang.String', 'int').implementation = function (name, age){
console.log("打印原始传入的name的值 = " + name);
console.log("打印原始传入的age的值 = " + age);
//3.修改值的时候要注意格式一样,不然会出错
name = "小翼-hook修改";
age = 18;
//4.执行原本的setInfo方法
this.setInfo(name, age);
}
5.主动调用静态函数和主动调用非静态函数的区别
ps:主动调用和hook不同,hook是程序逻辑执行到那才会执行,而主动调用则是程序逻辑即使不走那里,也会执行
主动调用在逆向中的运用:
-
跳过复杂逻辑:在某些程序中,可能存在复杂的验证或初始化过程,这些过程对于逆向分析来说可能是冗余的。通过主动调用,逆向工程师可以直接调用关键功能,跳过这些复杂逻辑,从而提高分析效率。
-
访问未公开接口:程序可能有一些内部函数或接口并未对外公开,但在逆向分析时,我们可能需要访问这些功能。通过主动调用,可以强制执行这些未公开的函数,以便更好地理解程序的工作原理。
-
调试困难代码:有些程序可能包含难以调试的代码,如硬件相关的操作、系统调用等。通过主动调用,可以在控制条件下执行这些代码,便于观察其行为和效果。
-
绕过保护措施:在一些加壳或加密的程序中,可能存在各种保护措施,如反调试、代码混淆等。通过主动调用,可以在一定程度上绕过这些保护措施,直接执行关键代码。
-
功能验证:在逆向某个功能时,通过主动调用可以验证我们对程序逻辑的理解是否正确。这有助于确保逆向分析的结果准确可靠。
-
模块化分析:通过主动调用,可以将程序拆分成多个模块进行独立分析。这样有助于降低分析的复杂度,使逆向工作更加有序。
-
自动化测试:在逆向过程中,可以通过编写脚本实现主动调用,从而自动化测试程序的不同功能,提高逆向分析的效率。
//正向代码-
private static String str = "初识文本,无修改";
// 非静态成员函数
private void SetNonStaticStr() {
str = "非静态成员函数-主动调用";
}
// 静态成员函数
private static void SetStaticStr() {
str = "静态成员函数-主动调用";
}
//主动调用静态函数
//1.通过包名+类名获取User类对象
var MainActivity = Java.use("com.zero.frida05.MainActivity");
//2.不用和hook一样使用implementation等关键字,直接类名+方法名就行
MainActivity.SetStaticStr();
// 使用 Java.choose 来枚举所有活动的 MainActivity 实例 这里选择用choose是因为这个程序打开的界面就是MainActivity,
// 属于是系统已经帮我们实例化好了,直接拿来用就行,所以选择用choose,如果没有系统帮忙实例化,需要自己用其他方法
Java.choose('com.zero.frida05.MainActivity', {
onMatch: function (instance) {
// 当找到一个实例时,调用它的非静态方法
instance.SetNonStaticStr();
},
onComplete: function () {
// 当所有实例都被枚举完毕时调用,这里一般不用,但一定要有
console.log("Finished enumrating instances");
}
});
6.主动调用静态成员和主动调用非静态成员的区别
//正向代码
private static String str1 = "静态初始文本,无修改";
private String str2 = "非静态初始文本,无修改";
//逆向代码
var MainActivity = Java.use("com.zero.frida06.MainActivity");
//访问或者修改成员的时候.value 一定要有,非常关键!!!
MainActivity.str1.value = "主动修改静态成员";
//逆向代码
//使用现有实例
Java.choose('com.zero.frida06.MainActivity', {
onMatch: function (instance) {
//访问或者修改成员的时候.value 一定要有,非常关键!!!
instance.str2.value = "主动修改非静态成员";
},
onComplete: function () {
console.log("Finished enumrating instances");
}
});
7.hook导出函数和非导出函数
①确定手机架构,获取对应so,以及要hook的函数-IDA定位拿到函数名
adb shell getprop ro.product.cpu.abi
//正向代码
// 声明 native 方法-都是导出函数-JAVA层
//需要注意的是mul函数会调用so里面的非导出函数,因为安卓不能直接调用非导出函数
public native int add(int num1, int num2);
public native int mul(int num1, int num2);
//底下都为c或者C++内的函数
extern "C"
JNIEXPORT jint JNICALL
Java_com_zero_frida07_MainActivity_add(JNIEnv *env, jobject thiz, jint num1, jint num2) {
return num1 + num2;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_zero_frida07_MainActivity_mul(JNIEnv *env, jobject thiz, jint num1, jint num2) {
return multiply(num1,num2);
}
jint multiply(jint num1, jint num2) {
return num1 * num2;
}
//hoo导出函数
// 1.指定要Hook的so文件名和要Hook的函数名,函数名即IDA导出表中显示的函数名
var nativePointer = Module.findExportByName("libfrida07.so", "Java_com_zero_frida07_MainActivity_add"); // 查找so库中的导出函数
// 2.使用 Interceptor.attach 挂钩到这个函数
Interceptor.attach(nativePointer, {
onEnter: function(args){
// 函数被调用时执行的代码
// args 是一个包含函数参数的数组args[0] args[1]是系统相关,一般不用管,
//args[2]args[3]之后的都是我们传入的参数
console.log("args[0] = " + args[0]);
console.log("args[1] = " + args[1]);
console.log("args[2] = " + args[2].toInt32());
console.log("args[3] = " + args[3].toInt32());
},
onLeave: function(retval){
// 函数即将返回时执行的代码
// retval 是函数的返回值
// 你可以修改返回值,例如:retval.replace(1234);
console.log("返回值修改前 = " + retval.toInt32());
retval.replace(10000);
console.log("返回值修改后 = " + retval.toInt32());
}
});
//逆向js代码-非导出函数
// 1.获取目标模块的基地址
var base_address = Module.findBaseAddress('libfrida07.so');
// 2.寻找非导出函数的偏移量,通常通过分析 IDA Pro、Ghidra 或其他逆向工程工具获得
var offset = 0x238A0; // 替换 0x12345 为你的函数偏移量
// 3.计算函数的绝对地址,有时候会因为某些情况offset + 1 或者+n 例如:Thumb指令集
var function_address = base_address.add(offset);
// 使用 Interceptor API 来 hook 函数-之后和导出函数差不多
//唯一区别就是非导出函数需要自己修正地址
Interceptor.attach(function_address, {
// 在函数执行前调用
onEnter: function (args) {
//和非导出函数差不多
console.log("非导出函数-args[2] = " + args[2].toInt32());
console.log("非导出函数-args[3] = " + args[3].toInt32());
},
// 在函数执行后调用
onLeave: function (retval) {
retval.replace(10088);
}
});
8.主动调用so内的函数-
关键字:
①findExportByName详解
②NativeFunction详解
//libc.so是系统so库只要具备so库的apk,基本都会有libc.so这个so库
var fopen_addr = Module.findExportByName("libc.so", "fopen");
var fopen = new NativeFunction(fopen_addr, "pointer", ["pointer", "pointer"]);
var fputs_addr = Module.findExportByName("libc.so", "fputs");
var fputs = new NativeFunction(fputs_addr, "int", ["pointer", "pointer"]);
var fclose_addr = Module.findExportByName("libc.so", "fclose");
var fclose = new NativeFunction(fclose_addr, "int", ["pointer"]);
var filename = Memory.allocUtf8String("/sdcard/frida08.txt");
var mod = Memory.allocUtf8String("a");
var neirong = Memory.allocUtf8String("frida练习\r\n这是第二行,恭喜逆向成功\r\b");
var fp = fopen(filename, mod);
fputs(neirong, fp);
fclose(fp);
9.打印函数的调用栈-Java与So层
//Java调用栈
//1.获取MainActivity类
var MainActivity = Java.use('com.zero.frida09.MainActivity');
//2.挂钩z函数
MainActivity.z.implementation = function () {
//3.通过Exception打印当前调用栈
var Exception = Java.use("java.lang.Exception");
var instance = Exception.$new("print_stack");
var stack = instance.getStackTrace();
console.log(stack);
//4.只要调用了$new,就应该调用这个来释放掉
instance.$dispose();
//5.调用原始的z函数实现
this.z();
//6.打印堆栈信息后用文本工具打开,(\r)代替(,)可以让调用信息看的更舒服ps:search mode->extended
};
//so调用栈
var base_address = Module.findBaseAddress('libfrida09.so');
var offset = 0x23AB0;
var function_address = base_address.add(offset);
Interceptor.attach(function_address, {
// 在函数执行前调用
onEnter: function (args) {
console.log("\r\n=============================" + " Stack int =======================\r\n");
console.log("base_address:", base_address);//获得地址要减去base_address基址
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n'));
console.log("\r\n=============================" + " Stack out =======================\r\n");
},
// 在函数执行后调用
onLeave: function (retval) {
}
});