摘要:
目标:heap out-of-bounds(OOB)堆越界内存写漏洞。
原因:a.不同的OOB有不同的漏洞能力;b.构造exp会用到很多syscall,是模块化的过程。
成果:本文总结了linux堆OOB漏洞利用的挑战,实现了KOOBE分析框架。给定一个PoC,KOOBE自动分析OOB漏洞并使用符号化追踪来总结PoC的能力,提出新型能力导向型fuzzing来搜索含其他能力的路径,识别合适的target objects
来评估漏洞可利用性,生成能实现IP劫持的exploit。
实验:17个最新的内核OOB漏洞(其中7个有CVE编号,5个有公开exp),最后成功利用11个。
说明:本文不讨论保护机制KASLR/SMAP/SMEP,因为绕过保护机制发生在IP劫持之后。KEPLER[56]已经能利用IP劫持实现任意代码执行。
1.背景与示例
(1)定义与利用
OOB的能力定义:分为3类,从哪开始写,能写多少字节,可以写入的值(形式化定义见$4.2)。eg,CVE-2016-6187可以写1个字节;CVE-2017-7184可以写很多字节,但只能写入固定值。
VO/TO定义:我们把发生溢出的对象叫做vulnerable object
,将被覆盖的包含关键指针的对象叫做target object
(SLAKE论文将之称为victim object
)。
利用方法:发生OOB漏洞后,我们需要在vulnerable object
后面布置victim object
以覆盖关键数据,实现IP劫持。
难点:a.PoC没有触发漏洞所有的能力;b.为找到合适的内存布局,搜索空间过大。
(2)漏洞示例
OOB漏洞点在12行,vul
对象是Type1
类型,size(Type1)
== size(Type3)
== size(Type4)
,11行错误将vul
对象转化为Type2
类型,导致溢出。溢出内容gsock.option
默认值是0x08080000000000
,可通过调用sys_setsockopt
来控制(13行),这一点在PoC中体现不出来,称之为addtional capability
。
(3)利用步骤
-
能力搜集
分析漏洞代码的逻辑,调整PoC中syscall的参数、添加额外syscall或多次触发溢出漏洞。
-
堆风水
耗尽当前的cache,确保VO和TO能连在一起。
-
TO选择
TO中必须含关键数据,如函数指针、数据指针、非指针域。
函数指针:直接控制流劫持。
数据指针:若指向的结构之后会被写,则构造任意写;若指向另一个含函数指针的结构,则构造任意代码执行。
非指针域:需具体分析,
struct cred
或者reference counter
(表示对象的引用次数,可能可以构造UAF)。
例如CVE-2018-5703,选择
Type3
作为TO,因为前8字节恰好是函数指针,Type4
不适用,因为关键数据不在前8字节,无法覆盖;CVE-2016-6187是off-by-one
,只能溢出1字节且覆盖为null,最好选择首字节为reference_counter
的TO(如Type5
),以构造UAF,有2000多个这样的对象可能有用。
-
利用合成
修改PoC来排布VO和TO,触发调用。
-
绕过防护并任意代码执行
- KASLR:其他信息泄露漏洞或侧信道Meltdown[36]、Spectre[34]、RIDL[54]、ZombieLand[46]。
- SMEP:ROP/JOP。
-
SMAP:physmap[33, 58],KEPLER[56]也能绕过SMEP/SMAP。
2.设计
KOOBE流程:a.用符号化追踪技术来总结PoC的能力;b.对备选的TO和能力判断可利用性;c.若不能利用,则探索新的能力;d.若确定了合适的TO,则调整PoC来合成exploit(采用堆风水)。
2.1 漏洞分析
目标:给定PoC,定位漏洞点(包括KASAN不能发现的),识别VO。
KASAN问题:KASAN原理是采用redzone识别OOB,若溢出覆盖相邻的对象,则无法识别。且KASAN不能确定具体的OOB指令和VO。
方法:对内存操作进行符号化追踪,如kmalloc/内存访问,将创建的对象用唯一的符号值表示,根据内存访问时指针的符号表达式的范围,来判定是否越界。如CVE-2018-5703中,第9行的对象用vul + offsetof(Type2, option)
表示,vul就是唯一的符号值;Figure 3b访问指针的符号表达式是vul + i/8
,vul和i是符号值,由于i没有约束,访问VO可能越界。
2.2 能力总结
能力定义:
OOB write set:E—符号执行引擎支持的所有符号表达式,P—路径集,Np—漏洞点的集合(pP),OOB写表示为Tp={(offpi,lenpi,valpi)|iNp off,len,valE},off—相对于VO起始地址的偏移,len—写长度,val—溢出字节的字节值。off,len,val都是符号值,又称为
OOB offset
/OOB length
/OOB value
,for循环中同一点的OOB write
一起称为OOB access
。capacity:路径p的能力表示为Cp={sizep, Tp, f(p)| sizep E},size—VO的大小(符号化),Tp—访问范围,f(p)—p的路径约束。若VO大小可控(符号化),则可选的TO更多,所以也作为能力之一。
-
Capacity Comparison:∀e1,e2 ∈ E, e1 ≼ e2 表示e1符号表达式等于e2或者e1是常数(可从e2中取值该常数)。
∀p1, p2 ∈ P, Tp1i ≼ Tp2i if offp1i ≼ offp2i ∧ lenp1i ≼ lenp2i ∧ val1i ≼ valp2i
∀p1,p2 ∈P, Cp1 ≼Cp2 if sizep1 ≼sizep2 ∧ ∀i∈ Np1 Tp1i ≼ Tp2i
示例:例如CVE-2018-5703,Poc的能力、完整的能力表示如下,显然Corig ≼ Ccomp。
Corig={sizeof(Type1), {(offsetof(Type2, option), 8, 0x08080000000000 )}, }
Ccomp={sizeof(Type1), {(offsetof(Type2, option), 8, val)}, {val != -1}}
能力生成:漏洞点分为函数调用(如memcpy)或指令。建模内存拷贝函数可简化能力总结的过程,offset—第1个参数—目标地址,value—第2个参数—源地址,length—第3个参数。
2.3 能力探索
问题:同一漏洞有不同的漏洞点和相应路径,可能有不同的能力;同一漏洞点,不同的路径约束,有不同的能力(如CVE-2018-5703)。而给定的PoC只有1条路径,不能全面总结漏洞能力,该PoC的能力不一定能利用漏洞。
解决:见Figure 4若已知的能力无法利用,则采用能力导向型fuzzing,探索新的PoC挖掘新的能力,再重新进行能力总结和可利用性评估。
能力导向型fuzzing:
- Syakaller不足:单纯追求覆盖率,对于新的OOB能力没有用。
- 改进:给定PoC和对应的OOB能力,对PoC进行变异,将OOB能力和覆盖率一起作为反馈,然后将含新能力的新PoC交给符号追踪器进行能力总结。
- 具体:每次执行测试程序,搜集
OOB write set
的具体值(覆盖字节长度+覆写的字节内容)作为能力反馈。搜集OOB write set
是采用轻型的动态插桩技术来完成,具体技术请看第5章。 - 问题1:testcase去重,以免重复fuzz。若能力总结时已知覆盖值可为任意值,则没必要再对覆盖值进行fuzz(只需在能力总结时,计算出
OOB write set
中值的范围,就能用于筛除testcase)。 - 问题2:种子选择。实际运行时,会生成更多能提升覆盖率的种子。所以,用2个队列分别保存提升覆盖率的种子和提升能力的种子,以同等概率选择其中一个队列的种子。实际效果很好。
2.4 评估可利用性
目标:搜索合适的TO,进行利用合成。
target constraints
定义:即TO被覆盖并利用的条件,TO的大小(和VO等大)、TO关键数据的位置(函数/数据指针、reference counter)、关键数据的预期值范围(如指针必须指向有效地址,内核或用户空间)。
方法:将target constraints
交给solver求解,无解则分析下一个TO。
内存布局:
Fig. 5a
显示了VO/TO的布局和各字段的含义,用内存对象M来建模VO/TO,可用符号化能力的data/indexes/offsets
来更新M,详见第3节。示例:
Fig. 5b
显示了CVE-2018-5703的可利用性判断过程。评估了2个TO(Type3
和Type4
),1—VO和TO的大小必须相等;2/3/4—用OOB offset
/OOB length
/OOB value
去更新内存对象M;5—路径约束+TO关键数据的预期值,这里Type4预期值不能满足(OOB length
有限)。策略:若能力不能利用TO,一是可多次触发同一能力,如CVE-2017-7184(
Fig. 3b
)可多次触发将指针覆盖为null;二是组合多种能力,因为不同的能力可能有不同的OOB value
。具体算法见Appendix A(贪心算法)。-
距离函数:目的是利用某能力使TO中关键数据更贴近于目标值。引入距离函数见
Table 1
,距离越短,说明该能力更适合写TO关键数据。若符合则距离值返回0,否则返回1个正的距离值(如果是data pointer
,则只有当OOB value
在[MIN_POINTER, MAX_POINTER]
之间才返回0)。
2.5 合成利用
步骤:首先利用堆风水布局内存(采用add_key()
、msgsnd()
、sendmsg()
耗尽cache);再插入syscall来分配/引用TO(详见Appendix B),搜集公开exp中用到的TO和使用方法,database格式见Fig. 12
。
绕过防护:只进行IP劫持,不考虑绕过防护。
3.实现
组成:Syzkaller、二进制符号执行S2E、二进制分析angr。
代码:7510行C++,S2E进行能力总结和可利用性评估;2271行python,angr进行漏洞静态分析;1106行Go,Syzkaller探索路径(新的PoC新的能力)。
(1)动态插桩——支持能力导向的fuzzing
使用:S2E+Syzkaller
方法:使用S2E进行符号化追踪、生成能力的符号化表示,使用S2E动态插桩以支持能力导向的fuzzing。
动态插桩:插桩跳过漏洞点导致OOB write
的指令(同时记录每次OOB access
的操作数),避免触发crash导致重启。
(2)支持符号化长度
使用:S2E
问题:前面提到过,更新内存模型M采用的是M[offset: offset+length-1] = value[0: length-1]
(见4.4节),offset/length/value都是符号化的,而S2E对length的符号化支持不佳,若length取具体值,则会低估漏洞能力(即覆盖字节数)。
符号化长度原因:a.需求解最小的OOB length
,避免覆盖系统其他关键数据;b.必须根据VO长度来限制约束OOB length
。
解决:给定一个OOB write(off,len,val)
,若len具体化为10,则对内存对象M每个字节单独初始化。基于能力导向的fuzzing来寻找更大的OOB length
。
// ite表示 if-then-else; offset_dummy表示该偏移处的字节不能更新。
for i in [0, 10]:
M[ite(i < len, i+off, offset_dummy)] = val[i]
(3)搜集loop中的能力
问题:如遇到Fig. 6
循环,溢出长度取决于符号化的n。S2E符号执行无法将符号值n传递给i。
// Figure 6: An example of overflow with a loop
1. void loop(n) //n = 64
2. vul = (char*)kmalloc(32);
3. for(i=0;i
解决:借鉴SAGE[24]的loop-guard pattern-matching
规则,自动推断index i的公式。所以采用angr静态分析涉及漏洞点的循环。
(4)处理符号化的index和循环边界以解决路径冲突
问题:符号化追踪PoC时,如果想要少覆盖1字节,调整符号化length进行求解时,就会产生新的路径约束,合并这些路径约束(使得PoC仍经过原来的漏洞点)很困难。以往的path kneading
[13]技术不适用Linux内核,太耗时了。
解决:可删除数组索引和循环边界的过度约束。原因是限制内存索引index没意义,因为每次运行时,内存对象的地址会变(如动态分配堆对象)。
(5)去掉不重要的约束
问题:路径约束太复杂,很难求解。
解决:去掉一些约束,如printk()
,与利用无关;重复调用的syscall,同样的参数,只保留最后一次的约束,如race漏洞,能提升效率。
(6)TO搜集
目标:解析debug信息,获取包含重要的数据(指针/引用计数)的结构,发现多达2615个,保存其offset、size、usage(触发其allocate
/ dereference
的syscall)。
方法:如何识别分配/引用TO的syscall?实现LLVM pass,首先构造全内核的调用图,然后搜索allocate
/ dereference
点;同时搜集常用的TO,如key
、packet_sock
、ip_mc_socklist
;还可以借鉴SLAKE[20]。
4.实验
准备:数据集选17个漏洞(7个CVE,10个来自syzbot)。环境是Ubuntu 16.04 system running on a desktop with 16G RAM and Intel(R) Core i7-7700K CPU @ 4.20GHz * 8
。
实验结果:生成更多EXP,其中6个没有公开exp;6个没发现可用的能力不等于不可利用。实验结果见Table 2
和Table 3
(注意:可用TO个数 == potential EXP
)。
Appendix 可利用性评估算法
算法:遍历OOB能力,结合备选的TO,根据距离函数计算距离,若距离为0,则表示TO中的关键数据能够被覆盖成预期值,则表示漏洞能够利用(多个能力可以一起使用)。