JavaScript API 手册 | Frida JavaScript-api 中文手册
frida_Android
运行frida常用命令
运行
adb push 名 /data/local/tmp
adb root
adb shell chmod +x /data/local/tmp/fridaad64
adb shell /data/local/tmp/frida-server -l 0.0.0.0:27042 #默认27042端口
adb forward tcp:27042 tcp:27043
Frida -U -f 包 -l hookbool.js
-U 指定USB设备
-f 用app包名spwn方式启动
-P 指定APP的pid,先要自行启动
-pause 暂停
-l 加载hook脚本
hookjs
对fridahook脚本的一点理解
我们用fridahook一个函数时可以说是我们从apk里面钩住了一个函数,我们可以替换这个函数的任何内容。也可以说我们建立了一个管道在这个函数方法上,代码执行就像是一个水流,当水流到这个函数时,我们把水引到了我们的js这里,让它流经我们想要流过的地方。
一些frida方法的收集
send(); //发送信息到控制台.
对hook脚本的初步解析
//在启动时就被hook的方法,通常用于绕过反调试,java.perform()是一个frida的接口
//几乎一切操作都在这个函数执行
Java.perform(function(){
let MainActivity = Java.use("packname.MainActivity"); //实例化一个类
MainActivity["isEmu"].implementation = function () {
console.log(`MainActivity.isEmu is called`);
let result = this["isEmu"]();
console.log(`MainActivity.isEmu result=${result}`);
return 0; //一般可以修改返回值如果报错用false,修改返回值
};
//调用方法并返回值。
MainActivity["g4tk4y"].implementation = function () {
console.log(`MainActivity.g4tk4y is called`);
let result = this["g4tk4y"]();
console.log(`MainActivity.g4tk4y result=${result}`);
return result; //修改返回值
};
//java.choose会遍历类的实例,调用方法,用于无法获得实例时动态引用,
//修改成员变量。我的理解是寻找一个系统自己的实例,而不是我们自己去实例化。
Java.choose("packname.MainActivity",{
onMatch:
function(x){
console.log("ok" + x);
let result = x.g4tk4y();
console.log(result);
},
onComplete: function () {
console.log("end");
}
});
var MainActivity = Java.use("packname.MainActivity");
//overload 选择被重载的对象,funname是方法名,int是要重载的类型
MainActivity.funname.overload('int').implementation = function (x) {
console.log("ok" + x);
//可修改结果,另一种修改返回值的方法
var result = "";
return result;
};
})
function hook() { //把方法封装为一个函数,在运行起来后可以直接调用执行
Java.perform(function(){
let MainActivity = Java.use("packname.MainActivity");
MainActivity["g4tk4y"].implementation = function () {
console.log(`MainActivity.g4tk4y is called`);
let result = this["g4tk4y"]();
console.log(`MainActivity.g4tk4y result=${result}`);
return result;
};
MainActivity.aaa("x"); //调用函数并传参
})
};
(function () { //(function(){..})();这样的形式会直接执行函数,
//以下函数用dia提取,用于hookso层的函数
// @ts-ignore
function print_arg(addr) {
try {
var module = Process.findRangeByAddress(addr);
if (module != null) return "\n"+hexdump(addr) + "\n";
return ptr(addr) + "\n";
} catch (e) {
return addr + "\n";
}
}
// @ts-ignore
function hook_native_addr(funcPtr, paramsNum) {
var module = Process.findModuleByAddress(funcPtr);
try {
Interceptor.attach(funcPtr, {
onEnter: function (args) {
this.logs = "";
this.params = [];
// @ts-ignore
this.logs=this.logs.concat("So: " + module.name + " Method: Java_work_pangbai_debugme_MainActivity_g4tk4y offset: " + ptr(funcPtr).sub(module.base) + "\n");
for (let i = 0; i < paramsNum; i++) {
this.params.push(args[i]);
this.logs=this.logs.concat("this.args" + i + " onEnter: " + print_arg(args[i]));
}
}, onLeave: function (retval) {
for (let i = 0; i < paramsNum; i++) {
this.logs=this.logs.concat("this.args" + i + " onLeave: " + print_arg(this.params[i]));
}
this.logs=this.logs.concat("retval onLeave: " + print_arg(retval) + "\n");
console.log(this.logs);
}
});
} catch (e) {
console.log(e);
}
}
// @ts-ignore
hook_native_addr(Module.findBaseAddress("libdebugme.so").add(0xff0), 0x1);
})();
//重载函数的其他类型
.overload()
.overload('int')
.overload('java.lang.Exception')
.overload('android.content.Context')
.overload('java.lang.String')
.overload('android.content.Context', 'java.lang.String')
.overload('java.io.BufferedInputStream', 'java.io.BufferedInputStream', 'int')
.overload('android.content.Context', 'java.lang.String', 'java.lang.String', 'java.lang.String')
上面的方法都是被动调用,下面一段脚本是主动调用脚本
Java.perform(function(){
var main =Java.use("com.moible.r15.main").$new();
var input = "66.666s";
var result = main.getit(input);
console.log(result);
})
有时候我们想要把一个调用方法封装在一个函数里面,在之后手动调用,然而在我们后面调用时可能会出现报错
这个时候我们要使用一下代码使handle能够在别的实例中运行。
send(Java.available);
function get(){
Java.perform(function () {
send(Java.androidVersion);
send(Java.isMainThread());
Java.scheduleOnMainThread(function () {
send(Java.isMainThread());
var main = Java.use("com.moible.r15.main").$new(); //记得实例化new
var input = "66.666s"; //设置参数
var result = main.getit(input);
console.log(result);
});
});
}
一些常见错误
关于雷电模拟器spawn启动时卡在waitdebug界面的的原因解决方案
在用雷电模拟器调试时,有可能会卡在wait for debug,这是因为雷电的adb调试会给我们自动设置调试的应用,从而卡在wait界面。只要在开发者选项中把选择调试的应用改成”无”就行了。
关于frida调试应用闪退的解决方案
端口没有设置好.设置一下端口就行。
adb forward tcp:27042 tcp:27043
脚本运行过程中报错
查找报错点,找到报错的方法,分析,有必要时直接hook下来,修改函数的返回值等内容。
frida_win_processes
frida可以hookwindows上的的进程,详见功能 |Frida • 世界一流的动态仪表工具包
frida -l hook.js -n [name]
这里的name是进程的程序名字。
先介绍一下frida的processAPI。bbs.kanxue.com/article-342.htm
var process = Process.findModuleByAddress(address) //根据包含的地址查找模块
var process = Process.findModuleByName() //根据名字查找在内存中的模块(str)->(模块对象)
process.base //模块基地址
process.name //模块名
process.size //模块大小
process.path //模块路径
setInterval(f, delay) //每隔delay毫秒调用f,返回一个id,使用clearinterval(id)取消对setIntervar()的调用
ptr(addr) //以指针形式调用地址数据
hexdump(addr,{offset:0,length:64,header:true,absi:true}) //把一个区域内的内存按格式导出
this.context //访问当前hook的上下文信息,其中this.context.rax可以获取寄存器的值
hook脚本来自Windows | Frida • A world-class dynamic instrumentation toolkit
通用脚本
//const baseAddr = Module.findBaseAddress('Jvm.dll'); //获取hook对象的拓展模块基地址
//console.log('Jvm.dll baseAddr: ' + baseAddr);
var process = Process.findModuleByName("xx.exe"); //以名称获取进程中的模块
var baseaddr = process.base
//const f = resolveAddress('0x1FF44870'); //输入函数在ida中显示的地址,获得在内存中的地址
Interceptor.attach(baseaddr.add(0x0000), { // hook函数,函数被调用时执行
// 被hook时打印函数信息
onEnter(args) {
console.log('');
console.log('[+] Called SetAesDeCrypt0' + f); //被hook的函数地址
console.log('[+] Input: ' + args[0]); // 参数args[1],args[2]...
aegs[0] = 111; //修改参数
dumpAddr('Input', args[1], args[3].toInt32()); //导出指针参数的数据
this.outptr = args[2]; //保存参数的值以便函数结束时查看
this.outsize = args[3].toInt32();
var rdx=this.context.rdx; //以上下文获取寄存器的值
console.log(hexdump(ptr(rdx),{length: 16,ansi:true})); //导出寄存器指向内存的数据
},
onLeave(retval) { //函数结束时执行
dumpAddr('Output', this.outptr, this.outsize); // 获取我们保存的指针参数的值
console.log('[+] Returned from setAesDecrypt0: ' + retval); //函数的返回值
}
});
function dumpAddr(info, addr, size) { //导出内存数据
if (addr.isNull())
return;
console.log('Data dump ' + info + ' :');
const buf = addr.readByteArray(size);
// 如果想要色彩高亮,ansi为true
console.log(hexdump(buf, { offset: 0, length: size, header: true, ansi: false }));
}
function resolveAddress(addr) { //用IDA显示的地址获取当前内存中的地址,其实自己手算偏移也行
const idaBase = ptr('0x1FEE0000'); //输入IDA中显示的基地址,用于计算地址偏移
const offset = ptr(addr).sub(idaBase); //计算地址偏移
const result = baseAddr.add(offset); //计算现在在内存中的地址
console.log('[+] New addr=' + result); // 输出在内存中的地址
return result;
}
手动调用函数
var f = new NativeFunction(ptr(addr), 'void', ['int']); //函数地址,返回类型,参数类型,调用函数
f(1911); //调用函数并串参
实用脚本,一般dump寄存器的值
var inter=setInterval(function () {
var process = Process.findModuleByName("1.exe");
var baseaddr = process.base
console.log("base"+baseaddr);
clearInterval(inter);
console.log(hexdump(baseaddr.add(0x005160),{length:255,ansi:true}))
Interceptor.attach(baseaddr.add(0x001E7F), {
onEnter: function (args) {
var rax=this.context.rax;
console.log("secret" +rax);
console.log(hexdump(ptr(rax),{length: 48,ansi:true}));
}}),
},1)