frida 高级 api 食用方法(三)
1. 前提准备
本文章是对看雪教程的简单总结,可快速了解 frida hook api
的使用方法
这里提供下参考文档及其注意事项
2. frida 的使用
2.1. hook java 层的 jni 函数
注册时随便输入后点击好吧,最后 app
闪退
教程中先根据 AndroidManifest.xml
找到程序入口 MainActivity
,在其中找到启动了另一个 activity
: starActivity
为 RegActivity
。
在 RegActivity
中需要关注输入框的相关处理 saveSN
函数。
可以看到 saveSN
为 java
层的 jni
函数
对应的 frida
代码如下,frida -U com.gdufs.xman -l hook_xman.js
:
需要将 kill_process
给 hook
掉,不要让它执行 killProcess
方法。
| function hook_java() {
Java.perform(function () {
var MyApp = Java.use("com.gdufs.xman.MyApp");
// hook java 层的jni函数
MyApp.saveSN.implementation = function (str) {
console.log("MyApp.saveSN:", str);
this.saveSN(str);
};
var Process = Java.use("android.os.Process");
Process.killProcess.implementation = function (pid) {
//this.killProcess(pid);
console.log("Process.killProcess:", pid);
};
console.log("hook_java");
});
}
|
然后,输入注册码点好吧后就不会闪退了,但也没有其它现象。
2.2. 获取模块基址,hook 导出函数
解决输入注册码后闪退的问题后,接着分析 saveSN 的方法,需要 ida 来分析对应 so 文件的逻辑。
发现 ida
中的导出函数中没有 saveSN
方法。
1.首先看下 JNI_OnLoad
方法
2.ctrl + s
快捷键看下 init_array
,看有没有函数,此情况没有,不用看了。
我们需要查看 jni_onload
方法,f5
反编译后查看
先 Hide casts
是的代码清晰
再添加 jni_h
的 c
头,或者直接 Set item type
即可。
整理后的代码如下,可以看出为 saveSN
为动态注册的函数:
| jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
if ( !(*vm)->GetEnv(vm, &g_env, 65542) )
{
j___android_log_print(2, "com.gdufs.xman", "JNI_OnLoad()");
native_class = (*g_env)->FindClass(g_env, "com/gdufs/xman/MyApp");
if ( !(*g_env)->RegisterNatives(g_env, native_class, off_5004, 3) )
{
j___android_log_print(2, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() ok");
return 65542;
}
j___android_log_print(6, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() failed");
}
return -1;
}
|
点击 off_5004
进入,可以看到如下,再点击 n2
就可以跳转到 saveSN
的函数中。
n2
反编译后的代码为:
| // saveSN
int __fastcall n2(_JNIEnv *a1, int a2, int a3)
{
int v5; // r7
const char *v7; // r6
const char *v8; // r5
int v9; // r4
int v10; // r0
char v11; // r2
signed int v12; // [sp+8h] [bp-38h]
char v13[20]; // [sp+10h] [bp-30h] BYREF
v5 = j_fopen("/sdcard/reg.dat", "w+");
if ( !v5 )
return j___android_log_print(3, "com.gdufs.xman", byte_2E5A);
strcpy(v13, "W3_arE_whO_we_ARE");
v7 = a1->functions->GetStringUTFChars(a1, a3, 0);
v8 = v7;
v12 = j_strlen(v7);
v9 = 2016;
while ( 1 )
{
v10 = v8 - v7;
if ( v8 - v7 >= v12 )
break;
if ( v10 % 3 == 1 )
{
v9 = (v9 + 5) % 16;
v11 = v13[v9 + 1];
}
else if ( v10 % 3 == 2 )
{
v9 = (v9 + 7) % 15;
v11 = v13[v9 + 2];
}
else
{
v9 = (v9 + 3) % 13;
v11 = v13[v9 + 3];
}
*v8++ ^= v11;
}
j_fputs(v7, v5);
return j_fclose(v5);
}
|
我们要 hook
下 n2
函数,n2
函数为导出函数:
这里:n2 - base_myjni
就等于 ida
静态分析地址加一,打开 ida
的 opcode
即可查看。
2.3. 枚举模块的符号
我们需要 hook n2
中的 GetStringUTFChars
这个关键方法, String -> jstring -> char *
的转换。
查看 initSN
函数,并根据上图 hook FindClass
,GetStaticFieldID
和 SetStaticIntField
方法
n1
反编译后的代码为:
| int __fastcall n1(int a1)
{
int v2; // r0
int v3; // r4
int v4; // r0
int v5; // r7
const char *v6; // r5
int v8; // r0
int v9; // r1
v2 = j_fopen("/sdcard/reg.dat", "r+");
v3 = v2;
if ( !v2 )
{
v4 = a1;
return setValue(v4, 0);
}
j_fseek(v2, 0, 2);
v5 = j_ftell(v3);
v6 = j_malloc(v5 + 1);
if ( !v6 )
{
j_fclose(v3);
v4 = a1;
return setValue(v4, 0);
}
j_fseek(v3, 0, 0);
j_fread(v6, v5, 1, v3);
v6[v5] = 0;
if ( !j_strcmp(v6, "EoPAoY62@ElRD") )
{
v8 = a1;
v9 = 1;
}
else
{
v8 = a1;
v9 = 0;
}
setValue(v8, v9);
return j_fclose(v3);
}
|
2.4. hook libart 的一些函数
并打印调用栈
2.5. hook libc的函数
需要 hook j_strcmp
函数,frida -U --no-pause -f com.gdufs.xman -l hook_xman.js
hook
结果就可以知道,需要将 args[0]
与 args[1]
的结果相同,就可以使得 m
赋值为 1,就可以注册正常。
可以使用 adb
命令:echo "EoPAoY62@ElRD" > /sdcard/reg.dat
也可以使用 frida api
来做到。
2.6. Frida 操作文件 api
2.6.1. Frida 的 File api写文件
使用 frida
的 file api
写文件代码如下:
2.6.2. 把 C 函数定义为 NativeFunction 来写文件
这是另一种 frida
操作文件的 api
写法:
3. 总结及补充
这里放上所涉及到的 frida js hook
脚本:
| function hook_java() {
Java.perform(function () {
var MyApp = Java.use("com.gdufs.xman.MyApp");
// hook java 层的jni函数
MyApp.saveSN.implementation = function (str) {
console.log("MyApp.saveSN:", str);
this.saveSN(str);
};
var Process = Java.use("android.os.Process");
Process.killProcess.implementation = function (pid) {
//this.killProcess(pid);
console.log("Process.killProcess:", pid);
};
console.log("hook_java");
});
}
function hook_native() {
//获取模块的基址
var base_myjni = Module.findBaseAddress("libmyjni.so");
if ( ) {
//获取模块的导出函数
var n2 = Module.findExportByName("libmyjni.so", "n2");
//thumb的函数,0x000011F8, 实际地址0xdba461f9
console.log("base_myjni:", base_myjni, "n2:", n2);
//hook模块的导出函数
Interceptor.attach(n2, {
onEnter: function (args) {
console.log("n2 onEnter:", args[0], args[1], args[2]);
}, onLeave: function (retval) {
}
});
}
}
function hook_libart() {
var module_libart = Process.findModuleByName("libart.so");
var symbols = module_libart.enumerateSymbols(); //枚举模块的符号
var addr_GetStringUTFChars = null;
var addr_FindClass = null;
var addr_GetStaticFieldID = null;
var addr_SetStaticIntField = null;
for (var i = 0; i < symbols.length; i++) {
var name = symbols[i].name;
if (name.indexOf("art") >= 0) {
// 函数不能有 CheckJNI
if ((name.indexOf("CheckJNI") == -1) && (name.indexOf("JNI") >= 0)) {
if (name.indexOf("GetStringUTFChars") >= 0) {
console.log(name);
addr_GetStringUTFChars = symbols[i].address;
} else if (name.indexOf("FindClass") >= 0) {
console.log(name);
addr_FindClass = symbols[i].address;
} else if (name.indexOf("GetStaticFieldID") >= 0) {
console.log(name);
addr_GetStaticFieldID = symbols[i].address;
} else if (name.indexOf("SetStaticIntField") >= 0) {
console.log(name);
addr_SetStaticIntField = symbols[i].address;
}
}
}
}
//hook jni的一些函数
if (addr_GetStringUTFChars) {
Interceptor.attach(addr_GetStringUTFChars, {
onEnter: function (args) {
//打印调用栈
// console.log('addr_GetStringUTFChars onEnter called from:\n' +
// Thread.backtrace(this.context, Backtracer.FUZZY)
// .map(DebugSymbol.fromAddress).join('\n') + '\n');
}, onLeave: function (retval) {
// retval const char*
console.log("addr_GetStringUTFChars onLeave:", ptr(retval).readCString(), "\r\n");
}
});
}
if (addr_FindClass) {
Interceptor.attach(addr_FindClass, {
onEnter: function (args) {
console.log("addr_FindClass:", ptr(args[1]).readCString());
}, onLeave: function (retval) {
}
});
}
if (addr_GetStaticFieldID) {
Interceptor.attach(addr_GetStaticFieldID, {
onEnter: function (args) {
console.log("addr_GetStaticFieldID:", ptr(args[2]).readCString(), ptr(args[3]).readCString());
}, onLeave: function (retval) {
}
});
}
if (addr_SetStaticIntField) {
Interceptor.attach(addr_SetStaticIntField, {
onEnter: function (args) {
console.log("addr_SetStaticIntField:", args[3]);
}, onLeave: function (retval) {
}
});
}
}
function hook_libc() {
//hook libc的函数
var strcmp = Module.findExportByName("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) {
}
});
}
function write_reg_dat() {
//frida 的api来写文件
var file = new File("/sdcard/reg.dat", "w");
file.write("EoPAoY62@ElRD");
file.flush();
file.close();
}
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");
console.log("addr_fopen:", addr_fopen, "addr_fputs:", addr_fputs, "addr_fclose:", addr_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");
var open_mode = Memory.allocUtf8String("w+");
var file = fopen(filename, open_mode);
console.log("fopen file:", file);
var buffer = Memory.allocUtf8String("EoPAoY62@ElRD");
var ret = fputs(buffer, file);
console.log("fputs ret:", ret);
fclose(file);
}
function main() {
hook_java();
hook_native();
hook_libart();
hook_libc();
}
setImmediate(main);
|