最近拿到一个远控木马样本,行为特征非常典型:它表面只是启动了一个正常程序,实际上却在申请进程时先让目标“挂起”,再往这个进程的地址空间里写入恶意代码,最后恢复线程让代码跑起来——这就是俗称的“傀儡进程”(Process Hollowing)技术。很多朋友一听到“远程木马创建傀儡进程”,第一反应是打开任务管理器找陌生进程,但真实分析远没有这么简单。这篇文章就拆解我分析模拟项目X时的完整思路:从进程创建的本质行为,到内存写入与线程恢复的取证链条,再到最后提炼可用于检测的IOC。无论你是刚刚上手恶意样本分析,还是已经在给安全设备写检测规则,都可以拿来参考。
1. 先把“傀儡进程”这个事说透:远程木马为什么要这么做
傀儡进程技术不是新东西,但它至今仍是远程控制类木马最常用的“借壳上市”方式。要理解它,建议先放下“进程是个程序”的直觉,把进程看成“一块被操作系统托管的内存区域 + 一堆线程执行上下文”。木马做的事情本质上不是创建了一个新进程,而是把一块已经合法存在的进程空间“腾空”,然后在里面塞入自己的PE镜像。这也是为什么很多安全设备最初只看到正常进程在运行,却看不到恶意载荷。
1.1 从表象看本质:进程不是“被创建”,而是“被替换”
一个常规进程的启动流程很简单:系统加载器读取磁盘上的可执行文件,映射到内存,设置好入口点,然后运行主线程。而典型的傀儡进程实现流程通常是这样的:
- 用
CreateProcess创建目标进程,并传入CREATE_SUSPENDED标志,让进程在入口点执行前先“冻结”。 - 拿到目标进程的主线程句柄和进程句柄后,调用
NtUnmapViewOfSection(或ZwUnmapViewOfSection)把目标进程原有的地址空间映像全部卸载掉,相当于把房间里的家具清空。 - 在目标进程地址空间中重新分配内存,地址和大小往往刻意对齐到与恶意PE所需相同,然后把恶意PE的节区逐一写入。
- 设置目标进程主线程的上下文,将入口点
RIP/EIP指向恶意PE的入口点。 - 调用
ResumeThread恢复线程,恶意代码就在“别人的外壳”里开始运行。
表面上看,进程名、路径、签名可能都是正常的,比如svchost.exe或notepad.exe,但实际执行的代码已经变成攻击者的后门。这就是“傀儡”二字的来源:身体是合法程序的,灵魂却是木马的。
1.2 木马钟情的三个理由:隐蔽、信任、对抗
为什么远程木马偏爱这种技术?我总结下来主要是三点:
- 隐蔽性:安全人员排查进程时,看到的进程名称和路径都来自正常程序,不会立刻触发警觉。即使杀毒软件做进程行为检测,也很难靠静态路径判断好坏。
- 信任传递:很多防护策略会信任已签名的微软进程、浏览器进程或办公软件进程。木马将代码寄生在这些进程内部,可以继承这种信任关系。某些EDR对“可信进程”放行,正好给傀儡进程留了门。
- 对抗检测:相比直接注入一个远程线程,傀儡进程更像是“从内部替换执行流”。常规的API钩子如果只监控
CreateRemoteThread,很容易漏掉这个场景,因为它根本没有创建新线程,只是改写了原有线程的上下文。
1.3 与标题匹配的样本特征:什么样的木马会这么干
我在分析中遇到的模拟项目X,属于典型的远控木马属性:带有网络回连功能、键盘记录模块和文件管理模块。它选定的傀儡进程通常是系统自带的svchost.exe或RuntimeBroker.exe,原因很简单——这些进程在系统里数量多、不起眼,而且经常有合法的网络通信行为,后续做外联流量更容易混入正常流量。木马创建傀儡进程后,还会把原始可执行文件删除或标记为隐藏,增加取证难度。如果你的样本也表现出“父进程正常、目标进程路径异常、内存里出现非模块代码”这些特征,基本就可以往傀儡进程方向查了。
2. 实用的样本分析环境与工具选型
分析恶意样本,最忌讳直接在实体机上双击运行。傀儡进程技术本身不可怕,可怕的是分析环境没准备充分,导致样本在分析机上二次感染。我这里把环境搭建和工具选型整理成了一套可复用的流程。
2.1 隔离环境搭建,先别急着点样本
我常用的方案是虚拟机 + 快照 + 网络隔离。虚拟机建议选用当前主流虚拟化平台,操作系统用Windows 10或Windows 7均可(Windows 7对老样本兼容性更好,很多早期远控木马在Win10上会因为API差异提前退出)。关键步骤:
- 安装完系统后,先做一次干净快照,之后每做完一步操作就再做一次快照,保证样本行为可回溯。
- 网络设置为“仅主机模式”或直接断网。如果要模拟网络回连,最好在虚拟机里安装一个流量监控代理,或使用本地端口重定向工具,而不是直接让样本连到真实外网。
- 关闭Windows Defender实时保护,否则样本还没跑起来就被清掉了,干扰分析。关闭后仍然要小心,不要把恶意样本拖到宿主机。
- 安装分析工具后再次做快照,保持工具链基线一致。
在我实际分析中,还会额外开启“显示完整命令行”和“显示所有用户进程”等视图选项,因为木马往往会操纵服务宿主进程并修改命令行参数,这些细节在默认任务管理器里根本看不到。
2.2 这几款工具是分析“傀儡进程”的主力
我常备的工具组合是:Process Explorer + Process Monitor + x64dbg/WinDbg + 内存转储工具。选型原因很简单,它们恰好覆盖了进程对象、文件/注册表行为、API级调用和内存内容四个层面。
- Process Explorer:可查看进程的父进程链、线程起始地址、加载的DLL列表和签名状态。傀儡进程最直观的一个特征,就是主线程的“开始地址”(Start Address)指向的内存区域没有对应的模块路径,或者模块路径显示为“未映射”。这一步基本上几秒就能看出问题。
- Process Monitor:用于记录进程对文件、注册表、网络和进程/线程的操作。它能看到
CreateProcess的调用、WriteFile到目标进程内存的痕迹、ResumeThread事件,以及样本释放了什么文件。分析傀儡进程时,我通常先用它抓一遍全量日志,再按进程名过滤。 - x64dbg / WinDbg:需要动态调试的时候用。傀儡进程的关键API调用(如
NtUnmapViewOfSection、SetThreadContext)可以在调试器里下断点,直接观察寄存器参数。如果样本有反调试,我会先跑一次无调试的Procexp/Procmon监控,再决定是否上调试器。 - 内存转储与取证工具:当进程已经是“傀儡”状态时,直接用工具把目标进程的内存dump下来,再用PE解析工具搜索
MZ头,可以还原恶意镜像。常见做法是手动全进程转储,然后用工具分析转储文件。
2.3 建立操作基线,别把系统正常行为当恶意
工具归工具,分析前我强烈建议先在没有运行样本的虚拟机里记录一份“正常基线”。包括:当前有哪些关键进程、各自路径是什么、加载了哪些DLL、主线程起始地址分布、网络连接状态。因为傀儡进程技术本身是“借用合法进程的外壳”,如果不知道正常情况下的系统进程长什么样,很容易在后续分析时把正常行为误判为恶意。基线不需要太复杂,抓两至三次,确认系统稳定即可。之后跑样本时对比差异,效率和准确率都会明显提升。
3. 手把手还原一次“傀儡进程”创建全过程
下面这段是我分析模拟项目X时实际操作记录的浓缩版。我会尽量按“动态监控 → 静态定位 → 内存取证”的顺序来讲,这也是我日常分析恶意样本的主线。
3.1 动态分析实录:你能看到什么
我先在虚拟机里打开Process Monitor,设置过滤器只抓取目标样本产生的进程、线程和文件操作。然后双击样本,几秒钟后看到一条非常清晰的行为链:
- 样本进程启动,路径位于临时目录,文件名为随机字符串。
- 随即调用
CreateProcess,映像路径指向C:\Windows\System32\svchost.exe,但是命令行参数与正常服务不同,且创建标志中出现了“挂起”。 - 紧接着有一条针对目标进程的
WriteFile或Process VirtualAlloc操作,写入的内存大小和恶意PE的SizeOfImage吻合。 - 几毫秒后出现
ResumeThread调用,目标进程的线程恢复运行。 - 后续网络连接模块开始工作,与外部地址建立连接。
只看Process Monitor日志,已经能确定这是一个典型的“挂起创建→写内存→恢复线程”链路。不过Procmon并不能直接证明写入的内存就是恶意PE,所以还需要配合Process Explorer确认线程起始地址。此时打开Process Explorer,定位到那个svchost.exe,查看它的线程选项卡,主线程的“开始地址”一栏显示为0x0000000140001000这类地址,而对应模块是“未映射”。正常svchost.exe的线程起始地址大多在ntdll.dll或svchost.exe的模块范围内,看到未映射地址基本可以判定傀儡进程已成立。
3.2 静态逆向关键点:怎么定位镂空代码
动态行为已经锁定了大方向,接下来我会上x64dbg附加到样本进程,在CreateProcessEx、NtUnmapViewOfSection、SetThreadContext、ResumeThread这几个API上下断点。命中后重点看几个参数:
CreateProcess的dwCreationFlags:是否包含0x08000004(即CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT的组合)。NtUnmapViewOfSection的BaseAddress:通常指向的是目标进程镜像基址。SetThreadContext的ContextRecord:关注RIP/EIP是否被改成恶意PE的入口点。
对于有混淆壳的样本,这会变成一个“先脱壳再分析”的步骤。我一般先用通用脱壳工具试一遍,再在内存中搜索特征字符串和PE头。模拟项目X的载荷部分做过一层简单的字符串加密,但在内存中解密后,能看到明显的控制协议特征,例如用于区分心跳包、命令执行结果、文件上传结果的标记字符。静态定位时,推荐把恶意PE从目标进程内存中dump出来,修复IAT后再导入IDA/Ghidra查看。这里要注意:修复IAT并不总能自动化完成,有时需要对照原进程模块列表手动补全,非常考验耐心。
3.3 建立攻击链:一张表看清关键API与信号
把上面的分析结果整理成表格,后续写报告或做检测规则时都很方便。以下是我常用的攻击链对照表:
| 攻击阶段 | 关键API/操作 | 恶意目的 | 关键信号 |
|---|---|---|---|
| 创建挂起进程 | CreateProcess(..., CREATE_SUSPENDED) | 拿到合法进程外壳 | 创建标志含挂起位,目标进程非常规用户启动 |
| 卸载原镜像 | NtUnmapViewOfSection | 清空目标进程原有代码 | 对目标进程执行内存映射卸载 |
| 写入恶意PE | VirtualAllocEx/WriteProcessMemory | 将恶意镜像放入目标进程 | 目标进程内存中出现新PE头,或写入区域与正常模块无关 |
| 设置入口点 | SetThreadContext | 劫持主线程执行流 | 主线程起始地址不在任何已加载模块内 |
| 恢复运行 | ResumeThread | 触发恶意代码执行 | 紧接着出现网络连接或文件释放行为 |
这张表的价值在于,检测规则不一定要死盯某一个API,而是可以组合多个信号来降低误报。比如仅“创建挂起进程”这一项在很多合法安装兼容场景也会出现,但如果后续紧跟“卸载原镜像”和“写进程内存”,那几乎可以判定为恶意。
3.4 内存取证确认:地址空间里到底放了什么
动态和静态分析做完后,我还会做一个验证性动作:把目标进程的完整内存dump下来,再在dump文件里搜索PE头。这一步能直接把“傀儡进程”实锤。
操作流程大致如下:
- 用Process Explorer选中那个
svchost.exe,右键创建完整转储文件。 - 转储文件保存后,用二进制查看工具搜索
MZ(0x4D 0x5A),并检查对应位置的e_lfanew字段是否指向合法的PE签名。 - 一旦确认存在独立的PE镜像,进一步提取该镜像的导入表和导出表。远控木马通常会导入
ws2_32.dll的网络函数和wininet.dll的HTTP函数,可以直接按导入函数名确定其功能范围。 - 记录恶意PE的编译时间戳、文件大小、节区特征,作为IOC的一部分。
在实际操作中,我发现有些恶意PE会混淆节区头和字符串,导致搜索MZ时漏报。这时可以换用Volatility等内存取证工具做pslist、dlllist、malfind扫描,malfind会主动匹配“可执行内存但没有对应模块”的区域,用来找傀儡进程更加高效。
4. 从分析到检测:这是最值得沉淀的部分
分析傀儡进程的最终目的通常是防住它,而不是仅仅“看懂”它。所以我每次分析完样本,都会顺手提炼检测思路和应急响应步骤,这里把最实用的部分分享出来。
4.1 检测规则思路:告警不能只看“创建进程”
如果你使用的是Sysmon,建议重点关注几个事件ID:
- 事件ID 8(CreateRemoteThread):虽然傀儡进程不通过远程线程启动,但写入内存后恢复线程时,部分变种会用远程线程方式触发,这个规则仍然有意义。
- 事件ID 10(ProcessAccess):记录了进程句柄的打开权限。如果某个进程以
PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD权限打开另一个进程,通常是注入行为。 - 事件ID 1(ProcessCreate)结合命令行:注意创建标志中的挂起位在事件日志里不一定直接体现,但可通过父进程和创建时间关系辅助判断。
我自己的规则组合是:创建挂起进程 + 写入目标进程内存 + 恢复线程,三个行为在短时间内相继发生,且目标进程是系统关键进程。在此基础上做一个评分,高危进程如svchost.exe、winlogon.exe权重加高。这样能大大降低误报。
如果要在终端侧做文件特征检测,可以写一个简单的YARA规则,匹配恶意PE的特征字符串或导入表特征。这里给出一个示意(仅为说明,不可直接用于实战):
rule Suspicious_PE_InMemory_Indicators { meta: author = "analyst" description = "Detect suspicious PE embedded with network functions" strings: $mz = { 4D 5A } $ws = "ws2_32.dll" nocase $win = "wininet.dll" nocase $cmd = "cmd.exe" nocase condition: $mz at 0 and 2 of ($ws, $win, $cmd) }提示:YARA规则只是静态层面的辅助,对于加了强壳的样本,建议配合行为规则一起使用,不要单独依赖文件特征。
4.2 事件溯源与应急处置
当终端告警确认是远程木马创建傀儡进程后,应急响应要尽快执行,但也不能慌。我通常按以下顺序操作:
- 隔离主机:断开网络连接,但保留内存转储的现场,避免木马继续外传数据。
- 确认进程树:用Process Explorer或系统命令记录当前傀儡进程的PID、父进程、启动时间、路径。
- 抓取内存:优先dump可疑进程和样本原始进程的内存,这是后续分析的关键证据。
- 清理持久化:远控木马一般会在注册表启动项、计划任务或服务中留下持久化入口,需要逐个排查。常见的检查点包括
HKCU\Software\Microsoft\Windows\CurrentVersion\Run、服务列表、启动文件夹。 - 恢复系统镜像:如果目标进程被替换,最简单的方法是用系统文件检查工具恢复原始镜像,或者从干净备份还原该文件。
- 全盘查杀与流量回溯:确认没有第二阶段载荷残留后,再考虑恢复业务。流量侧要回溯木马外联时间和数据包,确定受影响范围。
有一点要特别提醒:击杀傀儡进程前,如果不确定原始进程是什么,不要直接结束进程树,否则可能影响系统稳定性。我通常会先记录原始进程的路径和参数,再单独结束样本创建的傀儡进程,观察是否还有其他子进程重新拉起。
4.3 我踩过的两个坑:规则误报与反分析干扰
规则误报方面,最典型的是把“合法软件兼容性助手”或“系统更新服务”的挂起创建行为判成恶意。很多安装包为了等待另一个进程完成后返回,会用挂起方式临时启动子进程,但它们不会卸载原进程镜像,也不会写入额外PE。所以检测规则里一定要把“卸载原进程地址空间”或“写入PE头”这种特征加上,只靠挂起创建是不充分的。
反分析干扰方面,模拟项目X里有两个细节很值得提:一是它会在启动后检测是否处于虚拟机环境,如果发现调试器或特定虚拟机痕迹,会直接退出;二是它会把核心载荷延迟到系统空闲几分钟后再执行,用来绕过沙箱的时间窗口。遇到这种情况,我的对策是先跑一遍无调试动态监控,确认它确实会执行,再结合静态分析和内存取证。如果样本不执行,就手动修改环境特征,或者直接回到静态层面分析,不要在一棵树上吊死。
5. 常见问题与排查技巧实录
这部分整理了我多次处理类傀儡进程样本时遇到的高频问题和对应解法,你可以当成一份速查表用。
5.1 问题速查表
| 现象 | 原因 | 解决方法 |
|---|---|---|
| Process Monitor中看不到写内存操作 | 样本使用了直接系统调用,绕过了用户态API钩子 | 启用内核态日志或使用ETW跟踪,或直接对比进程内存前后变化 |
| Process Explorer显示“未映射”的线程起始地址 | 傀儡进程已创建,恶意PE在目标进程空间运行 | 立即dump内存,并搜索PE头确认镜像 |
| 样本在虚拟机中双击后无任何行为 | 存在反虚拟机/反调试检测 | 修改虚拟机特征,或先静态分析,再尝试动态调试配合内存补丁 |
| 疑似傀儡进程,但完整转储文件过大 | 目标进程占用了大量内存 | 过滤分页/非分页内存,直接搜索PE签名,或使用内存取证工具的malfind模块 |
| 调试器附加后样本自动退出 | 反调试线程检测调试端口 | 使用隐藏调试器插件,或在非调试模式下采集行为日志 |
| 多个svchost.exe进程分不清哪个是傀儡 | 只看进程名没有区分 | 按启动时间、父进程、命令行、内存中是否存在未映射线程逐一排查 |
5.2 一段排查经验谈
我个人习惯的顺序是:先看父子进程关系,再看内存特征,最后才去碰API细节。父子进程关系可以快速告诉你这条进程链是否正常;内存特征能在一分钟之内确认傀儡进程;API细节是用来写报告和检测规则时才会投入大量精力去分析。
真的在应急现场时,时间往往不会站在你这边。别一上来就开调试器单步跟踪,那样很可能被反调试干扰,还浪费大量时间。先把Process Explorer里的线程起始地址、Process Monitor里的行为日志抓下来,再决定是否深入。等样本已经定性为远程木马傀儡进程了,后面拆解它的具体控制命令和网络协议,反而是按部就班的机械活了。
最后再分享一个小技巧:分析完一批样本后,把提取到的进程路径、恶意PE哈希、API调用序列和检测规则沉淀到自己的知识库里。下次再遇到同类的时候,直接拿旧规则去匹配新样本,速度快到能让你“提前下班”。傀儡进程技术再怎么变,底层始终绕不开“挂起创建、卸原映像、写新PE、恢复线程”这四板斧;我们只要把板斧的痕迹看得足够清晰,攻击者就很难藏住手。