概述:异步通知 IWbemServices::ExecNotificationQueryAsync 的使用问题。调用 CancelAsyncCall 之后导致程序崩溃。
代码参考微软官方示例:示例:通过 WMI 接收事件通知 - Win32 apps | Microsoft Learn
0x01 复现场景
参考微软官方代码实现一个进程,使用 windbg 加载进程,然后等待进程结束,就能看到 windbg 报错 0xC0000005。
0x02 dmp分析
报错详情
(调用 CancelAsyncCall 的进程)主要问题还是访问了空指针导致的,并且是 ecx。是类成员函数调用时出现异常。this 指针为空。
堆栈
确定是 RPC 调用。下一步查询下 RPC 调用方。
ole32!GetChannelCallMgr 函数原型:
1 2 HRESULT GetChannelCallMgr (RPCOLEMESSAGE *pMsg, IUnknown * pStub, IUnknown *pServer, CRpcChannelBuffer::CServerCallMgr **ppStubBuffer)
直接看第一个参数 pMsg
。
查看下调用方
成员变量 reserverd1
保存了RPC相关信息。 先 dds
再 dt
查看其内容。
CAsyncCall
的成员可以查看 Windows-Server-2003/com/ole32/com/dcomrem/call.hxx at 5c6fe3db626b63a384230a1aa6b92ac416b0765f · selfrender/Windows-Server-2003 中相关源码。
成员变量 _hRPC
来自于其继承的父类 CMessageCall
_hRpc
记录了调用方的句柄。查看 0x0479ba28
内存,在其附近找到了调用方的 PID 和 TID。如下图所示:
看下 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: 1b 34.1b 38 Suspend: 1 Teb: 7f fdf000 Unfrozen # ChildEBP RetAddr Args to Child 00 0018e8 b0 773354 ac 76 d6a8b7 00000002 0018e904 ntdll!KiFastSystemCallRet (FPO: [0 ,0 ,0 ])01 0018e8 b4 76 d6a8b7 00000002 0018e904 00000001 ntdll!NtWaitForMultipleObjects+0xc (FPO: [5 ,0 ,0 ])02 0018e950 77420f 8d 0018e904 0018e978 00000000 kernel32!WaitForMultipleObjectsEx+0x11d (FPO: [Non-Fpo])03 0018e9 a4 76832507 0000002 c 0018e9 ec ffffffff USER32!RealMsgWaitForMultipleObjectsEx+0x13c (FPO: [Non-Fpo])04 0018e9 cc 768325b c 0018e9 ec ffffffff 0018e9 fc ole32!CCliModalLoop::BlockFn+0x97 (FPO: [Non-Fpo]) (CONV: thiscall) [d:\longhorn\com\ole32\com\dcomrem\callctrl.cxx @ 1162 ] 05 0018e9 f4 76938f 62 ffffffff 002 c27a0 0018 eb00 ole32!ModalLoop+0x5b (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\chancont.cxx @ 211 ] 06 0018 ea10 76939 ce1 00000000 0018 eb14 00000000 ole32!ThreadSendReceive+0x12c (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 4831 ] 07 0018 ea38 76939b 4d 0018 eb00 002 cf4d8 0018 eb5c ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0x194 (FPO: [Non-Fpo]) (CONV: thiscall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 4386 ] 08 0018 eb18 76833f 94 002 cf4d8 0018 ec40 0018 ec24 ole32!CRpcChannelBuffer::SendReceive2+0xef (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 4009 ] 09 0018 eb34 76833f 46 0018 ec40 0018 ec24 002 cf4d8 ole32!CCliModalLoop::SendReceive+0x1e (FPO: [Non-Fpo]) (CONV: thiscall) [d:\longhorn\com\ole32\com\dcomrem\callctrl.cxx @ 849 ] 0 a 0018 ebac 76853811 002 cf4d8 0018 ec40 0018 ec24 ole32!CAptRpcChnl::SendReceive+0x73 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\callctrl.cxx @ 578 ] 0b 0018 ec00 7712074 c 002 cf4d8 0018 ec40 0018 ec24 ole32!CCtxComChnl::SendReceive+0x1c5 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\ctxchnl.cxx @ 734 ] 0 c 0018 ec18 771207 ad 002 a37bc 0018 ecdc 771211b 0 RPCRT4!NdrProxySendReceive+0x43 0 d 0018 ec24 771211b 0 45542f 97 0018f 0d8 070001f 3 RPCRT4!NdrpProxySendReceive+0xc (FPO: [0 ,0 ,0 ])0 e 0018f 09c 77120822 70b 62ba8 70b 621fc 0018f 0d8 RPCRT4!NdrClientCall2+0x5e9 0f 0018f 0c0 770 aeb83 0018f 0d8 00000004 0018f 0f0 RPCRT4!ObjectStublessClient+0x6f 10 0018f 0d0 6f c1a87e 002 a37bc 00000000 80041032 RPCRT4!ObjectStubless+0xf 11 0018f 0f0 004b 2ec2 00 eea0fc 00000000 80041032 fastprox!CSinkProxyBuffer::XSinkFacelet::SetStatus+0x2e (FPO: [Non-Fpo])12 0018f 128 770b 3419 00262914 00000000 80041032 unsecapp!CStub::SetStatus+0x7b (FPO: [Non-Fpo])13 0018f 150 77121 d35 004b 2e47 0018f 350 00000005 RPCRT4!Invoke+0x2a 14 0018f 180 771220 a1 00000000 002 aeb9c 002 df118 RPCRT4!NdrStubCall2+0x28a (FPO: [SEH])15 0018f 1c8 770b 6e5f 76866918 70b 62ba8 00000000 RPCRT4!NdrStubCall2+0x4ab (FPO: [SEH])16 0018f 1cc 76866918 70b 62ba8 00000000 00000000 RPCRT4!NdrpClientUnMarshal+0x464 (FPO: [SEH])17 0018f 574 77122465 002 d1e78 002 cf658 002 c31b8 ole32!NdrOleAllocate18 0018f 5c4 6f c1a8b7 002 d1e78 002 c31b8 002 cf658 RPCRT4!CStdStubBuffer_Invoke+0xa0 (FPO: [SEH])19 0018f 5d8 7693 a8c5 00 eea174 002 c31b8 002 cf658 fastprox!CSinkStubBuffer::XSinkStublet::Invoke+0x36 (FPO: [Non-Fpo])1 a 0018f 620 7693 aa59 002 c31b8 002b d800 002b 5eb0 ole32!SyncStubInvoke+0x3c (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 1161 ] 1b 0018f 66c 768661 d6 002 c31b8 002 cfd60 00 eea174 ole32!StubInvoke+0xb9 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 1372 ] 1 c 0018f 748 768660e7 002 cf658 00000000 00 eea174 ole32!CCtxComChnl::ContextInvoke+0xfa (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\ctxchnl.cxx @ 1255 ] 1 d 0018f 764 76866 df5 002 c31b8 00000001 00 eea174 ole32!MTAInvoke+0x1a (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\callctrl.cxx @ 2020 ] 1 e 0018f 790 7693 a981 002 c31b8 00000001 00 eea174 ole32!STAInvoke+0x46 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\callctrl.cxx @ 1839 ] 1f 0018f 7c4 7693 a79b d0908070 002 cf658 00 eea174 ole32!AppInvoke+0xaa (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 1060 ] 20 0018f 8a0 7693 ae2d 002 c3160 002 abff0 00000400 ole32!ComInvokeWithLockAndIPID+0x32c (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 1675 ] 21 0018f 8c8 76866b cd 002 c3160 00000400 0029 cc10 ole32!ComInvoke+0xc5 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\channelb.cxx @ 1445 ] 22 0018f 8dc 76866b 8c 002 c3160 0018f 99c 00000400 ole32!ThreadDispatch+0x23 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\chancont.cxx @ 298 ] 23 0018f 920 7741f d72 00 c600f6 00000400 0000b abe ole32!ThreadWndProc+0x167 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\longhorn\com\ole32\com\dcomrem\chancont.cxx @ 654 ] 24 0018f 94c 7741f e4a 76866 aef 00 c600f6 00000400 USER32!InternalCallWinProc+0x23 25 0018f 9c4 7742018 d 00000000 76866 aef 00 c600f6 USER32!UserCallWinProcCheckWow+0x14b (FPO: [Non-Fpo])26 0018f a28 7742022b 76866 aef 00000000 0018f a68 USER32!DispatchMessageWorker+0x322 (FPO: [Non-Fpo])27 0018f a38 004b 4fd7 0018f a4c 71b 6d20c 004b 9118 USER32!DispatchMessageW+0xf (FPO: [Non-Fpo])28 0018f a68 004b 6749 457 c3509 004b 954c 00000001 unsecapp!MessageLoop+0x28 (FPO: [Non-Fpo])29 0018f a98 004b 41a2 00000002 00262228 002614 a0 unsecapp!main+0x38c (FPO: [Non-Fpo])2 a 0018f adc 76 d6d411 7f fd7000 0018f b28 7731152f unsecapp!_initterm_e+0x163 (FPO: [Non-Fpo])2b 0018f ae8 7731152f 7f fd7000 772f 5944 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])2 c 0018f b28 77311502 004b 42dc 7f fd7000 ffffffff ntdll!__RtlUserThreadStart+0x23 (FPO: [Non-Fpo])2 d 0018f b40 00000000 004b 42dc 7f fd7000 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 : public ICancelMethodCalls, public IMessageParam {public : CMessageCall ();protected : virtual ~CMessageCall (); virtual void UninitCallObject () ;public : virtual HRESULT InitCallObject (CALLCATEGORY callcat, RPCOLEMESSAGE *message, DWORD flags, REFIPID ipidServer, DWORD destctx, COMVERSION version, CChannelHandle *handle) ; STDMETHOD (QueryInterface)(REFIID riid, LPVOID *ppv) = 0 ; STDMETHOD_ (ULONG,AddRef)(void ) = 0 ; STDMETHOD_ (ULONG,Release)(void ) = 0 ; STDMETHOD (Cancel)(ULONG ulSeconds) = 0 ; STDMETHOD (TestCancel)(void ) = 0 ; 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" ); } 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; } 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; } 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) ; HRESULT RslvCancel (DWORD &dwSignal, HRESULT hrIn, BOOL fPostMsg, CCliModalLoop *pCML) ; protected : CALLCATEGORY _callcat; DWORD _iFlags; SCODE _hResult; HANDLE _hEvent; HWND _hWndCaller; IPID _ipid; HANDLE _hSxsActCtx;public : DWORD _server_fault; CDestObject _destObj; void *_pHeader; CChannelHandle *_pHandle; handle_t _hRpc; IUnknown *_pContext; RPCOLEMESSAGE message; SChannelHookCallInfo hook; DWORD _dwErrorBufSize;protected : ULONG m_ulCancelTimeout; DWORD m_dwStartCount; CCtxCall *m_pClientCtxCall; CCtxCall *m_pServerCtxCall; };
134行 message
成员的结构体就是我们用来解析 reserved1 成员时使用的结构体。
可以使用 `RPCOLEMESSAGE` 的原因就是因为在 `CAsyncCall` 的另外一个函数中,当 RPC 服务端响应时,reserved 1 被 `_hRPC` 赋值了。而 `_hRpc` 则是在rpc建立连接时绑定的(调用函数为 [RpcBindingInqAuthInfoExW 函数 (rpcdce.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/rpcdce/nf-rpcdce-rpcbindinginqauthinfoexw))。
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 { message.reserved1 = _hRpc; I_RpcSend ( (RPC_MESSAGE *) &message ); } }