内核攻击方法概述

攻击方法

  • ROP
    • ret2usr
    • pt_regs
    • sycrop
    • ret2dir
  • heap
    • heap spray
    • heap overflow
    • double free
    • Cross cache overflow
    • page level heap fenshui
  • Race Condition
  • USMA
  • 基于idt的内存搜索

ROP

ret2usr

  • 由于KPTI的出现,ret2usr实际上已经不可用了。简单来说,ret2usr的核心就是利用内核的ring 0权限,执行用户空间的代码来实现提权。
  • 绕过SMAP与SMEP SMAP和SMEP是 x64 限制内核和用户空间的数据访问的一个架构功能,通过CR4寄存器的低位来判断是否开启。 开启后 从内核态访问用户态的数据会直接panic,因此通过在ROP链中插入 修改 cr4 寄存器的gadget即可绕过
  • gdb 无法查看 cr4 寄存器的值,可以通过 kernel crash 时的信息查看。为了关闭 smep 保护,常用一个固定值 0x6f0,即 mov cr4, 0x6f0。

pt_regs 与 KROP

在5.xx版本(笔者还没有检查具体是哪些版本),或者高版本没有开启 CONFIG_RANDOMIZE_KSTACK_OFFSET

  • pt_regs是进入内核态时,压入栈中的结构
struct pt_regs {  
/*  
* C ABI says these regs are callee-preserved. They aren't saved on kernel entry  
* unless syscall needs a complete, fully filled "struct pt_regs".  
*/  
unsigned long r15;  
unsigned long r14;  
unsigned long r13;  
unsigned long r12;  
unsigned long rbp;  
unsigned long rbx;  
/* These regs are callee-clobbered. Always saved on kernel entry. */  
unsigned long r11;  
unsigned long r10;  
unsigned long r9;  
unsigned long r8;  
unsigned long rax;  
unsigned long rcx;  
unsigned long rdx;  
unsigned long rsi;  
unsigned long rdi;  
/*  
* On syscall entry, this is syscall#. On CPU exception, this is error code.  
* On hw interrupt, it's IRQ number:  
*/  
unsigned long orig_rax;  
/* Return frame for iretq */  
unsigned long rip;  
unsigned long cs;  
unsigned long eflags;  
unsigned long rsp;  
unsigned long ss;  
/* top of stack page */  
};

我们注意到,这些内容,由用户态的寄存器决定,可以由我们控制。

因此这些部分可以用于布置ROP链, 当劫持到内核某个结构体的函数指针时,只需要寻找到一条形如 “add rsp, val ; ret” 的 gadget 便能够完成 ROP

具体而言,当通过syscall触发进入内核态前,我们通过在用户态控制所有寄存器,之后,触发syscall时,在syscall_entry 会将用户态的所有寄存器压入栈中来保存运行状态,这时,如果我们能劫持控制流,并通过类似 add rsp, val ; ret 的gadget来迁移栈,在我们可以控制的pt_regs上进行ROP

然而,在之后的内核版本中,加入了CONFIG_RANDOMIZE_KSTACK_OFFSET , 使得在进入内核时,会产生一个随机栈偏移,使得此利用的稳定性下降。

ret2dir

  • 这个没太懂啥意思 内核堆区 direct_mapping_arean 存在对于整个物理内存的映射,因此,通过mmap在用户态喷射的匿名页面,实际上也从此分配。

通过mmap大量分配,可以获取到 kernel 上一块近乎连续的物理内存,因此,通过不断堆喷布置gadget滑块,然后随机选择一个内核基地址进行栈迁移,最终就有很大概率命中我们写入的页面。

sycrop

通过下硬件断点在用户态触发的方式,可以将寄存器内容推送到与 per_cpu_entry_area 固定偏移的DB stack上,而在linux 6.2之前, per_cpu_entry_area 没有加入随机化,地址固定,所以可以达到在内核固定地址造ROP链的手段

work_for_cpu_fn 这实际上是一个tricks,在内核很难ROP时,可以利用

static void work_for_cpu_fn(struct work_struct *work)

{

  struct work_for_cpu *wfc = container_of(work, struct work_for_cpu, work);
  wfc->ret = wfc->fn(wfc->arg);

}

在劫持rsi的情况。

这个函数可以实现执行一次函数调用,并将返回值保存

overview

注意到,上述列出的几个攻击方法,实际上核心问题就是ROP链写在哪些地方。

pt_regs: 写在内核栈上 ret2dir: 写在direct mapping arena sycrop: 写在加入随机化的区域 由于ROP可以很方便劫持控制流,所以使用ROP攻击内核时,一般使用 commit_cred 进行提权

遗憾的是,在高版本内核,由于CFI的引入,很多时候难以找到完善的gadget进行利用,限制了ROP的使用

heap

todo