【bsauce读论文】KOOBE:Towards Facilitating Exploit Generation of Kernel Out-Of-Bounds-USENIX2020

摘要:

目标: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

Figure1-cve-2018-5703.png

(3)利用步骤

  1. 能力搜集

    分析漏洞代码的逻辑,调整PoC中syscall的参数、添加额外syscall或多次触发溢出漏洞。

  2. 堆风水

    耗尽当前的cache,确保VO和TO能连在一起。

  3. 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多个这样的对象可能有用。

    Figure3-vulnerability.png

  4. 利用合成

    修改PoC来排布VO和TO,触发调用。

  5. 绕过防护并任意代码执行

    • KASLR:其他信息泄露漏洞或侧信道Meltdown[36]、Spectre[34]、RIDL[54]、ZombieLand[46]。
    • SMEP:ROP/JOP。
    • SMAP:physmap[33, 58],KEPLER[56]也能绕过SMEP/SMAP。


      Figure2-利用步骤.png

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—漏洞点的集合(p\inP),OOB写表示为Tp={(offpi,lenpi,valpi)|i\inNp \bigwedge off,len,val\inE},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 \in 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 )}, \phi}

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挖掘新的能力,再重新进行能力总结和可利用性评估。

Figure4-KOOBE_Overviw.png

能力导向型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)、关键数据的预期值范围(如指针必须指向有效地址,内核或用户空间)。

Figure5-可利用性评估.png

方法:将target constraints交给solver求解,无解则分析下一个TO。

  • 内存布局:Fig. 5a显示了VO/TO的布局和各字段的含义,用内存对象M来建模VO/TO,可用符号化能力的data/indexes/offsets来更新M,详见第3节。

  • 示例:Fig. 5b显示了CVE-2018-5703的可利用性判断过程。评估了2个TO(Type3Type4),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)。

    Table1-距离函数.png

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<n;i++) 
4.  vul[i] = 0;//OOB Point

解决:借鉴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,如keypacket_sockip_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 2Table 3(注意:可用TO个数 == potential EXP)。

Table2-result_CVE.png

Table3-result_syzbot.png

Appendix 可利用性评估算法

算法:遍历OOB能力,结合备选的TO,根据距离函数计算距离,若距离为0,则表示TO中的关键数据能够被覆盖成预期值,则表示漏洞能够利用(多个能力可以一起使用)。

Algorithm1-可利用性评估算法.png