bsauce读论文:FUZE- Towards Facilita
摘要
目标:自动识别内核UAF漏洞可利用性,并为内核UAF漏洞利用提供便利。
技术:FUZE使用内核模糊测试以及符号执行来发现、分析和评估对于漏洞利用有帮助的系统调用。
结果:Linux 64位,15个真实内核UAF漏洞,FUZE可辅助内核UAF漏洞利用生成,并辅助一些内核缓解机制的绕过。
1.Introduction
内核UAF利用要点:确定内存对象释放的时机,根据释放对象的大小和堆分配器类型,选取合适的syscall并构造合适的参数,在释放对象处喷射特定的数据,使得之后对释放对象重引用时出现错误。
FUZE具体步骤:
a.输入一个只会造成崩溃的PoC;
b.使用内核fuzzing插入不同的syscall,改变崩溃的上下文(有利于堆喷);
c.使用符号执行追踪有利于利用的基元特征,根据总结的方法评估这些基元特征。
FUZE用处:
a.识别利用必须用到的syscall;
b.自动计算需往释放对象喷射的数据;
c.定位堆喷射和漏洞利用的时间窗;
d.绕过缓解机制SMEP/SMAP。
2.背景与挑战
手动利用步骤(已知PoC):
a.找到引发悬垂指针的syscall和引用该指针的syscall;
b.分析释放块的大小和分配器类型,找到能堆喷的syscall(在释放对象上布置数据);
c.根据PoC语义,计算堆喷射数据,调整syscall参数;
d.修正PoC程序。
Figure1-手动构造exploit流程2.1 内核UAF漏洞的PoC示例
13:socket()创建socket对象fd;
16-17:创建2个线程,循环调用setsockopt()、bind(),产生竞争条件;
3:setsockopt()在内核创建一个新对象,插入到内核的一个双向链表中;
每轮循环结尾调用close()释放新创建的对象,但是不会把setsockopt创建于内核的对象从链表中移除,留下1个悬垂指针(head node的next link);下一次迭代又创建新对象,setsockopt()创建内核对象并加入到队列,引用了头节点的NEXT悬垂指针,将prev指针指向自身,引发错误。流程见Figure2。
Table1-Poc示例 Figure2-UAF的PoC漏洞流程2.2 构造exp挑战
CVE-2017-15649功能是把新对象地址写入内核区域,但写数据、写地址不可控,关键是找到合适的syscall来进行堆喷射,控制释放对象的填入数据。
3.Overview
CVE-2017-15649分析:
对于CVE-2017-15649,若在close()之后插入sendmsg()(悬垂指针产生和引用之间),能够引用悬垂指针并往释放对象布置数据,改变了崩溃时的上下文,可能劫持程序控制流。
3.1 需求设计
(1)追踪漏洞对象、悬垂指针的出现和引用,便于分析者选择合适syscall和堆喷的时间窗。
(2)辅助合成新的PoC,引发不同的上下文。
(3)自动选择有利于利用的上下文。
(4)自动计算喷射数据。
3.2 顶层设计
运行PoC,结合ASAN与动态追踪方法,获取释放对象和时间窗口信息;通过fuzzing在时间窗口中插入代码,自动定位是哪个syscall能导致崩溃时的上下文改变,也即在时间窗口中引用释放对象的syscall;根据利用方法总结可利用性状态机,使用符号执行技术自动评估上下文的可利用性,这个方法很难,不如假定能控制释放对象的内容,将释放对象符号化,计算堆喷内容。
4.设计
主要分三个方面,关键信息搜集,负责收集利用必须的信息;内核fuzzing,负责识别引用悬垂指针的syscall;符号执行,负责确定哪种状态能使内核执行导向可利用的状态机,并计算堆喷数据。
4.1关键信息搜集
目标:收集利用必须的信息。
技术:KASAN[19]+动态追踪。
KASAN:
a.获取释放对象的基址和大小;
b.获取释放和产生悬垂指针的语句;
c.悬垂指针引用的语句。
动态追踪:
a.追踪内存分配/释放操作和相应PID,并与PoC关联;
b.用ftrace插桩获取PoC中syscall信息。
Figure5-KASAN日志与动态追踪路径其他信息:
见Figure 5。可看出释放对象地址是0xffff88003280e600,被kfree()释放。根据内存管理操作与对应的PID,可知道路径上syscall的生命周期,并找到与释放操作有关的close()。由于socket()调用不完整,可知是引用悬垂指针的syscall,从KASAN日志可见,具体引用指令是dev_add_pack+0x304/0x310。根据这些信息和源码,可弄清悬垂指针如何被引用以及追踪悬垂指针属于哪个变量。
4.2 内核fuzzing
目的:识别引用悬垂指针的syscall(改变运行时的上下文)。
(1)fuzzing上下文初始化
a.为了在悬垂指针出现后开始内核fuzzing,且不被PoC中悬垂指针引用所干扰,必须准确定位悬垂指针出现点和PoC中的引用点(根据KASAN+trace信息确定即可)。
b.消除PoC中悬垂指针引用的干扰。方法是重构Poc,采用插桩方法,悬垂指针产生后就导向内核fuzzing,见Table 2。对单线程PoC,插入return语句;对多线程PoC,如Table 1,每个迭代都有可能产生悬垂指针就在迭代结尾插入ioctl与特制内核模块(检查悬垂指针出现,并重定向到内核fuzzing)。
问题:KASAN只能检查悬垂指针引用,不能终止PoC,所以不能仅依靠KASAN。
解决:
a.从KASAN日志找引用DP的代码语句;
b.分析内核源码,找存释放对象地址的变量;
c.找变量的内存地址;
d.出现地址(目标对象)则重定向到内核fuzzing。
Table2-PoC重组(2)基于上下文的内核fuzzing
基于Syzkaller,采用syscall序列,根据分支覆盖反馈变异参数。
Fuzz方式:单线程—上下文初始化后就开始内核fuzzing;多线程—并发fuzz测试,见Table 3。
Table3-并发内核fuzz测试加速策略:
a.初始上下文与内核fuzz进行参数共享,例如,syscall的参数采用初始上下文中的文件描述符;
b.只fuzz部分syscall,搜索使用了该释放对象的内核模块。
4.3 符号执行
目标:确定哪种状态能使内核执行导向可利用的状态机,并计算堆喷数据。
(1)符号执行设置
时机:定位PoC中悬垂指针引用点(之前),暂停内核执行,将运行时上下文传给符号执行。
方法:
通过KASAN与动态trace获取DP引用代码,对应到二进制代码,下断点,检查DP是否出现(对象是否释放),若出现,则设置上下文,开始符号执行。
(2)识别可利用的状态机
首先将释放对象符号化。
基元特征说明:
a.控制流劫持:检查分支跳转指令,目标地址是否为符号值。
b.错误写:检查所有写指令的目标地址或源寄存器是否包含符号字节。
基元特征评估:
a.绕过SMEP:原理——先执行gadget
xchg eax,esp;ret,使栈位于用户空间也即将eax设置为用户空间地址,使用栈和内核指令构造ROP链,这样就没有直接执行用户空间的指令;控制流劫持——检查目标地址能否指向以上提到的gadget,检查eax是否在(0x10000,i)有效内存域之间;控制读写——误导内存管理在用户空间分配一个新对象,由于用户空间可控,新对象上可布置函数指针。
b.绕过SMAP:先设置rdi为预定值(如0x6f0),执行流转到native_write_cr4()。该函数负责设置CR4寄存器,CR4的第21位控制SMAP状态,可关闭SMAP。
注意:本文不考虑绕过KASLR[12,16]。符号执行可以自动计算释放对象上应该喷射的数据。
4.4技术讨论
问题1:指令访问地址是符号值,可具体化位一个用户空间地址,用户空间可控。
问题2:释放与DF引用发生在同一个syscall中,则不需要内核fuzzing,直接符号执行。
5.实现
FUZZ组成:动态追踪,内核fuzzing,符号执行。64位Linux系统,运行于QEMU并开启KVM。
(1)动态追踪
采用ftrace记录内存分配与释放信息,如kmalloc(),kmem_cache_allocate(),kfree(),kmem_cache_free()等。
(2)内核fuzzing
采用syzkaller[2]—覆盖引导型内核fuzzer,目的是找到引用DP的syscall并获得不同上下文,扩展syzkaller能基于进程ID和进程名检查内核崩溃。
(3)符号执行
采用angr[1],先在DP引用前建立内核快照;再用QEMU控制台接口恢复当前寄存器值、内核代码节和释放对象所在页;通过hook angr中的mem_read、mem_write来检测是否存在对未初始化内存的访问,根据需求转移目标页;扩展angr,添加具体化策略类来处理符号化地址。
6.实验评估
两种编译,一种开启KASAN与KOV,一种不开启;不考虑ASLR;漏洞利用代码见[3]。
原本只有5个能过SMEP,FUZE能过10个;原本能过2个SMAP,FUZE能过3个;并且FUZE有助于生成不同的exp。
Table4-FUZZ对可利用性的实验结果