Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
SPiCa — 基于eBPF的Linux rootkit检测器,使用多通道交叉视图分析(sched_switch、NMI、/proc)来检测DKOM、tracepoint篡改和进程隐藏,并具备硬件级完整性验证。 | Kitploit
工具/GitHubGitHub/0xkirisame/spica
防御工具危害指标 (IOC) 管理取证分析恶意软件分析二进制分析入侵检测论文与研究学习与教育事件响应异常检测
GitHub0xkirisame/spica

SPiCa

1046292个月前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

基于eBPF的Linux rootkit检测器,使用多通道交叉视图分析(sched_switch、NMI、/proc)来检测DKOM、tracepoint篡改和进程隐藏,并具备硬件级完整性验证。

查看仓库

SPiCa

系统进程完整性 & 跨视图分析

SPiCa

"我要歌唱,所以闪耀吧,SPiCa……"

SPiCa 是一款基于 eBPF 的 Linux 内核级恶意软件检测器,用 Rust 编写。其名称来源于初音未来的歌曲《SPiCa》及其所指的恒星——角宿一(室女座α星),即室女座中最亮的那颗星。肉眼看似一颗星的它,实际上是一对分光双星:两颗恒星相互绕转,如果不测量它们的光谱,就无法区分它们是独立的天体。SPiCa 将相同的原理应用于内核观测:通过物理上不同的机制,多个独立通道测量相同的内核状态,抑制其中一条通道的内核级恶意软件会在其他通道中暴露无遗。

免责声明: 本代码库中有相当一部分代码是借助 GLM 生成或重构的。我们进行了严格的测试和迭代设计,但在投入生产前,请自行审查代码的安全性和性能。


目录

  1. 威胁模型
  2. 架构概述
  3. sched_switch 观测通道
  4. NMI 完整性通道
  5. 检测逻辑
  6. 验证器限界地址保密性
  7. LSM 映射访问门控
  8. 密钥管理与混淆
  9. 绑定 PCR 的 TPM 密封(设计)
  10. 纵深防御
  11. BTF Bug 事件
  12. 已知局限性与攻击面
  13. 构建与运行
  14. 路线图
  15. 术语表

1. 威胁模型

受限攻击者:eBPF 内核级恶意软件

SPiCa 的设计目标是击败 eBPF 受限攻击者——即拥有提升权限(CAP_BPF 或 CAP_SYS_ADMIN)并将特权 eBPF 程序加载到内核中的攻击者。此类攻击者本质上弱于 LKM 内核级恶意软件,因为 BPF 验证器施加了严格的限制:

限制对攻击者的影响
有界循环(每个程序约 100 万条指令)无法扫描全部内核内存
指针安全性(直接解引用需要可证明有效且有界的对象)无法跟随任意指针
受限辅助函数(按程序类型白名单)无法调用 kmalloc、vmalloc 或任何内核分配函数
无法任意写内核(bpf_probe_write_kernel 受到严格限制)无法修改 task_struct、tracepoint funcs 数组或 BPF 程序字节码
映射访问限定于已加载的引用无法访问属于其他程序的映射(除非通过 ID 枚举——见 §7)

当启动后通过 LSM 门控阻止了 LKM 时,这种受限攻击者便是实际剩余的威胁。SPiCa 的抗规避机制是针对这一威胁校准的——每项防御都如实说明其所覆盖和未覆盖的范围。

非目标

  • 国家级内核漏洞利用——无需 init_module 即可造成任意内核写入的内存破坏。SPiCa 通过阻断较易实现的 LKM 途径来提升防御底线,但并未封顶攻击能力。
  • 在 SPiCa 启动前已加载的恶意 LKM——纵深防御(安全启动、模块签名、IMA)是启动窗口期所必需的。
  • 验证器漏洞利用——如果 BPF 验证器存在缺陷(历史 CVE:CVE-2020-27194、CVE-2022-23222、CVE-2023-2163),攻击者将脱离受限模型,获得任意内核代码执行能力。这是一个独立的威胁类别。SPiCa 的防御仅在验证器正确的前提下有效。

SPiCa 是纵深防御栈中的最后一道防线,而非其上层防御的替代品。


2. 架构概述

SPiCa 运行四个挂载到内核钩子的 eBPF 程序,外加一个用户态检测引擎,该引擎将它们的输出与系统对自身的视图(/proc)进行交叉关联。

三条观测通道,每条通道仅能通过一种成本递增的机制被压制

通道机制压制方式压制成本
/procprocfs 目录枚举DKOM(从任务列表中取消进程链接)低——内核级恶意软件的成熟技术
sched_switch调度器上的 eBPF tracepoint从 funcs 数组中分离程序;过滤上下文中等——需要对 funcs 数组进行内核写入
NMI硬件性能计数器中断 → eBPF 性能事件挂接 IDT/NMI 处理程序几乎必然导致内核恐慌——硬件中断,不可屏蔽

核心架构特性:内核级恶意软件无法在不使压制行为本身变得可检测或破坏系统稳定的情况下,同时压制所有三条通道。 压制 NMI 需要修改 IDT(中断描述符表),这在大多数内核上会导致系统崩溃。这就是"活炸弹"——攻击者通往完全盲区的唯一路径,很可能导致系统崩溃。

检测模型——三句话```

sched sees it, /proc doesn't → DKOM NMI finds sched's heartbeat stopped → TAMPER sched goes globally silent, /proc != empty → SILENT

每个检测类别都是一个差分判决:两个或多个通道之间的差异。检测引擎是一个纯函数,基于注册表 + /proc 快照 + 通道时间戳 —— 无 I/O、无副作用、完全可进行单元测试。

### NMI 重新设计:从观察到完整性

在原始设计中,NMI 是第二个进程观察通道,它对 CPU 进行采样并报告当前运行的任务。这是冗余的:sched\_switch 已经观察调度,而 NMI 通过不同的机制采样相同的数据。这种冗余消耗了每个 CPU 每秒约 1000+ 个环形缓冲区事件,而这些事件 99.999% 的情况下只是确认“是的,调度程序正在执行其工作。”

在重新设计的架构中,**NMI 被从进程观察重新定位为跟踪点完整性验证**。它不再报告哪个进程在 CPU 上运行,而是通过读取一个共享的 `.bss` 心跳来验证 `sched_switch` 是否真正在运行。这带来了:

1. 消除了约 99% 的 NMI 环形缓冲区流量(接近零稳态事件)
2. 直接检测跟踪点分离、抑制以及 BTF 附加失败(最初的 BTF 错误——参见 [§11](#11-the-btf-bug-incident))
3. 从硬件中断运行,位于跟踪点分发路径之外——免疫于 `bpf_override_return`、kprobe 拦截和函数数组操作
4. 通过直接内存访问读取 `.bss` 全局变量,而非 BPF 辅助函数——免疫于对辅助函数的 `fmod_ret`

---

## 3. sched\_switch 观察通道

一个挂载在 `sched_switch` 跟踪点上的 eBPF 程序在每次内核将进程调度到 CPU 上时触发。它使用传统的(非 BTF)固定偏移读取,直接从跟踪点参数中读取传入任务的 PID 和 comm:```
ctx.read_at::<u32>(56)    → next_pid
ctx.read_at::<[u8;16]>(40) → next_comm

故意不使用 BTF/CO-RE。 追踪点的参数布局在不同内核版本间是稳定的(它是追踪点 ABI 的一部分)。使用硬编码偏移量避免了 BTF 解析结构导航的内核版本脆弱性。这是一个有意的设计选择,记录在 §11 中。

每次调用时,程序执行以下操作:

  1. 从追踪点上下文读取 next_pid 和 next_comm
  2. 过滤掉空闲任务(PID 0)
  3. 使用 BASE_KEY 对 ProcessInfo 结构体进行 XOR 混淆
  4. 提交到 sc_sched 环形缓冲区
  5. 将 bpf_ktime_get_ns() 写入 .bss 全局变量 SCHED_HEARTBEAT — NMI 完整性检查器监视的心跳

程序在此上下文中故意避免使用 bpf_get_current_pid_tgid()。在 sched_switch 时刻,“current”是传出任务,而不是传入的任务。追踪点参数提供了正确的(传入)进程标识。

时间基准纪律

检测引擎使用单一的单调时间基准:自 SPiCa 进程启动以来的纳秒数(从 run_detection() 入口开始的 Instant::now())。当调度事件到达时,事件处理程序存储调用者提供的 now(进程本地纳秒),而不是事件中嵌入的 eBPF bpf_ktime_get_ns() 值。这避免了将内核启动时间与进程本地时间混合时可能出现的时间基准不匹配——早期版本中存在此错误,导致所有活性谓词静默失败。


4. NMI 完整性通道

设计:.bss 心跳,无 BTF,无内核结构遍历

NMI 程序附加到硬件性能计数器 (HW_CPU_CYCLES) 上,该计数器在硬件中断级别触发。NMI 无法通过 cli 禁用——抑制它们需要重新编程 PMU 或挂钩 IDT,这两种操作都很明显且具有破坏性。

完整性检查机制非常简单:``` .bss (shared within SPiCa's ELF object — all programs see the same memory): SCHED_HEARTBEAT ← written by sched_switch on every invocation NMI_LAST_HB ← NMI's record of the last heartbeat it observed NMI_FIRST_TICK ← first NMI invocation timestamp (grace period reference) NMI_LAST_EMIT ← throttle: last event emission timestamp

sched_switch runs → SCHED_HEARTBEAT = bpf_ktime_get_ns()

NMI fires (once per second, throttled): if SCHED_HEARTBEAT == NMI_LAST_HB: if past grace period (5 seconds since first NMI tick): → sched_switch stopped running → emit event_type=1 (TAMPER) else: → sched_switch is alive → emit event_type=0 (heartbeat OK) NMI_LAST_HB = SCHED_HEARTBEAT

### 为什么这能检测到范围内的每一种攻击

| 攻击 | 发生了什么 | NMI 观察到的情况 |
|---|---|---|
| 从 funcs 数组中分离出的 Tracepoint | sched_switch 停止执行 | `SCHED_HEARTBEAT` 冻结 → 侦测到篡改 |
| BTF 错误 / 附加失败(静默) | sched_switch 从未执行 | `SCHED_HEARTBEAT` 保持为 0 → 侦测到篡改(宽限后) |
| 对调度函数执行 `bpf_override_return` | 如果它阻止了 tracepoint 触发 | `SCHED_HEARTBEAT` 冻结 → 侦测到篡改 |
| 字节码原地修补 | 需要任意内核写入(LKM 级别) | 不在 eBPF 威胁模型范围内 |
| 环形缓冲区消费者指针被操纵 | sched 事件未到达用户空间 | `SCHED_HEARTBEAT` 仍在推进(程序在运行)→ 无虚假篡改;用户空间通过 `max(sched_last)` 过期检测到 → 静默 |

### 为什么特别选择 `.bss`

`.bss` 全局变量存储在 BPF 程序的内部数据段中,由加载程序管理的内部数组映射支持。它们:

- **不可单独固定** — 不会作为命名映射出现在 `/sys/fs/bpf/` 中
- **无法通过 `bpf_map_update_elem` 钩子截获** — `.bss` 写入是直接内存存储,而非映射更新系统调用。旧的 `sc_canary` 机制(通过比较 `.bss` 副本与命名映射副本来检测 `bpf_map_update_elem` 截获)不再需要。
- **在同一 ELF 对象的不同程序间共享** — sched_switch 和 NMI 通过 `.bss` 通信,无需任何外部接口

### 对 BPF 级别截获的免疫

NMI 完整性检查器在结构上对 BPF 覆盖攻击免疫,原因在于一个基本属性:`bpf_override_return` 截获的是函数**调用**,但 NMI 检查器并不*调用*它所验证的事物——它直接*读取 `.bss` 内存*。你无法覆盖内存读取的返回值,因为内存读取不是函数调用。

此外:
- `bpf_probe_read_kernel`(用于替代方案中的内核结构读取)是一个故障安全的辅助函数,可接受任何地址——但 SPiCa 的 `.bss` 心跳设计甚至不需要它。检查器通过直接加载指令读取 `.bss` 全局变量。
- NMI 程序在 NMI 上下文中运行,在该上下文中 kprobes 在结构上不可靠(内核会延迟或抑制它们)。针对检查器执行的基于 kprobe 的攻击将与硬件对抗。

### NMI 事件语义

NMI 环形缓冲区(`sc_nmi`)携带轻量级事件:

| 事件类型 | 含义 | 用户空间操作 |
|---|---|---|
| 0 | 心跳——NMI 存活,sched_switch 存活 | 更新 `last_nmi_heartbeat` 时间戳 |
| 1 | 侦测到篡改——NMI 存活,sched_switch 心跳冻结 | 立即打印 `[TAMPER]` |

事件最多每秒触发一次(由 `NMI_LAST_EMIT` 节流)。如果 NMI 环形缓冲区静默超过 5 秒,用户空间将触发 `[SILENT]`——NMI 通道本身已失效。

---

## 5. 检测逻辑```mermaid
graph TD
    subgraph RING0["Kernel Space: Four eBPF Programs"]
        direction TB
        SCHED_P["TracePoint, sched_switch<br/>read next_pid, next_comm<br/>write SCHED_HEARTBEAT (.bss)<br/>XOR obfuscate → sc_sched"]
        NMI_P["PerfEvent hardware NMI<br/>read SCHED_HEARTBEAT (.bss)<br/>compare to NMI_LAST_HB<br/>frozen → event_type=1 (TAMPER)<br/>alive → event_type=0 (heartbeat)<br/>→ sc_nmi"]
        LSM_P["BPF LSM, kernel_read_file<br/>READING_MODULE<br/>gate=0: allow + log<br/>gate=1: EPERM + log → sc_lsm"]
        WATCH_P["TracePoint, sched_process_exit<br/>current == SPICA_PID (.bss)? → sc_wd flag"]
    end
下载工具