一、目标

一、目标

今天给大家介绍一个新朋友, QBDI

它可以快速集成到你的frida脚本里面来进行汇编级的Trace

二、步骤

安装

在Androd上使用QBDI非常方便,先去官网下载一个最新版本

https://github.com/QBDI/QBDI/releases/download/v0.10.0/QBDI-0.10.0-android-AARCH64.tar.gz

针对我们Frida的使用,主要是里面的两个文件 libQBDI.sofrida-qbdi.js , 前一个是注入库,后一个是js的封装

然后把 libQBDI.so 放到 /data/local/tmp 目录

adb push libQBDI.so /data/local/tmp

使用

第一种用法是主动调用目标函数来Trace

由于需要加载 frida-qbdi.js 模块,所以这里我们需要用到frida的模块开发。 通常的方法是 采用大胡子的 https://github.com/oleavr/frida-agent-example 一个使用TS的模版。

不过懒得再去学一个TS语言了,所以我们今天用另外一个办法

index.js
import {
    VM,
    InstPosition,
    VMAction,
    Options,
    MemoryAccessType,
    AnalysisType,
    RegisterAccessType,
    OperandType,
    SyncDirection
} from "./frida-qbdi.js";

.... call fridaQBDI

然后使用 frida-compile 编译

// 编译
frida-compile -w index.js -o frida-qbdi-agent.js
// 启动Hook
frida -Uf com.example.myapplication --runtime=v8 -l frida-qbdi-agent.js

主动调用

考虑apk中的so里面有一个addTestFenfei函数

extern "C" int addTestFenfei(){
    int a = 2;
    int b = 3;
    int c = a ^ b;
    c = c + a + b;
    LOGD("addTestFenfei : %d", c);
    return c;
}

我们在warp_vm_run.js里面就可以用下面的方式来主动调用

/**
 * warp_vm_run的函数定义不要修改
 * @param {*} vm_run_func   会调用qbdi的vm.call
 * @param {*} log_file_path 日志文件的路径
 */
export default function warp_vm_run(vm_run_func, log_file_path) {
    let libnative_name = "libmyapplication.so"; // 替换为你的.so库的名称
    let func_name = "addTestFenfei";

    let libnative_base = Process.findModuleByName(libnative_name).base;
    console.log("warp_vm_run libnative_base 0x" +  libnative_base.toString(16));

    // 获取native方法地址
    const func_addr = Module.findExportByName(libnative_name, func_name);
    console.log("func_addr = " + func_addr);

    let ret = vm_run_func(null,func_addr, [], log_file_path,false);
        console.log(ret);
}

然后挂上心爱的frida执行

Failed to load /data/local/tmp/libQBDI.so (dlopen failed: couldn't map "/data/local/tmp/libQBDI.so" segment 1: Permission denied)

报错了,木有权限, 那就给它

#setenforce 0

每一行的 执行代码,寄存器变化和内存读写就都打印出来了

start vm.call ===
0x77d5661b00 [libmyapplication.so!0x1eb00]         stp        x29, x30, [sp, #-16]!         r[FP=0x77d6b03100 LR=0x2a SP=0x77d6b03080]   w[SP=0x77d6b03070]
memory write at 77d6b03070, data size = 8, data value = 77d6b03100
memory write at 77d6b03078, data size = 8, data value = 2a
0x77d5661b04 [libmyapplication.so!0x1eb04]         mov        x29, sp         r[SP=0x77d6b03070]   w[FP=0x77d6b03070]
0x77d5661b08 [libmyapplication.so!0x1eb08]         adrp        x8, #172032          w[X8=0x77d568b000]
0x77d5661b0c [libmyapplication.so!0x1eb0c]         ldrb        w8, [x8, #3912]         r[X8=0x77d568b000]   w[W8=0x1]
memory read at 77d568bf48, data size = 1, data value = 1
0x77d5661b10 [libmyapplication.so!0x1eb10]         cbz        w8, #32         r[W8=0x1]
0x77d5661b14 [libmyapplication.so!0x1eb14]         adrp        x1, #-40960          w[X1=0x77d5657000]
0x77d5661b18 [libmyapplication.so!0x1eb18]         adrp        x2, #-40960           w[X2=0x77d5657000]
0x77d5661b1c [libmyapplication.so!0x1eb1c]         add        x1, x1, #1895         r[X1=0x77d5657000]   w[X1=0x77d5657767]
0x77d5661b20 [libmyapplication.so!0x1eb20]         add        x2, x2, #806         r[X2=0x77d5657000]   w[X2=0x77d5657326]
0x77d5661b24 [libmyapplication.so!0x1eb24]         mov        w0, #3          w[W0=0x3]
0x77d5661b28 [libmyapplication.so!0x1eb28]         mov        w3, #6          w[W3=0x6]
0x77d5661b2c [libmyapplication.so!0x1eb2c]         bl        #148868         r[SP=0x77d6b03070] w[LR=0x77d5661b30]
0x77d56860b0 [libmyapplication.so!0x430b0]         adrp        x16, #16384         w[X16=0x77d568a000]
0x77d56860b4 [libmyapplication.so!0x430b4]         ldr        x17, [x16, #3032]         r[X16=0x77d568a000] w[X17=0x78cb2dd788]
memory read at 77d568abd8, data size = 8, data value = 78cb2dd788
0x77d56860b8 [libmyapplication.so!0x430b8]         add        x16, x16, #3032         r[X16=0x77d568a000] w[X16=0x77d568abd8]
0x77d56860bc [libmyapplication.so!0x430bc]         br        x17         r[X17=0x78cb2dd788]
0x77d5661b30 [libmyapplication.so!0x1eb30]         mov        w0, #6         w[W0=0x6]
0x77d5661b34 [libmyapplication.so!0x1eb34]         ldp        x29, x30, [sp], #16         r[SP=0x77d6b03070]  w[FP=0x77d6b03100 LR=0x2a SP=0x77d6b03080]
memory read at 77d6b03070, data size = 8, data value = 77d6b03100
memory read at 77d6b03078, data size = 8, data value = 2a
0x77d5661b38 [libmyapplication.so!0x1eb38]         ret         r[LR=0x2a]
cost is 0.063s
0x6

如果要调用 有参数的函数,可以这么来做

let ret = vm_run_func(null,func_addr, [2,3], log_file_path);

Hook替换

有些时候,我们不想构造参数去主动调用函数,而是想在app执行的过程中,去hook替换目标函数,然后打印它的真实流程。

这里要注意的就是两点, 1是替换,2是更新上下文,也就是更新寄存器的值

在traceCodeQBDI.js文件 vm_run函数里面,

// postSync 是否同步回来
function vm_run(ctx,func_ptr, args, log_file_path,postSync) {
    let start_time = new Date().getTime();

    let vm = new VM();
    vm.setOptions(Options.OPT_DISABLE_LOCAL_MONITOR | Options.OPT_BYPASS_PAUTH | Options.OPT_ENABLE_BTI)

    var state = vm.getGPRState();
    vm.allocateVirtualStack(state, 0x100000);

    // 同步寄存器
    if(postSync){
        console.log("==== synchronizeContext FRIDA_TO_QBDI ");
        state.synchronizeContext(ctx,SyncDirection.FRIDA_TO_QBDI);
    }

    ......

    // 同步寄存器
    if(postSync){
        console.log("synchronizeContext QBDI_TO_FRIDA ====");
        state.synchronizeContext(ctx,SyncDirection.QBDI_TO_FRIDA);
    }
    return ret;

}

然后在warp_vm_run中做替换

export default function warp_vm_run(vm_run_func, log_file_path) {
    let libnative_name = "libmyapplication.so"; // 替换为你的.so库的名称

    let libnative_base = Process.findModuleByName(libnative_name).base;
    console.log("warp_vm_run libnative_base 0x" +  libnative_base.toString(16));

    // hook替换
    //*
    let env = Java.vm.tryGetEnv();
    console.log(JSON.stringify(env));

       let func_name = "Java_com_example_myapplication_MainActivity_FFTestAdd";
       let func_addr = Module.findExportByName(libnative_name, func_name);
    console.log("Hook func_addr = " + func_addr);

    Interceptor.replace(func_addr,new NativeCallback(function (vmEnv,vmContext,a,b){
        console.log(" ============== ");
        console.log("[+] " + func_addr.sub(libnative_base) + "(" + a + ", " + b + ") called");

        // 恢复被替换的函数入口
        Interceptor.revert(func_addr);
        Interceptor.flush();
        console.log(env.handle);

        // // qbdi执行
        var retVal = vm_run_func(this.context, func_addr, [vmEnv,vmContext,a,b],log_file_path,true);
        // 继续替换吧
        warp_vm_run(vm_run_func,log_file_path);

        const resultStr = env.stringFromJni(retVal);
        console.log("Result: " + resultStr);

        return retVal;
    }, "pointer", ["pointer", "pointer","int","int"]));
    // */


}

这样执行就可以打印出实际app跑的时候的指令了。

三、总结

指令级的Trace还有很多应用场景,比如只打印XOR指令,或者只监控SVC指令。

参考资料

https://github.com/lasting-yang/frida-qbdi-tracer

https://blog.quarkslab.com/why-are-frida-and-qbdi-a-great-blend-on-android.html

ffshow
1:ffshow

当星星变成星空,梦想也就近在咫尺了

100

关注微信公众号,最新技术干货实时推送

100