内核攻击方法概述
攻击方法
- 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