一道虚拟机

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

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

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

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

 struct vm{
        char * mem;  //malloc分配的内存区
        char * stack; // vm里面栈空间,指向栈顶
        uint stack_size; // 栈空间的大小
        uint mem_size; //内存区的大小
        char bp[1048-0x18]; //breakpoint 断点区
    }

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

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

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

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

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

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做了一个检查。

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

这里是什么意思呢,esp要小于栈的大小。ebp要小于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: 。

ezarch.zip (877.7 KB)

:grin: 给师傅递茶,这次比赛时间太紧了,第一天没打,第二天只打到5点多就去忙了