如何正确挂钩IWbemServices::ExecMethod以捕获WMI方法参数
如何正确挂钩IWbemServices::ExecMethod以捕获WMI方法参数
要搞定挂钩IWbemServices::ExecMethod来捕获WMI方法参数这件事,咱们得先理清为啥常规方法行不通,再一步步走正确的虚函数表(VTable)挂钩路子。
为啥常规挂钩方法不管用?
首先得明确,IWbemServices是个COM接口,它的方法都是通过虚函数表调用的,FastProx.dll虽然是WMI的核心实现库,但根本没导出ExecMethod这个符号——这就是你用GetProcAddress找不到它的原因。至于IDA断点没触发,大概率是因为WMI调用走了COM代理/存根,你没命中到真正执行逻辑的模块,或者断点设错了位置。
正确思路:虚函数表(VTable)挂钩
COM对象的所有接口方法都存在vtable里,所以咱们的核心玩法是:拿到IWbemServices的实例指针,找到它的vtable中ExecMethod对应的条目,直接对这个条目动手挂钩。
一步步实现详解
1. 先找准ExecMethod在vtable里的偏移
COM接口的vtable是严格按接口声明顺序排列的,IWbemServices继承自IUnknown,所以前3个条目固定是QueryInterface、AddRef、Release。要找到ExecMethod的偏移,有两个靠谱办法:
- 翻
wbemcli.h里的IWbemServices声明,数清楚从IUnknown之后到ExecMethod有多少个虚函数,按每个函数占8字节(64位)或4字节(32位)计算偏移; - 直接用IDA打开
FastProx.dll,找到CWbemSvcWrapper::XWbemServices的vtable,一眼就能定位ExecMethod的位置——64位系统下它的偏移通常是0xC0,对应vtable数组的第36个元素(0xC0 / 8 = 36)。
2. 用MinHook实现挂钩(完整可参考代码)
下面结合你之前的Demo,整理出能跑通的挂钩代码,核心是先拿到vtable里的原始函数地址,再用MinHook替换成咱们的钩子函数:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <comutil.h> #include <wbemcli.h> #include "MinHook.h" #pragma comment(lib, "wbemuuid.lib") #pragma comment(lib, "comsuppw.lib") #pragma comment(lib, "MinHook.lib") // 定义ExecMethod的函数原型,严格遵循COM调用约定 typedef HRESULT (STDMETHODCALLTYPE* ExecMethod_Type)( __RPC__in IWbemServices* This, __RPC__in const BSTR strObjectPath, __RPC__in const BSTR strMethodName, __RPC__in long lFlags, __RPC__in_opt IWbemContext* pCtx, __RPC__in_opt IWbemClassObject* pInParams, __RPC__deref_opt_inout_opt IWbemClassObject** ppOutParams, __RPC__deref_opt_inout_opt IWbemCallResult** ppCallResult ); ExecMethod_Type OriginalExecMethod = nullptr; // 咱们的钩子函数,在这里捕获参数 HRESULT STDMETHODCALLTYPE Hooked_ExecMethod( __RPC__in IWbemServices* This, __RPC__in const BSTR strObjectPath, __RPC__in const BSTR strMethodName, __RPC__in long lFlags, __RPC__in_opt IWbemContext* pCtx, __RPC__in_opt IWbemClassObject* pInParams, __RPC__deref_opt_inout_opt IWbemClassObject** ppOutParams, __RPC__deref_opt_inout_opt IWbemCallResult** ppCallResult ) { // 1. 先捕获并打印调用的类名和方法名 wprintf(L"[捕获到WMI调用] 类路径: %ws | 方法名: %ws\n", strObjectPath, strMethodName); // 2. 解析输入参数(比如Win32_Process::Create的CommandLine参数) if (pInParams != nullptr) { VARIANT varParam; VariantInit(&varParam); if (SUCCEEDED(pInParams->Get(L"CommandLine", 0, &varParam, nullptr, nullptr))) { if (varParam.vt == VT_BSTR) { wprintf(L"[参数] CommandLine: %ws\n", varParam.bstrVal); } VariantClear(&varParam); } } // 3. 调用原始函数,保证WMI调用正常执行 HRESULT hr = OriginalExecMethod(This, strObjectPath, strMethodName, lFlags, pCtx, pInParams, ppOutParams, ppCallResult); // 4. 也可以在这里处理返回结果 if (SUCCEEDED(hr) && ppOutParams != nullptr && *ppOutParams != nullptr) { VARIANT varReturn; VariantInit(&varReturn); if (SUCCEEDED((*ppOutParams)->Get(L"ReturnValue", 0, &varReturn, nullptr, nullptr))) { wprintf(L"[返回值] ReturnValue: %d\n", varReturn.lVal); VariantClear(&varReturn); } } return hr; } int main() { // 初始化COM环境 HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); if (FAILED(hr)) { printf("CoInitializeEx失败: 0x%X\n", hr); return 1; } CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); // 获取IWbemLocator实例 IWbemLocator* pLoc = NULL; hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc); if (FAILED(hr)) { printf("CoCreateInstance失败: 0x%X\n", hr); CoUninitialize(); return 1; } // 连接到WMI命名空间 IWbemServices* pSvc = NULL; hr = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &pSvc); if (FAILED(hr)) { printf("ConnectServer失败: 0x%X\n", hr); pLoc->Release(); CoUninitialize(); return 1; } // 设置COM安全 blanket CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE); // 获取IWbemServices的vtable指针 void** vtable = *reinterpret_cast<void***>(pSvc); // 64位系统下ExecMethod的偏移是0xC0,对应vtable[36](0xC0 / 8 = 36) ExecMethod_Type targetFunc = reinterpret_cast<ExecMethod_Type>(vtable[36]); // 初始化MinHook并挂钩 if (MH_Initialize() != MH_OK) { printf("MH_Initialize失败\n"); goto Cleanup; } if (MH_CreateHook(reinterpret_cast<void*>(targetFunc), reinterpret_cast<void*>(Hooked_ExecMethod), reinterpret_cast<void**>(&OriginalExecMethod)) != MH_OK) { printf("MH_CreateHook失败\n"); goto Cleanup; } if (MH_EnableHook(reinterpret_cast<void*>(targetFunc)) != MH_OK) { printf("MH_EnableHook失败\n"); goto Cleanup; } printf("挂钩成功,触发WMI调用测试...\n"); // 触发一次WMI调用,测试钩子是否生效 BSTR className = SysAllocString(L"Win32_Process"); BSTR methodName = SysAllocString(L"Create"); IWbemClassObject* pClass = NULL; hr = pSvc->GetObject(className, 0, NULL, &pClass, NULL); if (SUCCEEDED(hr)) { IWbemClassObject* pInParamsClass = NULL; hr = pClass->GetMethod(methodName, 0, &pInParamsClass, NULL); if (SUCCEEDED(hr)) { IWbemClassObject* pInParams = NULL; hr = pInParamsClass->SpawnInstance(0, &pInParams); if (SUCCEEDED(hr)) { VARIANT varCommand; VariantInit(&varCommand); varCommand.vt = VT_BSTR; varCommand.bstrVal = SysAllocString(L"calc.exe"); pInParams->Put(L"CommandLine", 0, &varCommand, 0); VariantClear(&varCommand); IWbemClassObject* pOutParams = NULL; pSvc->ExecMethod(className, methodName, 0, NULL, pInParams, &pOutParams, NULL); if (pOutParams) pOutParams->Release(); pInParams->Release(); } pInParamsClass->Release(); } pClass->Release(); } SysFreeString(className); SysFreeString(methodName); Cleanup: // 清理资源 if (pSvc) pSvc->Release(); if (pLoc) pLoc->Release(); MH_DisableHook(reinterpret_cast<void*>(targetFunc)); MH_Uninitialize(); CoUninitialize(); system("pause"); return 0; }
几个关键注意点
- 32/64位适配:vtable的偏移在32位系统下是
0x60(对应vtable[24],0x60/4=24),要根据编译架构调整; - 跨架构COM调用:如果你的进程是32位,而WMI服务是64位(反之亦然),COM会用代理模块
wbemprox.dll,这时候你需要把挂钩代码注入到目标架构的进程里; - 参数解析:
pInParams是IWbemClassObject类型,要用它的Get方法提取参数,注意正确处理VARIANT类型的内存释放。
内容来源于stack exchange
相关产品推荐
相关产品推荐

