AI摘要:本文详细介绍了七种常见的Windows注入技术,包括早期APC注入、线程劫持、反射DLL注入、进程空洞、Atom Bombing、Heaven's Gate和事务空洞,并分析了它们如何规避EDR检测。此外,文章还探讨了驱动层免杀技术,如绕过PatchGuard、禁用回调函数、绕过驱动签名以及内核Shellcode/模块加载等方法。

常见注入技术

Early Bird Injection(早期 APC 注入)是在创建新进程的早期阶段利用 APC(异步过程调用)执行恶意代码的一种技术。攻击者通常使用 CreateProcessCREATE_SUSPENDED 标志创建合法进程,并调用 VirtualAllocEx/WriteProcessMemory 将 shellcode 写入进程内存。然后使用 QueueUserAPC 将 shellcode 地址加入主线程的 APC 队列,最后调用 ResumeThread 恢复线程。因为 APC 会在主线程执行前首先被调度执行,恶意代码可以在主线程入口点之前运行,从而绕过绝大多数在入口点(如 NtResumeThread 等)设置的钩子。关键步骤伪代码如下:

// 1. 创建目标进程(挂起)
CreateProcess(targetExe, NULL, ..., CREATE_SUSPENDED, ... , &pi);
// 2. 在目标进程中分配内存并写入 shellcode
remotePtr = VirtualAllocEx(pi.hProcess, NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, remotePtr, shellcode, shellcodeSize, NULL);
// 3. 对目标主线程排队 APC,使其执行 shellcode
QueueUserAPC((PAPCFUNC)remotePtr, pi.hThread, 0);
// 4. 恢复线程执行 APC
ResumeThread(pi.hThread);

由于恶意代码在进程入口点之前就执行,安全产品若仅在入口点或常用 API 调用处钩子监测(如检测 CreateRemoteThread 等),往往无法捕获此行为。真实案例中,“TurnedUp” 后门、Carberp 等恶意软件都采用了此技术来躲避检测。

  • 背景原理:利用 Windows 线程恢复时会先处理 APC 的机制,使代码在主线程真正执行前运行,从而绕过入口钩子。
  • 实现步骤(见上述伪代码):依次调用 CreateProcess(CREATE_SUSPENDED)VirtualAllocExWriteProcessMemoryQueueUserAPCResumeThread
  • 绕过 EDR:因为安全软件往往钩子在常规线程或 API 调用处,Early Bird 技术在主线程一开始运行前就执行了 shellcode,EPP/EDR 对其难以监测。

线程劫持(Thread Hijacking) 指攻击者劫持目标进程的一个现有线程,通过修改其寄存器上下文来执行恶意代码。这与常见的 CreateRemoteThread 注入不同,步骤通常为:首先通过 OpenProcess 获取目标进程句柄,VirtualAllocEx/WriteProcessMemory 将 shellcode 写入目标进程内存;使用 CreateToolhelp32Snapshot+Thread32First/Next 找到目标进程内一个线程 ID,然后 OpenThread 打开该线程句柄;接着调用 SuspendThread 挂起该线程,GetThreadContext 获取其上下文(包括指令指针 RIP);将 RIP 修改为 shellcode 地址,再用 SetThreadContext 更新;最后 ResumeThread 恢复线程。此时,被劫持线程会执行注入的 shellcode,并在完成后继续原有流程。例如:

// 1. 注入 shellcode
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
LPVOID remote = VirtualAllocEx(hProc, NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProc, remote, shellcode, size, NULL);
// 2. 找到一个线程并挂起
DWORD tid = FindTargetThreadId(pid);
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
SuspendThread(hThread);
// 3. 修改线程上下文,将 RIP 指向 shellcode
CONTEXT ctx = { CONTEXT_ALL };
GetThreadContext(hThread, &ctx);
ctx.Rip = (DWORD64)remote;
SetThreadContext(hThread, &ctx);
// 4. 恢复线程
ResumeThread(hThread);

通过线程劫持,恶意代码以合法进程的线程身份运行,可能躲过一些基于进程的监测,同时由于没有使用常见的远程线程 API(CreateRemoteThread)来直接创建线程,部分 EDR 钩子可能难以检测或遗漏。不过该方法的漏洞是需要 SuspendThread/SetThreadContext 等函数,这些调用也可能被安全软件监控到。

反射 DLL 注入(Reflective DLL Injection) 是一种直接从内存加载 DLL 的技术,由 Stephen Fewer 提出。攻击者将一个 DLL 的字节流发送到目标进程内存,然后调用该 DLL 中导出的 ReflectiveLoader 函数(通常通过 CreateRemoteThread 或简单 shellcode 调用)来自行完成加载。ReflectiveLoader 先确定自身映像在内存中的地址并解析自身 PE 头,然后在目标进程中分配连续内存并加载 DLL 的各节,包括修复重定位信息和导入表。它解析目标进程的 kernel32.dll 导出表,获取 LoadLibraryAGetProcAddressVirtualAlloc 等函数地址,以便在内存中手动调用这些函数加载依赖的库。最后 ReflectiveLoader 调用新加载映像的 DllMain 完成 DLL 加载。示例伪代码流程:

// 从文件或网络获取 DLL 数据
BYTE *dllData = LoadDllBytes("payload.dll");
// 注入字节流到目标进程内存
LPVOID remoteDll = VirtualAllocEx(hProc, NULL, dllSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProc, remoteDll, dllData, dllSize, NULL);
// 在目标进程中创建远程线程执行 ReflectiveLoader(假设已知偏移)
DWORD loaderOffset = GetReflectiveLoaderOffset(dllData);
CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)((BYTE*)remoteDll + loaderOffset), NULL, 0, NULL);

反射注入的优势在于无需调用系统的 LoadLibrary 等函数,也不在磁盘留痕。恶意 DLL 从内存中自举加载,对常见基于磁盘或 API 调用的检测(如 LoadLibrary 钩子)天然规避。但注意,虚拟内存分配和写入也可能被监控,故其“隐蔽性”更多在于文件系统和常规 DLL 加载路径,而非抗监控 API 调用。

进程空洞(Process Hollowing) 技术与线程劫持类似,但其目标是新创建的可执行进程。典型过程为:调用 CreateProcessCREATE_SUSPENDED 标志创建合法进程;使用 ZwUnmapViewOfSection(或 NtUnmapViewOfSection)卸载该新进程原有的代码段;然后 VirtualAllocEx 分配新映像空间,WriteProcessMemory 将恶意 PE 镜像写入;调整 PEB 或调用 SetThreadContext 将线程的 RIP 指向恶意镜像的入口点;最后 ResumeThread 恢复线程执行。其关键伪代码为:

// 1. 创建目标进程(挂起)
CreateProcess(targetExe, NULL, ..., CREATE_SUSPENDED, ... , &pi);
// 2. 卸载其原始映像
NtUnmapViewOfSection(pi.hProcess, baseAddress);
// 3. 分配空间并写入恶意映像
LPVOID newBase = VirtualAllocEx(pi.hProcess, imageBase, newImageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, newBase, maliciousImage, newImageSize, NULL);
// 4. 修改线程上下文,让 RIP 指向恶意入口
CONTEXT ctx = { CONTEXT_ALL };
GetThreadContext(pi.hThread, &ctx);
ctx.Rip = (DWORD64)newBase + offsetToEntry;
SetThreadContext(pi.hThread, &ctx);
// 5. 恢复线程,执行恶意代码
ResumeThread(pi.hThread);

这一技术会创建一个看似合法的新进程(父进程与被劫持进程相同,因此权限常继承自原进程),同时隐藏实际执行的恶意代码。对 EDR 而言,因为整个流程使用了被认为“正常”的进程启动流程,部分安全产品可能仅检测是否为常见恶意进程而忽略了内部替换。MITRE 也指出,进程空洞的行为伪装于合法进程之下,可逃避一些监控。但需要注意, ZwUnmapViewOfSectionSetThreadContext 等 API 也可能被内核监控到。

Atom Bombing 是 EnSilo(后被 Fortinet 收购)团队提出的新型注入技术,它利用 全局原子表(Global Atom Table) 和 APC 来实现写-执行-恢复三阶段注入。其基本思路是:攻击者调用 GlobalAddAtom 将恶意 shellcode 以字符串形式存入全局原子表(一个所有进程共享的系统表);得到一个 atom ID。然后,攻击者用 QueueUserAPCGlobalGetAtomName 函数排入目标进程线程的 APC 队列,将原子表中的 shellcode 复制到目标进程内存中的指定位置。最后恢复线程执行后续代码(可以通过修改线程返回地址或再次注入跳转)。整个过程“写入数据-执行代码-清理”三个阶段如图示:

  • 写入阶段(Write-What-Where):用 GlobalAddAtom(shellcode) 将 shellcode 写入原子表。
  • 执行阶段(Execution):攻击者需要让目标进程调用 GlobalGetAtomName(atomID, buffer, size),以将 shellcode 从原子表写入 buffer(由攻击者控制)。这通常通过排队 APC 给目标线程,在恢复线程时触发函数调用实现。由于 QueueUserAPC 实际上调用的是 RtlDispatchAPC(可携带一个参数),攻击者可以构造合适参数使目标线程执行 GlobalGetAtomName。这样避免了使用 WriteProcessMemory 等常规函数。
  • 恢复阶段(Restoration):清除原子表条目,继续让目标线程执行写入的 shellcode。

BeyondTrust 总结称,“Atom Bombing” 利用了原子表和 APC,能够在另一进程上下文中执行代码,并通过将代码附加到线程来在其可恢复状态时运行 payload。示例伪代码大致为:

// 1. 存储 shellcode 到全局原子表
ATOM atom = GlobalAddAtom(shellcode);
// 2. 让目标线程调用 GlobalGetAtomName,将数据复制到目标内存 (此步需配合 APC 机制)
//    例如:QueueUserAPC((PAPCFUNC)&GlobalGetAtomName, targetThread, atom);
// 3. 目标线程恢复后,shellcode 即执行。

由于此注入过程不调用常见的写内存 API(而是利用原子表和合法 API 调用),许多防护方案难以预先识别。不过一旦原子表调用被触发或 shellcode 在进程内存中存在,也有可能被细致监控捕获。

Heaven’s Gate 技术源自早期 Windows-on-Windows (WoW64) 兼容层,允许在 32 位进程中切换到 64 位模式直接执行系统调用。其本质是通过修改代码段寄存器(CS)并使用远跳指令 (jmp far/call far) 强制 CPU 进入 x64 模式,然后直接执行 64 位 syscall,绕过 WOW64 对 32 位系统调用的重定向和用户态钩子。Red Canary 研究指出,Heaven’s Gate 使得恶意软件能在 32 位进程中以 64 位代码身份运行,从而避开对 32 位 API 调用(例如 32 位 ntdll 钩子)的监测。其关键流程为:

  • 切换到 64 位:在 32 位代码中加载 64 位代码段选择子到 CS,使用 jmp farcall far 跳转到 64 位代码段。
  • 执行系统调用:处于 64 位模式后,直接执行所需的 64 位 syscall,而不是通过 WOW64(32 位 ntdll)进行。这允许以未被 32 位监控的方式执行操作。
  • 恢复 32 位:通过另一个远跳切换回 32 位模式,继续正常程序流程。

例如,攻击者可能在 32 位进程中注入一段简单 shellcode,将 CS 设置为 64 位代码段选择子后跳转到一个 64 位系统调用例程,从而让原本只能监控 32 位 API 的 EDR 逃避检测。最新 Windows 版本(Win10+)中,这种技巧因启用了控制流保护(CFG)等机制已被部分缓解。

事务空洞(Transacted Hollowing / Process Doppelgänging) 利用 Windows 的事务性 NTFS (TxF) 特性来进行“无文件”的进程注入,被 MITRE 归为 T1055.013。其步骤可概括为:

  1. 事务覆盖(Transact):创建一个针对合法可执行文件的 TxF 事务 (CreateTransactionCreateFileTransacted),在该事务中将目标文件内容覆盖为恶意 PE 镜像(此时修改只对事务上下文可见,文件系统中仍保留原始文件)。
  2. 加载映像(Load):使用 ZwCreateSection(或 NtCreateSectionEx)基于“被污染”的文件内容创建一个映像节,加载恶意映像到内存中。
  3. 回滚事务(Rollback):撤销事务,使磁盘上的文件恢复为原始合法内容。此时磁盘上没有留下恶意文件痕迹。
  4. 执行(Animate):使用诸如 ZwCreateProcessExZwCreateThreadEx 等底层 API,从之前创建的恶意映像节中创建并启动一个新进程。

结果是恶意代码在新进程中执行,但系统中看不到该可执行映像的恶意版本。该方法避免了使用诸如 VirtualAllocExWriteProcessMemorySetThreadContext 等常见监控 API,因此可以规避对这些函数调用的检测。同时,因为被感染的进程从合法父进程创建,其权限也继承自父进程,通常不会导致权限提升提示。

驱动层免杀技术

PatchGuard 限制:Windows 的内核补丁保护(PatchGuard)强制禁止对关键内核结构(如 SSDT、IDT、内核代码段等)进行未经授权的修改,否则会触发蓝屏。这意味着即使攻击者进入内核模式,也不能随意通过补丁或钩子修改这些结构而不引发系统崩溃。因此,大多数 EDR 在内核态也避免修改内核函数,而是选择在用户态进行监控。同时,这也限制了攻击者在内核中直接钩取或隐藏代码的能力,导致内核层绕过通常需要借助合法方法或利用漏洞暂时获得内核内存写入权限,然后恢复系统。攻击者常通过安装漏洞驱动(Bring Your Own Vulnerable Driver, BYOVD)来获得读写内核内存的能力,间接绕过 PatchGuard 的限制。

回调函数绕过:EDR/杀软常通过注册内核回调来拦截关键事件,例如使用 PsSetCreateProcessNotifyRoutine 捕获进程创建、PsSetCreateThreadNotifyRoutine 捕获线程创建、PsSetLoadImageNotifyRoutine 捕获模块加载,以及注册表键值改动回调(CmRegisterCallback)来实现策略。攻击者的策略是禁用或清空这些回调,让 EDR 不再收到通知。例如,如果能通过内核漏洞写入内存,就可以调用 PsRemoveCreateProcessNotifyRoutinePsRemoveLoadImageNotifyRoutine 等移除现有回调,或直接清空回调列表指针。正如研究所述,一旦禁用过滤驱动与回调例程,EDR 的用户态钩子和事件捕获就会失效,终端上视角丧失。例如,有工具(如 EDRSandBlast)利用漏洞驱动来关闭注册表和进程回调,使得之后的活动不被监控。攻击者还可尝试修改注册表回调(CmRegisterCallback)和驱动保护注册表键(PsSetLoadImageNotifyRoutine)来规避钩子保护。总之,通过内核写入篡改这些回调表,攻击者可暂时屏蔽 EDR 的监测。

驱动签名绕过:在 64 位 Windows 上,驱动加载前需通过签名验证(Code Integrity,DSE)。为绕过这一限制,攻击者常使用 DSEFix、VirtualBox 提权、Windows 漏洞等技术关闭或绕过签名检查。例如,DSEFix 利用系统漏洞或带有特权的驱动写入 nt!g_CiOptions 全局变量,将其置零来取消签名强制(部分老旧版本的 Windows 可通过该方法实现)。更高级的攻击如 BlackLotus Bootkit 利用 UEFI 启动链路漏洞,在系统启动时禁用安全启动和 HVCI,从而允许加载未经签名的内核模块(BlackLotus 是首个已知的活动 UEFI Bootkit,可在启动阶段篡改安全策略)。现代 Windows 中,为防护签名绕过,必须针对具体漏洞打补丁并使用 HVCI 强化。

内核 Shellcode/模块加载:获得内核写入权限后,攻击者可以直接在内核中加载自定义代码。常见做法包括:

  • 加载合法驱动:如果签名绕过成功,可以通过注册表或 ZwLoadDriver/SCM 加载一个恶意 .sys 驱动。如先进入 测试签名模式 (bcdedit /set testsigning on) 后,通过 sc create/start 命令加载未签名驱动。
  • 手动映射驱动:构造 DRIVER_OBJECT 并调用 DriverEntry,手动在内核分配内存并复制驱动镜像;或利用现有驱动的 IoCreateDeviceMmMapIoSpaceZwMapViewOfSection 等函数将驱动镜像映射到内核地址空间,然后调用 DriverEntry。例如,著名的 KDU 工具即通过这一技术将任意内核映像加载到内核中。
  • 执行内核 Shellcode:在加载已有驱动或创建驱动对象后,可在其卸载例程或自定义 DPC/工作线程中执行 shellcode。攻击者也可直接在内核内存(如通过 ExAllocatePoolWithTag)写入 shellcode,并调用 PsCreateSystemThread 在内核态启动线程运行它。也有方案通过修改内核函数指针(如 HalDispatchTableWin32kServiceTable)让系统调用触发跳转执行 shellcode。
  • 利用漏洞触发执行:利用常见内核漏洞(如记忆越界写等)修改内核结构或函数指针,让恶意 shellcode 随自然机制运行。例如可修改 KiServiceTable 中某一条目地址,使得调用对应系统服务时触发内核代码执行。

这些方法都需要内核写入权限,通常通过漏洞或信任驱动实现。一旦内核态恶意代码执行,攻击者就可以最高权限控制系统,极难被用户态 EDR 侦测到。

引用文章

最后修改:2025 年 07 月 13 日
如果觉得我的文章对你有用,请随意赞赏