万字长文:手把手带你入门 eBPF Pwn
字数 6397
更新时间 2026-03-12 13:42:52

eBPF 与 eBPF Pwn 入门教学文档

1. 什么是 eBPF

eBPF 是一项革命性的 Linux 内核技术,允许用户编写的程序在内核空间中安全、高效地运行。其核心思想是“可编程内核”。

1.1 核心概念

  • 附着点:eBPF 程序可以被附加到内核中的几乎任何地方,如网络数据包处理路径、系统调用、内核函数入口/出口、跟踪点等。文档以 seccomp 为例,说明其本身就是一个附着在系统调用上的 eBPF 程序,用于在系统调用前执行安全检查。
  • 安全模型:eBPF 程序由用户编写并提交给内核,这带来了安全隐患。为确保安全,内核通过 eBPF 验证器 对程序进行严格的静态分析和模拟执行检查,只有通过检查的程序才能被加载。
  • 执行架构:eBPF 程序运行在一个专用的、精简的虚拟机中。这个虚拟机有自己的寄存器集、指令集和栈。为了获得接近原生代码的性能,eBPF 字节码通常会被即时编译器 编译为目标平台(如 x86-64)的本地机器码。

eBPF Pwn 的核心目标:通过构造特殊的 eBPF 程序,利用验证器逻辑的漏洞,制造验证器对程序状态的判断与程序实际运行时状态之间的差异(即“不一致寄存器”),从而绕过安全限制,实现内核态代码执行或提权。

2. eBPF 程序架构

2.1 寄存器

eBPF 虚拟机包含 11 个 64 位通用寄存器(R0-R10)和一个程序计数器(PC),一个固定大小的栈(通常为 512 字节)。它们在 x86-64 上的映射关系如下:

eBPF 寄存器 映射 x86_64 寄存器 主要用途
R0 rax 函数返回值,辅助函数调用结果
R1 rdi 函数调用第一个参数(argv1),上下文指针
R2 rsi 函数调用第二个参数(argv2)
R3 rdx 函数调用第三个参数(argv3)
R4 rcx 函数调用第四个参数(argv4)
R5 r8 函数调用第五个参数(argv5)
R6 rbx 被调用方保存寄存器
R7 r13 被调用方保存寄存器
R8 r14 被调用方保存寄存器
R9 r15 被调用方保存寄存器
R10(只读) rbp 栈帧指针,指向栈底

2.2 验证器

验证器是 eBPF 安全的核心。它对提交的程序执行以下检查:

  1. 结构验证:确保程序无不可达指令,避免死循环(早期内核直接禁止循环)。
  2. 模拟执行与状态跟踪:验证器模拟执行每一条指令,跟踪所有寄存器和栈的状态变化,确保没有越界读写、非法指针解引用等不安全行为。

2.3 寄存器状态跟踪

验证器为每个寄存器维护一个 struct bpf_reg_state 结构,主要包含类型信息和值范围信息。

寄存器类型

  • NOT_INIT:未初始化,使用会导致验证失败。
  • SCALAR_VALUE:标量值(整数),可进行算术运算,但不能直接作为指针进行内存访问。
  • POINTER_TYPE:指针类型。验证器会记录指针指向的对象类型(如 PTR_TO_CTXPTR_TO_MAP_VALUEPTR_TO_STACK 等)以及允许的偏移范围。

关键数据结构

  • {s,u}{min,max}_value:记录有符号和无符号整数的最小、最大值。
  • struct tnum:跟踪寄存器中哪些位是已知的(value),哪些位是不确定的(mask)。例如 (0x0; 0x1) 表示低 1 位不确定(可能是 0 或 1),高位确定为 0。

2.4 验证器规则摘要

  • 标量与指针SCALAR 寄存器不能直接当指针解引用。
  • 指针运算
    • 允许 ptr + known_safe_offset
    • ptr + unknown_scalar 之后的结果不能直接解引用,需经边界检查。
    • 禁止 ptr + ptrptr - ptr 仅在两个指针指向同一内存对象时允许,结果变为 SCALAR
    • 指针只能进行 Add/Sub 运算以修改偏移,Mul, Div, Mod, Shift, And, Or, Xor 对指针非法。
  • ALU32 操作:会清零高 32 位。
  • 内核指针保护:不能将内核指针(如 sk_buff 地址)直接存入 Map 让用户态读取。

2.5 ALU Sanitation

这是针对指针运算的加固机制。当对一个指针寄存器进行加减某个偏移量时,内核会插入额外的指令来验证结果偏移的合法性,确保指针不会越出其被允许的活动范围。

  • 低版本绕过
    1. 构造“不一致寄存器”,使验证器认为指针值未变,但实际值已变,导致活动范围计算错误,从而越界访问。
    2. 当指针已处于其活动范围的边界时,加固指令中的 aux->alu_limit - 1 可能导致无符号数下溢回绕,使边界变得极大。
  • 高版本(v5.11.17+)加强:低版本的直接指针算术越界被禁止。高版本通常需要利用 bpf_skb_load_bytes 等辅助函数配合构造的越界参数来实现利用。

3. eBPF 指令与编程

3.1 指令编写辅助宏

推荐使用内核头文件中的宏(如 samples/bpf/bpf_insn.h)来编写指令,提高可读性。

3.2 主要指令类别

3.2.1 ALU 指令

逻辑 说明
BPF_ALU64_REG(OP, DST, SRC) dst = dst OP src 64位寄存器间运算
BPF_ALU32_REG(OP, DST, SRC) (u32)dst = dst OP src 32位寄存器间运算,清零高32位
BPF_ALU64_IMM(OP, DST, IMM) dst = dst OP imm 64位寄存器与立即数运算
BPF_ALU32_IMM(OP, DST, IMM) (u32)dst = dst OP imm 32位寄存器与立即数运算
常见 OPBPF_ADD/SUB/MUL/DIV/AND/OR/XOR/LSH/RSH/ARSH

3.2.2 赋值指令

逻辑 说明
BPF_MOV64_REG(DST, SRC) dst = src 64位寄存器拷贝
BPF_MOV32_REG(DST, SRC) (u32)dst = src 32位寄存器拷贝
BPF_MOV64_IMM(DST, IMM) dst = imm32 加载32位立即数到64位寄存器
BPF_MOV32_IMM(DST, IMM) (u32)dst = imm32 加载32位立即数

3.2.3 内存读写指令

逻辑 说明
BPF_LDX_MEM(SIZE, DST, SRC, OFF) dst = *(size *)(src+off) 从内存加载到寄存器
BPF_STX_MEM(SIZE, DST, SRC, OFF) *(size *)(dst+off) = src 从寄存器存储到内存
BPF_ST_MEM(SIZE, DST, OFF, IMM) *(size *)(dst+off) = imm 存储立即数到内存
BPF_LD_IMM64(DST, IMM) dst = imm64 加载64位立即数
BPF_LD_MAP_FD(DST, MAP_FD) dst = map_ptr 将 Map 的文件描述符转换为指针
内存大小标识:BPF_DW(8字节), BPF_W(4字节), BPF_H(2字节), BPF_B(1字节)。

3.2.4 跳转与调用指令

逻辑 说明
BPF_JMP_REG(OP, DST, SRC, OFF) if (dst OP src) PC += off 寄存器间比较跳转
BPF_JMP_IMM(OP, DST, IMM, OFF) if (dst OP imm) PC += off 寄存器与立即数比较跳转
BPF_CALL_FUNC(FUNC) r0 = helper_func(...) 调用内核辅助函数
常见条件码 OPBPF_JEQ(==), BPF_JNE(!=), BPF_JGT(> 无符号), BPF_JGE(>= 无符号), BPF_JLT(< 无符号), BPF_JLE(<= 无符号)。

3.2.5 其他重要指令

  • BPF_EXIT_INSN(): 结束程序,返回值在 R0 中。

3.3 编程注意事项

  1. 调用辅助函数(如 bpf_map_lookup_elem)后,必须检查返回值 R0 是否为 NULL,否则验证器会拒绝程序。
    BPF_CALL_FUNC(BPF_FUNC_map_lookup_elem),
    BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
    BPF_EXIT_INSN(),
    
  2. eBPF 程序末尾通常需要设置返回值并退出:
    BPF_MOV64_IMM(BPF_REG_0, 0),
    BPF_EXIT_INSN(),
    

4. 利用与攻击面 (eBPF Pwn)

4.1 核心目标:构造不一致寄存器

攻击的核心是找到验证器逻辑漏洞,制造一个寄存器,在验证器看来是某个值(例如 0),但在实际运行时是另一个值(例如 1)。这通常通过研究 adjust_scalar_min_max_vals 等验证器核心函数中的边界条件或错误假设来实现。

4.2 攻击原语:操控 BPF Map

创建 BPF_MAP_TYPE_ARRAY 类型的 Map 时,内核会分配一个 struct bpf_array 结构。

  • struct bpf_array 的第一个成员是 struct bpf_map map
  • struct bpf_map 的第一个成员是 const struct bpf_map_ops *ops,这是一个包含各种操作函数指针的虚表(例如 map_lookup_elem, map_update_elem)。
  • Map 的数据(value)存储在 struct bpf_array 之后的柔性数组里。

利用思路

  1. 泄露地址:利用越界读,将 bpf_array->map.ops (指向内核 .data 段的虚表)读出来,从而计算内核基址。
  2. 任意地址读写
    • 在可控的 value 区域伪造一个 bpf_map_ops 虚表。
    • 利用漏洞(如越界写)将某个 Map 的 map->ops 指针覆盖为指向我们伪造的虚表。
    • 当内核调用该 Map 的虚函数(如 update_elem)时,实际上会跳转到我们控制的函数指针,配合 map->map_type 等参数的篡改,可以实现任意地址读/写。

4.3 利用链概览

  1. 构造漏洞:编写 eBPF 程序,触发特定的验证器漏洞(如错误的位移运算处理),产生一个“不一致寄存器”。
  2. 越界访问:利用这个寄存器对指针进行运算,使验证器认为访问合法,实际却越界读写了相邻的 struct bpf_array 结构。
  3. 信息泄露:越界读出 ops 指针,计算出内核基址和关键函数地址。
  4. 篡改虚表:在 Map 的 value 区布置伪造的 ops,并利用越界写将目标 Map 的 ops 指针指向它。
  5. 实现任意读写:通过调用被劫持的 Map 操作函数,实现任意地址读/写原语。
  6. 提权:修改当前进程的 cred 结构体,或执行内核 ROP 链,获得 root 权限。

5. 调试与诊断

  • /proc 接口
    • /proc/sys/net/core/bpf_jit_enable:控制 JIT 编译(0 关闭,1 开启,2 开启并输出调试 trace)。
    • /proc/sys/kernel/unprivileged_bpf_disabled:控制非特权用户调用 bpf(2) 的权限。
  • 内核调试:可以通过在 array_map_alloc, sk_filter_trim_cap 等关键函数下断点,结合 JIT 编译后的代码区域,单步跟踪 eBPF 程序的执行。

6. 例题分析思路

6.1 D^3CTF 2022 d3bpf

  • 漏洞:内核 Patch 错误地处理了 BPF_RSH 指令。当右移位数 >= insn_bitness(64)时,验证器认为结果寄存器为已知的 0,但实际 CPU 执行时会将移位计数 & 63,导致实际执行 reg >> 0,值不变。
  • 利用:利用此漏洞构造一个验证器认为值为 0,实际值很大的“不一致寄存器”,用于与指针相减,实现越界访问 struct bpf_arrayops 域,进而泄露内核基址并篡改虚表。

6.2 UofTCTF 2026 extended-eBPF

  • 漏洞:内核 Patch 禁用了 ALU Sanitation,并修改了 is_safe_to_compute_dst_reg_range 中对于移位指令安全性的判断逻辑。
  • 利用:构造验证器认为值为 1,实际值为 0 的“不一致寄存器”(通过 1 ARSH reg,其中 regumin_val 为 0)。利用此寄存器进行后续计算,实现越界读写。通过篡改 Map 的 map_typeops 虚函数指针,将 map_update_elem 调用转化为对 map_get_next_key 的调用,并利用该函数的写操作实现任意地址写。

6.3 DownUnderCTF 2025 Rolling Around

  • 漏洞:内核为 eBPF 新增了 ROL 指令,但验证器模拟 32 位循环左移的算法 (>> (64 - imm)) 与 JIT 编译器生成的代码实际执行逻辑 (>> (32 - imm)) 不一致。
  • 利用:利用此不一致性构造“不一致寄存器”,进而实现越界读写和利用。

7. 总结

eBPF Pwn 是一个深度依赖对 Linux 内核 eBPF 子系统,特别是验证器实现细节理解的攻击领域。攻击者需要:

  1. 精读源码:深入分析 kernel/bpf/verifier.c,尤其是 adjust_scalar_min_max_vals 等关键函数。
  2. 理解状态跟踪:掌握验证器跟踪寄存器状态(类型、范围、tnum)的模型。
  3. 寻找逻辑差异:在验证器的模拟逻辑与 JIT 编译后的实际执行逻辑之间寻找差异,这是漏洞的主要来源。
  4. 熟悉利用模式:掌握通过篡改 BPF Map 虚表实现信息泄露和任意地址读写的通用利用链。

本教学文档基于提供的链接内容整理,涵盖了 eBPF 基础、架构、安全机制、编程方法及漏洞利用的核心知识框架,为进一步研究和实战提供了详尽的基础。

相似文章
相似文章
 全屏