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安全, 二进制分析, 云安全运维, 恶意软件分析, 数据擦除, 沙箱增强, 脆弱性评估, 脚本宿主, 自动化对象, 进程注入, 逆向工具