Cisco-Talos/iDispatch_Logging
GitHub: Cisco-Talos/iDispatch_Logging
通过挂钩 COM 激活路径并封装 IDispatch 接口,实现对 Windows 脚本恶意软件的实时行为追踪与深度分析。
Stars: 0 | Forks: 0
# DispatchLogger
Windows COM 监控层,适用于脚本宿主、PowerShell、EXE 以及任何滥用后期绑定自动化对象的程序。
该 DLL 注入到目标进程中,挂钩核心 COM 激活路径,并将其能接触到的每一个 `IDispatch` 封装在一个实时日志代理中。您可以获得脚本行为的实时跟踪——包括方法调用、参数、返回值、生成的子 COM 对象、枚举器、名字对象绑定,甚至 ByRef 输出参数。
它是为恶意软件分析和红队取证而构建的,但如果您想了解 VBScript、JScript、HTA、Office 宏或 PowerShell 自动化到底在底层做了什么,它也同样非常实用。
欲了解更多信息,请查看 [Cisco Talos Blogs](https://blog.talosintelligence.com/transparent-com-instrumentation-for-malware-analysis) 上的完整文章
快速入门说明:
* 所有 C 项目均使用 VS 2022 构建
* 代码库中包含注入器和日志解析器
* 注入器和 idispLogger.dll 可同时构建为 32/64 位版本。
* 您需要运行 DebugView 或 IPC 调试消息查看器来接收输出:
* [DebugView (Sysinternals)](https://learn.microsoft.com/en-us/sysinternals/downloads/debugview)
* [Persistent Debug Print Window (VBForums)](https://www.vbforums.com/showthread.php?874127-Persistent-Debug-Print-Window)
## 此工具解决的问题 ✅
脚本恶意软件在各方面都依赖于 COM:
* `Scripting.FileSystemObject` 用于文件 I/O
* `WScript.Shell` 用于进程启动和注册表写入
* `MSXML2.XMLHTTP` 用于下载并执行
* WMI 对象用于侦查
* 通过名字对象实现 `GetObject("winmgmts:...")` 风格的实时系统访问
传统的沙箱和字符串转储工具会遗漏很多此类行为,因为:
* 危险部分仅存在于运行时
* 对象被动态传递
* 子 COM 对象在静态代码中不明显
* 某些对象只能通过 `GetObject()` / 运行对象表访问,而非普通的 `CoCreateInstance`
DispatchLogger 通过以下方式直接解决这一问题:
* 挂钩 COM 激活本身
* 强制任何转变为 `IDispatch` 的对象通过我们的代理
* 记录对 `Invoke()` 的每次调用(方法、属性获取/设置)及其类型化的参数值和返回值
## 高层架构
### 1. API 挂钩层
我们对 `ole32.dll` 及其相关库中的一系列 COM 相关 API 进行了 Detour(挂钩):
* `CoCreateInstance` – 经典的 COM 激活
* `CoGetClassObject` – 获取实际构建面向脚本对象的类工厂
* `CLSIDFromProgID` – 解析 `"Scripting.FileSystemObject"` → CLSID,以便我们可以用人类可读的名称标记对象
* `CoGetObject` – 由 VB / VBScript `GetObject(...)` 用于绑定名字对象,如 WMI 命名空间、运行中的 COM 服务器等。
* `GetActiveObject` – 从运行对象表中提取(相当于“与已运行的 Excel 实例进行交互”)
* `MkParseDisplayName` – 解析名字对象名称,以便我们拦截并封装它们
每个挂钩都会记录请求的内容,然后在可能的情况下返回*我们*封装过的对象,而不是原始对象。
如果目标仅暴露 `IUnknown`,我们会立即 `QueryInterface` 查询 `IDispatch` 并对其进行封装。
### 2. ClassFactoryProxy (工厂级拦截)
当脚本代码执行 `CreateObject("WScript.Shell")` 时,VBScript/JScript **不会**为了 `IDispatch` 而直接调用 `CoCreateInstance`。它向 COM 请求类工厂,调用 `IClassFactory::CreateInstance()` 并请求 **IUnknown**,只有在此之后才请求 `IDispatch`。这意味着简单的 `CoCreateInstance` 挂钩会遗漏大部分内容。
我们通过封装 `CoGetClassObject` 返回的 `IClassFactory` 来解决这个问题:
```
class ClassFactoryProxy : public IClassFactory {
// Intercepts CreateInstance()
// If the created object can speak IDispatch / IDispatchEx,
// we replace it with our DispatchProxy before the script ever sees it.
}
```
这保证了对以下高价值 ProgID 的首次接触可见性:
* `Scripting.FileSystemObject`
* `WScript.Shell`
* `Scripting.Dictionary`
* `MSXML2.XMLHTTP`
(以及任何流经 `CoGetClassObject` 的其他内容)
### 3. DispatchProxy (单对象拦截)
每个支持 `IDispatch` 的 COM 对象都会被封装在一个 `DispatchProxy` 中。该代理:
* 实现 `IUnknown` / `IDispatch`,以便脚本可以继续正常使用它
* 记录对 `Invoke()` 的每次调用
* 通过 `ITypeInfo` 解析人类可读的方法/属性名称
* 转储所有参数及其变体类型
* 记录返回值
* 递归封装任何返回的 `IDispatch`、`IUnknown`(即 QI 为 `IDispatch` 的对象)或枚举器,以便子对象也得到跟踪
当您的脚本调用类似以下内容时:
```
set fso = CreateObject("Scripting.FileSystemObject")
set fldr = fso.GetFolder("C:\Temp")
for each f in fldr.Files
WScript.Echo f.Path
next
```
您不仅会看到“GetFolder called”。
您还会获得:
* Folder 的新封装代理
* 用于 `For Each` 的封装枚举器
* 记录了返回字符串的每个属性获取 (`.Path`)
## 当前版本的高级功能
### ✔ 返回值的递归封装
如果某个方法返回另一个 COM 对象,我们会立即封装该子对象,并在描述性名称(如 `FileSystemObject.GetSpecialFolder`)下继续跟踪它。您看到的是整个对象图,而不仅仅是根对象。
### ✔ 枚举器拦截 (`IEnumVARIANT`)
我们也代理 `IEnumVARIANT`。这意味着即使是 `For Each` 循环也是可见的。每个生成的项都会被检查,如果是 COM 对象,它也会在脚本看到之前被封装。这可以捕获 WMI 记录集、文件列表等。
### ✔ ByRef 输出参数处理
许多 COM API 通过 `ByRef` 参数而不是返回值交还新对象。
在 `Invoke()` 期间,我们遍历参数列表,检测 `VT_BYREF`,对其解引用,如果返回的是新 COM 对象,我们会封装它并原地替换。您仍然可以获得完整的下游日志记录,并带有正确的身份跟踪。
### ✔ 名字对象 / `GetObject()` / WMI 路径记录
我们挂钩:
* `CoGetObject` (VB `GetObject("winmgmts:...")`)
* `MkParseDisplayName` (名字对象字符串 → `IMoniker`)
并且我们用 `MonikerProxy` 封装生成的名字对象。
`MonikerProxy` 拦截 `IMoniker::BindToObject`,记录请求了哪个名字对象、请求了哪个接口(`IUnknown` vs `IDispatch`),并在解析为自动化对象时再次替换为 `DispatchProxy`。这涵盖了像 WMI 和从未触及 `CoCreateInstance` 的运行中 COM 服务器之类的后期绑定内容。
### ✔ 运行对象表 / `GetActiveObject`
如果脚本尝试通过 `GetActiveObject` 获取现有的正在运行的 COM 服务器(Excel、Word 等),我们也会拦截它,记录 CLSID,并在交还之前再次封装返回的自动化对象。
### ✔ 感知 IDispatchEx,但安全
我们检测 `IDispatchEx` 和 `IDispatch`,在 `QueryInterface` 中记录接口查询,并避免谎报我们没有完全实现的接口。这可以防止脚本宿主在探测扩展调度功能时崩溃。
### ✔ 实时 IPC 到您的调试控制台
所有日志行均通过 `WM_COPYDATA` 推送到 VB6 “Persistent Debug Print Window”,并带有 PID/TID 前缀以便多进程区分。
https://www.vbforums.com/showthread.php?874127-Persistent-Debug-Print-Window
如果该窗口不存在,我们会回退到 `OutputDebugStringA`,因此 DebugView 仍然可以看到输出。
https://learn.microsoft.com/en-us/sysinternals/downloads/debugview
## 输出样式(示例)
您会看到类似这样的结构化信息:
```
[HOOK] CoGetClassObject: WScript.Shell ({CLSID...}) Context=0x1
[CoGetClassObject] Got IClassFactory for WScript.Shell - WRAPPING!
[FACTORY] CreateInstance: WScript.Shell requesting IUnknown
[FACTORY] CreateInstance SUCCESS: Object at 0x12345678
[FACTORY] !!! Replaced object with proxy!
[PROXY #1] >>> Invoke: WScript.Shell.Run (METHOD) ArgCount=2
[PROXY #1] Arg[0]: "cmd.exe /c whoami"
[PROXY #1] Arg[1]: 0
[PROXY #1] <<< Result: 0x00000000 (HRESULT=0x00000000)
[PROXY #2] >>> Invoke: FileSystemObject.OpenTextFile (METHOD) ArgCount=2
[PROXY #2] Arg[0]: "C:\Temp\dropper.exe"
[PROXY #2] Arg[1]: 2
[PROXY #2] <<< Result: IDispatch:0x03AD6C14
[PROXY #2] !!! Wrapped returned IDispatch as new proxy
```
这直接来自 `DispatchProxy::Invoke()` 及其相关函数。它使用 `ITypeInfo` 解析方法名称,记录标志(`METHOD`、`PROPGET` 等),按正确的逆序遍历参数,并记录返回值,包括字符串、数字、布尔值、数组和对象指针。
## 使用模型
* 您将 `DispatchLogger.dll` 注入到目标进程中(wscript.exe、cscript.exe、powershell.exe 等)。
* 加载时,`DllMain`/`InstallHooks()` 定位 `ole32.dll`,使用您的挂钩引擎修补相关导出函数(如 `CoCreateInstance`、`CoGetClassObject` 等)并开始记录。
您可以:
* 在注入器下启动 `wscript.exe script.vbs` 以分析经典的 VBS 恶意软件
* 启动 `powershell.exe -File script.ps1` 并观察来自 PowerShell 的 COM 自动化
* 注入到已经正在运行且滥用 COM 的进程中(即使它根本不涉及 WSH)
## 注入器
**用途**
`SimpleInjector` 是 `iDispLogger.dll` 的配套启动器。它创建或附加到目标进程并注入记录器 DLL。如果找到 dbgWindow.exe,它将自动启动。
**关键行为**
* 显示 /h /? /help(以及 -h 变体)的用法
* 如果无参数:运行 `cscript.exe tests\TestScript.vbs`(默认)。(双击行为)
* 如果单个参数是 `.vbs`、`.js`、`.wsf`、`.hta` 文件:在 `cscript.exe "script"` 下运行它;如果单个参数是 `.exe`,则直接运行该 exe。
* 如果多个参数:将第一个参数视为可执行文件,其余作为参数传递。
* 确保 `dbgwindow.exe`(VB6 调试接收器)正在运行,并将尝试从当前或父目录启动它。
* 实现经典的 `LoadLibrary` 远程线程 DLL 注入到挂起的子进程中:创建挂起的进程 → 写入 DLL 路径 → CreateRemoteThread(LoadLibraryA) → 恢复线程。
* 等待子进程退出并在等待时泵送消息,以便 GUI 应用程序保持响应。
**CLI 示例**
```
# 默认 (如果你有 tests/TestScript.vbs) - 只需双击
injector.exe
# 使用 cscript 运行脚本
injector.exe malware.js
# 启动任意 exe + args
injector.exe powershell.exe -File "analyze.ps1"
# 带 args 运行 wscript
injector.exe "wscript.exe" "test.vbs"
```
**说明与提示**
* 注入器会在当前目录或上一级目录中查找 `iDispLogger.dll`;如果您将二进制文件存储在其他位置,请调整路径。
* 如果缺少 `dbgwindow.exe`,注入器会发出警告,记录器将回退到 `OutputDebugStringA` (DebugView)。
* 注入器返回子进程句柄(在您查看日志时保持进程打开)。它在等待时使用消息循环,因此不会阻塞子进程中的 GUI 消息处理。
## 日志解析器 —— 快速实用
可以使用 `log_parser.py` 或 `logRecon.exe` 将详细的 IPC 日志解析为人类可读的形式。这些工具仅以易于消化的格式显示 COM 操作。
## 说明 / 限制
* 仅限 Windows。这是 COM。
* 需要 DLL 注入和对系统 COM 导出函数(`CoCreateInstance` 等)的运行时补丁。
* 目前我们将自己呈现为 `IDispatch` 并代理 `IDispatchEx`,而不是声称完整的原生 `IDispatchEx` 实现。这避免了脚本探测动态成员时发生崩溃。完整的 `IDispatchEx` 展示已在计划中。
* 根本不暴露自动化的对象(纯自定义接口,无 `IDispatch`/`IDispatchEx`)仍会在创建时被记录,但显然不会生成 Invoke() 跟踪。我们仍会记录对它们的 `QueryInterface` 尝试并记录失败。
## 致谢
NTCore Hooking Engine 作者: Daniel Pistelli许可证:Public Domain http://www.ntcore.com/files/nthookengine.htm diStorm 作者 Gil Dabah. 版权所有 (C) 2003-2012 Gil Dabah. diStorm at gmail dot com 许可证:BSD https://github.com/gdabah/distorm DispatchLogger 由 Cisco Talos 构建 许可证:Apache 2.0 作者:David Zimmer
标签:API Hooking, C++, COM监控, Conpot, DAST, EDR, IDispatch, PowerShell安全, SSH蜜罐, UML, VBScript分析, Windows安全, 二进制分析, 云安全运维, 恶意软件分析, 数据擦除, 沙箱增强, 脆弱性评估, 脚本宿主, 自动化对象, 进程注入, 逆向工具