美文网首页
记录一下YAHFA相关

记录一下YAHFA相关

作者: 约你一起偷西瓜 | 来源:发表于2021-05-14 12:16 被阅读0次

    首先写一个安卓demo如下

        findViewById(R.id.sample_text).setOnClickListener(v -> {
                try {
                    Method doWork1 = MainActivity.class.getDeclaredMethod("doWork1");
                    Method doWork2 = MainActivity.class.getDeclaredMethod("doWork2");
                    Method doWork3 = MainActivity.class.getDeclaredMethod("doWork3");
    
                    calledBefore(doWork1,doWork2,doWork3);
                    HookMain.backupAndHook(doWork1,doWork2,doWork3);
                    calledAfter(doWork1,doWork2,doWork3);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            });
        }
    
        private static void doWork1() {
            Log.i(TAG, "doWork1");
        }
        private static void doWork2() {
            Log.i(TAG, "doWork2");
        }
        private static void doWork3() {
            Log.i(TAG, "doWork3");
        }
    
        public native void calledBefore(Method doWork1, Method doWork2, Method doWork3);
        public native void calledAfter(Method doWork1, Method doWork2, Method doWork3);
    

    两个native方法(calledBefore/calledAfter)在native层啥也不用做 只是拿到一个反射方法

    顺带附上hook代码,主要是用frida展示artmethod指针内存的变化

    var getArtMethod = new NativeFunction(Module.findExportByName('libnative-lib.so','getArtMethod'),'pointer',['pointer','pointer'])
    
    Interceptor.attach(Module.findExportByName('libnative-lib.so','Java_com_lzy_yahfa_MainActivity_calledBefore'),{
        onEnter:function(args){
            LOG("\n----------------------- Before -----------------------\n",LogColor.RED)
            showLog(args[0],args[2],args[3],args[4])
        },
        onLeave:function(ret){
    
        }
    })
    
    Interceptor.attach(Module.findExportByName('libnative-lib.so','Java_com_lzy_yahfa_MainActivity_calledAfter'),{
        onEnter:function(args){
            LOG("\n----------------------- After -----------------------\n",LogColor.RED)
            showLog(args[0],args[2],args[3],args[4])
        },
        onLeave:function(ret){
    
        }
    })
    
    function showLog(a0,a1,a2,a3){
    
        LOG(" ----- ORG  ----- ",LogColor.YELLOW)
        var method = getArtMethod(a0,a1)
        seeHexA(method,p_size*8)
        LOG("entry_point_from_quick_compiled_code -> "+method.add(p_size*7).readPointer()+" ---> "+method.add(p_size*7).readPointer().readPointer())
        LOG("\n")
        
        LOG(" ----- Hook  ----- ",LogColor.YELLOW)
        var method = getArtMethod(a0,a2)
        seeHexA(method,p_size*8)    
        LOG("entry_point_from_quick_compiled_code -> "+method.add(p_size*7).readPointer()+" ---> "+method.add(p_size*7).readPointer().readPointer())
        LOG("\n")
        
        LOG(" ----- Back  ----- ",LogColor.YELLOW)
        var method = getArtMethod(a0,a3)
        seeHexA(method,p_size*8)
        LOG("entry_point_from_quick_compiled_code -> "+method.add(p_size*7).readPointer()+" ---> "+method.add(p_size*7).readPointer().readPointer())
    }
    

    这里我用的是google原生系统 8.1
    直接去参考源码得到 artMethod 704行 结构体长这样:

    4       GcRoot<mirror::Class> declaring_class_;
    
    4       std::atomic<std::uint32_t> access_flags_;   
    
    4       uint32_t dex_code_item_offset_;
    
    4       uint32_t dex_method_index_;
    
    2       uint16_t method_index_;
    
    2       uint16_t hotness_count_;
    
    12      struct PtrSizedFields {
    4           ArtMethod** dex_cache_resolved_methods_;
    
    4           void* data_;
    
    4           void* entry_point_from_quick_compiled_code_;
            } ptr_sized_fields_;
    

    运行起来点击textview即可得到一下日志

    三个java方法的art结构体信息

    cpu三级流水线可知程序运行到0xf48bc018的时候
    pc应该是往后的两条指令,即为0xf48bc020


    ORG的entry_point_from_quick_compiled_code

    这里计算一下跳板地址跳到了哪里


    跳转位置

    很明显这个位置就是 hook 函数的 entry_point_from_quick_compiled_code_

    继续看一下 BackUp 函数的实现


    实现

    第一条指令
    ldr r0, [pc, #0xc]
    就是把 pc+0x2+0xc位置的值给到了r0,即等价于
    ldr r0,=0xf410307c

    第二条指令
    stmdb sp!, {r0}
    r0压栈

    第三条指令
    ldr r0, [pc]
    等价于
    ldr r0,=0xf36174d1 (这个地址就是他的默认入口)

    第四条指令
    ldm sp!, {pc}
    将sp出到pc,也就是配置r0的值为第一个org method指针,并恢复pc为默认的函数入口 0xf36174d1

    tips:

    1. 这里的 ldm stmdb 进行入栈出栈并不影响栈顶指针
    2. 这里的函数入口处对sp的读写没关系,不用关心覆盖问题

    总的来说:
    替换函数用到了跳板操作
    备份函数用到了跳板操作加上一个备份还原,用到了sp,在函数开时候用不用管覆盖问题,在中途用可以考虑使用超出栈顶指针的部分内存-

    相关文章

      网友评论

          本文标题:记录一下YAHFA相关

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