KEPLER Facilitating Control-flow Hijacking Primitive Evaluation for Linux Kernel Vulnerabilities

前言

本文是总结论文: KEPLER Facilitating Control-flow Hijacking Primitive Evaluation for Linux Kernel Vulnerabilities 及其相关 ppt ,主要简述针对 Linux 内核控制流劫持提权过程中遇到的一些问题以及如何选取合适的 gadget 进行利用链的构造。

背景

  • OS 内核大多用 C/C++ 实现(Linux: C ; Windows: C/C++)。很容易出现内存破坏漏洞如:Out of Bounds Access, Use-After-Free, data race,在 C++中还可能出现 type confusion。然而漏洞缓解机制( Exploit Mitigation )的出现,使得漏洞利用愈发困难。
  • 自动生成漏洞利用系统的工作流程主要分为如下两步:1.识别漏洞利用原语。2.评估漏洞利用原语。具体来说,首先,通过 PoC 输入触发的崩溃路径,搜索预先定义的利用原语,即使得程序到可利用状态。 之后,在确定了漏洞利用原语之后,添加漏洞利用约束并执行约束求解以生成具体的输入以执行预定的漏洞利用技术。
  • 漏洞利用原语是一种可以使攻击者进行攻击的一种机器状态。常见的两种漏洞利用原语:Data flow,Control flow。

利用过程中的一些挑战

控制流劫持内核漏洞利用一般流程

  • 调整系统调用参数和内存布局
  • 获取控制流劫持原语(处于可利用的状态,例如 Buffer Overflow 后控制了 rip。)
  • ROP payload 执行( 基本方法是:commit_creds(prepare_kernel_cred(0)) -> 恢复上下文返回用户空间 -> execve(“/bin/sh”,NULL,NULL) )

利用流程的关键在于从劫持控制流到如何执行 payload的过程中绕过各种漏洞缓解机制。

kernel exploit mitigations

有了 SMEP/SMAP 保护,就不能直接发起传统的 ret2user/pivot2usr 攻击 。因为physmap区域不再可执行,故ret2dir攻击也不合适。借助对敏感数据(例如进程凭证和页表)的写保护,也不能通过将CFHP(control-flow hijacking primitive,控制流劫持原语)转换为内存写原语来直接覆盖这些数据以提升特权。攻击者可能会想到 cr4-pinging 攻击,通常需要两个CFHP:一个用于加载cr4寄存器,另一个用于启动 ret2user/pivot2usr。然而,这通常是不可行的,因为基于虚拟化的虚拟机管理程序可以通过检查 vmexit 来轻松检测出 cr4 寄存器的行为。即使没有对 cr4 寄存器的保护,利用 cr4-flipping 攻击或类似的利用多个 CFHP 的利用此 CFHP 的利用技术仍然面临接下来存在的问题。

ill-suited exploit primitive

Stack pivoting 是ROP攻击中至关重要的步骤,尤其是在攻击者无法控制堆栈上的内容(例如,CFHP并非由堆栈溢出导致的情况)时。 在用户空间中,许多堆漏洞利用都是依靠栈迁移 gadget(例如,libc中的函数swapcontext()setcontext() )来引导ROP攻击。

但是,在目标内核中很难通过此CFHP将堆栈指针迁移到可控的内存区域。主要原因如下:首先,由于SMAP的存在,不能使用传统的 gadget 如 xchg eax,esp; ret 返回用户空间;其次,在内核空间进行栈迁移又缺少类似 xchg r**,rsp; ret, mov rsp,[r**]; jmp rxx , mov rsp,r**; ret 这种连续的 gadget,又或者可用的gadget 不可避免的使得利用路径陷入 pitfall (例如 xchg rsp, r14 ; jmp rsp虽然可以进行栈迁移,但会造成panic );最后,如果使用 call copy_from_user(dst, src, size)从用户态拷贝预先设置好的栈布局进行迁移,则需要控制 rdi、rsi、rdx 三个寄存器,通常较为困难。

exploit path pitfall

尽管 Linux 内核中没有任何 gadget 直接提权,但是最好寻找能只触发一次漏洞(“single-shot” exploitation)从而完成利用的 gadget,因为其仅需要单个 CFHP,并且可以稳定地在内核上下文中执行任意代码。 具体来说,如下图右图所示,“single-shot” exploitation 可以用单个 CFHP 完成利用,因此能够在 CFHP 返回后规避利用路径上的 pitfall 。下面两张图,左图表示触发两次漏洞来构造利用链很可能引发 pitfall 而造成利用失败,右图表示只触发一次漏洞来构造利用链的情况。

“single-shot” Exploitation 介绍

“single-shot” Exploitation主要基于两个关键的思路:

  • 使用 I/O 函数(如 copy_from_user()copy_to_user() )绕过 SMAP (在I/O交互时保护将临时禁用)以此打破用户态到内核态的数据隔离,并通过单个 CFHP 提高利用成功率。

  • 找到几类合适的 gadget 并将其组合,使得只使用单个 CFHP 实现利用成为可能。

下图展示了 “single-shot” Exploitation 中各种 gadget 的组合方式,首先使用 Blooming Gadget 来提升 CFHP 的寄存器控制数量(如仅能控制 rdi 到控制更多寄存器);Bridging Gadget 用来绕过利用过程中可能出现的 pitfall,第一个间接跳转可以用来绕过保护措施,第二个间接跳转用来跳转到 ROP 链进行提权;Stack Disclosure Gadget 利用copy_to_user()泄露内核栈信息,从而在做溢出时绕过canary;Stack Smashing Gadget 可以通过将任意长度的 payload 从用户空间传输到内核堆栈辅助利用,而无需假设堆栈位置已知。

Blooming Gadget

为了使得CFHP可以控制更多寄存器,因此引入 Blooming Gadget。 尽管Linux是用C编写的,但其代码却充分展现了面向对象编程的特性。 通常,“ self” 对象作为函数的第一个参数通过 rdi 传递 ,函数包含使用函数指针的间接调用,该指针位于作为参数传递的对象中。 因此可以让 CFHP 承担这些职能,以滥用类型混淆。

上图为一个 Blooming Gadget 的示例。内核函数 aliasing_gtt_unbind_vma()包含一个间接调用,该调用带有三个参数,这些参数通过解引用第一个参数* vma来计算。假设有一个可以控制 rdi 的 CFHP,即可在第3行 同时控制 rdi,rsi 和 rdx。注意,只有在 rdi 开始可控的情况下,Blooming Gadget 才起作用。

Bridging Gadget

下图中显示的regcache_mark_dirty()函数就是这样一个 Bridging Gadget,它包含两个间接调用,第2行的map-> lock和第5行的map-> unlock

bridging_gadget

与这两个间接调用相关的函数指针包含在函数的第一个参数引用的数据对象中。为了获得这两个函数指针的控制,可以首先使用ret2dir 来分配 physmap 页面。 然后,将寄存器 rdi 指向适当的位置,并将 rip 设置到 Bridging Gadget 的入口位置。

如上图所示,假定在A和B位置构造的数据代表辅助 gadget 的地址以及负责泄漏 canary 的 Stack Disclosure Gadget,以及分别与 stack smach 有关的 gadget 的入口地址。 然后,通过执行Bridging Gadget,可以使用第一个间接调用来第一个泄漏 canary 。 在调用返回到 copy_to_user() 之后,连续的间接调用之间没有对第二个函数指针施加附加约束的操作。 因此,可以使用第二个函数指针执行 stack smash,而不会发生意外终止。

Stack-Smashing Gadget

利用 copy_from_user(void* dst, void* src, unsigned long length)函数可以绕过 SMAP 保护获取用户空间的数据到内核空间。

从攻击者的角度来看,在调用copy_from_user() 之前需要满足以下三个要求,则可能导致内核堆栈溢出:

  1. 参数 dst(如 rdi)指向当前内核堆栈;
  2. 参数 src(如 rsi)指向任何用户空间地址,以便其内容可由攻击者控制;
  3. 参数 length(如 rdx)大于当前的堆栈帧导致内核堆栈溢出。

在Linux4.15中有 91% 的 copy_from_user()函数将目标dst设置为内核堆栈上变量的地址,因此会将用户数据复制到内核堆栈中,至此可以满足要求1。 但是,并不能保证一开始就能劫持 rdirsirdx,即并不能满足要求2和3。 后文的 Blooming gadget 会解决相应问题。下图展示了 Stack-Smashing Gadget 在内核中的源码和汇编代码。

Stack Disclosure Gadget and Auxiliary Function

Stack Disclosure Gadget 具体来说是选取用来将数据从内核堆栈复制到用户空间的copy_to_user()函数。建立了将数据从内核堆栈迁移到用户空间的通道,可以利用该内核功能的特性来泄露内核堆栈上的 canary。Auxiliary Function 应具有堆栈canary保护,并在其自己的建立栈帧的函数序言之后包含一个可控的间接调用。 它的堆栈框架布局可以与 Stack Disclosure Gadget 配对以形成“完整”堆栈框架,并通过在堆栈上放置有效的 canary 来绕过检查。

上图展示了一个泄露 canary 的流程。将CFHP重定向到 Auxiliary Function,在 Auxiliary Function 的序言之后,该函数保存了寄存器并建立了堆栈帧,间接调用在①中将call rax设置为 Stack Disclosure Gadget。然后copy_to_user() 将当前堆栈帧的内容复制到用户空间,触发page fault 以强制copy_to_user()的返回值为非零,这是因为采用了短返回路径如②。 在函数返回之前,执行堆栈 canary 完整性检查③,因为 Auxiliary Function 在当前堆栈帧中放置了有效的堆栈 canary,所以 canary 检查成功通过并返回到 Auxiliary Function 的调用者。

实现

上图显示了 KEPLER 自动执行利用 CFHP 进行利用所必需的分析任务。KEPLER 先从给定内核镜像中通过静态分析提取出可用的 gadget ,之后通过符号执行将备选的 gadget 组合成最终的利用链。基本思想是使用 Linux 内核中的CFHP引导传统的ROP攻击。 在更高层面上,通过 “single-shot” 利用链将基于函数指针损坏的原语(CFHP)转换为基于堆栈溢出的原语(CFHP’)。

总结

论文作者针对 Linux 通过控制流劫持提权过程中遇到的一些问题总结出一种可以绕过当前大多数缓解措施且稳定的 利用链构造方法,并且编写出相关工具可以自动化生成利用链。本文并未对该工具做详细介绍,感兴趣的同学可以从参考资料中阅读其工具源码。

参考资料

[1]https://github.com/ww9210/kepler-cfhp/tree/master/slides
[2]http://www.personal.psu.edu/yxc431/publications/KEPLER.pdf

4 Likes

这个是属于哪个方向?

严格来讲,这篇论文主要介绍的是自动生成利用链的工具,但他工具只开源了一部分,我这边没跑起来。所以就总结了一下论文前半部分绕过大多数保护机制的通用利用链生成的思路。

2 Likes

c语言难道就没有type confusion吗? 哈哈,好久没碰内核来

这篇文章我可以理解为 在不调试的前提下,通过分析cfg能把各个寄存器的状态在触发漏洞那一点计算出来?

他工具开源出来的大概分两部分:一部分是通过ida脚本收集那些gadget,而且vmlinux是需要符号表的。另一部分是接收ida脚本收集的gadget和保存漏洞触发时的qemu的快照,根据这两个输入用angr去符号执行生成rop链,最后生成exp。

emmmm, 我觉得里面几个gadget的想法挺亮,其他还好。