【bsauce读论文】ELOISE:利用 Elastic Objects 绕过内核KASLR防护

论文来源:CCS-2020-ELOISE-A Systematic Study of Elastic Objects in Kernel Exploitation

摘要

目标:目前有人尝试过利用 elastic objects来绕过KASLR,但不确定是否适合大多数内核漏洞。

解决:实现原型ELOISE(ExploitabLe Object dIScovEry)——使用静态/动态分析技术来识别elastic object,再采用Z3约束求解来配对相应的内核漏洞。

实验:在主流系统(Linux\FreeBSD\XNU)上测试,发现elastic object很普遍,测试40个内核漏洞,能帮助多数漏洞绕过KASLR和heap cookie protector,甚至实现任意写。最后作者还提出一种防护机制,使用独立的cache来隔离elastic object,且带来的额外开销可忽略不计。

1.Introduction

贡献

  • 设计和开发一种系统性的方法,验证elastic object利用的危害性。

  • 实现工具ELOISE,帮助发现elastic object并匹配到对应的漏洞。

  • 提出针对elastic object的防御机制。

2.background

elastic object特征:开头一个length域表示elastic buffer的大小,当内核访问该buffer时,length能控制内核读写的范围。作用——内核开发者可以通过手动控制内存分配,节省内存,通过提高 cache hit rate [64]来提高内核性能。

elastic object利用示例xfrm_replay_state_esn就是典型的elastic object,包含两个关键字段,一个是缓冲区字段bmp,一个是长度字段bmp_len,对应系统调用recvmsg可以读取的bmp。利用vul obj溢出改大bmp_len,就能泄露与xfrm_replay_state_esn相邻的对象,例如包含函数指针的ext4_file_operations结构。有的可以泄露堆栈cookie,如果能覆盖指针,甚至能任意读。

Figure 1-exploit example.png

挑战:1.内核得含有elastic object;2.能够将elastic object泄露到用户;3. vul objecteslatic object在相同的cache/zone,且漏洞能覆盖elastic object的length区域。

威胁模型:SMEP/SMAP[17] /KPTI [19] /W⊕R;假设内核堆的freelist有随机化(Linux和XNU有,FreeBSD没有),因为[6, 28, 44]已研究过绕过freelist随机化;POC的漏洞能力。


3.技术方法

3.1 识别elastic object候选

(1)查找struct候选项

首先,结构平坦化,若结构S的成员fi是结构变量,则用fi的成员替换fi;若fi是多维数组,则用一维替代;若fi是union变量,则用新结构{S1, S2, ..., Su }替代,u是union中不同定义的数量。

然后,只要结构中含整数变量(可能是长度字段),则视为结构候选项。

(2)查找elastic object的分配点

步骤:分3步,先定位所有的堆内存分配点,再判断分配的对象是否为候选的struct,最后检查到达该分配点是否需要root权限。

  • 定位分配点:两类——使用通用cache/zone(kmalloc, kalloc, malloc);使用特殊cache/zone(kmem-cache, mcache_alloc, uma_zalloc)。

  • 确定分配对象的type:对分配函数的返回值,进行use-def分析,追踪和type castingpointer dereferencingargument passing相关的指令,这些指令的操作数显示了object的类型。

  • 确定路径权限:检查是否存在一条到达分配点的路径,不调用capable(CAP_SYS_ADMIN)— Linux、priv_check, priv_check_cred—FreeBSD、priv_check_cred()—XNU 这些函数。若需要root权限,则派出相应分配点和候选struct。

(3)记录struct候选项的cache类型

方法:对于特殊cache/zone的函数(kmem-cache, mcache_alloc, uma_zalloc),第1个参数就显示了struct类型,例如kmem_cache_zalloc(seq_file_cache)分配struct seq_file类型的对象;对于通用cache/zone的函数(kmalloc, kalloc, malloc),第1个参数是常量或变量加常量,表示chunk大小,即可得知相应的cache/zone大小。

3.2 筛选elastic object候选项

筛选规则:整数变量表示kernel buffer的大小;存在一条路径能泄露elastic object的数据。

方法:总结泄露函数,见 Table 3。泄露函数一般含两个参数,一个长度 n 一个地址 addr。以这两个参数为污点源,进行过程间的前向污点分析,看看长度参数 n 源自哪里。

Table 3-migrate data functions.png

见Figure 2,从污点源(参数n)开始,反向跟踪数据流,并检查这些参数源自的内存区域,若 n 源自栈变量或全局内存区域,则丢弃该调用点;如果 n 来自内核堆(如 Figure 2 中,n=objB->N),则保留该调用点,继续反向分析找 addr 参数的污点源(Figure 2 中的 src),反向跟踪数据流,跟踪所有初始化了相应addr参数的赋值点(例如 src =&buffersrc =&objA-> buffersrc = objA-> p),根据这些赋值点来分析src的来源——能泄露的目标。addr 的来源有3种:

  • ❶ 若src指向栈内存( src =&buffer),如果可以控制堆上的长度变量(n=objB->N),则可能可以泄露栈canary和返回地址。

  • ❷ 若src指向堆对象内部(src =&objA-> buffer),则可能可以通过溢出来覆盖长度变量(n=objB->N),泄露heap cookie或绕过KASLR。

  • ❸ 若src等于堆对象内部的某个指针(src = objA-> p),则可能可以任意读写。则需要继续反向污点分析,确认 p 和 N 是否位于同一个object,如果位于同一object,有可能通过堆溢出同时控制 p 和 N,实现任意读。可读中断描述符表(IDT)绕过KASLR,或者dump内存来查找"root:!:"字符串(该字符串是/etc/shadow文件的一部分),泄露用户密码hash,再利用 Ripper密码爆破器破解 [43, 56]。

Figure 2-taint analysis.png

3.3 匹配漏洞和elastic object

(1)准备工作

elastic object信息:(1)所用的cache/zone类型;(2)object中length所在偏移;(3)若 p 和 N 在同一object,p的偏移;(4)内核分配该object的分配点;(5)泄漏点;(6)泄露能力。

识别漏洞能力:a. GDB 跟踪PoC 触发流程;b.识别vul object所属的cache类别,总结漏洞覆写能力。例如,见Figure 3,vul obj属于 kmalloc-128,能溢出相邻区域的第1个8字节和第3个8字节,第1个8字节要>=128,第3个8字节要==1024(可参考[11,12]使vul objelastic object相邻)。

Figure 3-vulnerability capability.png

漏洞能力表示:用2元组表示,[(VCache1, Cap1), ... , (VCachen, Capm)]VCachei—能溢出的cache种类,Capj—能溢出的范围。若某漏洞能溢出多个区域,则 Capj 用 list — [(Rj1|Opj1|Vj1), ... , (Rjx |Opjx |Vjx )] 表示,Rjk—被溢出的区域在哪里,OpjkVjk — 溢出可控的能力。

示例:见Figure 3,漏洞能力表示为(kmalloc-128, [([0, 8)⩾128), ([8, 16)=1024)])

(2)匹配过程

给定漏洞,首先选取cache/zone匹配的 elastic object;再检查指向elastic buffer的指针是否和 length 处于同一object(一起覆写,可能可以任意读);最后检查length能否被覆写。例如,length位于第3个8字节,elastic object位于 kmalloc-192,漏洞能力 ([16, 24)=1024),所以可以将length覆写为1024并访问相邻快,泄露heap cookie或绕过KASLR。

(3)检测可利用性的策略

a. 对于堆上的elastic buffer,检查可控制的buffer size是否为所在cache的两倍(确保能读下一整个cache的内容);

b.对于栈上的elastic buffer,检查可控制的buffer size是否大于elastic buffer所在的栈帧的大小(确保能泄露canary和返回地址)。

(4)后续利用问题

问题:需避免走不到泄露函数或者内核触发页访问错误。

解决:给定漏洞和对应的elastic object,获取所有到达泄露函数的路径,见Figure 4。首先,收集路径上的指针引用,然后收集到达泄露函数时必经的分支约束。对于指针引用要确保指向合法地址,对于分支约束要注意在elastic object中布置的数据和分支变量的数据依赖。

示例:见Figure 4,溢出覆盖了elastic object中的length-N和相邻区域f,预先识别出两条到达泄露点的路径,筛选出和N、f相关的约束(e.g., C1=obj.f>0; C2=obj.N<8),这样该约束(C1&C2)就作为配对过程的额外限制条件。

Figure 4-constraint extraction.png


4.实现

bitcode生成:编译配置是使用defconfig。由于编译优化和大量使用load/store指令会给别名分析和控制流图构建带来负担,ELOISE使用LLVM自带的WriteBitcodeToFile()函数,在 mem2reg pass和 一些负责优化的pass之间调用该函数,以减少load/store指令的使用并构建变量的SSA形式。

控制流图构建与别名分析:借鉴 [47,48] 的两层类型分析pass来构建调用图,并砍掉了和.init.text节中函数相关的边,接着构建上下文敏感的控制流图(添加3种边——BranchInstCallsiteReturnInst);采用LLVM 自带的 AliasAnalysis pass(别名分析用于确定分配内核object的type并进行前向污点分析)。注意,识别elastic object时,只有两变量为 must-alias关系时才将污点变量传播给别名变量(存在欠污染under-tainting问题,但降低了误报)。

识别elastic object:跟踪内存分配函数,分析返回值,确定对象的type。ELOISE 根据3种治疗来推断类型信息,Getelementpr —— 其参数包含指向object的指针和该object的类型信息;BitCast —— 其参数包含指向object的指针,另外两个参数确定了cast之前和之后的类型信息;CallInst —— 其调用参数可能含有 指向object的指针。

筛选elastic object:从泄露函数的长度参数开始反向污点分析。问题一,若发生多层引用A->B->len,B才是真正的elastic object,所以发生多层引用时只向前污染1个LoadInst。问题二,区分内存区域(stack / heap / global),内存区域来自AllocaInst 则为栈,内存区域表示为global/static 变量则为global,其他情况则视为堆区域。

收集关键约束:收集到达泄露点的路径中,和elastic object相关的约束(用户可控制走向)。例如,若elastic object 区域用于 CmpInst指令,具体是ICMP_ULT>),TRUE分支能走到泄漏点则保留>谓词,反之保留=<

object和漏洞配对:ELOISE 使用Z3 solver[60] 来配对。具体来说,用 BitVec 表示对象,用算术表示内存值的范围。例如,对于kmalloc-128中的约束[8, 16)<8,创建x = BitVec('x', 128*8)来表示该对象,x << (8*8)>> (112*8) 表示范围 [8, 16)(x << (8*8)>> (112*8)< 8) 即表示该约束,最后把约束集交给Z3求解,如果有解则说明漏洞能力和elastic object相匹配,可以利用。如果某个约束中包含变量(e.g., [0, 8 ),则跳过该路径,因为变量使得利用性不可判定。


5.实验评估

5.1 实验设计

(1)评估ELOISE 对elastic object 的识别(误报FP与漏报FN)

检查漏报:人工识别vs ELOISE,由于内核代码量过大,人工识别困难,故采用随机采样方法。Linux中取800/6383个elastic object,手动识别分配点、到达泄露函数的路径,再将结果和 ELOISE 对比。

随机取样的科学性:a.分配点取样是随机的,涵盖内核12.5%的分配点;b.Linux/FreeBSD/XNU内核有共性,Linux可以代表另2个内核;c.手动分析时由2个经验丰富的Linux内核研究者完成。

检查误报:syzkaller+手动 vs ELOISE。对Linux和FreeBSD,首先在 ELOISE 识别出的分配点和释放点插桩 panic 函数,采用syzkaller来fuzz,看是否能生成输入,能到达分配点并释放elastic buffer 中的数据。对于fuzz不可达的点,再进行人工分析确认,仍然不可达的点就是 ELOISE的误报。对于XNU内核,没有可用的fuzzing工具,只能纯人工分析。

(2)评估绕过防护的能力

防护:KASLR, heap protector, stack canary。

实验:选40个内核漏洞(很有代表性,包含所有类型的堆漏洞,如OOB、UAF、Double-Free;包含所有漏洞利用研究中涉及的堆漏洞类型;包含随机10个szbot [26] 上的漏洞,被syzkaller挖到但还没有CVE编号;包含近三年公布的XNU和FreeBSD漏洞),见Table 7,包含人工总结的漏洞能力。

漏洞选取依据:对FreeBSD和XNU,选取标准是——a.含触发漏洞的PoC;b.不需要特定的硬件驱动和root权限;c.PoC的崩溃可复现。

实验环境:漏洞都移植到Linux-v5.5.3、FreeBSD-v12.1、XNU-4903.221.2。

(3)评估 ELOISE 辅助利用的作用

实验:找了6个内核研究者,分为两组,给定5个漏洞和相应PoC,要求写出绕过KASLR的exp。对于A组,提供ELOISE工具来辅助找elastic object和配对漏洞。每隔半小时记录两组的进展(进展分为3个阶段,漏洞能力探索、elastic object收集、内存结构布置),根据每个阶段的耗时来量化ELOISE 辅助利用的作用。

5.2 实验一结果

漏报:ELOISE 识别出 97 / 800个 elastic object, 对应98个泄露路径,人工找到的是ELOISE 的子集,说明漏洞率很低。

误报:人工验证74 / 97个为正确的elastic object,误报率可容忍。误报主要源于不准确的调用图构建,例如,只有当硬件设备插入时,ieee80211_set_probe_resp()register_leaf_sysctl_tables()函数才会分配probe_respctl_table 对象,但 ELOISE把 这些对象和未授权系统调用联系在了一起。

elastic object可利用性和普遍性:见Table 4~6,70/74个elastic object 可以帮助泄露堆数据,绕过KASLR 和 heap cookie protector;28/74个可以任意读;5/74个可以泄露栈canary。Linux / FreeBSD / XNU 分别有39,483 / 44,956 / 22,307 次分配和使用这74个对象。

elastic object所需权限:60/74不需要任何权限;14/74需要 CAP_NET_ADMINCAP_AUDIT_READ 权限。

elastic object cache/zone覆盖:见Table 4~6,74个 elastic object 涉及大多数通用和特殊的cache / zone(eg,kmalloc-16384 — Linux, mbuf — FreeBSD, pipe_zone — XNU)。见Table 1,4,5,6带星标的18/74个对象的大小是可变的,提高了可利用性。

elastic object泄露路径及约束:见Table 1,4,5,6,ip_options有多个泄露路径(能从堆和栈泄露),其余对象都只有1条;10/74个对象的泄露路径的约束中含变量,不适合利用。

5.3 实验二结果

绕过防护:见Table 7 和 Table 2,27/40漏洞可以绕过KASLR和 heap cookir protector,12/27可以泄露栈canary,8/27可以任意读。

利用的丰富性:26 / 27(除了CVE-2017-2370)可以利用多个elastic object,CVE-2017-7184可利用最多。丰富性取决于两点,一是漏洞可溢出多种cache,二是覆盖elastic object的限制较少。

失败案例:13个利用失败。CVE-2018-5703, CVE-2018-12233, CVE-2018-1000112, 3d67[68] 只能覆写 vul obj内部数据;CVE-2018-18559, CVE-2017-15649, CVE-2017-10661, CVE-2019-6225, 422a[69] ,ELOISE 无法找不到任何 elastic object 的length 和被溢出的区域重叠;CVE-2018-4243 只能把elastic object 对象的length 覆写为0;CVE-2019-5603, CVE-2019-5596, bf96[74] 只能溢出 特殊的cache / zone,没有合适的elastic object 可用。

5.4 实验三结果

结果:A组在ELOISE的帮助下成功完成5个漏洞的内核基址泄露,B组全部失败。细节可见 Appendix A.5


6.防护机制

6.1 现有的防护机制

  • heap freelist randomization [16, 23],[16] 指出freelist随机化对于UAF和Double-Free漏洞无效,[6, 28, 44] 都研究了如何绕过该机制(都是iOS)。

  • structure layout randomization [15],防止修改elastic object中的length,但该机制依赖一个随机种子来进行随机化,可以泄露该种子。

  • "USERCOPY" checking [22],Linux / XNU 支持,保证length参数不超过 cache /zone slot 或 stack frame的大小。但它只对 copy_{from/to}_user()copyout() 进行length检查,不包含其他传递函数,且对合法的length范围限制不严。

6.2 我们的防护方法

方法:使用shadow caches/zones 隔离 elastic objects,例如,用 kmalloc-isolated-16 替代 kmalloc-16。不同于 xMP [58] 页粒度的隔离机制,这个粒度的隔离仍然能覆写。

实现:在Linux kernel上实现,在boot阶段创建 shadow cache,修改内核加上 flag __GFP_ISOLATE作为编译选项,在kmalloc附近,可以选择是否将elastic object 分配到 shadow cache

性能评估:环境是 1.6 GHz CPU, 16GB RAM, and 500 GB HDD,内核版本 v5.5.3,benchmark选择 3种,LMbench v3.0 [51] ——测系统调用和I/O操作的延迟和带宽、Phoronix Test Suite 9.8 [1]——5个真实应用、定制的系统调用序列(能到达elastic object分配点和相应泄漏点)——对防护方法进行压力测试。结果见Table 8,性能平均开销是0.19%,可忽略不计。

安全评估:采用作者提出的防护方案,对Table 7中所有漏洞(除了 CVE-2017-7184 和 CVE-2017-17053),ELOISE 找不到可用的 elastic object。对于 CVE-2017-7184 和 CVE-2017-17053,他们的vul object位于 shadow caches,虽然还能利用elastic object泄露数据,但是泄露不出重要数据(shadow caches中找不到包含函数指针的object),无法绕过KASLR。


7.相关工作

提高可利用性:侧信道攻击 [24, 34]——利用硬件特性 Intel TSX timing attack [37]、pre-fetch [27]、out-of-order execution [46];新的利用方法——ret2dir [39]、 KEPLER [79]、Data-only attack [9]。

辅助利用:自动生成exp——[4, 7, 8, 33, 62, 63, 66];辅助生成exp——[5, 29, 30, 36, 59, 78, 82];two memory collision attack [81] 辅助UAF的堆喷射;确定性的栈喷射技术和消耗内存的喷射技术,来利用栈 Use-Before-Initialization漏洞 [49]、扩展栈喷射技术 [14];FUZE针对内核UAF漏洞 [80];能力导向的fuzzing技术 搜集OOB漏洞的能力 [10];SLAKE 自动化内核堆布局 [11]。

8.Conclusion

本文对elastic object进行系统性的研究,显示elastic object 几乎总能帮助内核漏洞绕过防护机制,如KASLR、heap cookie protector、stack canary,并提出轻型的防护机制,将elastic object 放在不同的 cache/zone中。

你可能感兴趣的:(【bsauce读论文】ELOISE:利用 Elastic Objects 绕过内核KASLR防护)