EZVM做题记录
在fnz比赛中,遇到一道对于本人现阶段较有挑战性的一道VM题,这里记录一下做题的过程。
VM题目一般会用程序语言去模拟某些解释器性质功能,往往拥有较大的代码量,需要逆向去还原题目的指令集功能以及解释器相关的结构体等
题目是EZVM,打开时看看保:没开pei,Partial RELRO,其他保护全开。libc版本来到了2.35,每次给了libc的题目可以利用strings libc.so.6 | grep "ubuntu"来看一下题目给的libc版本,特别是有堆利用的题目。
ida打开程序,总览main函数,交互包括读取name,opcode,data,并进入一个类似于操作opcode与data的解析函数,根据返回值去调用输出name与输出data的函数。这个题目存在一个比较关键的结构,其管理了一个模拟的栈,其对应的成员如图:
。题目中有些关键的栈操作函数如果不在ida中转化变量为结构体还是很难逆的。
这里实现了栈的push功能。关键逻辑还是在解析函数中,对于存储opcode的栈依次弹栈并switch_case判断opecode跳到不同操作函数中(已经算是非常直接的解析逻辑了。
这里关注两个函数:push_align与write_align
- push_align会pop work_arena栈顶值作为相对当前栈顶的偏移push对应位置的8字节内容
- write_align会分别pop write_content与align作为写入地址以及相对当前栈顶的偏移
观察程序发现三个栈都处于堆中,其实不难发现我具有堆空间内的任意地址读写。初步想法是寻找堆区域的libc地址,对地址做操作打__free_hook(当时忘记是libc3.35了。。)。然而因为执行解析操作时堆结构并没有free的unsorted_bin,堆空间也就没有libc地址可以用了。再次观察堆空间有的堆块:除了三个栈本体与存name的chunk,还有三个my_stack管理结构。而对于栈的实体的定位取决于第一个字长数据,将work_area栈管理结构中指针改到pei中地址,相当于实现了ELF文件的任意读写。不难想到直接劫持got表是最直接的方法,刚好后续会puts一个我们控制指向内容的name指针。直接name为/bin/sh即可!
补充:注意题目的交互方式,opcode逐字节进行了特定处理,data对输入数字进行字节转换,并以空格分隔每个数字
Exp.py
1 | from pwn import * |