当前位置: 首页 > 工具软件 > unidbg > 使用案例 >

unidbg第一讲例子讲解com.github.unidbg.android.CrackMe

洪开诚
2023-12-01

讲解例子


com.github.unidbg.android.CrackMe

需求


1:trace code

2:打印函数调用链

实现步骤:


2.1:实现目标trace code的具体步骤如下

2.1.1:需要将DynarmicFactory替换成Unicorn2Factory,否则会报异常java.lang.UnsupportedOperationException。修改后的代码如下:

 public CrackMeTrace() {
        executable = new File("unidbg-android/src/test/resources/example_binaries/crackme1");
        emulator = AndroidEmulatorBuilder.for32Bit()
                .setProcessName(executable.getName())
                .setRootDir(new File("target/rootfs"))
//                .addBackendFactory(new DynarmicFactory(true))
                .addBackendFactory(new Unicorn2Factory(true))
                .build(); 
        Memory memory = emulator.getMemory();
        LibraryResolver resolver = new AndroidResolver(19);
        memory.setLibraryResolver(resolver);// 设置系统类库解析

        module = emulator.loadLibrary(executable);
    }

2.1.2: 打开原来的代码行

emulator.traceCode(module.base, module.base + module.size);

2.1.3: 进一步优化,把日志持久化到文件中

String traceFile = "unidbg-android/src/test/java/com/github/unidbg/android/CrackMeTracetraceCode.txt";
        PrintStream traceStream = null;
        try {
            traceStream = new PrintStream(new FileOutputStream(traceFile), true);
            emulator.traceCode(module.base, module.base+module.size).setRedirect(traceStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

2.1.4 输出的日志如下:

### Trace Instruction [    crackme1] [0x007a0] [ 5c c0 9f e5 ] 0x400007a0: ldr ip, [pc, #0x5c]
### Trace Instruction [    crackme1] [0x007a4] [ 5c 20 9f e5 ] 0x400007a4: ldr r2, [pc, #0x5c]
### Trace Instruction [    crackme1] [0x007a8] [ 00 48 2d e9 ] 0x400007a8: push {fp, lr}
### Trace Instruction [    crackme1] [0x007ac] [ 0c c0 8f e0 ] 0x400007ac: add ip, pc, ip
### Trace Instruction [    crackme1] [0x007b0] [ 04 b0 8d e2 ] 0x400007b0: add fp, sp, #4
### Trace Instruction [    crackme1] [0x007b4] [ 50 30 9f e5 ] 0x400007b4: ldr r3, [pc, #0x50]
### Trace Instruction [    crackme1] [0x007b8] [ 10 d0 4d e2 ] 0x400007b8: sub sp, sp, #0x10

2.1.5 日志格式进行讲解:

### Trace Instruction [    模块名] [相对地址] [ ins.bytes的16进制(关注点1) ] (指令地址:助记符(ldr) opstr( ip, [pc, #0x5c]这个是什么东东))

其中:关注点1不太理解,它的实现方式如下:

for (byte b : ins.bytes) {
    sb.append(' ');
    String hex = Integer.toHexString(b & 0xff);
    if (hex.length() == 1) {
        sb.append(0);
    }
    sb.append(hex);
}

助记符如:mnemonic如ldr,push,add

opstr:这是什么东东,不太懂。它的代码实现是ins.opStr。输出内容是:ip, [pc, #0x5c]

2.1.6 :如果想调整打印信息的话可以研究下AbstractARMEmulator类的

private void printAssemble(PrintStream out, Capstone.CsInsn[] insns, long address, boolean thumb)方法。

调用流程如下:

14 com.github.unidbg.arm.ARM.assembleDetail(ARM.java:1032) 在这里拼凑指令详情
13 com.github.unidbg.arm.ARM.assembleDetail(ARM.java:901)
12 com.github.unidbg.arm.AbstractARMEmulator.printAssemble(AbstractARMEmulator.java:210)
11 com.github.unidbg.arm.AbstractARMEmulator.printAssemble(AbstractARMEmulator.java:190)
10 com.github.unidbg.AssemblyCodeDumper.hook(AssemblyCodeDumper.java:84)//如果listener的话在这里进行调用
9 com.github.unidbg.arm.backend.UnicornBackend$1.hook(UnicornBackend.java:211)
8 unicorn.Unicorn$NewHook.onCode(Unicorn.java:96)
7 unicorn.Unicorn.emu_start(Native Method)
6 com.github.unidbg.arm.backend.UnicornBackend.emu_start(UnicornBackend.java:354)
5 com.github.unidbg.AbstractEmulator.emulate(AbstractEmulator.java:370)
4 com.github.unidbg.arm.AbstractARMEmulator.eEntry(AbstractARMEmulator.java:249)
3 com.github.unidbg.linux.LinuxModule.callEntry(LinuxModule.java:248)
2 com.github.unidbg.android.CrackMe.crack(CrackMe.java:103)
1 com.github.unidbg.android.CrackMe.main(CrackMe.java:26)

printAssemble调用的是AbstractARMEmulator类里面printAssemble方法.这里面并有判断是否是thumb指令的逻辑。

     下面代码需要求助,有理解的请指教

ARM类的里的方法:
    public static String assembleDetail(Emulator<?> emulator, Instruction ins, long address, boolean thumb, boolean current) {
        Memory memory = emulator.getMemory();
        char space = current ? '*' : ' ';
        StringBuilder sb = new StringBuilder();

        Module module = memory.findModuleByAddress(address);
//表示点1:这是什么?
        String maxLengthSoName = memory.getMaxLengthLibraryName();

        if (module != null) {
            sb.append('[');
            appendHex(sb, module.name, maxLengthSoName.length(), ' ', true);
            sb.append(space);
//表示点2:这这个地方调用的方法appendHex里的第二个参数和第三个参数是什么意思?
            appendHex(sb, address - module.base + (thumb ? 1 : 0), Long.toHexString(memory.getMaxSizeOfLibrary()).length(), '0', false);
            sb.append(']').append(space);
//表示点3:这里的地址为什么是0xfffe0000L ,并且条件为什么且要求maxLengthSoName != null
        } else if (address >= 0xfffe0000L && maxLengthSoName != null) { // kernel
            sb.append('[');
//表示点4:这这个地方调用的方法appendHex里的第二个参数和第三个参数是什么意思?
            appendHex(sb, "0x" + Long.toHexString(address), maxLengthSoName.length(), ' ', true);
            sb.append(space);
//表示点5:这这个地方调用的方法appendHex里的第二个参数和第三个参数是什么意思?
            appendHex(sb, address - 0xfffe0000L + (thumb ? 1 : 0), Long.toHexString(memory.getMaxSizeOfLibrary()).length(), '0', false);
            sb.append(']').append(space);
        }
        sb.append("[");
//表示点6:这这个地方调用的方法appendHex里的第二个参数和第三个参数是什么意思?
        appendHex(sb, Hex.encodeHexString(ins.getBytes()), 8, ' ', true);
        sb.append("]");
        sb.append(space);

        appendHex(sb, ins.getAddress(), 8, '0', false);
        sb.append(":").append(space);
//标识点7:这里打印的是指令
        sb.append('"').append(ins).append('"');

        capstone.api.arm.OpInfo opInfo = null;
        capstone.api.arm64.OpInfo opInfo64 = null;

//标识点8:获取opInfo,关键opInfo是什么东东。
        if (ins.getOperands() instanceof capstone.api.arm.OpInfo) {
            opInfo = (capstone.api.arm.OpInfo) ins.getOperands();
        }
        if (ins.getOperands() instanceof capstone.api.arm64.OpInfo) {
            opInfo64 = (capstone.api.arm64.OpInfo) ins.getOperands();
        }
//标识点9:分32位或64位打印内存详情。为甚要针对ldr 和 str才进行操作?
        if (current && (ins.getMnemonic().startsWith("ldr") || ins.getMnemonic().startsWith("str")) && opInfo != null) {
    //标识点10:在下个代码框也需要大家指点。
            appendMemoryDetails32(emulator, ins, opInfo, thumb, sb);
        }
        if (current && (ins.getMnemonic().startsWith("ldr") || ins.getMnemonic().startsWith("str")) && opInfo64 != null) {
            appendMemoryDetails64(emulator, ins, opInfo64, sb);
        }

        return sb.toString();
    }
private static void appendMemoryDetails32(Emulator<?> emulator, Instruction ins, capstone.api.arm.OpInfo opInfo, boolean thumb, StringBuilder sb) {
        Memory memory = emulator.getMemory();
        MemType mem = null;
        long addr = -1;
        Operand[] op = opInfo.getOperands();

        // ldr rx, [pc, #0xab] or ldr.w rx, [pc, #0xcd] based capstone.setDetail(Capstone.CS_OPT_ON);
        if (op.length == 2 &&
                op[0].getType() == Arm_const.ARM_OP_REG &&
                op[1].getType() == Arm_const.ARM_OP_MEM) {
            mem = op[1].getValue().getMem();

            if (mem.getIndex() == 0 && mem.getScale() == 1 && mem.getLshift() == 0) {
                UnidbgPointer base = UnidbgPointer.register(emulator, mem.getBase());
                long base_value = base == null ? 0L : base.peer;
                addr = base_value + mem.getDisp();
            }

            // ldr.w r0, [r2, r0, lsl #2]
            OpShift shift;
            if (mem.getIndex() > 0 && mem.getScale() == 1 && mem.getLshift() == 0 && mem.getDisp() == 0 &&
                    (shift = op[1].getShift()) != null) {
                UnidbgPointer base = UnidbgPointer.register(emulator, mem.getBase());
                long base_value = base == null ? 0L : base.peer;
                UnidbgPointer index = UnidbgPointer.register(emulator, mem.getIndex());
                int index_value = index == null ? 0 : (int) index.peer;
                if (shift.getType() == Arm_const.ARM_OP_IMM) {
                    addr = base_value + ((long) index_value << shift.getValue());
                } else if (shift.getType() == Arm_const.ARM_OP_INVALID) {
                    addr = base_value + index_value;
                }
            }
        }

        // ldrb r0, [r1], #1
        if (op.length == 3 &&
                op[0].getType() == Arm_const.ARM_OP_REG &&
                op[1].getType() == Arm_const.ARM_OP_MEM &&
                op[2].getType() == Arm_const.ARM_OP_IMM) {
            mem = op[1].getValue().getMem();
            if (mem.getIndex() == 0 && mem.getScale() == 1 && mem.getLshift() == 0) {
                UnidbgPointer base = UnidbgPointer.register(emulator, mem.getBase());
                addr = base == null ? 0L : base.peer;
            }
        }
        if (addr != -1) {
            if (mem.getBase() == Arm_const.ARM_REG_PC) {
                addr += (thumb ? 4 : 8);
            }
            int bytesRead = 4;
            if (ins.getMnemonic().startsWith("ldrb") || ins.getMnemonic().startsWith("strb")) {
                bytesRead = 1;
            }
            if (ins.getMnemonic().startsWith("ldrh") || ins.getMnemonic().startsWith("strh")) {
                bytesRead = 2;
            }
            appendAddrValue(sb, addr, memory, emulator.is64Bit(), bytesRead);
            return;
        }

        // ldrd r2, r1, [r5, #4]
        if ("ldrd".equals(ins.getMnemonic()) && op.length == 3 &&
                op[0].getType() == Arm_const.ARM_OP_REG &&
                op[1].getType() == Arm_const.ARM_OP_REG &&
                op[2].getType() == Arm_const.ARM_OP_MEM) {
            mem = op[2].getValue().getMem();
            if (mem.getIndex() == 0 && mem.getScale() == 1 && mem.getLshift() == 0) {
                UnidbgPointer base = UnidbgPointer.register(emulator, mem.getBase());
                long base_value = base == null ? 0L : base.peer;
                addr = base_value + mem.getDisp();
                if (mem.getBase() == Arm_const.ARM_REG_PC) {
                    addr += (thumb ? 4 : 8);
                }
                appendAddrValue(sb, addr, memory, emulator.is64Bit(), 4);
                appendAddrValue(sb, addr + emulator.getPointerSize(), memory, emulator.is64Bit(), 4);
            }
        }
    }

2.2 打印函数调用链

2.2.1 参考 [龙哥投稿] Unidbg Hook 大全 - REAO里的Function Tracing

2.2.2  针对pom.xml里capstone的版本为3.1.2时实现如下:

private void traceFn(){
//        这个代码是没法trace 导入函数的
        PrintStream traceStream = null;
        try {
            // 保存文件
            String traceFile = "unidbg-android/src/test/java/com/github/unidbg/android/CrackMeTracetraceFunctions.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) {
                if(size>8){
                    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("    |    ");
                        }
                        finalTraceStream.println("  "+"sub_"+Integer.toHexString((int) (address-module.base))+"  ");
                    }
                } 
            } 
            @Override
            public void onAttach(UnHook unHook) {

            }

            @Override
            public void detach() {

            }
        }, module.base, module.base+module.size, 0);
    }

2.2.3  输出结果为:

    |        |        |      sub_25a8  

2.2.4 打印unidbg调用流程

14 com.github.unidbg.arm.ARM.assembleDetail(ARM.java:1032) 在这里拼凑指令详情
13 com.github.unidbg.arm.ARM.assembleDetail(ARM.java:901)
12 com.github.unidbg.arm.AbstractARMEmulator.printAssemble(AbstractARMEmulator.java:210)
11 com.github.unidbg.arm.AbstractARMEmulator.printAssemble(AbstractARMEmulator.java:190)
10 com.github.unidbg.AssemblyCodeDumper.hook(AssemblyCodeDumper.java:84)//如果listener的话在这里进行调用
9 com.github.unidbg.arm.backend.UnicornBackend$1.hook(UnicornBackend.java:211)
8 unicorn.Unicorn$NewHook.onCode(Unicorn.java:96)
7 unicorn.Unicorn.emu_start(Native Method)
6 com.github.unidbg.arm.backend.UnicornBackend.emu_start(UnicornBackend.java:354)
5 com.github.unidbg.AbstractEmulator.emulate(AbstractEmulator.java:370)
4 com.github.unidbg.arm.AbstractARMEmulator.eEntry(AbstractARMEmulator.java:249)
3 com.github.unidbg.linux.LinuxModule.callEntry(LinuxModule.java:248)
2 com.github.unidbg.android.CrackMe.crack(CrackMe.java:103)
1 com.github.unidbg.android.CrackMe.main(CrackMe.java:26)
 

2.2.5  小结

        经龙哥指导,暂时是没法trace 导入函数的。期望后续可以再优化。

遗留问题:


1:如何判断是thumb,  

ARM.isThumb(backend)。

  参考网址: ARM指令集与Thumb指令集--区别关联

我读了下上面的文章,对arm指令和thumb指令的区别和联系了解了下。对其中什么时候是thumb的理解为:CPSR的T=0:arm,T=1:thumb。

其中CPSR和SPSR是什么,我这参考的文章是详细解读ARM寄存器之CPSR,这里解释

程序状态寄存器CPSR,程序状态保存寄存器SPSR

2:Instruction 类也需要给出指导。

        2.1 Mnemonic  :  助记符,用来代替机器指令的操作码

        2.2  address:

        2.3 size:

        2.4 opStr: 

         2.5 regName

         2.6 operands

         2.7 regsAccess

下一步要研究的问题:

        1: arm 反汇编,通过学些汇编我们可以尝试着去阅读汇编指令。我阅读的是下列网址ARM 反汇编基础。补充说明下,

                                1.1    AArch64是一种汇编指令集,可以通过百度了解;

                                 1.2  armeabi 是CPU架构。在这个博客中中有说明。

        2:

                 汇编指令,建议参考汇编语言(第3版) 王爽著

                链接:https://pan.baidu.com/s/1oEMR6iC9vSV0keL-HgzYcw 
                提取码:jp3w

        3: IDA静态分析

                3.1  快捷键

                                3.1.1   g:跳转到指定地址

                                3.1.2   冒号(:) 可以写注释

4  CyberChef简介

   CyberChef是英国情报机构政府通信总部(GCHQ)发布了一款新型的开源Web工具,为安全从业人员分析和解密数据提供了方便。

GitHub链接:

https://github.com/gchq/CyberChef

CyberChef是一个Web工具,直接访问:https://gchq.github.io/CyberChef/ 即可使用。右上角About/Support可以查看一些常见问题和解答

       

总结:


 学习如逆水行舟,不进则退。

感谢:


         感谢龙哥小组的支持。https://reao.io/archives/90/

         感谢 尼古拉斯.张三

         感谢 @风吟、

         感谢  r0ysue

 类似资料: