frida native端使用笔记:
注意:frida hook native端的一个宗旨就是拿到内存地址 有了地址就可以操作
hexdump(ptr(this.arg2).add(Process.pointerSize).readPointer())
ptr(retval).readCString()
hexdump(args[1], { length: parseInt(args[2]) })
hexdump(ptr(this.arg8).add(Process.pointerSize * 2).readPointer())
ptr(Java.vm.tryGetEnv().getByteArrayElements(args[i])).readCString(parseInt(args[i + 1]))
特别重点:
1.获取上下文context
var context = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext();
2.获取JNIEnv函数
Java.vm.tryGetEnv().getStringUtfChars() 可以代替hook JNIEnv的函数
打印调用栈:
//打印调用栈
console.log('addr_GetStringUTFChars onEnter called from:\n' +
Thread.backtrace(this.context, Backtracer.FUZZY)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
重点一:frida hook JNIEnv的函数
因为JNIEnv的函数都在libart.so文件中 所以枚举libart.so文件
var module_libart = Process.findModuleByName("libart.so");
var symbols = module_libart.enumerateSymbols(); // enumerateSymbols函数的作用就是枚举模块的所有符号
var addr_GetStringUTFChars = null;
for (var i = 0; i < symbols.length; i++) {
var name = symbols[i].name; // name获取符号的名字
if (name.indexOf("art") >= 0) {
if ((name.indexOf("CheckJNI") == -1) && (name.indexOf("JNI") >= 0)) {
if (name.indexOf("GetStringUTFChars") >= 0) {
console.log(name);
addr_GetStringUTFChars = symbols[i].address; // address获取符号的地址
}
}
}
}
// 拿到地址后就可以进行hook
if (addr_GetStringUTFChars) {
Interceptor.attach(addr_GetStringUTFChars, {
onEnter: function (args) {
}, onLeave: function (retval) {
// retval const char*
console.log("addr_GetStringUTFChars onLeave:", ptr(retval).readCString(), "\r\n");
}
});
}
重点二:frida hook libc.so文件的函数
function hook_libc() {
//hook libc的函数
var strcmp = Module.findExportByName("libc.so", "strcmp"); // 获取libc.so文件中的strcmp函数
console.log("strcmp:", strcmp);
Interceptor.attach(strcmp, {
onEnter: function (args) {
var str_2 = ptr(args[1]).readCString();
if (str_2 == "EoPAoY62@ElRD") {
console.log("strcmp:", ptr(args[0]).readCString(),
ptr(args[1]).readCString());
}
}, onLeave: function (retval) {
}
});
}
重点三:frida native 主动写文件的两种方式
通过frida file api来写文件
function write_reg_dat() {
//frida 的api来写文件
var file = new File("/sdcard/reg.dat", "w");
file.write("EoPAoY62@ElRD");
file.flush();
file.close();
}
通过把C函数定义为NativeFunction来写文件
function write_reg_dat2() {
//把C函数定义为NativeFunction来写文件
var addr_fopen = Module.findExportByName("libc.so", "fopen");
var addr_fputs = Module.findExportByName("libc.so", "fputs");
var addr_fclose = Module.findExportByName("libc.so", "fclose");
var fopen = new NativeFunction(addr_fopen, "pointer", ["pointer", "pointer"]);
var fputs = new NativeFunction(addr_fputs, "int", ["pointer", "pointer"]);
var fclose = new NativeFunction(addr_fclose, "int", ["pointer"]);
var filename = Memory.allocUtf8String("/sdcard/reg.dat"); // 这里是需要注意的点 需要使用Memory api 来将字符串转为native可用格式
var open_mode = Memory.allocUtf8String("w+");
var file = fopen(filename, open_mode); // 打开文件 返回一个文件指针
var buffer = Memory.allocUtf8String("EoPAoY62@ElRD");
fputs(buffer, file); // 写入文件 fputs函数有两个参数 参数1:buffer 为需要写入的值 参数2:file 为需要写入的文件指针
fclose(file); // 关闭文件
}
重点四:frida hook dlopen 一开始就获取加载的so文件
function hook_dlopen() {
// 低版本Android系统使用diopen
var dlopen = Module.findExportByName(null, "dlopen");
Interceptor.attach(dlopen, {
onEnter: function (args) {
this.call_hook = false;
var so_name = ptr(args[0]).readCString(); // 获取加载的so文件
if (so_name.indexOf("libhello-jni.so") >= 0) {
console.log("dlopen:", ptr(args[0]).readCString());
this.call_hook = true;
}
}, onLeave: function (retval) {
if (this.call_hook) {
inline_hook();
}
}
});
// 高版本Android系统使用android_dlopen_ext
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
this.call_hook = false;
var so_name = ptr(args[0]).readCString(); // 获取加载的so文件
if (so_name.indexOf("libhello-jni.so") >= 0) {
console.log("android_dlopen_ext:", ptr(args[0]).readCString());
this.call_hook = true;
}
}, onLeave: function (retval) {
if (this.call_hook) {
inline_hook();
}
}
});
}