unidbg 例子讲解com.github.unidbg.android.CrackMe
Posted 阳光下的小鱼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
以上是关于unidbg 例子讲解com.github.unidbg.android.CrackMe的主要内容,如果未能解决你的问题,请参考以下文章