使用qemu-user仿真到反向工程二进制文件

2021-05-05 20:59:21

QEMU主要被称为在Linux的KVM下提供完整的系统仿真的软件。此外,它可以在没有KVM的情况下使用,可以从硬件级别完全仿真机器。最后,有qemu-user,它允许仿真各个程序。这就是这个博客的帖子。

Qemu-user的主要用例实际上不是逆向工程,而是仅在另一个CPU架构上运行程序。例如,Alpine开发人员利用QEMu-user,当他们使用dabuild(1)来交叉编译其他架构的Alpine软件包:qemu-user用于运行配置脚本,测试套件等。对于那些目的而言,QEMU-User非常好:我们甚至考虑使用它来建立3.15发布中的整个RISCV64架构。

但是,大多数人都没有意识到您可以运行一个qemu-user仿真器,该模拟器将与主机相同的架构。毕竟,这将是一个有点奇怪的,对吗?最多也不知道您可以使用GDB控制模拟器,这是可能的,并且允许您调试检测的二进制文件,如果正在调试。

但是,您不需要GDB,这是一个强大的逆向工程工具。仿真器本身包括许多强大的跟踪功能。让我们通过写作和编译示例程序来查看它们,通过计算数字是偶数还是奇数的方法来进行一些递归:

#include< stdbool.h> #include< stdio.h> BOOL ISODD(int x); BOOL ISEVEN(int x); BOOL ISODD(int x){ 返回x!= 0&& Iseven(x - 1); } BOOL ISEVEN(int x){ 返回x == 0 || ISODD(X - 1); } int main(void){ printf(" iseven(%d):%d \ n",1025,Iseven(1025)); 返回0; }

下一步是为您的体系结构安装QEMU用户仿真器,在这种情况下我们想要Qemu-x86_64包:

通常,您还需要安装QEMU-OPENRC包并启动QEMU-BINFMT服务,以允许模拟器处理任何无法自然运行的程序,但这在这里无关紧要直接仿真器。

我们将要做的第一件事是检查以确保仿真器可以运行我们的示例程序:

好吧,一切似乎都很好。在我们使用模拟器使用GDB之前,请通过跟踪功能播放稍微播放。通常在逆向工程一个程序时,通常使用像符号这样的跟踪程序。这些追踪程序非常有用,但它们遭受了设计漏洞:他们使用Ptrace(2)来完成跟踪,可以通过追踪的程序来检测。但是,我们可以使用qemu-user以对正在分析的程序透明的方式进行跟踪:

$ qemu-x86_64 -d strace ./表达 22525 arch_prctl(4098,274903714632,136818691500777464,274903714112,274903132960,465)= 0 22525 SET_TID_ADDRESS(274903715728,274903714632,136818691500777490314632,1368186915007774464,4632,1368186903777490314632,1368186915007774464,4632,136818691500777414,274903774112,0,4632,1112,0,465)= 22525 22525 BRK(NULL)= 0x0000004000005000 22525 BRK(0x0000004000007000)= 0x0000004000007000 22525 MMAP(0x0000004000005000,4096,PROT_NONE,MAP_PRIVATE | MAP_ANONOMOUSS | MAP_FIXED,-1,0)= 0x0000004000005000 22525 MPROTECT(0x0000004001899000,4096,prot_read)= 0 22525 MPROTECT(0x0000004000003000,4096,prot_read)= 0 22525 IOCTL(1,Tiocgwinsz,0x00000040018052B8)= 0({55,236,0,0}) ISEVEN(1025):0 22525 WRITEV(1,0x4001805250,0x2)= 16 22525 Exit_Group(0)

但我们可以做更多。例如,我们可以了解CPU如何将程序破坏到完整的Micro-Ops的翻译缓冲区:

$ qemu-x86_64 -d op ./表达 op: ld_i32 tmp11,env,$ 0xfffffffffffffffff0 brcond_i32 tmp11,$ 0x0,lt,$ l0 ---- 000000400185AFB 000000000000000000 丢弃CC_DST. 丢弃CC_SRC. 丢弃CC_SRC2. 丢弃CC_OP. mov_i64 tmp0,$ 0x0 MOV_I64 RBP,TMP0 ---- 000000400185叶0000000000000031. mov_i64 tmp0,rsp MOV_I64 RDI,TMP0 - 000000400185CYB01 0000000000000031. mov_i64 tmp2,$ 0x4001899dc0 MOV_I64 RSI,TMP2 - 000000400185CYB08 000000000000000031. mov_i64 tmp1,$ 0xfffffffffffffffff0 mov_i64 tmp0,rsp AND_I64 TMP0,TMP0,TMP1 mov_i64 rsp,tmp0 mov_i64 cc_dst,tmp0 ---- 000000400185EB0C 0000000000000019. MOV_I64 TMP0,$ 0x400185EB11 Sub_i64 TMP2,RSP,$ 0x8 qemu_st_i64 tmp0,tmp2,leq,0 MOV_I64 RSP,TMP2 mov_i32 cc_op,$ 0x19 goto_tb $ 0x0. MOV_I64 TMP3,$ 0x400185EB11 ST_I64 TMP3,ENV,$ 0x80 EXIT_TB $ 0x7F72EBAFC040 set_label $ l0. EXIT_TB $ 0x7f72ebafc043 [...]

如果要对每个执行的指令追踪实际的CPU寄存器,也是可能的:

$ qemu-x86_64 -d cpu ./表达 RAX = 000000000000000000 RBX = 000000000000000000 RBX = 000000000000000000 RSI = 000000000000000000 rbp = 0000000000000000000000000000000000000000000000000000000000 rsp = 0000004001805690 R8 = 000000000000000000 R11 = 000000000000000000 r11 = 000000000000000000 R12 = 000000000000000000 R14 = 000000000000000000 R14 = 000000000000000000 RIP = 000000400185AFB RFL = 00000202 [-------] CPL = 3 II = 0 A20 = 1 SMM = 0 HLT = 0 ES = 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 CS = 0033 000000000000000000 FFFFFFFF 00EFFB00 DPL = 3 CS64 [-RA] SS = 002B 000000000000000000000000000000000000000000000000000000000000 DPL = 3 DS [-wa] DS = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 FS = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 GS = 000000000000000000000000000000000000 LDT = 00000000000000000000000000000000000000000000000000000000 0000000000000000 00008200 DPL = 0 LDT TR = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00008B00 DPL = 0 TSS64忙 GDT = 000000400189F000 0000007F IDT = 000000400189E000 000001FF CR0 = 80010001 CR2 = 000000000000000000CR3 = 000000000000000000 CR4 = 00000220 DR0 = 0000000000000000 DR1 = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 DR3 = 000000000000000000 DR6 = 00000000FFFF0FF0 DR7 = 0000000000000400 CCS = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 CCO = EFLAGS EFER = 0000000000000500. [...]

$ qemu-x86_64 -d in_asm ./表达 ----------------- 在: 0x000000400185AFB:XOR%RBP,%RBP 0x000000400185 efe:mov%rsp,%rdi 0x000000400185EB01:LEA 0x3B2B8(%RIP),%RSI#0x4001899DC0 0x000000400185eb08:$ 0xfffffffffffffff0,%rsp 0x000000400185EB0C:CallQ 0x400185EB11 ----------------- 在: 0x000000400185EB11:Sub $ 0x190,%RSP 0x000000400185EB18:MOV(%RDI),%EAX 0x000000400185EB1A:MOV%RDI,%R8 0x000000400185EB1D:INC%EAX 0x000000400185EB1F:CLTQ. 0x000000400185EB21:MOV 0x8(%r8,%rax,8),%rcx 0x000000400185EB26:MOV%rax,%rdx 0x000000400185EB29:Inc%Rax 0x000000400185EB2C:TEST%RCX,%RCX 0x000000400185EB2F:JNE 0x400185EB21 [...]

所有这些选项以及更多,也可以堆叠。有关更多想法,请查看qemu-x86_64 -d帮助。现在,允许使用GDB使用QEMU-User的GDBServer功能讨论此,它允许GDB控制远程计算机。

要在GDBSERVER模式下启动程序,我们将使用端口号的-g参数使用。例如,qemu-x86_64-g 1234 ./表达将使用端口1234上的gdbserver启动我们的示例程序。然后,我们可以将该GDBerver连接到GDB:

$ gdb ./表达 [...] 阅读符号./表达...... (GDB)目标远程localhost:1234 使用localhost远程调试:1234 0x000000400185处理? () (GDB)BR ISEVEN 断点1在0x4000001233:文件example.c,第12行。 (GDB)C 继续。 断点1,ISEVEN(x = 1025)在example.c:12 12返回x == 0 || ISODD(X - 1); (gdb)bt full #0 ISeven(x = 1025)在example.c:12 没有当地人。 #1 0x0000004000001269在main()在example.c:16 没有当地人。

所有这些都在没有任何知识或合作的情况下发生。至于其关注,它作为正常运行,没有Ptrace或任何其他怪异。

但是,这不是100%完美:一个程序可能是聪明的,运行CPUID指令并检查正版或验证,如果它没有看到它在合法的CPU上运行,则崩溃。值得庆幸的是,Qemu-user有能力使用-cpu选项来欺骗CPU。

如果您发现自己需要欺骗CPU,那么您可能会使用简单的CPU类型,如-CPU Opteron_g1-V1或类似的结果。 CPU类型欺骗Opteron 240处理器,这是市场上第一个X86_64 CPU之一。您可以通过执行qemu-x86_64 -cpu帮助,获得QEMU-User Emulator副本支持的CPU列表。

有很多qemu - 用户仿真可以做到帮助逆向工程,对于一些想法,看看qemu-x86_64-h或类似。