美文网首页
Trace大盘点

Trace大盘点

作者: fenfei331 | 来源:发表于2022-03-16 12:08 被阅读0次

    一、目标

    李老板: 奋飞呀,最近老听别人说Trace一下,啥是Trace呀?

    奋飞:老板,先把上次的加班费结算一下。

    main.jpg

    Trace就是在更高抽象层次上去追踪程序的运行流程。

    二、JNI_Trace

    jnitrace

    在Android下混饭吃,首推的就是jnitrace

    https://github.com/chame1eon/jnitrace

    老牌,经典,信息全,携带方便

    jnitrace -l libnative-lib.so com.example.myapplication

    TIP: -l 是监控的so名称, -l *就是监控所有so了。

    缺点就是,hook的太全,欺负欺负小厂的还行,大厂的反调试一上,分分钟教你做人。

    这里还有个优化的文章
    https://blog.seeflower.dev/archives/82/

    jnitrace-engine

    jnitrace-engien是基于jnitrace裁剪出来的库,你可以取其所需,只hook你关心的函数。

    https://github.com/chame1eon/jnitrace-engine

    编译的时候需要nodejs,然后有一段时间没有更新了,导致某一个frida的库版本比较老,反正我是没有编译过去,有搞定的老铁留个言。

    jtrace

    方便定制的Trace才是好工具,jtrace感觉就比较帅了,信息齐全,直接在_agent.js或者_agent_stable.js 里面加自己的逻辑就行。

    https://github.com/anjia0532/jtrace

    比较推荐的玩法就是:不要一上来就 hook_all_jni();, 而是指定分析某段函数

    Interceptor.attach(targetSo.add(0x56582+1),{
        onEnter:function(args){
    
            console.log(" ================ jni Onload");
            hook_all_jni();
        }
    });
    
    

    需要它的时候,就hook,不需要的时候注释掉。

    hook_art.js

    有了新朋友,也不忘老朋友,yang神 hook_art.js 依然是可以提供jni trace的。

    https://github.com/lasting-yang/frida_hook_libart

    你可以灵活的增加你需要hook的函数

    var addrCallStaticObjectMethodV = null;
    ...
    
    } else if (symbol.name.indexOf("CallStaticObjectMethodV") >= 0) {
        addrCallStaticObjectMethodV = symbol.address;
        console.log("CallStaticObjectMethodV is at ", symbol.address, symbol.name);
    }
    
    ...
    
    if (addrCallStaticObjectMethodV != null) {
        Interceptor.attach(addrCallStaticObjectMethodV, {
            onEnter: function (args) {
                if (args[3] != null) {
                        // var argsInt = parseInt(args[4]);
                        console.log("[CallStaticObjectMethodV] >>> args[3] = " + args[3]);
                                }else if (args[4] != null) {
                                        console.log("[CallStaticObjectMethodV] >>> args[4] = " + args[4]);
                                }else if (args[2] != null) {
                                        console.log("[CallStaticObjectMethodV] >>> args[2] = " + args[2]);                  
                                }else{
                            console.log("[CallStaticObjectMethodV]");
                              }
    
    
            },
            onLeave: function (retval) {
                                if(retval != null){
                                console.log("[CallStaticObjectMethodV] retval= " + JSON.stringify(retval));
                                }
                        }
        });
    }
    

    JNI-Frida-Hook

    JNI-Frida-Hook 也是个不错的选择,这个哥哥把函数名都给你定义好了,哪里想看,Hook哪里。

    https://github.com/Areizen/JNI-Frida-Hook

    art-tracer

    https://github.com/oleavr/art-tracer

    art-tracer说实话,我还没怎么用过,老铁们可以试试

    小小的总结一下JNI_Trace, jnitrace 能跑通的,首推。量大管饱,然后就是 jtrace ,定制方便,信息全。都崩溃的你怀疑人生的时候,老老实实用 hook_art.js ,能hook就行。

    三、Native_Trace

    trace_natives

    做技术,要相信一见钟情,第一次Native Trace用的就是他,层次分明,信息全,结合frida-trace使用,很奇妙的想法。

    你不会还在用 IDA 7.0吧?不会吧? 好巧,我也是,把这几行改改就行

    so_path, so_name = getSoPathAndName()
    # search_result = [f"-a '{so_name}!{offset}'" for offset in search_result]
    search_result = ["-a '{}!{}'".format(so_name,offset) for offset in search_result]
    search_result = " ".join(search_result)
    
    script_name = so_name.split(".")[0] + "_" + str(int(time.time())) +".txt"
    
    save_path = os.path.join(so_path, script_name)
    # with open(save_path, "w", encoding="utf-8")as F:
    with open(save_path, "w")as F:
        F.write(search_result)
    
    print("使用方法如下:")
    # print(f"frida-trace -UF -O {save_path}")
    print("frida-trace -UF -O {} !".format(save_path))
    

    你肯定要问,一见钟情有啥缺点没有?

    和jnitrace一样,hook的太多,很容易崩掉。大厂 so木办法玩。

    findhash

    findhash也是ida插件,他本来是为了检测加解密函数用的,批量hook一大堆可疑的加解密函数,但是它提供了一个超帅的批量hook模板,我们只要按照格式生成 hook_suspected_function 函数中的 const funcs 数组,就可以把他当成一个Native Trace库来用了。

    https://github.com/Pr0214/findhash

    那const funcs 数组如何生成呢? 这个 改造下之前的 trace_natives.py 是可以的,不过我还没搞。

    我改了下龙哥的例子,搞了个Unidbg的

    public static Map<Integer, Integer> subTraceMap = new HashMap<Integer, Integer>();
    public static Map<Integer, Integer> calcMap = new HashMap<Integer, Integer>();
    
    public static void PrintHookSubInfo(){
            //*
            System.out.println("subTrace len = " + subTraceMap.size());
            String strOut = "";
            for (Map.Entry<Integer, Integer> entry : subTraceMap.entrySet()) {
                int iShow = entry.getKey();
                // 为了和unidbg显示一致这里处理下
                if(iShow % 2 != 0){
                    iShow = iShow -1;
                }
                strOut = strOut + "  ,['sub_" + Integer.toHexString(iShow ) + "','0x" + Integer.toHexString((int)entry.getKey() ) + "']";
            }
            System.out.println(strOut);
            // */
    }
    
    
    private void traceFn(final long baseAddr, final long starAddr, final long endAddr) {
        // 这个代码是没法trace 导入函数的
        PrintStream traceStream = null;
        try {
            // 保存文件
            String traceFile = "/Users/fenfei/Desktop/money/ffFunctions.txt";
            traceStream = new PrintStream(new FileOutputStream(traceFile), true);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    
        final PrintStream finalTraceStream = traceStream;
    
        emulator.getBackend().hook_add_new(new BlockHook() {
            @Override
            public void hookBlock(Backend backend, long address, int size, Object user){
                // 块太小 影响 ida的 hook , 这里也可以改成 8  10 之类的
                if (size > 20)
                {
                    Instruction[] insns = emulator.disassemble(address, 4, 0);
                    // 只搞函数 就开启这个 
                    // if (insns[0].getMnemonic().equals("push"))
                    {
                        int level = emulator.getUnwinder().depth();
                        assert finalTraceStream != null;
                        for (int i = 0; i < level; i++) {
                            finalTraceStream.print("    |    ");
                        }
    
                        // 为了和 frida-trace 对应,所以加 1
                        //  Thumb 模式切换导致 hook失败
                        // Frida-trace显示函数地址的方式是“sub_Hook地址”,因为Thumb模式下要+1的缘故,所以Frida trace中“sub_123C”在IDA中显示是“sub_123B”,对照ida分析时要注意一下。
                        // finalTraceStream.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr) +1 ) + " ");
    
                        // 给 traceNative 函数用的, 显示的时候就不加 1
                        // finalTraceStream.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr)  ) + " ");
    
                        int iSize = insns[0].getSize();
                        int iUseAddr = 0;
                        if( iSize == 4){
                            // ARM模式 4字节
                            iUseAddr = (int) (address - baseAddr);
                        }else {
                            // THUMB模式 2 字节 ,hook的时候 + 1 ,
                            iUseAddr = (int) (address - baseAddr) + 1;
    
                        }
    
    
                        if(calcMap.containsKey(iUseAddr)){
                            int iValue = calcMap.get(iUseAddr);
                            calcMap.put(iUseAddr,iValue + 1);
    
                            // 4次以上的调用就不显示了, 也不用Native Trace了
                            if(iValue > 3){
                                subTraceMap.remove(iUseAddr);
                            }else{
                                System.out.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr)  ) + " ");
                                finalTraceStream.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr)  ) + " ");
                            }
    
                        }else{
                            calcMap.put(iUseAddr,1);
                            subTraceMap.put(iUseAddr,1);
    
                            System.out.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr)  ) + " ");
                            finalTraceStream.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr)  ) + " ");
                        }
    
                    }
                }
            }
    
            @Override
            public void onAttach(UnHook unHook){
    
            }
    
            @Override
            public void detach(){
    
            }
    
        }, starAddr, endAddr, 0);
    }
    
    // unidbg/unidbg-api/src/main/java/com/github/unidbg/unwind/Unwinder.java
    public final int depth(){
        int count = 0;
        Frame frame = null;
        while((frame = unw_step(emulator, frame)) != null) {
            if(frame.isFinish()){
                return count;
            }
            count++;
        }
        return count;
    }
    
    

    Native Trace有这两个就差不多了,崩溃的话就少hook点,把引发崩溃的hook点删除。

    Stalker

    大胡子还搞了个Stalker,也是很有野心的,但是对于国内App的内卷现状,效果只能说差强人意吧。

    https://frida.re/docs/stalker/

    fridaMemoryAccessTrace

    没有内存断点的ida没有灵魂。一个替代方案是从 findhash 里面扣出来的。

    MemoryAccessMonitor.enable({
        base: this.addr,
        size: 0x8 // qword
    }, {
        onAccess: function (details) {
            console.log("\n");
            console.log("B >>>监控到内存访问\n");
            console.log(details.from);
            console.log("B >>> 访问来自:"+details.from.sub(targetSo)+"(可能有误差)");
        }
    });
    

    缺点是 只会触发一次

    plus版本就是fridaMemoryAccessTrace了,你值得拥有。
    https://github.com/asmjmp0/fridaMemoryAccessTrace

    四、总结

    不要去期望完美的Trace工具,适合你的,你自己能动手调校的才是好工具。

    相关文章

      网友评论

          本文标题:Trace大盘点

          本文链接:https://www.haomeiwen.com/subject/kpkpdrtx.html