本文来自USENIX 2019,KEPLER- Facilitating Control-flow Hijacking Primitive Evaluation for Linux Kernel Vulnerabilities。源码见https://github.com/ww9210/kepler-cfhp。
摘要:
目标:漏洞利用面临的挑战有保护机制、缺陷路径和不合适的原语,很难手动构造exp。
解决:提出KEPLER,利用符号执行和特殊的gadget,自动生成"single-shot"利用链(漏洞只触发一次)。
结果:对比Q和FUZE,能生成更多有效的exp。
1.Introduction
背景:内核漏洞危害很大,修复人力有限,需根据漏洞可利用性评级来修复。内核漏洞自动利用有助于漏洞评级、CTF、验证保护机制的有效性。
自动利用步骤:一是识别利用原语,二是用约束求解求出输入。
挑战:a. 保护机制,如CFI(效率原因,Linux主版本未采用);b. 缺陷路径,数据构造错误可能导致访存错误、死循环等;c. 不合适的利用原语:漏洞本身的缺陷,如控制的寄存器个数有限。
KEPLER流程:先静态分析,找可用gadget;再内核符号执行,基于深搜的gadget缝合算法。
贡献:a. 提出"single-shot" 利用方法,自动生成rop;b. 半自动exp生成:利用IDA SDK、QEMU/KVM、angr;c.实用性好,对16个CVE、3个CTF能够生成有效的不同利用链。
2. 背景和相关工作
利用原语:CFHP、任意内存读写。
研究点:a. 利用原语评估,甚至能利用缺陷漏洞绕过防护;b. 开发工具,自动化利用生成。
2.1 利用原语识别
相关工作:AEG、mayhem、Mechanical Phish[62]、自动堆布局[30]、栈喷射[46]。
本文:假定漏洞点已经找到。
2.2 利用原语评估
目标:找到合适的利用方法。
相关工作:a. 构造ROP链,针对DEP保护的ret2stack-shellcode、ret2libc [3,9,62]、Q[60];b. 构造exp的技术,[48]、堆溢出[55]、内存碰撞机制[77];c. 针对CFI防护机制,如基于块的编程BOP[35]。
本文:a. 针对缺陷漏洞,存在利用上的限制;b. 针对内核,更复杂,而非用户层;c. 生成多种EXP,非单一。
2.3 内核利用与防护
a. SMEP——代码复用。
b. SMAP——一是cr4-flipping [41],缺点是需两次触发CFHP,且基于虚拟化的hypervisor会检测cr4是否被修改[49,51](通过检查vmexit);二是Ret2Dir[39],但physmap可以设定为不可执行。
c. CFI [16,70,26],考虑到效率,主流版本操作系统未启用,如CentOS、Ubuntu、Debian。
d. 禁止写进程凭证、页表、VDSO的防护机制[17,40,67]。
e. KASLR,侧信道攻击[34, 28](KPTI内核页表隔离机制可防[13]),任意读写[45, 7]。
3. 假设和威胁模型
(1)防护设置
SMEP、SMAP [11]、canary [22]、禁止写进程凭证[40]和页表[17]、KASLR、KPTI [13],基于虚拟化的hypervisor监控cr4是否被修改 [49],设置physmap不可执行,允许STATIC_USERMODEHELPER
这样就不能修改call_usermodehelper
变量,未开启CFI。
(2)可利用的原语
已知漏洞能导致CFHP(可通过手动分析或动态分析如符号跟踪,但不关注这一点),即劫持控制流;假定能泄露内存布局(.text节和physmap区域),但不存在任意地址读写(不能读canary)。
4.案例分析
漏洞CVE-2017-8890源码如下:
// ip_mc_socklist结构中next_rcu不可控,因为堆喷技术中不能控制前8字节
struct ip_mc_socklist {
struct ip_mc_socklist *next_rcu;
struct ip_mreqn multi;
unsigned int sfmode;
struct ip_sf_socklist *sflist;
struct rcu_head rcu;
};
// 遍历链表,不断调用kfree_rcu()释放所有元素,kfree_rcu()会触发执行rcu_do_batch()
void ip_mc_drop_socket(struct sock *sk){
struct inet_sock *inet = inet_sk(sk);
struct ip_mc_socklist *iml;
// inet->mc_list is a dangling pointer
while ((iml = inet->mc_list) != NULL) {
// iml is alias of the dangling pointer
inet->mc_list = iml->next_rcu;
// queuing a rcu_head for execution in the future
kfree_rcu(iml, rcu);
}
}
// CFHP
void rcu_reclaim(struct rcu_head *head){
head->func(head); // 控制流劫持点
}
void rcu_do_batch(...){
struct rcu_head *next, *list;
while (list) {
next = list->next; // next_rcu不可控
rcu_reclaim(list);
list = next;
}
}
4.1 漏洞和利用原语
漏洞分析:漏洞根源是对ip_mc_socklist
对象的UAF漏洞,导致悬垂指针inet->mc_list
与其别名*iml
的产生。kfree_rcu(iml)
触发rcu_do_batch()
回调函数,若iml->rcu_head
非空,则进一步调用rcu_reclaim()
。
利用:利用堆喷伪造rcu_head,触发CFHP,劫持rip;此时rdi指向可控区域。
4.2 构造exp的挑战
(1)保护机制
SMEP/SMAP限制ret2user/pivot2usr;physmap不可执行限制ret2dir;进程凭证和页表不可写限制覆盖cred;cr4-flipping
需触发漏洞两次,第一次翻转cr4,第二次进行ret2user/pivot2usr,但基于虚拟化的hypervisor可检测cr4是否被修改,且2次触发漏洞难度很大。
(2)路径缺陷
cr4-flipping
需触发漏洞两次,第1次利用native_write_cr4()
关闭SMAP后,但访问链表下一个元素iml->next_rcu->rcu
却不在控制之中,导致访存错误。原因是堆喷无法控制前8字节。见图Figure 1。
解决:避免访存错误,可用约束求解求出正确输入,但开销过大。理想方法是绕过缺陷路径,但堆喷不能控前8字节是硬伤。即便能劫持控制流,还是很难突破hypervisor保护下的SMAP。
(3)不合适的漏洞原语
一般利用栈迁移去执行用户空间的ROP链,但是有难点。一是利用xchg eax,esp; ret
绕不开SMAP保护;二是有效gadget不足,如xchg r**,rsp; ret, mov rsp,[r**]; jmp rxx
和 mov rsp,r**; ret
;三是可控的寄存器太少,只有rdi可控,而rsi、rdx不可控。
5. Overview
5.1 设计需求
总要求:输入CFHP状态,找到提权的利用路径,输出exp。
利用技术的要求:a. 绕过假设中的保护机制;b. 绕过缺陷路径,避免崩溃;c. 有效性,对于不好的CFHP,没有栈迁移gadget,也能完成利用;d. 便于自动化。
5.2 顶层设计
总体:输入内核Image,静态分析找到可用gadget,利用"single-shot"利用方法,构造利用链,将函数指针破坏漏洞(CFHP)转化为栈溢出漏洞(CFHP'),执行接下来的任意ROP链。见Figure2。
对比:见Figure3。常用的利用方法需触发漏洞2次,不可避免的触发缺陷路径中的访存错误,"single-shot"利用方法只触发漏洞1次即可执行接下来的ROP chain。
6. 设计
核心思路:
(1)breaking isolation with I/O functions
利用I/O函数绕过SMAP,copy_from_user
和copy_to_user
。见Figure4,先拷贝栈上数据出来,泄露canary,再向栈上拷贝rop chain进去。
(2)improving exploit success ratio with a single CFHP
利用两类gadget,blooming gadget
—控制寄存器,bridging-gadget
—绕过缺陷路径。
6.1 构造栈溢出
(1)stack-smashing gadget
直接跳转到这类gadget,构造栈溢出。例如copy_from_user(void* dst, void* src, unsigned long length)
,若满足以下条件,则会导致内核栈溢出:
a. dst(rdi)
指向当前内核栈;b. src(rsi
指向用户空间地址,使得拷贝内容可控;c. length(rdx)
大于当前栈帧的size。
Linux编程风格1:91%的copy_from_user
调用点,rdi指向内核栈,但rsi/rdx大小不确定。
(2)选择较短的返回路径(错误路径)
Linux编程风格2:99%的copy_from_user
调用都含有错误处理,即返回值非0则返回错误,这样可以快速退出,执行下一个gadget。见Table2。
目标:使copy_from_user
返回非0值。
方法:拷贝完一段数据后触发页错误,使得返回值非0,就能成功将数据拷贝到内核。
(3)触发页错误
方法:设置用户页1和页2相邻,页1放ROP,页2触发页错误;rsi=p1+PAGE_SIZE-n
,rdx=n+1
;copy_from_user
将返回1,表示有1字节未拷贝成功。见Figure5。
6.2 绕过Canary
(1)Stack-disclosure Gadget 栈泄露gadget 和 Auxiliary Function 辅助函数
栈泄露gadget:copy_to_user
。Linux编程风格3:594处copy_to_user
调用中,81%是将内核栈数据拷贝到用户空间。
辅助函数:用于创建相似的栈帧结构+canary,再call调用栈泄露gadget,使栈泄露gadget能正确返回并正常执行之后的gadget。
(2)泄露canary
要求:a. 正确构造copy_to_user
的参数;b. copy_to_user
返回值为非0;c. copy_to_user
返回时会检查canary;d. 栈上需正确布置返回地址以继续利用。
解决:a. 使用6.3节介绍的blooming gadget
控制rsi/rdx;b. 同copy_from_user
一样,利用userfaultfd触发页错误,返回非0值;c/d. 采用栈泄露gadget和辅助函数。
泄露canary原理:栈泄露gadget和辅助函数需满足,保存相同数量的寄存器、相同的canary位置、栈大小相差8字节(即返回地址)。利用CFHP执行到辅助函数,辅助函数先保存寄存器、创建栈帧,利用call rax
跳转到栈泄露gadget->1,copy_to_user
完成后触发页错误->2,走最短返回路径,返回前会检查canary->3,恰好用到辅助函数布置的canary。见Figure6。
6.3 整合->Single shot利用
目标:利用blooming gadget
控制寄存器rsi/rdx;利用bridging gadget
去组合链接gadget。
(1)blooming gadget
—控制rsi/rdx寄存器
启发:COOP[59],利用C++程序中的类型混乱,对象指针self通过rdi传递(函数地址和参数都通过这一个指针指向的对象传递)。
示例:见Table3。通过vma指针调用clear_range()
,参数也由vma控制,所以只要rdi可控,就能控制rdi/rsi/rdx。
条件:blooming gadget
起作用的前提是rdi可控,而CFHP通常可控rdi;否则若寄存器都不可控,只能劫持控制流,可利用gadget add rsp, 0x68; pop rdi; ret
,需满足栈上有连续2个8字节可控(rsp+0x68
和rsp+0x70
)。
(2)bridging gadget
——链接栈泄露+栈溢出
示例:见Table 4。regcache_mark_dirty
含2个间接调用map->lock
和map->unlock
。调用目标和参数都由rdi(map对象)控制。
// Table 4:
void regcache_mark_dirty(struct regmap *map) {
map->lock(map->lock_arg); // the 1st control-flow hijack
map->cache_dirty = true;
map->no_sync_defaults = true;
map->unlock(map->lock_arg); // the 2nd control-flow hijack
}
方法:先利用ret2dir[39]分配physmap页,并伪造数据对象;再设置好rdi后跳转到bridging gadget
。见Figure 7,A表示辅助函数+泄露gadget(copy_to_user),B表示栈溢出(copy_from_user)。
7.实现
代码量:8000行python,使用IDA Pro SDK[20] + angr[64]。
gadget布置方式:见Figure 8。
问题:CFHP上下文不同,导致对寄存器的控制情况不同,使得利用方式也不同。
方法:利用符号执行和深搜尝试所有可能的利用链组合。遇到以下情况则判断为不可用,以节省计算资源,一是内存访问到不可控内存;二是判断关键点的寄存器是否可控,泄露点的rdi/rdx,溢出点的rsi/rdx。
技术点:a. 处理路径爆炸,一是限制KEPLER最多执行20个基本块,二是扩展angr的ControlledData
—符号地址的具体化策略;b. 约束求解payload,angr的Z3消耗太多内存,KEPLER可以在约束求解完成后帮助释放内存;c. 构造栈溢出后需执行的ROP链,提权。
8. 案例分析与评估
8.1 setup
漏洞选取:16个真实漏洞+3个CTF题目,插入到kernel 4.15.0,含UAF/OOB。
配置:32-core Intel(R) Xeon(R) Platinum 8124M CPU and 256GB of memory 。对每个漏洞跑28个并发worker,符号化探索利用链。
8.2 single-shot方法的有效性
实验结果见Table5。
对比:10个有公开exp,只有5个能绕过威胁模型提到的保护机制;KEPLER能生成17个绕过威胁模型。Q、FUZE不行。
失败案例:CVE-2017-17053 和 CVE-2016-9793 均需访问用户数据才能控制rip,受SMAP限制。
8.3 工具的有效性
结论:a. 能生成多种利用链,丰富了利用方式;b. 不同的漏洞利用链个数不同,因为漏洞的状态机和上下文不同;c. 很高效,生成第1个利用链的时间很短。