【bsauce读论文】2023-CCS-RetSpill-内核栈内存ROP布置与提权技术

2024/05/21 Paper 共 4032 字,约 12 分钟

【bsauce读论文】2023-CCS-RetSpill:内核栈内存ROP布置与提权技术

基本信息

  • 原文标题:RetSpill: Igniting User-Controlled Data to Burn Away Linux Kernel Protections
  • 原文作者:Kyle Zeng, Zhenpeng Lin, Kangjie Lu, Xinyu Xing, Ruoyu Wang, Adam Doupé, Yan Shoshitaishvili, Tiffany Bao
  • 作者单位:Arizona State University
  • 关键词:Linux内核, 漏洞可利用性, 自动化评估, RetSpill
  • 原文链接:https://dl.acm.org/doi/10.1145/3576915.3623220
  • 开源代码:https://github.com/sefcom/RetSpill

1. 论文要点

论文简介:作者提出RetSpill,通过syscall将用户数据(ROP链)布置到内核栈上,然后结合控制流劫持(CFH)漏洞进行提权,能够绕过当前Linux内核上开启的所有防护机制(例如FG-KASLR)。作者还提出了新的防护机制。

主要内容:作者发现,通过syscall在内核栈平均可以布置11个ROP,足以构造任意读写和执行。

实验:通过对22个CFH漏洞的CVE进行测试,成功自动生成20个提权EXP。

2. 背景与介绍

防护机制:SMEP[47] / SMAP[12] / KPTI[64] / NX-physmap[31] / CR Pinning[63] / STATIC_USERMODE_HELPER[35] / RKP[56](不允许直接修改进程凭证)/ pt-rand[14](不允许直接修改内核页表来进行数据流攻击)/ RANDSTACK[53](随机化栈布局,防止利用未初始化使用漏洞) / STACK CANARY。

漏洞利用:目前最常见的是利用堆漏洞,先通过覆写堆对象上的函数指针来构造CFHP,然后组合其他利用原语来控制栈、执行ROP链,各种利用方法和所需的原语参见 Table 1。

1-Exploitation_Approach

栈迁移的局限:目前常用的方法是在堆上布置ROP,然后将栈指针指向堆上伪造的栈。本方法存在两个局限,一是不能直接重写payload,需要重新喷射包含payload的堆对象或再次触发漏洞,降低了漏洞利用的稳定性;二是依赖特定的内存布局、特定的ROP、额外的利用原语(例如寄存器控制)。本文提出的RetSpill方法需要的原语最少。

内核ROP:和用户空间ROP相比,内核ROP有两个要求。一是信息泄露,需要将信息传到用户空间,可先将信息存到某个寄存器,该寄存器在进行上下文切换时不会被清零;二是退出时要避免panic(可利用KPTI trampoline[38]返回用户空间、无限休眠[74]、调用do_task_dead()杀死当前任务)。

内核栈与Syscall:调用syscall时,会切换到内核栈,然后将用户上下文(寄存器)压到栈底部,将用户上下文称为 pt_regs,返回用户程序时会恢复用户上下文。

威胁模型:开启的保护机制类似Kepler[70],增加了FG-KASLR[34]。也即 SMEP, SMAP, KPTI, NX-physmap, CR Pinning, STATIC_USERMODE_HELPER, RKP, pt-rand, RANDKSTACK, STACK CANARY, FG-KASLR。假设已经有了CFH原语,且能够泄露内核基址。

3. RetFill利用

3-1. 数据注入

数据注入分类:本文主要关注直接数据注入。

  • 直接数据注入:通过syscall直接将数据(用户寄存器或用户内存)传入内核栈。例如,见Listing 1,poll调用 copy_from_user()将用户内存数据拷贝到内核栈上。
  • 间接数据注入:通过多个syscall来传递数据。例如,先调用open将数据存入内核堆,再调用readlink将数据载入内核栈。

2-poll

数据注入方法

  • (1)有效数据poll将0x1e0字节数据拷贝到stack_pps。如果攻击者控制了用户空间的file对象,通过file->f_op->poll劫持控制流时,就能控制内核栈上0x1e0字节的数据,再利用add rsp, X; ret gadget跳转到可控区域,执行ROP链。
  • (2)上下文寄存器:调用syscall时,内核栈上的pt_regs结构会保存用户上下文。
  • (3)调用约定:内核调用约定中,被调用者和调用者都需要保存寄存器(被称为callee-saved / caller-saved寄存器),一般在函数开头压栈,结尾出栈。两种方式布置数据:
    • 某些syscall会直接调用handler函数,将用户寄存器保存到pt_regs后,寄存器上还存有用户数据,handler函数内会把用户寄存器压栈。
    • syscall中的handler可能会调用其他helper函数,也会将用户寄存器压栈保存。
  • (4)未初始化内存:同一线程的上一syscall的栈数据仍存在栈上,如果在当前syscall函数中部劫持函数指针,就能避免栈初始化。可参考导向型栈喷射技术[45]来布置栈数据。例如,见Listing 2,原本会在recvmsg hadnler(第6行)初始化address对象,如果利用漏洞来覆写sock结构,控制sock->ops->recvmsg劫持控制流,这样就不会初始化address对象。

3-recvfrom

3-2. 执行ROP

跳转到ROP:可通过add rsp, X; ret gadget跳转到用户控制的ROP区域。

ROP链碎片化原因:一是不同的syscall数据注入能力有限,方法二最稳定;二是某些CFHP原语对参数有要求,例如CVE-2010-2959要求rdi为fd参数;三是用户可控的栈内存不连续,需跳转。

独立的ROP链:由于是在堆上构造的CFHP,所以EXP中各个线程都能触发,可创建子线程来触发ROP链,避免影响主线程。这样就能在不同的线程、不同的上下文下执行不同的ROP链,不需要多次触发漏洞。优势一,可在ROP链末尾调用do_task_dead优雅退出;优势二,可多次执行ROP链,绕过随机化保护,不影响稳定性。

任意读/写/执行:用户数据放栈上有利于无限次调用,每次调用时重新布置ROP即可。

3-3. 绕过保护机制

  • SMEP / SMAP / KPTI:不依赖用户数据,可绕过;
  • RANDKSTACK:在栈帧和pt_regs之间插入了随机偏移,只影响方法2(上下文寄存器)和方法4(未初始化内存)。方法2可能可用,由于随机偏移只有5bit是随机的,我们可以硬编码一个偏移,然后在ROP前置一些ret-sledret gadget),增大执行ROP的机率。如果系统禁用了panic_on_oops(发行版中只有CentOS是开启的),就能绕过RANDSTACK,可以在多个子进程中尝试执行payload,执行失败也不会触发崩溃。
  • STACKLEAK / STRUCTLEAK / INITSTACK:强制初始化栈,影响方法4(未初始化内存)。
  • FG-KASLR:boot阶段对内核函数地址随机化,但不会对asm内联代码随机化。RetSpill可以利用地址固定的gadget来构造任意读原语,动态泄露函数地址[38],绕过FG-KASLR。
  • KCFI / IBT:CFI保护机制,针对forward-edge CFH,部分阻止了CFHP攻击。例如,KCFI编译的内核中,__efi_call()没有验证其调用目标。还可以利用backward-edge来劫持控制流(示例参见[30])。
  • Shadow Stack:针对backward-edge CFH,本机制还没有在x86_64的Linux内核上实现,只能理论上分析如何绕过。可以利用forward-edge CFH,但不能执行ROP,可以利用JOP[3]/PCOP[55]。
  • CFI + Shadow Stack:无法绕过。

3-4. 半自动化

IGNI框架:半自动化生成RetSpill利用。生成的EXP仅执行commit_creds(init_cred)便返回到用户态。工作流见Figure 2。输入内核Image和CFHP的漏洞,输出 1) 负责栈移动的gadget; 2) ignite函数,负责数据注入,调用触发syscall,生成提权EXP。

技术实现:采用污点分析识别能够注入数据的syscall,采用符号执行angr生成ROP链。注意,目前没有用到未初始化内存来布置数据,因为无法确定性的控制内核栈内存

难点

  • 从大量syscall调用中识别出能触发CFH的syscall;
  • 识别用户可控的数据,且不干扰CFH原语的获得;
  • 如何在离散的区域布置ROP链。方法类似BOPC[28]

4-IGNI-Overview

4. 实验

数据选取:22个CVE,13个exp来自KHeaps[75],9个来自公开exp。

保护机制:SMEP, SMAP, KPTI, NX-physmap, CR pinning, STACK CANARY。未开启STATIC_USERMODE_HELPER, FG-KASLR, RKP, PT-Rand。

实验结果:见Table 4,在获得CFHP时在栈上平均可布置16.5个gadget;IGNI能自动生成20个EXP;采用自编写漏洞模块来测试保护机制绕过能力,能够成功绕过 RANDSTACK、KCFI / IBT、FG-KASLR。成功案例是CVE-2022-1786。

5-Overall-Results

5. 防护机制

思路:当前CFI+Shadow Stack还没有准备好,且依赖特殊硬件,本文方法是消除往内核栈布置用户数据的路径。

(1)上下文寄存器:可将上下文寄存器保存在task_struct上。

(2)未初始化内存:STACKLEAK / STRUCTLEAK / INITSTACK / RANDKSTACK 都能防护。

(3)有效数据 / 调用约定:在每个栈帧的底部都插入一个随机偏移,这样能防止攻击者猜中偏移使用到用户数据。如图。

6-defense

文档信息

Search

    Table of Contents