【Android安全】Frida Native hook汇总

1. 基础用法

(0)在so中找java层对应的native函数

  • ① 看so的Export函数列表(找静态注册的情形)
    若无,则可能为动态注册

  • ② 看so的.init_array段中是否有函数
    IDA中按Ctrl+S,进入.init_array
    在这里插入图片描述

系统加载so,在完成装载、映射和重定向以后,就首先执行.init和.init_array段的代码,之后如果存在JNI_OnLoad就调用该函数.我们要对一个so进行分析,需要先看看有没有.init_array section和.init section,so加壳一般会在初始化函数进行脱壳操作。

这里的.init_array段中没有函数
在这里插入图片描述

参考:http://pwn4.fun/2020/07/29/so%E6%96%87%E4%BB%B6%E7%9A%84-init-array%E6%AE%B5%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%BB%A3%E7%A0%81/

初始化和终止例程
动态库可以提供用于运行时初始化和终止处理的代码。每次在进程中装入动态库时,都会执行一次动态库的初始化代码。每次在进程中卸载动态库或进程终止时,都会执行一次动态库的终止代码。
在将控制权转交给应用程序之前,运行时链接程序将处理应用程序中找到的所有初始化节及所有装入的依赖项。如果在进程执行期间装入新动态库,则会在装入该目标文件的过程中处理其初始化节。初始化节 .preinit_array、.init_array 和 .init 由链接编辑器在生成动态库时创建。
运行时链接程序执行的函数的地址包含在 .preinit_array 和 .init_array 节中。这些函数的执行顺序与其地址在数组中的显示顺序相同。运行时链接程序将 .init 节作为单独的函数执行。如果某目标文件同时包含 .init 节和 .init_array 节,则会首先处理 .init 节,然后再处理该目标文件的 .init_array 节定义的函数。
动态可执行文件可在 .preinit_array 节中提供预初始化函数。这些函数将在运行时链接程序生成进程映像并执行重定位之后但执行任何其他初始化函数之前执行。 预初始化函数不允许在共享库中执行。

  • ③ 看有无JNI_OnLoad函数

基本就是这里了
在这里插入图片描述
重新定义g_env的类型为JNIEnv*

在这里插入图片描述
这样就能看到RegisterNatives函数了
在这里插入图片描述
RegisterNatives函数的第三参数是方法数组:

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
调用时多一个env对象作为第一参数
env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))

四个参数:
env是他的第一个参数(这个要注意)
clazz:指定的类,即 native 方法所属的类
methods:方法数组,这里需要了解一下 JNINativeMethod 结构体
nMethods:方法数组的长度

JNINativeMethod 结构体:
在这里插入图片描述
方法数组体现在IDA中的参数off_5004处:
在这里插入图片描述
可以看到,方法数组中包含initSN、saveSN等方法,说明这些Java方法是被JNI_Onload动态注册的。

可以看到,saveSN方法在n2+1处

我们进入到n2+1处,所看到的函数就是saveSN方法注册到的native函数。
在这里插入图片描述

参考:
https://blog.csdn.net/afei__/article/details/81031965
https://www.jianshu.com/p/eacdb203ea21

(1)按方法名看参数、bt、返回值、output参数

// 按方法名看参数、bt、返回值
var arg8;
var arg9;
var MtdName = "TssAesGcmDecrypt";
Java.perform(function () {
    Interceptor.attach(Module.findExportByName(SoName, MtdName), {
        onEnter: function (args) {
            send(MtdName + " hooked:");
            send("called from\\n" + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\\n"));
            console.log("\\n");
            send(MtdName + " args:");

            for (var it = 0; it < 9; it++) {
                send(MtdName + " args[" + it + "]=" + args[it]);
                if (args[it] > 0x15000000) 
                {
                    var buffer = Memory.readByteArray(args[it],128);
                }
                else
                {
                    var buffer = args[it];
                }
                console.log(buffer);
                console.log("\\n");
            }

            arg8 = parseInt(args[7]);
            arg9 = parseInt(args[8]);
        },

        onLeave: function (retval) {
            send(MtdName + " RetVal : " + retval);
            var buffer2 = Memory.readByteArray(retval, 128);
            console.log(buffer2);
            console.log("\\n");
            
            send(MtdName + " arg[7] : 0x" + arg8.toString(16));
            var buffer3 = Memory.readByteArray(ptr(arg8), 128);
            console.log(buffer3);
            console.log("\\n");

            send(MtdName + " arg[8] : 0x" + arg9.toString(16));
            var buffer3 = Memory.readByteArray(ptr(arg9), 128);
            console.log(buffer3);
            console.log("\\n");
            
            send(MtdName + " finished \\n");
        }
    });
});

(2)打印任意地址的内容

例如,打印返回值(地址)附近的内容

        onLeave: function (retval) {
            send("RetVal : " + retval);
            var buffer2 = Memory.readByteArray(retval, 128);
            console.log(buffer2);
            console.log("\\n");

            var tmp = parseInt(retval)+1032;
            send("RetVal + 1032: 0x" + tmp.toString(16));
            var buffer2 = Memory.readByteArray(ptr(tmp), 128);
            console.log(buffer2);
            console.log("\\n");

            var tmp = parseInt(retval)+1232;
            send("RetVal + 1232: 0x" + tmp.toString(16));
            var buffer2 = Memory.readByteArray(ptr(tmp), 128);
            console.log(buffer2);
            console.log("\\n");

            send(MtdName + " finished \\n");
        }

输出:

[*] RetVal : 0x730eb32c00
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  c2 34 2a bc bd ad 38 41 14 1e 72 16 87 9c f1 12  .4*...8A..r.....
00000010  1e 43 9b 50 de 6a 1a dc 22 c4 b8 82 d1 cc 3d 0b  .C.P.j..".....=.
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000070  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................


[*] RetVal + 1032: 730eb33008
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  96 ce b4 c4 9b b3 f4 f8 06 e4 91 13 00 00 00 00  ................
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000070  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................


[*] RetVal + 1232: 730eb330d0
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  a6 70 89 84 62 e4 52 bf 8b 77 f5 b3 30 f9 c6 24  .p..b.R..w..0..$
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000070  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................


[*] ParseGcmKey finished

(3)定位虚表函数偏移

在这里插入图片描述

//定位虚表函数偏移
Java.perform(function(){
    var Offset=0x1516C;//BLR             X8
    Interceptor.attach(Module.findBaseAddress(SoName).add(ptr(Offset)),{
        onEnter: function(args) {
            console.log("\\n" + Offset + " - x8: " +  this.context.x8);
            console.log("\\n" + "base : " + Module.findBaseAddress(SoName));
            var funcOff = parseInt(this.context.x8,16)-parseInt(Module.findBaseAddress(SoName),16);
            console.log("\\n" + "funcOffset : " + funcOff.toString(16) );
        },
        onLeave: function(retval){
            console.log(Offset +" finished \\n");
        }
    });
});

输出:

[*] Running hooking

86380 - x8: 0x7290b5c418

base : 0x7290b43000

funcOffset : 19418

ida中按g,跳转到相应函数的位置,定位到函数:
在这里插入图片描述
在这里插入图片描述

(4)判断地址是否能hook到

Java.perform(function(){
    var Offset=0xF86C;//LDR             X24, [X8,#8]
    Interceptor.attach(Module.findBaseAddress(SoName).add(ptr(Offset)),{
        onEnter: function(args) {
            console.log("\\n" + Offset.toString(16) + " hooked ");
        },
        onLeave: function(retval){
            console.log(Offset +" finished \\n");
        }
    });
});

(5)从地址读取ByteArray、String

var buffer = Memory.readByteArray(args[it],128);
var buffer = Memory.readCString(args[it]);
for (var it = 0; it < 2; it++) 
{
    send(MtdName4 + " args[" + it + "]=" + args[it]);
    if (args[it] > 0x15000000) 
    {
        if (it == 1)
        {
            var buffer = Memory.readByteArray(args[it],128);
        }
        else
        {
            var buffer = Memory.readCString(args[it]);
        }
    }
    else
    {
        var buffer = args[it];
    }
    console.log(buffer);
    console.log("\\n");
}

2. 进阶用法

(1)枚举so中的符号

function hook_libart() {
    var proc = Process.findModuleByName("libart.so");
    var symbols = proc.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) {
            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;
                }
            }
        }
    }
}

(2)通过frida 的api来写文件

function write_reg_dat() {
    //frida 的api来写文件
    var file = new File("/sdcard/reg.dat", "w");
    file.write("EoPAoY62@ElRD");
    file.flush();
    file.close();
}

(3)把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");

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

3. 一些注意事项

关于so分析时的Memory.readByteArray

不要这么写:

console.log("\\n"+Memory.readByteArray(ptr(0x7751a537ec),128));

就是不要在Memory.readByteArray前加"\n",会报错
可以用send
也可以单独将"\n"输出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值