组件与工具
WMIC
WMIC(Windows Management Instrumentation Command-line)是Windows操作系统的命令行工具,属于Windows管理工具类别,主要功能为通过命令行接口执行系统管理任务及查询信息。
例如,运维人员在命令提示符中使用 WMIC 查询电脑型号、磁盘序列号、网卡信息或进程列表时,本质上就是在通过 WMIC 调用 Windows 管理信息接口来获取系统数据。
svchost
svchost.exe 的全称是 Service Host,即“服务宿主进程”。它是 Windows 操作系统中的系统进程,主要功能为承载和管理系统服务,尤其是以 DLL 形式运行的服务组件。
例如,Windows Update、DHCP Client、DNS Client、Themes 等系统服务,很多都是由一个或多个 svchost.exe 进程统一承载运行的,因此在任务管理器中经常能看到多个 svchost 实例。
conhost
conhost.exe 的全称是 Console Window Host,即“控制台窗口宿主进程”。它是 Windows 操作系统中的系统进程,主要功能为为命令行程序提供控制台窗口支持,并处理控制台界面的显示与交互。
例如,当用户打开 cmd、PowerShell,或者一些需要弹出命令行窗口的安装程序、打包工具时,conhost.exe 往往会随之启动,用来负责控制台窗口的显示、输入和输出。
rundll32
rundll32.exe 是 Windows 提供的 DLL 宿主程序。它本身是一个 EXE,作用是创建一个进程上下文,加载指定的 DLL,并调用 DLL 中符合约定的导出函数。它不是把 DLL 当作 EXE 运行,也不是 Windows 加载 DLL 的必经组件。
它如何知道运行哪个 DLL
rundll32 不会扫描磁盘或自动猜测要加载的 DLL。通常由启动它的程序在命令行中明确提供 DLL 路径、导出函数名和参数:
rundll32.exe C:\Path\demo.dll,RunMe 参数
其中:
C:\Path\demo.dll是要加载的 DLL。使用绝对路径时目标明确;使用相对路径或只写文件名时,会受到当前目录和 DLL 搜索路径等因素影响。RunMe是 DLL 的导出函数名。逗号前后的 DLL 路径和导出函数名共同决定调用目标。参数是传给导出函数的命令行参数,具体如何解释由 DLL 自己决定。
rundll32 收到命令行后,概念上会完成以下动作:
读取命令行
↓
解析 DLL 路径、导出函数名和参数
↓
LoadLibrary(DLL)
↓
Windows Loader 映射 DLL,调用 DllMain(DLL_PROCESS_ATTACH)
↓
GetProcAddress(导出函数名)
↓
按约定调用导出函数
因此,真正被调用的是导出表中的函数,而不是 EXE 意义上的 main。DLL 也必须导出指定函数,并且函数的调用约定和参数布局要与 rundll32 的约定匹配;普通 DLL 即使能够被加载,也不一定能够被 rundll32 正确调用。DLL 的加载、DllMain、导出函数和执行上下文的详细关系见下方 DLL 一节。
谁会拉起 rundll32
任何有权限创建进程的程序都可以通过 CreateProcess、ShellExecute 等机制启动 rundll32。常见来源包括:
用户操作或系统 Shell
└─ explorer.exe
└─ rundll32.exe shell32.dll,Control_RunDLL ...
软件、安装器或更新器
└─ vendor.exe / installer.exe
└─ rundll32.exe vendor.dll,SomeFunction ...
命令行、脚本或计划任务
└─ cmd.exe / powershell.exe / 脚本宿主 / 计划任务宿主
└─ rundll32.exe xxx.dll,Entry ...
例如,用户通过资源管理器打开某项系统配置、控制面板功能或文件关联操作时,explorer.exe 可能作为发起者启动 rundll32。某些安装程序和厂商软件也可能把特定功能放在 DLL 中,再启动 rundll32 调用它。命令行、PowerShell、脚本、计划任务或服务相关组件同样可以启动它。
但在恶意行为中,常见链路可能是:
恶意文档或异常应用
↓
cmd.exe / powershell.exe
↓
rundll32.exe
↓
用户可写目录中的 DLL,Start
所以看到 rundll32.exe 时,不应只判断它是不是微软签名文件,而应沿着进程链回答:
谁创建了 rundll32?
↓
完整 command line 是什么?
↓
它加载了哪个 DLL?
↓
DLL 位于什么路径、由谁签名?
↓
调用了哪个导出函数?
↓
随后是否出现网络连接、持久化、注入或其他异常行为?
还要注意,绝大多数正常应用使用 DLL 时,并不会额外启动 rundll32,而是由自身进程通过导入表、LoadLibrary 和 GetProcAddress 直接加载 DLL。因此,app.exe → xxx.dll 与 app.exe → rundll32.exe → xxx.dll 是两条不同的加载链路;后者应结合具体软件设计和行为进行判断。
DLL
DLL(Dynamic Link Library,动态链接库)是 Windows 中用于复用代码和资源的文件格式,通常以 .dll 为扩展名。它不是一个可以独立双击运行的完整程序,而是由 EXE、服务或其他 DLL 在需要时加载,并向调用方提供函数、类、资源或驱动支持。
可以把 DLL 理解为“供多个程序按需使用的功能模块”。例如 kernel32.dll、user32.dll、advapi32.dll 都向程序提供常见的 Windows 能力;许多软件也会把自己的功能拆分成多个 DLL,便于更新和复用。
DLL 的执行模型
DLL 中当然包含可执行代码,通常可以在 PE 文件中看到 .text 段、导入表和导出表。容易混淆的是“入口”这个词:
- PE 入口点与
DllMain:DLL 被映射到进程后,由 Windows Loader 调用其 PE 入口点;在常规编译链中,这通常会进入 CRT 的 DLL 启动代码,再调用开发者提供的DllMain(如果有)。常见通知包括DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH。它是加载回调,不等同于 EXE 的main或WinMain,也不一定承载 DLL 的主要业务逻辑。 - 导出函数(Export):DLL 主动公开给其他模块调用的函数,例如
RunMe、DllGetClassObject或业务 API。调用方通过导出表按名称或序号找到函数地址后,才能执行对应代码。
因此,在常规 Windows 加载模型下,DLL 没有“自己创建进程并从 main 开始运行”的语义;它必须先被某个宿主进程加载。代码实际在哪个进程中执行,取决于加载它的宿主进程及其权限、令牌和运行环境。
宿主进程(EXE / 服务 / rundll32.exe)
↓ 映射 DLL
Windows Loader
├─ 解析依赖和重定位
├─ 调用 DllMain(DLL_PROCESS_ATTACH)
└─ 宿主调用某个导出函数
↓
DLL 业务代码
两种常见运行方式
1. 应用程序导入 DLL
这是普通软件最常见的方式。应用程序在编译和链接时声明要使用哪些 DLL 函数,生成的 EXE 会把依赖写入 PE 的 Import Table,其中的 Import Address Table(IAT) 保存运行时解析后的函数地址。
例如,DLL 导出 Add 函数,应用程序可以这样声明并调用:
__declspec(dllimport) int Add(int a, int b);
int main(void)
{
return Add(1, 2);
}
进程启动时,Windows Loader 大致执行以下工作:
CreateProcess(app.exe)
↓
读取 EXE 的 Import Table
↓
映射依赖 DLL(以及 DLL 的依赖)
↓
解析导出函数并填充 IAT
↓
调用 DLL 的 DllMain(DLL_PROCESS_ATTACH)
↓
进入 EXE 的进程入口,随后执行 main / WinMain
↓
应用通过 IAT 调用 DLL 函数
如果依赖的 DLL 找不到,或导出的函数无法解析,应用程序通常会在启动阶段失败。这个过程也可以由应用程序显式控制:程序运行中调用 LoadLibrary 加载 DLL,再用 GetProcAddress 获取导出函数地址,最后通过函数指针调用。这种按需加载常见于插件、可选功能和硬件兼容模块。
HMODULE h = LoadLibraryW(L"plugin.dll");
FARPROC p = GetProcAddress(h, "RunMe");
/* 将 p 转换为匹配的函数指针后调用 */
2. 通过 rundll32.exe 调用导出函数
rundll32.exe 是一个 EXE 宿主,也是 Windows 中常被关注的 LOLBin。它并不是把 DLL 当作 EXE 启动,而是负责加载 DLL、查找指定导出函数并调用它:
rundll32.exe C:\Path\demo.dll,RunMe 参数
概念上可以近似理解为:
HMODULE h = LoadLibraryW(L"C:\\Path\\demo.dll");
FARPROC p = GetProcAddress(h, "RunMe");
/* rundll32 按约定的参数格式调用 p */
实际执行顺序是:
CreateProcess(rundll32.exe)
↓
rundll32 解析 DLL 路径、导出函数名和参数
↓
LoadLibrary(DLL)
↓
Windows Loader 映射 DLL,并调用 DllMain(DLL_PROCESS_ATTACH)
↓
GetProcAddress(导出函数名)
↓
调用该导出函数
↓
函数返回,DLL 可被卸载
被 rundll32 调用的导出函数需要符合它约定的调用接口,常见原型为:
void CALLBACK RunMe(
HWND hwnd,
HINSTANCE hinst,
LPSTR lpszCmdLine,
int nCmdShow
);
这不是任意 DLL 的通用执行入口。DLL 必须导出指定函数,且函数的调用约定和参数布局要匹配;否则可能调用失败或导致宿主进程崩溃。DllMain 会在加载阶段执行,但 rundll32 真正希望调用的是命令行中逗号后的导出函数,例如 RunMe,而不是把 DllMain 当成 main 使用。
两种方式的关系可以概括为:
| 方式 | 谁负责加载 | 函数如何被找到 | 代码在哪执行 |
|---|---|---|---|
| 应用程序导入 | Windows Loader 或应用程序自身 | 导入表/IAT,或 GetProcAddress | 调用方 EXE、服务或插件宿主进程 |
rundll32 调用 | rundll32.exe | 命令行指定的导出函数 | rundll32.exe 进程 |
DLL 的“存在”不代表它一定正在运行。只有某个进程将它映射到自己的地址空间并执行其中代码时,才会在该进程的模块列表中看到它。一个 DLL 也可以同时被多个进程加载;每个进程仍有自己的虚拟地址空间和运行上下文。rundll32 也不会自动赋予 DLL 更高权限,DLL 能做什么主要取决于宿主进程的权限和安全边界。
安全关注点
DLL 是正常的软件构成方式,但也经常出现在安全告警和恶意代码分析中。排查时可重点关注:
- 加载来源:系统或可信软件是否从预期目录加载模块;临时目录、下载目录、用户可写目录中的 DLL 值得进一步核实。
- 签名与版本:模块是否有可信签名,签名主体、文件版本和所属软件是否匹配。
- 加载链路:哪个进程加载了它、父进程和命令行是否合理、加载后是否出现异常网络或进程操作。
- 宿主与调用方式:重点记录宿主进程、完整命令行、DLL 路径和导出函数名。
rundll32.exe本身是合法系统程序,但从临时目录、下载目录、网络共享路径或用户可写目录加载 DLL,或调用不常见导出函数时,应结合签名、来源和后续行为调查。 - 搜索路径风险:如果软件未使用明确路径,而是依赖 DLL 搜索顺序,攻击者可能将同名 DLL 放入可被优先搜索的位置。这类风险通常称为 DLL 搜索顺序劫持或 DLL 劫持。
单独发现一个非微软 DLL 并不足以定性恶意:浏览器、EDR、输入法、硬件驱动和企业客户端都会加载大量第三方模块。更可靠的判断应结合路径、签名、宿主进程、创建时间和后续行为。
COM
COM(Component Object Model,组件对象模型)是 Windows 的二进制组件标准。它规定了一个组件如何暴露对象、如何让其他程序创建并调用对象,以及如何在不同进程、甚至不同主机之间完成通信。
COM 关注的不是“一个文件”,而是“一个可被调用的对象及其接口”。例如资源管理器扩展、Office 自动化、浏览器控件、系统管理组件等,都可能以 COM 对象的形式被其他程序使用。COM 组件的实际实现经常位于 DLL 或 EXE 中,但 COM 本身并不等同于 DLL。
基本结构
COM 对象通常使用以下标识和注册信息被定位:
CLSID:组件类标识符,用于唯一标识可创建的 COM 类。ProgID:更易读的程序化名称,例如某些自动化组件会使用它。IID:接口标识符,用于标识对象支持的某个接口。- 注册表项:通常位于
HKCR\CLSID(实际由用户级和计算机级类注册信息合并呈现),描述 CLSID 对应的服务器位置和配置。
当程序请求创建一个 COM 对象时,系统会根据 CLSID 查找注册信息,再加载相应服务器或与之通信:
调用方
↓ CLSID / 接口
COM 运行库
↓ 查询注册信息
组件服务器
├─ 进程内服务器:DLL(InprocServer32)
└─ 进程外服务器:EXE / 服务(LocalServer32 等)
进程内 COM 组件直接作为 DLL 加载到调用方进程中,调用效率高,但组件崩溃可能影响宿主;进程外 COM 组件运行在独立进程中,隔离性更好,跨进程调用通常会借助 RPC。跨主机使用的 COM 通常称为 DCOM。
安全关注点
COM 的注册、激活和跨进程调用机制都值得关注:
- 异常注册项:CLSID 指向用户可写目录、临时目录或未知 DLL/EXE 时,应检查其来源和签名。
- COM 劫持:攻击者可能在用户级注册表中为特定 CLSID 写入恶意服务器路径,使部分程序在创建该组件时加载攻击者的代码。这是一种常见持久化思路。
- 宿主关系:
dllhost.exe、svchost.exe或业务软件启动的 COM 服务器并不天然异常;需要结合其加载模块、启动参数、账户和注册表映射判断。 - 权限边界:组件激活与对象调用会受到当前用户、完整性级别、DCOM 配置和组件自身访问控制的影响,不能仅凭某个 CLSID 存在就推断可以被任意程序利用。
COM 与 DLL 对比
COM 和 DLL 常被混为一谈,是因为许多 COM 组件确实由 DLL 实现;但两者处于不同抽象层级。DLL 是代码的交付和加载形式,COM 是组件对象的接口、激活和通信规范。
| 维度 | DLL | COM |
|---|---|---|
| 核心概念 | 可被进程加载的动态链接库文件 | 可被创建和调用的组件对象模型 |
| 关注对象 | 导出函数、资源、模块加载 | 对象、接口、类注册与对象激活 |
| 常见载体 | .dll 文件 | DLL、EXE 或服务均可实现 |
| 调用方式 | 导入表或运行时解析函数地址 | 通过 CLSID 创建对象,再通过接口调用 |
| 运行位置 | 通常加载到调用方进程 | 可进程内,也可运行在独立进程或远程主机 |
| 跨进程能力 | DLL 本身不提供 | 可通过 COM 代理和 RPC 完成 |
| 典型安全问题 | 恶意模块加载、DLL 劫持、侧载 | COM 劫持、异常组件注册、DCOM 配置风险 |
可以用下面这句话概括两者关系:
DLL 可以是 COM 的实现载体,
但 DLL 不会天然成为 COM 组件;
COM 组件也不一定必须由 DLL 实现。