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

讲解例子


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  打印出来的日志内容从哪里可以看到其实现?

 参考类:com.github.unidbg.AssemblyCodeDumper

参考方法:hook

调用位置:com.github.unidbg.AbstractEmulator类里的traceCode方法

public TraceHook traceCode(long begin, long end, TraceCodeListener listener) {
    AssemblyCodeDumper hook = new AssemblyCodeDumper(this);
    hook.initialize(begin, end, listener);
    backend.hook_add_new(hook, begin, end, this);
    return hook;
}

建议:如果想调整打印信息的话可以研究下AssemblyCodeDumper类里的hook方法.。下面针对hook方法进一步研究。

AssemblyCodeDumper类的hook方法
@Override
    public void hook(final Backend backend, long address, int size, Object user) {
        if (canTrace(address)) {
            try {
                PrintStream out = System.out;
                if (redirect != null) {
                    out = redirect;
                }
                /**
                 * 把这些文字指令翻译成二进制,这个步骤就称为 assembling,完成这个步骤的程序就叫做 assembler。它处理的文本,自然就叫做 aseembly code。
                 * 标准化以后,称为 assembly language,缩写为 asm,中文译为汇编语言
                 *
                 * printAssemble调用的是AbstractARMEmulator类里面printAssemble方法,这里面并有判断是否是thumb指令的逻辑。
                 */
                Instruction[] insns = emulator.printAssemble(out, address, size, new InstructionVisitor() {
                    @Override
                    public void visit(StringBuilder builder, Instruction ins) {
                        RegsAccess regsAccess = ins.regsAccess();
                        if (regsAccess == null) {
                            return;
                        }
                        short[] regsRead = regsAccess.getRegsRead();
                        for (short reg : regsRead) {
                            if (emulator.is32Bit()) {
                                if ((reg >= Arm_const.ARM_REG_R0 && reg <= Arm_const.ARM_REG_R12) ||
                                        reg == Arm_const.ARM_REG_LR || reg == Arm_const.ARM_REG_SP) {
                                    int value = backend.reg_read(reg).intValue();
                                    builder.append(' ').append(ins.regName(reg)).append("=0x").append(Long.toHexString(value & 0xffffffffL));
                                }
                            } else {
                                if ((reg >= Arm64_const.ARM64_REG_X0 && reg <= Arm64_const.ARM64_REG_X28) ||
                                        (reg >= Arm64_const.ARM64_REG_X29 && reg <= Arm64_const.ARM64_REG_SP)) {
                                    long value = backend.reg_read(reg).longValue();
                                    builder.append(' ').append(ins.regName(reg)).append("=0x").append(Long.toHexString(value));
                                } else if (reg >= Arm64_const.ARM64_REG_W0 && reg <= Arm64_const.ARM64_REG_W30) {
                                    int value = backend.reg_read(reg).intValue();
                                    builder.append(' ').append(ins.regName(reg)).append("=0x").append(Long.toHexString(value & 0xffffffffL));
                                }
                            }
                        }
                    }
                });
                if (listener != null) {
                    if (insns == null || insns.length != 1) {
                        throw new IllegalStateException("insns=" + Arrays.toString(insns));
                    }
                    listener.onInstruction(emulator, address, insns[0]);
                }
            } catch (BackendException e) {
                throw new IllegalStateException(e);
            }
        }
    }
printAssemble调用的是AbstractARMEmulator类里面printAssemble方法,这里面并有判断是否是thumb指令的逻辑。重点逻辑有下面几个。
ARM类的

方法1: static String assembleDetail(Emulator<?> emulator, Instruction ins, long address, boolean thumb)

方法2:public static String assembleDetail(Emulator<?> emulator, Instruction ins, long address, boolean thumb, boolean current) 这个是重中之中。我对这部分的解读也不太深刻。这里做出标识,希望有了解的朋友在评论区给出解释。

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

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  小结

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

遗留问题:


1:如何判断是thumb,  

ARM.isThumb(backend)这方法我还不知道原理呢,其指教。

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

总结:


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

感谢:


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

         感谢 尼古拉斯.张三

         感谢 @风吟、

         感谢  r0ysue

猜你喜欢

转载自blog.csdn.net/xubaoyong/article/details/121852339