com.github.unidbg.android.CrackMe
1:trace code
2:打印函数调用链
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.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 冒号(:) 可以写注释
CyberChef是英国情报机构政府通信总部(GCHQ)发布了一款新型的开源Web工具,为安全从业人员分析和解密数据提供了方便。
GitHub链接:
https://github.com/gchq/CyberChef
CyberChef是一个Web工具,直接访问:https://gchq.github.io/CyberChef/ 即可使用。右上角About/Support可以查看一些常见问题和解答
学习如逆水行舟,不进则退。
感谢龙哥小组的支持。https://reao.io/archives/90/
感谢 尼古拉斯.张三
感谢 @风吟、
感谢 r0ysue