【bsauce读论文】KEPLER-新型内核ROP构造方法

本文来自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。

【bsauce读论文】KEPLER-新型内核ROP构造方法_第1张图片
Figure1-path_pitfall.png

解决:避免访存错误,可用约束求解求出正确输入,但开销过大。理想方法是绕过缺陷路径,但堆喷不能控前8字节是硬伤。即便能劫持控制流,还是很难突破hypervisor保护下的SMAP。

(3)不合适的漏洞原语

一般利用栈迁移去执行用户空间的ROP链,但是有难点。一是利用xchg eax,esp; ret绕不开SMAP保护;二是有效gadget不足,如xchg r**,rsp; ret, mov rsp,[r**]; jmp rxxmov 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。

【bsauce读论文】KEPLER-新型内核ROP构造方法_第2张图片
Figure2-KEPLER_overall.png

对比:见Figure3。常用的利用方法需触发漏洞2次,不可避免的触发缺陷路径中的访存错误,"single-shot"利用方法只触发漏洞1次即可执行接下来的ROP chain。


【bsauce读论文】KEPLER-新型内核ROP构造方法_第3张图片
Figure3-single_shot_and twice.png

6. 设计

核心思路:

(1)breaking isolation with I/O functions

利用I/O函数绕过SMAP,copy_from_usercopy_to_user。见Figure4,先拷贝栈上数据出来,泄露canary,再向栈上拷贝rop chain进去。

【bsauce读论文】KEPLER-新型内核ROP构造方法_第4张图片
Figure4-single_shot_overview.png

(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。

【bsauce读论文】KEPLER-新型内核ROP构造方法_第5张图片
Table2-copy_from_user-retvalue.png

目标:使copy_from_user返回非0值。

方法:拷贝完一段数据后触发页错误,使得返回值非0,就能成功将数据拷贝到内核。

(3)触发页错误

方法:设置用户页1和页2相邻,页1放ROP,页2触发页错误;rsi=p1+PAGE_SIZE-nrdx=n+1copy_from_user将返回1,表示有1字节未拷贝成功。见Figure5。

【bsauce读论文】KEPLER-新型内核ROP构造方法_第6张图片
Figure5-page_fault.png

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。

【bsauce读论文】KEPLER-新型内核ROP构造方法_第7张图片
Figure6-leak_canary.png

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+0x68rsp+0x70)。

【bsauce读论文】KEPLER-新型内核ROP构造方法_第8张图片
Table3-blooming_gadget.png

(2)bridging gadget——链接栈泄露+栈溢出

示例:见Table 4。regcache_mark_dirty含2个间接调用map->lockmap->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)。

【bsauce读论文】KEPLER-新型内核ROP构造方法_第9张图片
Figure7-use_bridging_gadget.png

7.实现

代码量:8000行python,使用IDA Pro SDK[20] + angr[64]。

gadget布置方式:见Figure 8。

【bsauce读论文】KEPLER-新型内核ROP构造方法_第10张图片
Figure8-exploit_chain.png

问题: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限制。

Table5-experiment-result.png

8.3 工具的有效性

结论:a. 能生成多种利用链,丰富了利用方式;b. 不同的漏洞利用链个数不同,因为漏洞的状态机和上下文不同;c. 很高效,生成第1个利用链的时间很短。

你可能感兴趣的:(【bsauce读论文】KEPLER-新型内核ROP构造方法)