【COM】异步通知 ExecNotificationQueryAsync 的使用问题

概述:异步通知 IWbemServices::ExecNotificationQueryAsync 的使用问题。调用 CancelAsyncCall 之后导致程序崩溃。

代码参考微软官方示例:示例:通过 WMI 接收事件通知 - Win32 apps | Microsoft Learn

0x01 复现场景

  • 环境:windows server 2008

参考微软官方代码实现一个进程,使用 windbg 加载进程,然后等待进程结束,就能看到 windbg 报错 0xC0000005。

0x02 dmp分析

  1. 报错详情

    (调用 CancelAsyncCall 的进程)主要问题还是访问了空指针导致的,并且是 ecx。是类成员函数调用时出现异常。this 指针为空。

  2. 堆栈

    确定是 RPC 调用。下一步查询下 RPC 调用方。

    ole32!GetChannelCallMgr 函数原型:

    1
    2
    HRESULT GetChannelCallMgr(RPCOLEMESSAGE *pMsg, IUnknown * pStub,
    IUnknown *pServer, CRpcChannelBuffer::CServerCallMgr **ppStubBuffer)

    直接看第一个参数 pMsg

  3. 查看下调用方

    成员变量 reserverd1 保存了RPC相关信息。 先 ddsdt 查看其内容。

    CAsyncCall 的成员可以查看 Windows-Server-2003/com/ole32/com/dcomrem/call.hxx at 5c6fe3db626b63a384230a1aa6b92ac416b0765f · selfrender/Windows-Server-2003 中相关源码。

    成员变量 _hRPC 来自于其继承的父类 CMessageCall

    1
    293   handle_t            _hRpc;      // Call handle (not binding handle).

    _hRpc 记录了调用方的句柄。查看 0x0479ba28 内存,在其附近找到了调用方的 PID 和 TID。如下图所示:

  4. 看下 PID 对应的进程,如下图所示:

    unsecapp 也就是我们注册的异步通知的通知方。

0x03 问题分析

基本分析完 dmp 文件之后,可以确定是我们注册的异步通知在程序结束时被调用了,导致 unsecapp 通知过来之后,调用了空指针。

为什么会出现这种问题,可以看下 unsecapp 的堆栈,上边已经看到了具体的pid和tid,1b34.1b48, 这就直接看线程 1b38 的堆栈就可以了。

堆栈如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
   0  Id: 1b34.1b38 Suspend: 1 Teb: 7ffdf000 Unfrozen
# ChildEBP RetAddr Args to Child
00 0018e8b0 773354ac 76d6a8b7 00000002 0018e904 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
01 0018e8b4 76d6a8b7 00000002 0018e904 00000001 ntdll!NtWaitForMultipleObjects+0xc (FPO: [5,0,0])
02 0018e950 77420f8d 0018e904 0018e978 00000000 kernel32!WaitForMultipleObjectsEx+0x11d (FPO: [Non-Fpo])
03 0018e9a4 76832507 0000002c 0018e9ec ffffffff USER32!RealMsgWaitForMultipleObjectsEx+0x13c (FPO: [Non-Fpo])
04 0018e9cc 768325bc 0018e9ec ffffffff 0018e9fc ole32!CCliModalLoop::BlockFn+0x97 (FPO: [Non-Fpo]) (CONV: thiscall) [d:\longhorn\com\ole32\com\dcomrem\callctrl.cxx @ 1162]
05 0018e9f4 76938f62 ffffffff 002c27a0 0018eb00 ole32!ModalLoop+0x5b (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\chancont.cxx @ 211]
06 0018ea10 76939ce1 00000000 0018eb14 00000000 ole32!ThreadSendReceive+0x12c (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 4831]
07 0018ea38 76939b4d 0018eb00 002cf4d8 0018eb5c ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0x194 (FPO: [Non-Fpo]) (CONV: thiscall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 4386]
08 0018eb18 76833f94 002cf4d8 0018ec40 0018ec24 ole32!CRpcChannelBuffer::SendReceive2+0xef (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 4009]
09 0018eb34 76833f46 0018ec40 0018ec24 002cf4d8 ole32!CCliModalLoop::SendReceive+0x1e (FPO: [Non-Fpo]) (CONV: thiscall) [d:\longhorn\com\ole32\com\dcomrem\callctrl.cxx @ 849]
0a 0018ebac 76853811 002cf4d8 0018ec40 0018ec24 ole32!CAptRpcChnl::SendReceive+0x73 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\callctrl.cxx @ 578]
0b 0018ec00 7712074c 002cf4d8 0018ec40 0018ec24 ole32!CCtxComChnl::SendReceive+0x1c5 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\ctxchnl.cxx @ 734]
0c 0018ec18 771207ad 002a37bc 0018ecdc 771211b0 RPCRT4!NdrProxySendReceive+0x43
0d 0018ec24 771211b0 45542f97 0018f0d8 070001f3 RPCRT4!NdrpProxySendReceive+0xc (FPO: [0,0,0])
0e 0018f09c 77120822 70b62ba8 70b621fc 0018f0d8 RPCRT4!NdrClientCall2+0x5e9
0f 0018f0c0 770aeb83 0018f0d8 00000004 0018f0f0 RPCRT4!ObjectStublessClient+0x6f
10 0018f0d0 6fc1a87e 002a37bc 00000000 80041032 RPCRT4!ObjectStubless+0xf
11 0018f0f0 004b2ec2 00eea0fc 00000000 80041032 fastprox!CSinkProxyBuffer::XSinkFacelet::SetStatus+0x2e (FPO: [Non-Fpo])
12 0018f128 770b3419 00262914 00000000 80041032 unsecapp!CStub::SetStatus+0x7b (FPO: [Non-Fpo])
13 0018f150 77121d35 004b2e47 0018f350 00000005 RPCRT4!Invoke+0x2a
14 0018f180 771220a1 00000000 002aeb9c 002df118 RPCRT4!NdrStubCall2+0x28a (FPO: [SEH])
15 0018f1c8 770b6e5f 76866918 70b62ba8 00000000 RPCRT4!NdrStubCall2+0x4ab (FPO: [SEH])
16 0018f1cc 76866918 70b62ba8 00000000 00000000 RPCRT4!NdrpClientUnMarshal+0x464 (FPO: [SEH])
17 0018f574 77122465 002d1e78 002cf658 002c31b8 ole32!NdrOleAllocate
18 0018f5c4 6fc1a8b7 002d1e78 002c31b8 002cf658 RPCRT4!CStdStubBuffer_Invoke+0xa0 (FPO: [SEH])
19 0018f5d8 7693a8c5 00eea174 002c31b8 002cf658 fastprox!CSinkStubBuffer::XSinkStublet::Invoke+0x36 (FPO: [Non-Fpo])
1a 0018f620 7693aa59 002c31b8 002bd800 002b5eb0 ole32!SyncStubInvoke+0x3c (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 1161]
1b 0018f66c 768661d6 002c31b8 002cfd60 00eea174 ole32!StubInvoke+0xb9 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 1372]
1c 0018f748 768660e7 002cf658 00000000 00eea174 ole32!CCtxComChnl::ContextInvoke+0xfa (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\ctxchnl.cxx @ 1255]
1d 0018f764 76866df5 002c31b8 00000001 00eea174 ole32!MTAInvoke+0x1a (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\callctrl.cxx @ 2020]
1e 0018f790 7693a981 002c31b8 00000001 00eea174 ole32!STAInvoke+0x46 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\callctrl.cxx @ 1839]
1f 0018f7c4 7693a79b d0908070 002cf658 00eea174 ole32!AppInvoke+0xaa (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 1060]
20 0018f8a0 7693ae2d 002c3160 002abff0 00000400 ole32!ComInvokeWithLockAndIPID+0x32c (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 1675]
21 0018f8c8 76866bcd 002c3160 00000400 0029cc10 ole32!ComInvoke+0xc5 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 1445]
22 0018f8dc 76866b8c 002c3160 0018f99c 00000400 ole32!ThreadDispatch+0x23 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\chancont.cxx @ 298]
23 0018f920 7741fd72 00c600f6 00000400 0000babe ole32!ThreadWndProc+0x167 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\chancont.cxx @ 654]
24 0018f94c 7741fe4a 76866aef 00c600f6 00000400 USER32!InternalCallWinProc+0x23
25 0018f9c4 7742018d 00000000 76866aef 00c600f6 USER32!UserCallWinProcCheckWow+0x14b (FPO: [Non-Fpo])
26 0018fa28 7742022b 76866aef 00000000 0018fa68 USER32!DispatchMessageWorker+0x322 (FPO: [Non-Fpo])
27 0018fa38 004b4fd7 0018fa4c 71b6d20c 004b9118 USER32!DispatchMessageW+0xf (FPO: [Non-Fpo])
28 0018fa68 004b6749 457c3509 004b954c 00000001 unsecapp!MessageLoop+0x28 (FPO: [Non-Fpo])
29 0018fa98 004b41a2 00000002 00262228 002614a0 unsecapp!main+0x38c (FPO: [Non-Fpo])
2a 0018fadc 76d6d411 7ffd7000 0018fb28 7731152f unsecapp!_initterm_e+0x163 (FPO: [Non-Fpo])
2b 0018fae8 7731152f 7ffd7000 772f5944 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
2c 0018fb28 77311502 004b42dc 7ffd7000 ffffffff ntdll!__RtlUserThreadStart+0x23 (FPO: [Non-Fpo])
2d 0018fb40 00000000 004b42dc 7ffd7000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

可以看到消息都是通过 DispatchMessageW 发送出去的。在后续rpc通知的时候,客户端可能已经调用了 CancelAsyncCall。当rpc执行的时候,就会出现崩溃问题。

0x04 解决方法

CancelAsyncCall 方法之后让当前线程阻塞一点时间,等待上次 unsecapp rpc调用结束,再调用 release 释放 IWbemServices 等相关对象。

补充

reserverd1 变量说明:

还是这个项目 selfrender/Windows-Server-2003: This is the leaked source code of Windows Server 2003

先看一下 CMessageCall 作为 CAsyncCall 的父类是有哪些成员的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//-----------------------------------------------------------------
//
// Class: CMessageCall
//
// Purpose: This class adds a message to the call info. The
// proxie's message is copied so the call can be
// canceled without stray pointer references.
//
//-----------------------------------------------------------------
class CMessageCall : public ICancelMethodCalls, public IMessageParam
{
public:
// Constructor and destructor
CMessageCall();

protected:
virtual ~CMessageCall();
virtual void UninitCallObject();

public:
// called before a call starts and after a call completes.
virtual HRESULT InitCallObject(CALLCATEGORY callcat,
RPCOLEMESSAGE *message,
DWORD flags,
REFIPID ipidServer,
DWORD destctx,
COMVERSION version,
CChannelHandle *handle);

// IUnknown methods
STDMETHOD(QueryInterface)(REFIID riid, LPVOID *ppv) = 0;
STDMETHOD_(ULONG,AddRef)(void) = 0;
STDMETHOD_(ULONG,Release)(void) = 0;

// ICancelMethodCalls methods
STDMETHOD(Cancel)(ULONG ulSeconds) = 0;
STDMETHOD(TestCancel)(void) = 0;

// Virtual methods needed by every call type for cancel support
virtual void CallCompleted ( HRESULT hrRet ) = 0;
virtual void CallFinished () = 0;
virtual HRESULT CanDispatch () = 0;
virtual HRESULT WOWMsgArrived () = 0;
virtual HRESULT GetState ( DWORD *pdwState ) = 0;
virtual BOOL IsCallDispatched() = 0;
virtual BOOL IsCallCompleted () = 0;
virtual BOOL IsCallCanceled () = 0;
virtual BOOL IsCancelIssued () = 0;
virtual BOOL HasWOWMsgArrived() = 0;
virtual BOOL IsClientWaiting () = 0;
virtual void AckCancel () = 0;
virtual HRESULT Cancel (BOOL fModalLoop,
ULONG ulTimeout) = 0;
virtual HRESULT AdvCancel () = 0;
virtual void Abort() { Win4Assert(!"Abort Called"); }

// Query methods
BOOL ClientAsync() { return _iFlags & CALLFLAG_CLIENTASYNC; }
BOOL ServerAsync() { return _iFlags & CALLFLAG_SERVERASYNC; }
BOOL CancelEnabled() { return _iFlags & CALLFLAG_CANCELENABLED; }
BOOL IsClientSide() { return !(_iFlags & server_cs); }
BOOL FakeAsync() { return _iFlags & fake_async_cs; }
BOOL FreeThreaded() { return _iFlags & freethreaded_cs; }
void Lock() { _iFlags |= locked_cs; }
#if DBG == 1
void SetNAToMTAFlag() {_iFlags |= leave_natomta_cs;}
void ResetNAToMTAFlag(){_iFlags &= ~leave_natomta_cs;}
BOOL IsNAToMTAFlagSet(){ return _iFlags & leave_natomta_cs;}
void SetNAToSTAFlag() {_iFlags |= leave_natosta_cs;}
void ResetNAToSTAFlag(){_iFlags &= ~leave_natosta_cs;}
BOOL IsNAToSTAFlagSet(){ return _iFlags & leave_natosta_cs;}
#endif
BOOL Locked() { return _iFlags & locked_cs; }
BOOL Neutral() { return _iFlags & neutral_cs; }
BOOL ProcessLocal() { return _iFlags & process_local_cs; }
BOOL ThreadLocal() { return _iFlags & thread_local_cs; }
BOOL Proxy() { return _iFlags & proxy_cs; }
BOOL Server() { return _iFlags & server_cs; }

// Get methods
DWORD GetTimeout();
DWORD GetDestCtx() { return _destObj.GetDestCtx(); }
COMVERSION &GetComVersion() { return _destObj.GetComVersion(); }
CCtxCall *GetClientCtxCall() { return m_pClientCtxCall; }
CCtxCall *GetServerCtxCall() { return m_pServerCtxCall; }
BOOL GetErrorFromPolicy() { return (_iFlags & CALLFLAG_ERRORFROMPOLICY); }

HRESULT SetCallerhWnd();
HWND GetCallerhWnd() { return _hWndCaller; }
HANDLE GetEvent() { return _hEvent; }
CALLCATEGORY GetCallCategory(){ return _callcat; }
HRESULT GetResult() { return _hResult; }
void SetResult(HRESULT hr) { _hResult = hr; }
DWORD GetFault() { return _server_fault; }
void SetFault(DWORD fault) { _server_fault = fault; }
IPID & GetIPID() { return _ipid; }
HANDLE GetSxsActCtx() { return _hSxsActCtx; }

// Set methods
void SetThreadLocal(BOOL fThreadLocal);
void SetClientCtxCall(CCtxCall *pCtxCall) { m_pClientCtxCall = pCtxCall; }
void SetServerCtxCall(CCtxCall *pCtxCall) { m_pServerCtxCall = pCtxCall; }
void SetClientAsync() { _iFlags |= CALLFLAG_CLIENTASYNC; }
void SetServerAsync() { _iFlags |= CALLFLAG_SERVERASYNC; }
void SetCancelEnabled() { _iFlags |= CALLFLAG_CANCELENABLED; }
void SetErrorFromPolicy() { _iFlags |= CALLFLAG_ERRORFROMPOLICY; }
void ResetErrorFromPolicy() { _iFlags &= ~CALLFLAG_ERRORFROMPOLICY; }
void SetSxsActCtx(HANDLE hCtx);

// Other methods
HRESULT RslvCancel(DWORD &dwSignal, HRESULT hrIn,
BOOL fPostMsg, CCliModalLoop *pCML);

protected:
// Call object fields
CALLCATEGORY _callcat; // call category
DWORD _iFlags; // EChannelState
SCODE _hResult; // HRESULT or exception code
HANDLE _hEvent; // caller wait event
HWND _hWndCaller;// caller apartment hWnd (only used InWow)
IPID _ipid; // ipid of interface call is being made on
HANDLE _hSxsActCtx;// Activation context active in the caller's context

public:
// Channel fields
DWORD _server_fault;
CDestObject _destObj;
void *_pHeader;
CChannelHandle *_pHandle;
handle_t _hRpc; // Call handle (not binding handle).
IUnknown *_pContext;

// Structure
RPCOLEMESSAGE message;
SChannelHookCallInfo hook;
DWORD _dwErrorBufSize;

protected:
// Cancel fields
ULONG m_ulCancelTimeout; // Seconds to wait before canceling the call
DWORD m_dwStartCount; // Tick count at the time the call was made
CCtxCall *m_pClientCtxCall; // Client side context call object
CCtxCall *m_pServerCtxCall; // Server side context call object
};
  • 134行 message 成员的结构体就是我们用来解析 reserved1 成员时使用的结构体。

可以使用 RPCOLEMESSAGE 的原因就是因为在 CAsyncCall 的另外一个函数中,当 RPC 服务端响应时,reserved1 被 _hRPC 赋值了。而 _hRpc 则是在rpc建立连接时绑定的(调用函数为 RpcBindingInqAuthInfoExW 函数 (rpcdce.h) - Win32 apps | Microsoft Learn)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
inline void CAsyncCall::ServerReply()
{
Win4Assert( _hRpc != NULL );

if (_hResult != S_OK)
{
I_RpcAsyncAbortCall( &_AsyncState, _hResult );
}
else
{
// Ignore errors because replies can fail.
message.reserved1 = _hRpc;
I_RpcSend( (RPC_MESSAGE *) &message );
}
}

【COM】异步通知 ExecNotificationQueryAsync 的使用问题
https://hodlyounger.github.io/A_OS/Windows/COM/【COM】异步通知的使用问题/
作者
mingming
发布于
2024年2月6日
许可协议