KolbySnider/Remnant
GitHub: KolbySnider/Remnant
Remnant 是一个轻量级自定义 C2 框架,提供 Python 服务端、C 语言 Windows beacon 及进程内 BOF 执行能力,解决红队演练中对加密通信和模块化后渗透执行的需求。
Stars: 0 | Forks: 0
# Remnant C2
一个小型 C2 框架。Python 服务端带有交互式 CLI,C 语言编写的 Windows beacon,以及它们之间加密的自定义二进制协议。支持在进程内运行 Beacon Object Files。
## 包含内容
### 自定义二进制协议
所有 agent 通信都使用二进制传输格式。前 24 个字节为明文,以便服务端可以在不触及加密部分的情况下路由数据包;之后的所有内容均采用 AES-256-GCM 加密。
```
Offset Size Field
────── ──── ──────────────────────────────────────────────
0 4 length (bytes after this field, big-endian)
4 4 magic (0xDEADBEEF — wire format guard)
8 2 version (protocol version)
10 2 flags (ENCRYPTED | BATCH | ERROR | FRAGMENT)
12 4 agent_id (djb2 hash of agent UUID for routing)
16 4 command (PKG_CMD_* identifier)
20 4 request_id (ties task → result for correlation)
24 ... payload (AES-256-GCM encrypted when flagged)
```
命令 ID 按范围分组:生命周期在 `0x0001–0x00FF`,任务下发在 `0x0100–0x01FF`,BOF/加载器在 `0x0400–0x04FF`。批量标志位允许您将多个子数据包合并为一次签入。
### ECDH-P256 密钥交换
首次联系时,beacon 会生成一个临时的 ECDH-P256 密钥对,将 65 字节的非压缩公钥 (X9.62) 发送到 `/register`,并接收服务端的公钥。双方独立进行数学计算:
```
session_key = SHA-256(ECDH shared secret X-coordinate)
```
值得注意的一个坑:BCrypt 的 `BCRYPT_KDF_RAW_SECRET` 返回的 X 坐标是 little-endian 格式,因此 agent 需要将其反转为 big-endian 后再进行哈希。否则双方会推导出不同的密钥,导致一切失效。每次注册都会使用全新的密钥对;没有会话密钥会被重复使用。
### COFFLoader
COFFLoader 位于 `agent/src/loader/COFFLoader.c`。它负责解析符号、应用 x86-64 重定位,并在进程内调用 BOF 的 `go()`。在基础加载器模式之上还有一些额外功能:
BOF 通过 `HeapCreate(HEAP_NO_SERIALIZE)` 获得自己的堆,与 beacon 的分配器分离。如果 BOF 破坏了其堆,也不会牵连到 beacon,并且清理工作只需一次 `HeapDestroy` 调用即可完成。
每个加载器独立的、具有 4000 自旋计数的 `CRITICAL_SECTION` 保证了线程安全,调用者无需在外部加锁。
`__C_specific_handler`(x64 SEH 处理程序)直接从链接器解析,而不是通过 `GetProcAddress`,因为 `GetProcAddress` 无法找到编译器内部符号。注册该处理程序后,实际使用 SEH 的 BOF 就能正常工作了。
页边界对齐检查和读取保护会在重定位处理期间捕获越界,防止它们悄无声息地破坏内存。
可配置的超时时间(`COFF_DEFAULT_TIMEOUT_MS`)可以终止失控的 BOF,而不会卡死 beacon 循环。
在执行间隙,加载的段和已解析的 IAT 会在内存中通过一个基于栈 ASLR 和高精度计时器生成的单次运行密钥进行 XOR 加密。在 `go()` 运行前解密,返回时重新加密。
### 结构化任务下发
任务通过类型化队列传递,而不是原始命令字符串:
1. 操作员在 CLI 中输入 `shell ` 或 `bof [args]`。
2. 服务端将任务(对于 BOF,包括 `.obj`)打包到待处理队列中。
3. 在下一次签入时,该 agent 的所有待处理任务会被批量打包成一个加密响应。
4. beacon 根据命令 ID 进行分发。BOF 直接进入 COFFLoader,并附带预打包的参数缓冲区。
5. 输出结果会进入一个线程安全的缓冲区,并在下一次签入时作为 `PKG_CMD_TASK_OUTPUT` 数据包发送出去,通过 `request_id` 与原始任务关联。
### 持久化的 agent 状态
Agent 会话——包括会话密钥、任务历史记录、输出、统计数据——会被序列化到 `agents.json` 中,并在重启时重新加载。您可以在不丢失活动会话的情况下终止并重启服务端,只要 beacon 在其重试窗口内重新连接即可。
### 抖动休眠
`sleep_ms = BASE_MS + rand() % (JITTER_MS * 2) - JITTER_MS`,最低为 1 秒。编译时的默认值为 5000 毫秒基数,3000 毫秒抖动。
## 环境要求
**服务端**
- Python 3.8+
- `pip install flask cryptography`
**Agent(构建)**
- Windows,或带有 MinGW-w64 的 Linux
- `x86_64-w64-mingw32-gcc` 在 PATH 中
## 构建
Beacon 通过服务端 CLI 使用 `generate` 命令构建。服务端会调用 `agent/build.bat` 并将编译好的二进制文件输出到 `agent/build/` 目录。
运行服务端的机器上需要满足以下前置条件:
- `x86_64-w64-mingw32-gcc` 在 PATH 中 (MinGW-w64)
### generate 命令
```
> generate [--ip IP] [--port PORT] [--ua USER_AGENT] [--token AUTH_TOKEN]
[--sleep MS] [--jitter MS] [--https] [--out filename.exe]
```
所有标志位均为可选。默认值来源于服务端自身的监听配置和身份验证 token。
| 标志位 | 默认值 | 描述 |
|------|---------|-------------|
| `--ip` | 服务端监听 IP(或 `127.0.0.1`) | 写入 beacon 的 C2 回调地址 |
| `--port` | 服务端监听端口 | C2 回调端口 |
| `--ua` | `Mozilla/5.0 (Windows NT 10.0; Win64; x64)...` | HTTP User-Agent |
| `--token` | 服务端身份验证 token | 注册时发送的 `X-C2-Token` 标头 |
| `--sleep` | `5000` | 基础签入间隔,单位毫秒 |
| `--jitter` | `3000` | 对称抖动,单位毫秒 |
| `--https` | off | 使用 HTTPS(禁用证书验证) |
| `--out` | `beacon.exe` | `agent/build/` 下的输出文件名 |
### 示例
```
> generate
> generate --ip 10.0.0.1 --port 443 --out implant.exe
> generate --ip 10.0.0.1 --port 443 --https --token MyToken123 --sleep 10000 --jitter 5000 --out beacon_https.exe
```
成功后,服务端会打印出最终路径:
```
12:00:00 BUILD Building beacon.exe ip=10.0.0.1 port=443 https=False
12:00:01 OK Built /path/to/agent/build/beacon.exe
```
构建产物:
- `agent/build/` — beacon
- `agent/build/bofs/*.obj` — 编译好的 BOF(也会被复制到 `server/bofs/`)
## 运行
### 服务端
```
cd server
python c2server.py
```
可选的身份验证 token,这样只有知道它的 beacon 才能注册:
```
# Windows
set C2_AUTH_TOKEN=MyToken123 && python c2server.py
# Linux/macOS
C2_AUTH_TOKEN=MyToken123 python c2server.py
```
服务端会绑定 `0.0.0.0:8080`,重新加载任何已保存的 agent,并进入 CLI:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
C2 SERVER
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12:00:00 OK listening 0.0.0.0:8080
12:00:00 OK bof dir /path/to/server/bofs
12:00:00 OK upload dir /path/to/server/uploads
>
```
## 通信原理
**注册。** Beacon 将其 65 字节的 ECDH-P256 公钥 POST 到 `/register`。服务端生成自己的临时密钥对,推导出会话密钥(共享密钥 X 坐标的 SHA-256 哈希值),分配一个 UUID,并将其公钥连同 UUID 一起返回。双方在会话密钥不通过网络传输的情况下得出相同的密钥。
**签入。** Beacon 根据其抖动间隔定期请求 `POST /checkin/`。每次请求都会在一个加密数据包中携带自上次签入以来缓冲的所有输出。服务端对其进行解密、处理,并响应一个包含所有待处理任务的加密数据包。
**分发。** Beacon 解密响应,读取每个子数据包的命令 ID,并执行它。Shell 命令通过 `cmd.exe /c` 执行并捕获输出。BOF 则带着预打包的参数缓冲区进入 COFFLoader。
## CLI 参考
### 全局
| 命令 | 描述 |
|---------------|---------------------------------------|
| `list` | 列出所有已注册的 agent |
| `bofs` | 列出可用的 BOF 模块 |
| `use ` | 选择一个 agent(支持部分 UUID 匹配) |
| `clear` | 清除终端 |
| `help` | 显示命令参考 |
| `exit` | 关闭服务端 |
### Agent(执行 `use ` 后)
| 命令 | 描述 |
|----------------------|------------------------------------------------|
| `shell ` | 通过 `cmd.exe /c` 运行 shell 命令 |
| `bof [args]` | 运行带有可选类型化参数的 BOF |
| `output [n]` | 显示最后 N 次输出(默认 10) |
| `info` | 显示 agent 详情和会话密钥 |
| `stats` | 显示发送/接收的字节数、任务计数 |
| `kill` | 发送 `PKG_CMD_EXIT`;beacon 调用 ExitProcess |
| `back` | 取消选择当前 agent |
### BOF 参数语法
`bof` 的参数以空格分隔,带有可选的类型前缀。无前缀的 token 将被视为 UTF-8 字符串。
| 前缀 | 类型 | 示例 |
|----------|----------------|--------------------------|
| `s:` | UTF-8 字符串 | `s:C:\Windows\System32` |
| `w:` | UTF-16LE 字符串| `w:Administrator` |
| `i:` | 32 位整数 | `i:1337` |
| `h:` | 16 位短整型 | `h:443` |
| *(无)* | UTF-8 字符串 | `hostname` |
服务端会在将参数嵌入到任务数据包之前,以 BeaconPack 格式对其进行打包。Beacon 会将打包好的缓冲区直接传递给 `go(char *args, int len)`。
## 内置 BOF
| 模块 | 描述 |
|-------------------|------------------------------------------------------------------------|
| `whoami` | 当前用户、域、SID、特权级别、token 提升类型 |
| `pslist` | 进程列表 — PID、PPID、线程数、映像名称 |
| `sysinfo` | OS 版本、主机名、域、正常运行时间、架构 |
| `arp_cache` | 通过 `GetIpNetTable` 获取 ARP 表 |
| `tcp_connections` | 通过 `GetTcpTable2` 获取活动的 TCP 连接 |
| `dirlist` | 带有大小和属性的目录列表 |
### 编写您自己的 BOF
BOF 是使用 `gcc -c` 编译的标准 COFF 对象文件。它们导出 `go(char *args, int len)` 函数,并使用 `BeaconData*` API 来解析参数,使用 `BeaconPrintf` / `BeaconOutput` 来生成输出。将编译好的 `.obj` 文件放入 `server/bofs/` 目录后,即可立即通过 `bof` 命令调用。
```
#include "base.h"
void go(char *args, int len) {
if (!bofstart()) return;
datap parser;
BeaconDataParse(&parser, args, len);
char *target = BeaconDataExtract(&parser, NULL);
BeaconPrintf(CALLBACK_OUTPUT, "Target: %s\n", target);
bofstop();
}
```
## 致谢
本项目基于以下几个值得提及的开源项目构建:
**[TrustedSec](https://github.com/trustedsec)** — 这里的 COFFLoader 和更广泛的 BOF 设计在很大程度上借鉴了 TrustedSec 的开源工具。`beacon_compatibility` 垫片中的 KERNEL32$、ADVAPI32$、NTDLL$ 等 thunk 定义直接来源于他们的 BOF 头文件,并且包含的 BOF 模块也遵循了他们的编写规范。
**[Havoc C2](https://github.com/HavocFramework/Havoc)** — 通信协议的帧结构、命令 ID 布局以及批量数据包的概念均受到了 Havoc 的 agent-server 设计的影响。
标签:C2框架, IP 地址批量处理, 安全学习资源, 客户端加密, 网络安全, 逆向工具, 隐私保护