kernel从小白到大神(二)
字数 2157 2025-08-23 18:31:08
Linux Kernel Pwn 从入门到精通(二):保护机制与绕过技术
一、内核保护机制详解
1. KASLR (内核地址空间布局随机化)
- 基本概念:类似于用户态的ASLR,通过基地址+偏移的方式实现随机化
- 未开启时的地址:
- 内核代码段基址:
0xffffffff81000000 - 直接映射区(direct mapping area)基址:
0xffff888000000000
- 内核代码段基址:
2. FGKASLR (函数粒度KASLR)
- 特点:KASLR的增强版,以函数粒度重新排布内核代码
- 实现方式:
- 将不同函数放到不同的节(section)上
- 使用
kernel_symbol结构体记录函数偏移:struct kernel_symbol { int value_offset; // 函数偏移量 int name_offset; // 符号名称偏移量 int namespace_offset; // 符号命名空间偏移量 };
- 查找函数地址:
- 通过
__ksymtab段查找符号表 - 使用
__start___ksymtab和__stop___ksymtab定义符号表范围 each_symbol_section函数遍历符号表段
- 通过
3. STACK PROTECTOR
- 功能:类似于用户态的canary,防止栈溢出攻击
4. SMAP/SMEP
- SMAP(Supervisor Mode Access Prevention):防止内核态访问用户态数据
- SMEP(Supervisor Mode Execution Prevention):防止内核态执行用户态代码
- 绕过方式:
- 篡改CR4寄存器(第20位控制SMEP开关)
- ret2dir攻击(利用内核线性映射区)
5. KPTI (内核页表隔离)
-
原理:内核空间与用户空间使用两组不同的页表集
-
未开启时的布局:内核和用户空间共用一个页全局目录表(PGD)
-
开启后的变化:
- 内核页表和用户页表连续放置在8KB内存空间中
- 用户页表只有少量内核代码映射(系统调用入口点等)
- 用户地址空间在内核页表中不再有执行权限
-
系统调用处理流程(以x86_64为例):
- 通过
entry_SYSCALL_64进入内核态 - 使用
SWITCH_TO_KERNEL_CR3切换到内核页表 - 系统调用处理完成后使用
SWITCH_TO_USER_CR3_STACK切换回用户页表
- 通过
-
绕过方式:
- 使用gadget修改CR3寄存器,然后通过
iretq/sysret返回 - 直接使用
swapgs_restore_regs_and_return_to_usermode函数返回 - 使用
signal(SIGSEGV, shell)捕获异常信号
- 使用gadget修改CR3寄存器,然后通过
二、关键攻击技术详解
1. ret2dir攻击
-
原理:利用内核线性映射区对物理空间的完整映射
-
特点:可以绕过SMEP/SMAP/PXN等隔离防护
-
实现步骤:
- 在用户空间喷射大量相同payload(mmap或堆喷)
- 随机挑选direct mapping area上的地址(高概率命中payload)
- 通过内核地址访问用户空间数据
-
限制:高版本内核中direct mapping area没有可执行权限,需配合ROP
2. pt_regs结构利用
- 背景:系统调用时会将所有寄存器压入内核栈,形成pt_regs结构
- 结构定义(x86_64):
struct pt_regs { unsigned long r15; unsigned long r14; unsigned long r13; unsigned long r12; unsigned long rbp; unsigned long rbx; 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; // 其他寄存器... }; - 利用方式:
- 当控制rip后,通过
add rsp,0xn;ret调整栈指针 - 利用pt_regs中残留的寄存器值构造ROP链
- 当控制rip后,通过
三、实战案例分析
案例1:MINI-LCTF2022 - kgadget
-
保护机制:开启SMEP/SMAP,未开启KASLR
-
漏洞点:
call [rdx](可控rbx) -
利用技术:
- 利用pt_regs残留的r8、r9控制执行流
- physmap spray喷射提权payload
- 通过direct mapping area访问payload
-
关键代码:
// physmap spray for(size_t i=0; i<25000; i++) { physmap[i] = mmap(0, page_size, PROT_READ|PROT_WRITE, 0x2|0x20, -1, 0); memcpy(physmap[i], payload, page_size); } // 控制执行流 __asm__( "mov r9, rsp_ret;" "mov r8, try_hit;" // 其他寄存器设置... "syscall" );
案例2:长城杯京津冀2024初赛Kylin_driver
-
漏洞点:栈溢出+bypass KPTI
-
利用技术:
- 泄露栈数据获取基地址和canary
- 使用
swapgs_restore_regs_and_return_to_usermode返回用户态 - 信号处理获取root shell
-
ROP构造:
size_t swapgs_restore_regs_and_return_to_usermode = vmlinux_base + 0xc01026; qdata[idx++] = rdi_ret; qdata[idx++] = 0; qdata[idx++] = prepare_kernel_cred; qdata[idx++] = movRdiRax_ret; qdata[idx++] = commit_creds; qdata[idx++] = swapgs_restore_regs_and_return_to_usermode; // 其他寄存器设置...
案例3:hxpCTF 2020-kernel-rop
- 挑战:绕过FGKASLR
- 解决方案:
- 在.text节范围内查找gadget
- 通过
__ksymtab获取函数实际地址 - 分阶段构造ROP链
四、实用工具与技巧
-
ROPgadget查找:
ROPgadget --binary vmlinux --opcode "4881c490000000" -
寄存器状态调试:
__asm__( "mov r15, 0xbeefdead;" "mov r14, 0x11111111;" // 其他寄存器设置... "syscall" ); -
地址泄露检查:
void binary_dump(char *desc, void *addr, int len) { // 以16进制和ASCII格式打印内存内容 } -
状态保存:
void save_status() { __asm__( "mov user_cs, cs;" "pushf;" "pop user_rflags;" "mov user_sp, rsp;" "mov user_ss, ss;" ); }
五、防御与绕过总结
| 保护机制 | 绕过技术 | 适用场景 |
|---|---|---|
| KASLR | 地址泄露、暴力破解 | 信息泄露漏洞 |
| FGKASLR | ksymtab查找、节内gadget | 有符号表访问权限 |
| SMEP/SMAP | CR4篡改、ret2dir | 控制流劫持 |
| KPTI | CR3修改、专用返回函数 | 系统调用路径 |
| Stack Canary | 泄露、不覆盖 | 栈溢出漏洞 |
通过深入理解这些保护机制的工作原理和绕过技术,可以有效地进行内核漏洞利用和防御方案设计。