43、DLL注入技术(三):进程镂空(Process Hollowing)、Atom Bombing
这一章我们聊两个比较「硬核」的注入手法。一个是进程镂空,另一个是Atom Bombing。
说实话,这两个技术在实际对抗中出现的频率不算特别高,但一旦用上,往往就是比较高级的对抗场景。我个人觉得,理解它们的原理,比单纯会用更重要。
进程镂空(Process Hollowing)
进程镂空,说白了就是「借壳生蛋」。你启动一个合法的进程(比如notepad.exe),然后把它内存里的代码给替换成你的恶意代码。这样,系统眼里跑的是一个正常程序,但实际上执行的是你的逻辑。
我最早接触这个技术,是在分析一个APT样本的时候。那个样本启动了一个svchost.exe,但行为完全不对劲。后来一查,好家伙,进程镂空。
核心步骤
- 创建挂起进程:用
CreateProcess带上CREATE_SUSPENDED标志,启动一个合法进程,但让它停在入口点之前。 - 卸载原镜像:调用
NtUnmapViewOfSection,把目标进程里原本的exe镜像从内存中抹掉。 - 分配新内存:用
VirtualAllocEx在目标进程里重新分配一块内存,大小和你的恶意PE文件对齐。 - 写入恶意代码:把恶意PE的各个节(Section)写入目标进程的内存空间。
- 设置入口点:修改目标进程的上下文(
SetThreadContext),把入口点指向你的恶意代码。 - 恢复线程:调用
ResumeThread,让进程跑起来。这时候跑的就是你的代码了。
关键点:整个过程中,目标进程的PID没有变,文件路径也没有变。杀软如果只看进程创建日志,很难发现问题。
代码骨架
// 1. 创建挂起进程
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
CreateProcess(L"C:\\Windows\\System32\\notepad.exe", NULL,
NULL, NULL, FALSE, CREATE_SUSPENDED,
NULL, NULL, &si, &pi);
// 2. 卸载原镜像
NtUnmapViewOfSection(pi.hProcess,
(PVOID)imageBaseAddress);
// 3. 分配新内存
LPVOID newBase = VirtualAllocEx(pi.hProcess,
(LPVOID)targetBase,
sizeOfMaliciousPE,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
// 4. 写入节数据
WriteProcessMemory(pi.hProcess, newBase,
maliciousPE, sizeOfHeaders, NULL);
// ... 写入每个节
// 5. 设置入口点
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &ctx);
ctx.Eax = (DWORD)newBase + entryPointOffset;
SetThreadContext(pi.hThread, &ctx);
// 6. 恢复线程
ResumeThread(pi.hThread);
避坑指南:我曾经在32位和64位混合的环境里踩过坑。如果你注入的PE和目标进程的位数不一致,VirtualAllocEx分配的内存地址可能对不上。一定要检查IMAGE_NT_HEADERS里的ImageBase字段。
Atom Bombing
Atom Bombing这个名字听起来有点中二,但原理其实挺巧妙的。它利用的是Windows的原子表(Atom Table)机制。
原子表是系统提供的一种全局字符串存储机制。你可以把一个字符串存进去,得到一个16位的ID。其他进程可以通过这个ID把字符串取出来。
嗯,这里要注意:原子表本身是设计用来做字符串共享的,不是用来做代码注入的。但逆向工程师嘛,总得有点「创造性思维」。
攻击流程
- 创建原子:攻击进程调用
GlobalAddAtom,把恶意代码(或shellcode)作为字符串存入全局原子表。 - 触发目标进程加载:通过某种方式(比如APC、窗口消息、SetWindowsHookEx)让目标进程调用
GlobalGetAtomName,把原子里的内容读出来。 - 执行代码:目标进程读取到的内容被写入内存,然后通过回调或跳转执行。
个人经验:Atom Bombing最大的优势是不需要创建远程线程,也不需要修改内存权限。很多EDR对VirtualAllocEx和CreateRemoteThread的监控非常严格,但对原子表的操作几乎不设防。
为什么它能绕过检测?
你想想看,原子表操作本质上只是读写字符串。系统API里没有明显的「注入」痕迹。目标进程只是正常地调用GlobalGetAtomName,然后正常地执行一段内存里的代码。整个过程没有跨进程写入,没有远程线程创建。
我见过一些EDR产品,它们会监控WriteProcessMemory和CreateRemoteThread的调用对。但Atom Bombing完全绕过了这两个API,所以很容易漏掉。
局限性
| 限制项 | 说明 |
|---|---|
| 数据大小 | 原子表存储的字符串最大只能到1023字节。shellcode必须非常精简。 |
| 执行触发 | 需要目标进程主动调用GlobalGetAtomName,不能强制注入。 |
| 适用场景 | 更适合作为「第二阶段的加载器」,先注入一个小shellcode,再拉取完整的payload。 |
实战建议:如果你要写一个Atom Bombing的demo,建议配合APC注入一起用。先用原子表传递一个很小的loader,然后通过APC让目标进程执行它。loader再去下载或解密完整的DLL。
两种技术的对比
我个人觉得,进程镂空和Atom Bombing代表了两种完全不同的思路。一个是在进程创建阶段动手脚,一个是在运行时通过系统机制偷渡。
| 维度 | 进程镂空 | Atom Bombing |
|---|---|---|
| 隐蔽性 | 中等。创建挂起进程本身可能被监控。 | 较高。原子表操作不常见于监控列表。 |
| 复杂度 | 较高。需要处理PE解析、重定位等。 | 中等。主要是原子表API的使用。 |
| 适用场景 | 需要长期驻留、伪装成合法进程。 | 需要快速执行、绕过API监控。 |
| 对抗难度 | 需要监控进程创建和内存映射。 | 需要监控原子表操作和APC队列。 |
知识体系图
下面这张图展示了本章涉及的核心知识点和它们之间的关系:
写在最后
这两种技术,说实话,都不是「开箱即用」的。进程镂空需要你处理PE文件的基址重定位,Atom Bombing受限于1023字节的数据大小。但正因为它们有门槛,才值得深入理解。
我建议你动手写一个demo。先试试进程镂空,把一个简单的弹窗代码注入到notepad.exe里。然后再试试Atom Bombing,配合APC注入,看看能不能绕过你本机的杀软。
嗯,实践出真知。