• android逆向奇技淫巧三十一:unidbg常见功能代码


      逆向so,unidbg这种模拟器必不可少,其优势:

    •   ida、frida遇到了严重的反调试
    •        生产环境生成sign字段(配合springboot尤其方便,有现成的框架可以直接拿来用了:https://github.com/anjia0532/unidbg-boot-server)
    •        可以打印JNIEnv成员函数的调用日志,比如registerNatives、GetStringUTFChars等;

      这里列举一些常见的unidbg功能供大伙逆向的时候参考;

      1、hook代码:这是逆向最基本的功能之一,frida的hook代码都不陌生吧?unidbg底层用了hookZz的框架,所以hook的代码长这样的:

    public void hook(){
            //unidbg集成了HookZz框架
            HookZz hook = HookZz.getInstance(emulator);
            //直接hook add函数的地址,比通过符号hook更具有“普适性”
            hook.replace(module.base + 0x3DC + 1, new ReplaceCallback() {
                @Override
                public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                    //R2和R3才是参数,R0是env,R1是object
                    System.out.println(String.format("R2: %d, R3: %d",context.getIntArg(2),context.getIntArg(3)));
                    //把第二个参数R3改成5
                    emulator.getBackend().reg_write(Unicorn.UC_ARM_REG_R3,5);
                    return super.onCall(emulator, context, originFunction);
                }
    
                @Override
                public void postCall(Emulator<?> emulator, HookContext context) {
                    emulator.getBackend().reg_write(Unicorn.UC_ARM_REG_R0,10);
                    //返回值放R0,这里直接修改返回值
                    super.postCall(emulator, context);
                }
            }, true);
        }

       代码整体的结构和frinda的hook是不是很类似了?onCall就是刚进入函数时候的回调(本质就是在函数入口处hook),onPost就是在函数ret前的hook回调!

       2、打patch方法:hook本质也是patch,还有很多关键的跳转代码(android下的B、BL等)可能也要NOP掉才能按照我们自己的逻辑执行!最原始打patch的办法就是在IDA或010editor更改,为了更好地逆向so,unidbg也提供了打patch的方法,如下:

      public void patch(){
            UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0x3E8);
            byte[] code = new byte[]{(byte) 0xd0, 0x1a};//直接用硬编码改原so的代码:subs r0,r2,r3
            pointer.write(code);
        }
        public void patch2(){
            UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0x3E8);
            Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb);
            String s = "subs r0, r2, r3";
            byte[] machineCode = keystone.assemble(s).getMachineCode();
            //byte[] code = ;
            pointer.write(machineCode);
        }

          代码很简单,可以直接在目标位置写硬编码,也可以借助keystone写汇编代码!

       3、hook的时候需要知道so的基址和代码偏移,unidbg提供的方法如下:

    // 加载so到虚拟内存
    DalvikModule dm = vm.loadLibrary("libnative-lib.so", true);
    // 得到模块对象,然后根据导出的函数名找到函数入口偏移,比直接在代码写死地址灵活一些
    module = dm.getModule();
    int address = (int) module.findSymbolByName("funcNmae").getAddress();

       4、有一点可能会超出初学入门者的想象和预期,就是unidbg也支持单步调试,叫console debug,就是在console下输入各种命令调试!操作也简单:

      (1)先下个断点:当然这里也能制定特定的偏移地址

    emulator.attach().addBreakPoint(module.findSymbolByName("funName").getAddress());

      (2)代码运行到断点后正常情况下会停下,然后逆向人员就可以在console下输入各种命令操作了,原理和hyperpwn、gbd等类似,如下:   

             

       比如r是删除断点,b是增加断点,n是步过等!其他写方面的操作命令如下:

    wr0-wr7, wfp, wip, wsp <value>: write specified register
    wb(address), ws(address), wi(address) <value>: write (byte, short, integer) memory of specified address, address must start with 0x
    wx(address) <hex>: write bytes to memory at specified address, address must start with 0x

      如果命中断点后想做一个个性化的操作,但是又觉得在console上挨个敲命令麻烦,也可以写代码固化下来,比如这样:

    public void ReplaceArgByConsoleDebugger(){
        emulator.attach().addBreakPoint(module.findSymbolByName("funName").getAddress(), new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                RegisterContext context = emulator.getContext();
                String fakeInput = "hello world";
                int length = fakeInput.length();
                // 修改r1值为新长度
                emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R1, length);
                MemoryBlock fakeInputBlock = emulator.getMemory().malloc(length, true);
                fakeInputBlock.getPointer().write(fakeInput.getBytes(StandardCharsets.UTF_8));
                // 修改r0为指向新字符串的新指针
                emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, fakeInputBlock.getPointer().peer);
    
                Pointer buffer = context.getPointerArg(2);
                // OnLeave
                emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
                    @Override
                    public boolean onHit(Emulator<?> emulator, long address) {
                        String result = buffer.getString(0);
                        System.out.println("base64 result:"+result);
                        return true;
                    }
                });
                return true;
            }
        });
    }

        个人觉得和hook某个地址本质上是一样的,这种方式供参考!

       5、内存检索:搜索某些sign字段、字符串的时候特别重要,如下: 

    private Collection<Pointer> searchMemory(long start, long end, byte[] data) {
        List<Pointer> pointers = new ArrayList<>();
        for (long i = start, m = end - data.length; i < m; i++) {
            byte[] oneByte = emulator.getBackend().mem_read(i, 1);
            if (data[0] != oneByte[0]) {
                continue;
            }
            if (Arrays.equals(data, emulator.getBackend().mem_read(i, data.length))) {
                pointers.add(UnidbgPointer.pointer(emulator, i));
                i += (data.length - 1);
            }
        }
        return pointers;
    }

      6、条件断点:为了避免被过多信息干扰,很多时候的断点或hook是需要设置条件的,符合了条件才需要进一步打印出来查看结果,unidbg也不例外,也是这个思路。举个例子:比如strcat、strstr、strcmp这种函数,每时每刻都在被大量的模块调用,直接hook打印会产生大量无用日志,严重影响排查。同时大量日志得打印也会严重拖慢运行速度,所以需要自己写条件判断是否需要打印日志!比如这种:

      (1)只打印某个特定so调用的strcat函数:

    public void hookstrcmp(){
        long address = module.findSymbolByName("strcat").getAddress();
        emulator.attach().addBreakPoint(address, new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                RegisterContext registerContext = emulator.getContext();
                String arg1 = registerContext.getPointerArg(0).getString(0);
                String moduleName = emulator.getMemory().findModuleByAddress(registerContext.getLRPointer().peer).name;
                if(moduleName.equals("libxxx.so")){
                    System.out.println("strcat arg1:"+arg1);
                }
                return true;
            }
        });
    }

      (2)只打印某个特定函数中调用的strcat函数:

    // 早先声明全局变量 public boolean show = false;
    
    public void hookstrcat(){
        emulator.attach().addBreakPoint(module.findSymbolByName("targetfunName").getAddress(), new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                RegisterContext registerContext = emulator.getContext();
    
                show = true;//进入目标函数就把show设置为true,下面才好打印日志
                emulator.attach().addBreakPoint(registerContext.getLRPointer().peer, new BreakPointCallback() {
                    @Override
                    public boolean onHit(Emulator<?> emulator, long address) {
                        show = false;//离开目标函数就把show设置为false,下面才知道不打印日志
                        return true;
                    }
                });
                return true;
            }
        });
    
        emulator.attach().addBreakPoint(module.findSymbolByName("strcat").getAddress(), new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                RegisterContext registerContext = emulator.getContext();
                String arg1 = registerContext.getPointerArg(0).getString(0);
                if(show){
                    System.out.println("strcmp arg1:"+arg1);
                }
                return true;
            }
        });
    }

    总结:

    1、个人感受,逆向调试时还是IDA的图形化界面更方便,所以不到万不得已,我一般首选IDA调试分析!

    2、一旦后期要在生产线上生成sign字段,这时再用unidbg就更合适了!

    参考:

    1、unidbg常见方法和frida对照: https://reao.io/archives/90/

    2、frida长用的脚本代码:https://codeshare.frida.re/

                                             https://github.com/iddoeldor/frida-snippets  

  • 相关阅读:
    用tar命令把目标压缩包解压到指定位置
    testing and Deployment
    项目第二阶段进展
    注解使用中 @RequestMapping 和 @GetMapping @PostMapping 区别
    导入项目之最多的问题
    0 for前端之数据交互
    Required String parameter 'xxxxx' is not present] 报错400
    CDI Features
    初始化数据库问题
    mysql的时区问题
  • 原文地址:https://www.cnblogs.com/theseventhson/p/16418593.html
Copyright © 2020-2023  润新知