MB_SERVICE_NOTIFICATION,这是MessageBox函数提供的一个Type值,这个值允许通过CSRSS.exe而不是进程本身来弹出一个MessageBox。
当调用MessageBox时nType参数中包含了MB_SERVICE_NOTIFICATION或MB_DEFAULT_DESKTOP_ONLY标志,MessageBox将调用use32.dll内部的一个函数:ServiceMessageBox,代码如下:
代码:
MessageBoxWorker(...) { ....省略无关代码... if (dwStyle & (MB_DEFAULT_DESKTOP_ONLY | MB_SERVICE_NOTIFICATION)) { if (pMsgBoxParams->hwndOwner != NULL) { SetLastError(ERROR_INVALID_PARAMETER); return 0; } return ServiceMessageBox(pMsgBoxParams->lpszText, pMsgBoxParams->lpszCaption, dwStyle & ~MB_SERVICE_NOTIFICATION); } }
XP的话首先会判断当前是否是终端服务器版本(通过UserSharedData->SuiteMasks是否包含VER_SUITE_TERMINAL ),如果不是服务器版本,直接调用NtRaiseHardError 来通知CSRSS,VISTA则不会走这一步,因为VISTA默认就会有1个以上的会话在运行。
接着ServiceMessageBox会通过NtOpenThreadToken函数来试图打开当前线程的Token,此函数仅在当前线程进行了Token 模拟(Impersonation)时才会返回成功。若此函数返回了被模拟的线程Token,则通过NtQueryInformationToken(TokenSessionId)取得此TOKEN所在的会话ID。
接着,会通过kernel32!ProcessIdToSessionId调用NtQueryInformationProcess(ProcessSessionInformation)获得当前进程的SessionId(若失败,则从NtCurretnTeb()->Peb->SessionId获取)
最后,比较模拟的Token的SessionId是否等于当前进程的SessionId,若不等于,说明当前进程和被模拟的TOKEN不属于同一会话,此时,ServiceMessageBox会通过调用winsta!WinStationSendMessageW来实现弹框,WinStationSendMessageW函数调用CSmartSession::ShowMessageBox找到当前活动会话中的CSRSS,并调用RpcShowMessageBox来通知当前活动会话中的csrss来弹出通知对话框。
若会话相同,则仍旧调用NtRaiseHardError来通知默认ExceptionPort的csrss.
通常系统服务在弹出对话框前都会模拟当前活动会话中的TOKEN,这样调用MessageBox弹框时就可以在当前用户上显示出来。
在VISTA之前,因为默认会话和服务是在同一个会话中,所以我们一般感受到的服务弹框都是调用NtRaiseHardError实现的,只有在切换到其他会话时,才会出现调用 RpcShowMessageBox的情况
但VISTA之后,由于服务进程单独享有一个会话(0号会话),因此所有来自服务的弹框,都会通过RpcShowMessageBox来显示出来。
至于CSRSS如何处理MESSAGEBOX的RPC(HandleHandError等),早已是众所周之了,在这里不多赘述,感兴趣的可以参考WIN2K源代码