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);
    }
    }
ServiceMessageBox根据当前的会话状况不同调用不同的函数来实现弹框。

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源代码