【免费下载链接】Cybersecurity-Projects
Building 70 Projects ranging from beginner to advanced so anyone can — learn from, build upon, use as a reference, or even copy directly. Gamified Cybersecurity learning 👇
本指南是 Linux eBPF Security Tracer 学习系列的概念基石,系统讲解 eBPF 如何在内核中实现沙箱化的安全可观测性、系统调用为何是不可绕过的检测面,以及检测工程中"无状态规则 + 有状态关联"的设计思想。读完本文,你将掌握 eBPF 编译加载链路、本项目 14 类被追踪系统调用与 10 条 MITRE ATT&CK 映射规则的理论依据,并能独立理解常见检测陷阱与真实攻击案例的 syscall 视角。
eBPF:可编程内核可观测性
它是什么
eBPF(extended Berkeley Packet Filter)是一种允许你在 Linux 内核中运行小程序的技术,无需编写内核模块、也无需修改内核源码。你可以把它理解成一种安全、受沙箱约束的"内核脚本语言"。
当你加载一个 eBPF 程序时,内核的**验证器(verifier)**会对其做安全性检查——不允许无限循环、不允许越界内存访问、不允许崩溃内核——随后通过JIT 编译器将其编译为原生机器码。这意味着 eBPF 程序能以接近原生的速度运行,同时具备很强的安全保证。
为什么它对安全很重要
在 eBPF 出现之前,获得内核级可见性只有两个选项:
- 内核模块(kernel modules):权限完全,但一个 bug 就会让系统崩溃。在内核中加载不可信代码本质上充满风险。
- 系统调用追踪(strace/ptrace):安全但缓慢,基于 ptrace 的追踪会给被追踪进程带来 10–100 倍的性能开销。
eBPF 让你以用户空间的安全性获得内核级可见性,典型性能开销低于 1%,且验证器能保证你的程序不会弄崩内核。这正是 2020 年以来几乎所有主流云安全工具(Falco、Tetragon、Tracee、Datadog 的运行时安全模块)都以 eBPF 为底层根基的原因。
它是如何工作的
本项目的编译加载链路印证了经典 eBPF 数据流:
你的 Python 脚本 │ ▼ BCC 编译器 (Clang/LLVM) │ ▼ eBPF 字节码 │ ▼ 内核验证器 ──▶ 拒绝不安全的程序 │ ▼ JIT 编译器 │ ▼ 原生机器码 (挂载到 tracepoint) │ ▼ 每次匹配的系统调用触发 │ ▼ Ring Buffer ──▶ 你的 Python 回调在源码层面,这条链路由 loader.py 完整实现:TracerLoader.load()读取 src/ebpf/ 目录下的 C 程序文本,调用 BCC 的BPF(text=c_text)在运行时完成 Clang 编译、验证器检查与 JIT 加载;随后用bpf["events"].open_ring_buffer(self._callback)建立内核到用户空间的通信通道,并在poll()循环中通过bpf.ring_buffer_poll(timeout=100)持续拉取事件。
BCC vs libbpf:两种主流的 eBPF 开发框架
编写 eBPF 程序主要有两种框架:
BCC(BPF Compiler Collection)在运行时用 Clang/LLVM 编译你的 eBPF C 代码。优点是开发迭代快:把 C 代码写成 Python 字符串(或项目中的独立.c文件),加载即可运行。缺点是每台宿主机都需要安装 LLVM 与内核头文件,且每个程序大约占用 80MB 内存。
libbpf + CO-RE(Compile Once, Run Everywhere)在构建期只编译一次 eBPF 程序,借助 BTF(BPF Type Format)元数据让同一份二进制跨内核版本运行。Tetragon 等生产级工具采用这种方案,因为它更轻量(约 9MB)且生产主机上无需编译工具链。
本项目选择 BCC,因为它的定位是学习工具而非生产代理:Python API 让代码易读,运行时编译免去构建步骤,便于反复试验。仓库中的 5 个 C 源文件(process_tracer.c、file_tracer.c、network_tracer.c、privilege_tracer.c、system_tracer.c)正是由TRACER_FILES字典按类别映射、由 loader 逐个读取编译的。
系统调用:内核的前门
什么是系统调用
用户空间程序与内核的每一次交互都要经过系统调用。当cat读取文件时,它调用openat()获取文件描述符、read()读取内容、write()输出到 stdout;当curl连接服务器时,它调用socket()、connect()和read()。
这个过程无法绕开。即使恶意软件完全驻留内存、即使它用汇编编写,它要做任何有用的事仍必须发起系统调用。这让系统调用追踪成为一种极难规避的强大检测机制——本项目的 00-OVERVIEW.md 将其概括为"在事件发生时、于内核层面观察系统调用"。
安全相关的系统调用
Linux 拥有 300 多个系统调用,并非全部对安全有意义。本项目实际追踪的 14 种事件、5 个类别,与概念文档描述的安全维度一一对应:
| 安全维度 | 追踪的系统调用 | eBPF 源文件(tracepoint) | 事件常量 |
|---|---|---|---|
| 进程执行 | execve、clone | process_tracer.c(sys_enter_execve/sys_enter_clone) | 1、2 |
| 文件访问 | openat、unlinkat、renameat2 | file_tracer.c(sys_enter_openat/sys_enter_unlinkat/sys_enter_renameat2) | 3、4、5 |
| 网络活动 | connect、accept4、bind、listen | network_tracer.c(sys_enter_connect/sys_enter_accept4/sys_enter_bind/sys_enter_listen) | 6、7、8、9 |
| 权限变化 | setuid、setgid | privilege_tracer.c(sys_enter_setuid/sys_enter_setgid) | 10、11 |
| 系统操作 | ptrace、mount、init_module | system_tracer.c(sys_enter_ptrace/sys_enter_mount/sys_enter_init_module) | 12、13、14 |
这些事件常量的数值定义与类别归属,在 config.py 的EventType枚举和EVENT_TYPE_CATEGORIES映射中集中维护,保证了 C 侧与 Python 侧的事件协议一致。逐一说明它们为何重要:
进程执行——execve:每次运行新程序都会触发。这是安全监控最重要的系统调用:几乎每一起攻击都涉及执行某个东西——shell、payload,或某个被滥用的合法工具。
文件访问——openat:揭示进程正在读写哪些文件。攻击者读取/etc/shadow或向/etc/cron.d/写入,传递的是清晰的恶意信号。
网络活动——connect:暴露外连目标。一个 Web 服务器突然连接东欧某 IP 的 4444 端口就是危险信号;bind与listen则显示进程为入站连接(bind shell)开放端口。
权限变化——setuid、setgid:显示权限跃迁。进程调用setuid(0)企图变成 root,正是提权攻击的典型画面。
系统操作——ptrace、mount、init_module:ptrace用于调试但也被用于进程注入(MITRE ATT&CK T1055.008);mount可能暗示容器逃逸企图;init_module加载内核模块,是 rootkit 安装自身的方式。
以execve的追踪实现为例,process_tracer.c 中TRACEPOINT_PROBE(syscalls, sys_enter_execve)先events.ringbuf_reserve保留事件槽位,经fill_base()填充时间戳、PID/TGID、UID/GID、PPID 与进程名等公共字段,再用bpf_probe_read_user_str()安全读取用户空间的args->filename,最后ringbuf_submit提交。这段代码同时是概念文档所述"tracepoint 接口 + ring buffer 通信"的直接证据。
系统调用追踪面
用户空间进程 │ │ execve("/bin/bash", ...) │ openat("/etc/shadow", O_RDONLY) │ connect(sockfd, {ip=10.0.0.1, port=4444}) │ setuid(0) │ ▼ ┌─────────────────────────┐ │ 系统调用入口 │◀── eBPF tracepoint 挂载于此 │ (内核边界) │ └─────────────────────────┘ │ ▼ 内核实现检测工程
从系统调用到安全告警
单个系统调用孤立来看很少可疑:openat在繁忙系统上每秒触发成千上万次。检测工程的艺术在于识别哪些模式——是参数异常的单个事件,还是事件序列——意味着恶意活动。这正是本项目 detector.py 中DetectionEngine的职责:每一类规则要么独立评估单个事件,要么关联同一进程在时间窗口内的事件序列。
无状态检测
某些事件单独出现就足以可疑:
- 以 UID 1000 运行的进程调用
setuid(0),几乎必然是提权尝试 - Python 脚本读取
/etc/shadow,值得调查 init_module()加载内核模块,总是值得注意ptrace(PTRACE_ATTACH, target_pid)是一种代码注入原语
这些是"无状态"检测,因为每个事件被独立评估。具体到本项目,无状态规则在DetectionEngine._check_stateless()中逐条实现,并由 config.py 的DETECTION_RULES提供规则元数据(严重级与 MITRE 编号)。10 条规则完整清单如下:
| ID | 规则名称 | 严重度 | MITRE ATT&CK | 触发条件 |
|---|---|---|---|---|
| D001 | 权限提升(Privilege Escalation) | CRITICAL | T1548 | 非 root 进程调用setuid(0) |
| D002 | 敏感文件读取(Sensitive File Read) | MEDIUM | T1003.008 | 非 root 进程读取/etc/shadow等凭据文件(且非写操作) |
| D003 | SSH 密钥访问(SSH Key Access) | MEDIUM | T1552.004 | 非白名单进程访问 SSH 密钥材料 |
| D004 | 进程注入(Process Injection) | MEDIUM | T1055.008 | ptraceATTACH/SEIZE/SETREGS |
| D005 | 内核模块加载(Kernel Module Load) | HIGH | T1547.006 | 调用init_module |
| D006 | 反向 Shell(Reverse Shell) | CRITICAL | T1059.004 | connect与 shellexecve事件序列 |
| D007 | Cron 持久化(Persistence via Cron) | MEDIUM | T1053.003 | 向 cron 目录写入 |
| D008 | Systemd 持久化(Persistence via Systemd) | MEDIUM | T1543.002 | 向 systemd unit 目录写入 |
| D009 | 日志篡改(Log Tampering) | MEDIUM | T1070.002 | 日志文件删除或截断 |
| D010 | 可疑挂载(Suspicious Mount) | HIGH | T1611 | 调用mount |
规则背后有精细的上下文判断逻辑,仅举几例:
- D002(敏感文件读取):
SENSITIVE_READ_PATHS包含/etc/shadow、/etc/gshadow、/etc/sudoers等;同时要求event.uid != 0且不是写标志(_is_write_flags),从而避免把 PAM 的正常登录校验误报为攻击。 - D003(SSH 密钥访问):
CREDENTIAL_PATHS覆盖/.ssh/id_rsa、/.ssh/authorized_keys、/.aws/credentials等,但CREDENTIAL_ACCESS_ALLOWLIST(sshd、ssh、ssh-agent、gpg-agent等)内的合法进程被豁免。 - D004(进程注入):只对
PTRACE_ATTACH(16)、PTRACE_SEIZE(16902)、PTRACE_SETREGS(13)三个请求类型告警,因为普通调试并不总是使用这些原语。 - D009(日志篡改):
openat带O_TRUNC(512)标志打开/var/log/下文件,或unlinkat删除日志路径,均触发。
有状态检测(事件关联)
另一些威胁只有在关联多个事件时才变得可见。经典的反向 shell 模式:攻陷服务器的攻击者需要把交互式 shell 连回自己的机器,惯用手法是:
1. socket(AF_INET, SOCK_STREAM) # 创建 TCP socket 2. connect(sockfd, attacker_ip) # 连接到攻击者 3. dup2(sockfd, 0) # 重定向 stdin 到 socket 4. dup2(sockfd, 1) # 重定向 stdout 到 socket 5. dup2(sockfd, 2) # 重定向 stderr 到 socket 6. execve("/bin/bash") # 生成 shell这中间没有任何单个系统调用是可疑的:程序创建 socket、连接服务器、启动 shell 都是常态。但同一 PID 在数秒内先connect后执行 shellexecve,就是强烈的反向 shell 信号。
本项目将这条规则实现为有状态检测:DetectionEngine按 PID 维护最近事件的滑动窗口,_get_history()为每个 PID 创建deque(maxlen=MAX_EVENTS_PER_PID)(上限 64 条事件),_prune_history()与_sweep_stale()负责丢弃超过关联窗口(CORRELATION_WINDOW_SEC = 10秒)的过期事件。核心逻辑在_check_stateful()中双向匹配:
- 当 shell 二进制(
SHELL_BINARIES中的sh、bash、zsh等)执行execve时,检查同一 PID 的历史里是否有connect;若没有,再检查父 PID(event.ppid)的历史——覆盖"先连接后派生 shell"的常见形态; - 当
connect发生时,反向检查该 PID 历史里是否已有 shellexecve。
任一方向命中即触发 D006(CRITICAL)。整个评估流程由evaluate()统一驱动:记录单调时间戳 → 先试无状态规则 → 再试有状态规则 → 将事件写入历史并剪枝 → 每SWEEP_INTERVAL(1000 次事件)做一次全局过期清扫。
真实世界示例
2021 Log4Shell(CVE-2021-44228):初始漏洞利用触发一次 JNDI 查询,下载并执行 payload。从 syscall 视角看:Java 进程(出人意料地)调用connect()连向外部 LDAP 服务器,下载 class 文件,随后execve()生成 shell。基于 eBPF 的工具在 WAF 还在更新签名时就已实时发现它。
2020 SolarWinds 供应链攻击:被攻陷的 Orion 软件对外部avsvmcloud.com发起异常外连。syscall 追踪会显示 Orion 进程调用connect()到不在其正常通信模式内的 DNS/HTTP 端点。
Kubernetes 容器逃逸(CVE-2022-0185):该漏洞利用内核文件系统上下文处理中的堆溢出,攻击序列涉及在容器内以构造的参数调用mount()系统调用——这正是 Tetragon 等 eBPF 工具专门设计要捕获的行为,也正是本项目的 D010 规则追踪sys_enter_mount的原因。
MITRE ATT&CK 映射
MITRE ATT&CK 框架为描述攻击者行为提供了通用语言。本项目把每条检测规则映射到具体的 ATT&CK 技术与战术(完整 10 条规则即上节表格)。这里摘录概念文档中规则与技术、战术的对应关系:
| 检测 | 技术 | 战术 |
|---|---|---|
| 权限提升 | T1548 - Abuse Elevation Control | 权限提升 |
| 敏感文件读取 | T1003.008 - /etc/passwd 与 /etc/shadow | 凭据访问 |
| SSH 密钥访问 | T1552.004 - 私钥 | 凭据访问 |
| 进程注入 | T1055.008 - Ptrace 系统调用 | 防御规避 |
| 内核模块加载 | T1547.006 - 内核模块 | 持久化 |
| 反向 Shell | T1059.004 - Unix Shell | 执行 |
| Cron 持久化 | T1053.003 - Cron | 持久化 |
| 日志篡改 | T1070.002 - 清除 Linux 日志 | 防御规避 |
这套映射在 config.py 的DetectionRule中作为结构化元数据落地:每条规则携带rule_id、name、severity、mitre_id与description,当_apply_detection()命中规则时,这些字段被盖上对应的事件,从而让告警输出天然带上 ATT&CK 上下文。
常见陷阱
陷阱一:假设系统调用名称是稳定的
系统调用命名随架构与内核版本变化。在 x86_64 上,open()已被openat()取代为主流的文件打开系统调用。因此应始终使用 tracepoint 接口(如syscalls:sys_enter_openat),而不是对原始 syscall 函数使用 kprobe——因为 tracepoint 是稳定 ABI,kprobe 则挂载在内核内部实现函数上,随版本波动。本项目全部采用TRACEPOINT_PROBE宏(可见于 src/ebpf/ 下每个 C 文件),正是对这一原则的贯彻。
陷阱二:忽略事件量级
在繁忙服务器上,execve和openat每秒触发数百次。若检测引擎对每个事件做昂贵处理,必然落后。为此本项目使用ring buffer 而非 perf buffer(C 侧BPF_RINGBUF_OUTPUT(events, 1 << 18)即 256KB,Python 侧bpf["events"].open_ring_buffer(...)),并刻意保持检测逻辑简单:每个事件只做有限次字符串前缀/包含匹配,状态关联仅维护每 PID 的小型 deque。这样内核侧零拷贝投递、用户侧批量轮询,才能跟上真实系统的事件速率。
陷阱三:过度告警
如果每次openat("/etc/passwd")都触发告警,运维人员一天内就会禁用这个工具。好的检测工程意味着理解什么是正常:root 读取/etc/shadow是符合预期的(PAM 每次登录都会这么做),而一个 Python 脚本读取它才异常——上下文决定一切。这正是 D002 规则限定"非 root 进程"、D003 规则维护合法进程白名单的原因,也是严重度分级(LOW/MEDIUM/HIGH/CRITICAL)与-s最小严重度过滤存在的意义。
概念如何连接
将本项目的全部组件串成一条完整的流水线,即是概念文档的收束图:
eBPF 程序 ─────────────────────┐ (内核中的 C 代码) │ │ │ │ 捕获 syscall 参数 │ 编译 + 加载 │ │ ▼ │ Ring Buffer ◀───────────────────┘ │ via BCC Python │ 事件流向用户空间 ▼ 检测引擎 │ │ 对照规则评估 │ 关联事件序列 ▼ MITRE ATT&CK 映射 │ │ 分类严重度 ▼ 告警输出这条链路与源码一一对应:BCC 运行时编译 src/ebpf/ 的 C 程序(loader.py)→ ring buffer 投递 →parse_raw_event()用 ctypes 按 C 结构体布局解析(processor.py),可选从/proc/{ppid}/comm富化父进程名 →DetectionEngine.evaluate()运行规则(detector.py)→should_include()按严重度、PID、进程名、类别、仅告警等条件过滤 → 由 renderer 输出 live/JSON/table 格式。
行业标准
本项目的设计与多个行业框架对齐:
- MITRE ATT&CK for Linux:描述 Linux 系统上攻击者行为的框架,本项目每条规则均映射到其中的技术与战术;
- NIST SP 800-137:信息安全持续监控(ISCM)指南,eBPF 类工具直接支持其持续监控目标;
- CIS Controls v8 第 8 项:审计日志管理,eBPF 追踪提供的正是原始审计数据。
测试你的理解
为什么恶意软件无法通过从用户空间直接访问内核内存来规避基于 syscall 的检测?参考思路:用户空间进程无法直接读写内核内存——x86 架构通过特权级隔离禁止普通进程执行特权指令或访问内核地址空间,所有能产生实际效果的操作(打开文件、建立连接、派生进程)最终都必须跨过内核边界、经由系统调用完成。即使恶意软件完全驻留内存,它仍要发 syscall 才能做任何事。
看到 PID 4521 的序列:
socket(AF_INET, SOCK_STREAM)、connect(10.0.0.5:443)、execve("/usr/bin/curl")。这是反向 shell 吗?为什么?参考思路:对本工具而言不是。D006 规则要求execve的执行体是 shell 二进制(SHELL_BINARIES中的sh/bash/zsh等),而curl是网络客户端、不在 shell 名单内。更一般地说,curl 连 443 端口属于其正常用途,缺少"把标准 I/O 重定向到 socket 再起交互 shell"的提权语义;但若在真实环境中一个普通进程异常地调用这些 syscall,仍值得结合上下文排查。一条规则对每次
openat("/etc/passwd")触发告警。在每小时 100 个用户登录的服务器上,每小时预计多少误报?如何减少?参考思路:登录流程(PAM)会为每个登录用户读取/etc/passwd,若规则不区分调用者身份,每小时至少约 100 次误报(实际可能更多,因getpwuid等辅助查询也会触发)。减少误报的办法正是本项目 D002 的做法:限定调用者 UID(非 root)、排除合法服务白名单、并结合调用上下文(如是否同时出现写入标志或可疑文件名后缀)提高置信度。kprobe 与 tracepoint 挂载 eBPF 程序的差别是什么?生产环境哪个更可靠?参考思路:kprobe 挂载在内核内部函数(如
do_sys_openat2)上,能观测到更细粒度的内部细节,但内部函数名与签名随内核版本频繁变动,一旦目标函数被重构,程序即失效;tracepoint 挂载在内核为外部消费者显式暴露的稳定事件点(如syscalls:sys_enter_openat)上,是稳定 ABI。生产环境通常优先选用 tracepoint(这也是本项目全部使用TRACEPOINT_PROBE的原因);只有需要观测 tracepoint 未覆盖的深层路径时才退而求其次用 kprobe,并配合内核版本管理。
延伸阅读
- 必读:官方 eBPF 学习资源(ebpf.io);BCC 参考指南(覆盖全部 BCC API);MITRE ATT&CK 的 Linux 完整技术矩阵。
- 进阶:Liz Rice 的《Learning eBPF》全面讲解 eBPF 编程;Brendan Gregg 的《BPF Performance Tools》是 eBPF 系统分析的经典参考;Falco 规则仓库展示了生产工具如何定义检测规则。
- 仓库内继续深入:安装与快速上手见 00-OVERVIEW.md(含
./install.sh、sudo uv run ebpf-tracer等完整命令与预期输出);系统设计与数据流见 02-ARCHITECTURE.md;逐模块代码走读见 03-IMPLEMENTATION.md;扩展练习见 04-CHALLENGES.md。项目总览与完整检测规则表见 README.md。
【免费下载链接】Cybersecurity-Projects
Building 70 Projects ranging from beginner to advanced so anyone can — learn from, build upon, use as a reference, or even copy directly. Gamified Cybersecurity learning 👇
相关推荐
Linux eBPF Security Tracer 实战指南:基于 BCC 的实时系统调用安全监控与 MITRE ATT&CK 检测引擎
Linux eBPF Security Tracer 实战指南:基于 BCC 的实时系统调用安全监控与 MITRE ATT&CK 检测引擎 本指南围绕 Cybe
Linux eBPF Security Tracer 架构深度解析:从内核 tracepoint 到用户空间检测引擎的完整设计
Linux eBPF Security Tracer 架构深度解析:从内核 tracepoint 到用户空间检测引擎的完整设计 本指南以 linux ebpf
KernelSU 元模块 meta-overlayfs 指南:四步装好,模块改 system 才真正生效
KernelSU 元模块 meta overlayfs 指南:四步装好,模块改 system 才真正生效 装了 KernelSU,又刷了一批动 /system
操作系统驱动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考