byteCTF2019-ezarch

我意外之中做了这道题,事后才知道它来自哪里 (膜

周末。panda熊猫让扔过来一道,他说是pwn中分数最高的,由于时间太短,没能做出来,太遗憾。这里把当时思路记录一下。

从题目名字来看ezarch,arch架构 ?3个功能,设置内存,下断点,运行。后知后觉原来是个虚拟机。当时主要是先入为主了,一段段的去看代码。主要还是太急了,根据我之前分析php内核的虚拟机的结构,这个地方其实是有套路的,分析虚拟机最主要的两个目标是:

  • 虚拟机内部用来描述整个执行过程的指令集。
  • 单个指令对应的解释过程。

先看设置内存的过程。仔细分析定义内存的结构应该是下面这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct vm{
char * mem; //malloc分配的内存区
char * stack; // vm里面栈空间,指向栈顶
uint stack_size; // 栈空间的大小
uint mem_size; //内存区的大小
uint *[256]; //breakpoint 断点区
uint r0; // R0-R15
uint r1;
...
uint r15;
uint eip;
uint esp
uint ebp;
uint16 eflags;
}

这个结构存储在bss上,这里有一个结构虚拟机栈的空间是和这个结构紧靠着的。然后设置eip和esp,esp相关的寄存器。这里的寄存器都是相应的偏移值。这就是整个虚拟机内存初始化过程。

还有一个下断点的功能,就是把需要断住的eip储存在上面的bp空间里面。

最后来看虚拟机的执行器。就是这个地方浪费了我不少时间,一个switch,早就应该想到这个switch对应的就是处理过程的选择。

执行器会以步长10字节为一个opline来处理,opline的结构还是和通常的opline结构一样,opcode和操作数,操作数一般为两个,这里是没有返回值操作数的,一般返回值储存在前面两个操作数里面一个。

如果你是抱着上述思想来分析,整个执行器的过程就明了

1
2
3
4
5
6
7
8
9
10字节的opline结构
+0x0 opcode
+0x1 高4位操作数1类型,低四位操作数2类型
+0x2 4字节操作数1
+0x6 4字节操作数2

操作数类型 0 寄存器变量 R0-R15 16=ESP 17=EBP
操作数类型 1 立即数
操作数类型 2 取地址值,地址变量为寄存器变量即[reg]

再看switch里面对应的case,这里不用看完,第一个case相当于nop,第二个和第三个是加减指令。第四个是读写寄存器。就这几个case就够,下面的异或,并,且逻辑运算等就不用看了。

这里分布是比较有特点,vm栈空间是和控制vm的结构是紧靠在一起的。这里checksec一下,发现got表是可以写的,但是开了pie。如果我们能把栈移到got表上呢?这里对esp和ebp做了一个检查。

1
2
if ( v1 >= v2 || *(_DWORD *)(a1 + 1116) >= *(_DWORD *)(a1 + 16) || v2 <= *(_DWORD *)(a1 + 1120) )
return 1LL;

这里是什么意思呢,esp要小于栈的大小。ebp要小于mem的大小。mem使我们自定义的是可控的。

这里栈的大小是固定的为0x1000。如果我们设置的内存的大小大于栈的空间大小0x1000, 那么我们的ebp是可以指向超出栈的空间的。并且这里是开了pie的。所以要找一个地址来中转。这里vm的结构上有栈顶的指针,为了也能让esp偏移超出限制,这里先让ebp偏移指向vm->stack_size.改变栈的大小。下面就是写got的过程。

在加减的处理过程中,除了写固定的寄存器以外,也可以写[reg],所以这里我们需要改变vm->mem的指向,前面也说了vm上有栈顶的指针,栈顶为bss上的地址。

这里再让ebp指向vm->stack. 将vm->mem覆盖为vm->stack.后面的流程就明显了。覆盖malloc@got为one_gadget。

把思路记录一下。也是第一次看虚拟机的题目,不过相对来说这个虚拟机还是比较简单:slight_smile: 。