跳转至

frida 高级 api 食用方法(三)

1. 前提准备

本文章是对看雪教程的简单总结,可快速了解 frida hook api 的使用方法

这里提供下参考文档及其注意事项

2. frida 的使用

2.1. hook java 层的 jni 函数

注册时随便输入后点击好吧,最后 app 闪退

教程中先根据 AndroidManifest.xml 找到程序入口 MainActivity,在其中找到启动了另一个 activity: starActivityRegActivity

image-20220511145425675

RegActivity 中需要关注输入框的相关处理 saveSN 函数。

image-20220511145941964

可以看到 saveSNjava 层的 jni 函数

image-20220511145032277

对应的 frida 代码如下,frida -U com.gdufs.xman -l hook_xman.js

image-20220511145005637

需要将 kill_processhook 掉,不要让它执行 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,看有没有函数,此情况没有,不用看了。

image-20220511152217443

我们需要查看 jni_onload 方法,f5 反编译后查看

Hide casts 是的代码清晰

image-20220511152708334

再添加 jni_hc 头,或者直接 Set item type 即可。

image-20220511154032819

整理后的代码如下,可以看出为 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 的函数中。

image-20220511155118894

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);
}

我们要 hookn2 函数,n2 函数为导出函数:

这里:n2 - base_myjni 就等于 ida 静态分析地址加一,打开 idaopcode 即可查看。

image-20220511155608376

2.3. 枚举模块的符号

我们需要 hook n2 中的 GetStringUTFChars 这个关键方法, String -> jstring -> char * 的转换。

image-20220511161603360

查看 initSN 函数,并根据上图 hook FindClassGetStaticFieldIDSetStaticIntField 方法

image-20220511163641347

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 的一些函数

并打印调用栈

image-20220511170942904

2.5. hook libc的函数

需要 hook j_strcmp 函数,frida -U --no-pause -f com.gdufs.xman -l hook_xman.js

image-20220511164547279

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写文件

使用 fridafile api 写文件代码如下:

image-20220511170127518

2.6.2. 把 C 函数定义为 NativeFunction 来写文件

这是另一种 frida 操作文件的 api 写法:

image-20220511170142935

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);

评论

回到页面顶部