MessageBox细解(三)
0x00 简介
0.1 关于
MessageBox()一个看似简单的函数,其内部也有不少复杂的实现。前面的两个部分我们细节的了解了部分常用的MessageBox系列函数,并对未公开的MessageBoxTimeout()和MessageBoxWorker()进行了分析。但现阶段还是很贴近于我们一般的使用,没有具体的深入到实现原理。
第三部分我本来想一次性写完ServiceMessageBox()和SoftModalMessageBox()两个内容,但再仔细研究了SoftModalMessageBox()后我的想法被改变了,我没办法在一个篇幅内把两个函数很好的表达出来。所以第三部分我想全部交给ServiceMessageBox()。
0.2 前情提要
【WINAPI】MessageBox细解(一)
第一部分中主要讲解了三个MessageBox函数的内部实现,引出了MessageBoxWorker()和MessageBoxTimeout()并说明了如何直接使用这两个函数。
【WINAPI】MessageBox细解(二)
第二部分中主要讲解了MessageBoxWorker()和MessageBoxTimeout()两个函数的内部实现,MessageBoxTimeout()内部继续调用了MessageBoxWorker()。MessageBoxWorker()根据MB_SERVICE_NOTIFICATION,MB_DEFAULT_DESKTOP_ONLY和MB_TOPMOST三个标识,分成了两个大路,分别是ServiceMessageBox()和SoftModalMessageBox()。
0.3 工具
操作系统:Windows10。
调试器:Ollydbg以及IDA。
编译器:VS2019
0x01 程序对象
1.1 程序源码
#include <Windows.h>
#include "resource.h"
typedef int(_fastcall *MSGSERVICEPROC)(LPCWSTR,LPCWSTR,UINT,DWORD);
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) {
HMODULE hDllUser32;
MSGSERVICEPROC ServiceMessageBox;
if((hDllUser32 = LoadLibrary(TEXT("user32.dll"))) != NULL) {
//这个是10.0.19041.610的user32偏移,昨天系统自动更新了,凑合着吧。
ServiceMessageBox = (MSGSERVICEPROC)((DWORD)hDllUser32 + (DWORD)0x0007E986);
ServiceMessageBox(TEXT("Hello World"), TEXT("Test"), MB_OKCANCEL | MB_HELP | MB_ICONWARNING, 5000);
}
return 0;
}
1.2 运行结果
消息盒子在锁屏界面下正常下显示,5s后自动关闭。
测试了这个程序的一些人会发现在切换锁屏的时候会反复听到MB_ICONWARNING的警报声。这是什么原因呢?
你可以把等待时间设置为30s,等待20s后在切换锁屏,重新计时你可能会豁然开朗。但这个的具体缘由我不会说明,我并不打算在这几篇文章中写到内核部分。
0x02 函数细解
2.1 预备知识
标识MB_SERVICE_NOTIFICATION在服务进程中使用的一个标识,但提到服务进程不得不考虑一个问题。那就是会话隔离
把会话隔离在这里全部讲完有些许的不现实,在此就简单的介绍一下。在一般情况下是不能在其他的会话显示窗口的,服务器管理员应该对此会非常的熟悉。举个简单的例子,就像在服务进程(会话0)中无法通过直接普通的方式使用MessageBox()弹出消息盒子。
我们在服务程序中使用下列代码:
MessageBox(NULL, TEXT("Hello World!"), TEXT("Test"), MB_YESNOCANCEL);
显然因为会话隔离这个函数并没有任何的效果。
如果使用了MB_SERVICE_NOTIFICTION呢?在服务程序编写下列代码再次测试:
MessageBox(NULL, TEXT("Hello World!"), TEXT("Test"), MB_YESNOCANCEL | MB_SERVICE_NOTIFICATION);
我们在服务进程中弹出消息盒子一般是使用WTSSendMessage()之类的函数解决。但这样会不会显得MB_SERVICE_NOTIFICATION的目的和意义不是很好的理解。
那现在我们就来详细解释一下有关于ServiceMessageBox()的原理揭开这一层迷雾。
2.2 静态分析
2.2.1 反汇编代码
贴上反汇编代码
.text:69E7E566 ; int __fastcall ServiceMessageBox(const unsigned __int16 *szCaption, const unsigned __int16 *szTitle, unsigned int dwStyle, unsigned int dwMillisecond)
.text:69E7E566 ServiceMessageBox proc near ; CODE XREF: MessageBoxWorker(_MSGBOXDATA *)+1CB↑p
.text:69E7E566
.text:69E7E566 pUnicodeStringTitle= LSA_UNICODE_STRING ptr -40h
.text:69E7E566 pUnicodeStringText= LSA_UNICODE_STRING ptr -38h
.text:69E7E566 ReturnLength = dword ptr -30h
.text:69E7E566 pTextStringStart= dword ptr -2Ch
.text:69E7E566 pThreadSessionId= dword ptr -28h
.text:69E7E566 UnicodeStringEnd= dword ptr -24h
.text:69E7E566 pProcessSessionId= dword ptr -20h
.text:69E7E566 TokenHandle = dword ptr -1Ch
.text:69E7E566 Response = dword ptr -18h
.text:69E7E566 Parameters = dword ptr -14h
.text:69E7E566 dwCheckSum = dword ptr -4
.text:69E7E566 dwStyle = dword ptr 8
.text:69E7E566 dwMilliseconds = dword ptr 0Ch
.text:69E7E566
.text:69E7E566 mov edi, edi
.text:69E7E568 push ebp
.text:69E7E569 mov ebp, esp
.text:69E7E56B sub esp, 44h
.text:69E7E56E mov eax, ___security_cookie
.text:69E7E573 xor eax, ebp
.text:69E7E575 mov [ebp+dwCheckSum], eax
.text:69E7E578 push ebx
.text:69E7E579 push esi
.text:69E7E57A push edi
.text:69E7E57B mov edi, ecx
.text:69E7E57D lea eax, [ebp+TokenHandle]
.text:69E7E580 xor ecx, ecx
.text:69E7E582 mov ebx, edx
.text:69E7E584 inc ecx
.text:69E7E585 push eax ; TokenHandle
.text:69E7E586 push ecx ; OpenAsSelf
.text:69E7E587 push TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY ; DesiredAccess
.text:69E7E589 mov [ebp+Response], ecx
.text:69E7E58C call ds:__imp__GetCurrentThread@0 ; GetCurrentThread()
.text:69E7E592 push eax ; ThreadHandle
.text:69E7E593 call ds:__imp__NtOpenThreadToken@16 ; NtOpenThreadToken(x,x,x,x)
.text:69E7E599 test eax, eax
.text:69E7E59B js loc_69E7E68D
.text:69E7E5A1 lea eax, [ebp+ReturnLength]
.text:69E7E5A4 push eax ; ReturnLength
.text:69E7E5A5 push 4 ; TokenInformationLength
.text:69E7E5A7 lea eax, [ebp+pThreadSessionId]
.text:69E7E5AA push eax ; TokenInformation
.text:69E7E5AB push TokenSessionId ; TokenInformationClass
.text:69E7E5AD push [ebp+TokenHandle] ; TokenHandle
.text:69E7E5B0 call ds:__imp__NtQueryInformationToken@20 ; NtQueryInformationToken(x,x,x,x,x)
.text:69E7E5B6 push [ebp+TokenHandle] ; hObject
.text:69E7E5B9 mov esi, eax
.text:69E7E5BB call ds:__imp__CloseHandle@4 ; CloseHandle(x)
.text:69E7E5C1 test esi, esi
.text:69E7E5C3 js loc_69E7E68D
.text:69E7E5C9 lea eax, [ebp+pProcessSessionId]
.text:69E7E5CC push eax ; pSessionId
.text:69E7E5CD call ds:__imp__GetCurrentProcessId@0 ; GetCurrentProcessId()
.text:69E7E5D3 push eax ; dwProcessId
.text:69E7E5D4 call ds:__imp__ProcessIdToSessionId@8 ; ProcessIdToSessionId(x,x)
.text:69E7E5DA test eax, eax
.text:69E7E5DC jnz short loc_69E7E5EF
.text:69E7E5DE mov eax, large fs:30h
.text:69E7E5E4 mov eax, [eax+1D4h]
.text:69E7E5EA mov [ebp+pProcessSessionId], eax
.text:69E7E5ED jmp short loc_69E7E5F2
.text:69E7E5EF ; ---------------------------------------------------------------------------
.text:69E7E5EF
.text:69E7E5EF loc_69E7E5EF: ; CODE XREF: ServiceMessageBox+76↑j
.text:69E7E5EF mov eax, [ebp+pProcessSessionId]
.text:69E7E5F2
.text:69E7E5F2 loc_69E7E5F2: ; CODE XREF: ServiceMessageBox+87↑j
.text:69E7E5F2 cmp [ebp+pThreadSessionId], eax
.text:69E7E5F5 jz loc_69E7E68D
.text:69E7E5FB mov eax, offset dword_69E0954C
.text:69E7E600 test ebx, ebx
.text:69E7E602 jnz short loc_69E7E606
.text:69E7E604 mov ebx, eax
.text:69E7E606
.text:69E7E606 loc_69E7E606: ; CODE XREF: ServiceMessageBox+9C↑j
.text:69E7E606 test edi, edi
.text:69E7E608 jnz short loc_69E7E60C
.text:69E7E60A mov edi, eax
.text:69E7E60C
.text:69E7E60C loc_69E7E60C: ; CODE XREF: ServiceMessageBox+A2↑j
.text:69E7E60C mov esi, [ebp+dwMilliseconds]
.text:69E7E60F cmp esi, 0FFFFFFFFh
.text:69E7E612 jz short loc_69E7E621
.text:69E7E614 mov eax, esi
.text:69E7E616 xor edx, edx
.text:69E7E618 mov ecx, 3E8h
.text:69E7E61D div ecx
.text:69E7E61F mov esi, eax
.text:69E7E621
.text:69E7E621 loc_69E7E621: ; CODE XREF: ServiceMessageBox+AC↑j
.text:69E7E621 and [ebp+UnicodeStringEnd], 0
.text:69E7E625 mov ecx, edi
.text:69E7E627 lea edx, [ecx+2]
.text:69E7E62A
.text:69E7E62A loc_69E7E62A: ; CODE XREF: ServiceMessageBox+CE↓j
.text:69E7E62A mov ax, [ecx]
.text:69E7E62D add ecx, 2
.text:69E7E630 cmp ax, word ptr [ebp+UnicodeStringEnd]
.text:69E7E634 jnz short loc_69E7E62A
.text:69E7E636 sub ecx, edx
.text:69E7E638 mov edx, ebx
.text:69E7E63A sar ecx, 1
.text:69E7E63C lea eax, [edx+2]
.text:69E7E63F mov [ebp+pTextStringStart], eax
.text:69E7E642
.text:69E7E642 loc_69E7E642: ; CODE XREF: ServiceMessageBox+E6↓j
.text:69E7E642 mov ax, [edx]
.text:69E7E645 add edx, 2
.text:69E7E648 cmp ax, word ptr [ebp+UnicodeStringEnd]
.text:69E7E64C jnz short loc_69E7E642
.text:69E7E64E sub edx, [ebp+pTextStringStart]
.text:69E7E651 xor eax, eax
.text:69E7E653 push eax
.text:69E7E654 lea eax, [ebp+Response]
.text:69E7E657 sar edx, 1
.text:69E7E659 push eax
.text:69E7E65A push esi
.text:69E7E65B push [ebp+dwStyle]
.text:69E7E65E lea eax, [ecx+ecx]
.text:69E7E661 push eax
.text:69E7E662 push edi
.text:69E7E663 lea eax, [edx+edx]
.text:69E7E666 push eax
.text:69E7E667 push ebx
.text:69E7E668 push [ebp+pThreadSessionId]
.text:69E7E66B xor ebx, ebx
.text:69E7E66D push ebx
.text:69E7E66E call ds:__imp__WinStationSendMessageW@40 ; WinStationSendMessageW(x,x,x,x,x,x,x,x,x,x)
.text:69E7E674 cmp al, 1
.text:69E7E676 jnz short loc_69E7E689
.text:69E7E678 mov eax, [ebp+Response]
.text:69E7E67B cmp eax, 7D00h
.text:69E7E680 jz short loc_69E7E689
.text:69E7E682 cmp eax, 7D02h
.text:69E7E687 jnz short loc_69E7E6EA
.text:69E7E689
.text:69E7E689 loc_69E7E689: ; CODE XREF: ServiceMessageBox+110↑j
.text:69E7E689 ; ServiceMessageBox+11A↑j
.text:69E7E689 mov eax, ebx
.text:69E7E68B jmp short loc_69E7E6EA
.text:69E7E68D ; ---------------------------------------------------------------------------
.text:69E7E68D
.text:69E7E68D loc_69E7E68D: ; CODE XREF: ServiceMessageBox+35↑j
.text:69E7E68D ; ServiceMessageBox+5D↑j ...
.text:69E7E68D push edi ; SourceString
.text:69E7E68E lea eax, [ebp+pUnicodeStringText]
.text:69E7E691 push eax ; DestinationString
.text:69E7E692 call ds:__imp__RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)
.text:69E7E698 push ebx ; SourceString
.text:69E7E699 lea eax, [ebp+pUnicodeStringTitle]
.text:69E7E69C push eax ; DestinationString
.text:69E7E69D call ds:__imp__RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)
.text:69E7E6A3 lea eax, [ebp+pUnicodeStringText]
.text:69E7E6A6 mov [ebp+Parameters], eax
.text:69E7E6A9 lea eax, [ebp+pUnicodeStringTitle]
.text:69E7E6AC mov [ebp+Parameters+4], eax
.text:69E7E6AF mov eax, [ebp+dwStyle]
.text:69E7E6B2 mov [ebp+Parameters+8], eax
.text:69E7E6B5 mov eax, [ebp+dwMilliseconds]
.text:69E7E6B8 mov [ebp+Parameters+0Ch], eax
.text:69E7E6BB lea eax, [ebp+Response]
.text:69E7E6BE push eax ; Response
.text:69E7E6BF push 1 ; ValidResponseOptions
.text:69E7E6C1 lea eax, [ebp+Parameters]
.text:69E7E6C4 push eax ; Parameters
.text:69E7E6C5 push 3 ; UnicodeStringParameterMask
.text:69E7E6C7 push 4 ; NumberOfParameters
.text:69E7E6C9 push 50000018h ; ErrorStatus
.text:69E7E6CE call ds:__imp__NtRaiseHardError@24 ; NtRaiseHardError(x,x,x,x,x,x)
.text:69E7E6D4 test eax, eax
.text:69E7E6D6 js short loc_69E7E6E0
.text:69E7E6D8 mov eax, [ebp+Response]
.text:69E7E6DB cmp eax, 0Bh
.text:69E7E6DE jb short loc_69E7E6E3
.text:69E7E6E0
.text:69E7E6E0 loc_69E7E6E0: ; CODE XREF: ServiceMessageBox+170↑j
.text:69E7E6E0 xor eax, eax
.text:69E7E6E2 inc eax
.text:69E7E6E3
.text:69E7E6E3 loc_69E7E6E3: ; CODE XREF: ServiceMessageBox+178↑j
.text:69E7E6E3 mov eax, ds:dword_69E09520[eax*4]
.text:69E7E6EA
.text:69E7E6EA loc_69E7E6EA: ; CODE XREF: ServiceMessageBox+121↑j
.text:69E7E6EA ; ServiceMessageBox+125↑j
.text:69E7E6EA mov ecx, [ebp+dwCheckSum]
.text:69E7E6ED pop edi
.text:69E7E6EE pop esi
.text:69E7E6EF xor ecx, ebp
.text:69E7E6F1 pop ebx
.text:69E7E6F2 call @__security_check_cookie@4 ; __security_check_cookie(x)
.text:69E7E6F7 leave
.text:69E7E6F8 retn 8
.text:69E7E6F8 ServiceMessageBox endp
其中部分局部变量和结构成员的命名是个人自己取得。
2.2.2 核心代码
以前两篇相同,这么长的反汇编代码一行行解释显然不合乎情理,我将择取核心代码用于解释这个函数。
-
核心代码A:69E7E57D~ 69E7E59B
先使用NtOpenThreadToken()获取该线程的Token。如果获取失败则跳转执行NtRaiseHardError()。(NtOpenThreadToken()若出现错误,返回内核状态0xCxxxxxxx。且这个函数显然需要在管理员及以上权限才能正常执行)。 -
核心代码B:69E7E5A1~ 69E7E5F5
先通过使用NtQueryInformationToken()获取线程的会话ID,如果获取失败则跳转执行NtRaiseHardError()。
然后在通过ProcessIdToSessionId()获取该进程的会话ID(请注意线程和进程的区别),如果获取失败则直接通过PEB获取。
比较进程和线程的会话ID,如果相同则跳转执行NtRaiseHardError(),如果不相同则跳转执行WinStationSendMessageW()。 -
核心代码C:69E7E5FB~ 69E7E68B
对字符串进行检查,处理等待时间参数看来WinStationSendMessageW()的等待时间单位是秒,计算字符串的大小,然后通过调用WinStationSendMessageW()向线程的会话ID弹出消息盒子。检查返回值。 -
核心代码D:69E7E68D~69E7E6E3
通过获取RtlInitUnicodeString()获取标题和正文的字符串的Unicode字符串结构,初始化params数组,调用NtRaiseHardError()。检查返回值。
以上就是ServiceMessageBox()的全部核心代码。
2.2.3 核心代码问答
核心代码A的问答
-
疑惑:我的程序总是会返回0xC000007D错误?
答:你是否获取了管理员权限或者在服务中运行。 -
疑惑:NtOpenThreadToken()怎么使用?
答:详细可以参考OpenThreadToken(),唯一的区别在于NtOpenThreadToken()返回的是返回值是NTSTATUS。
核心代码B的问答
-
疑惑:NtQueryInformationToken()怎么使用?
答:微软有文档。 -
疑惑:还会出现线程的会话ID与进程的会话ID不同的情况?
答:你可能很少编写服务程序,那我就介绍一种情况吧。
if(ImpersonateSelf(SecurityImpersonation)) {
if(OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hToken)) {
dwSessionId = WTSGetActiveConsoleSessionId();
if(SetTokenInformation(hToken, TokenSessionId, &dwSessionId, sizeof(DWORD))) {
MessageBox(NULL, TEXT("修改成功"), TEXT("来自服务进程的消息"), MB_OK | MB_SERVICE_NOTIFICATION);
}
CloseHandle(hToken);
}
}
使用SetTokenInformation()修改线程Token的会话ID(注:SetTokenInformation()修改会话ID需要具有System权限)。
MessageBox()在服务进程中正常显示消息盒子。
- 疑惑:PEB是什么?
答:PEB是进程环境块的简称,一般FS段寄存器在系统中指向TEB,其偏移30h就是PEB的地址。这个内容我可以足够写多一篇,请百度解决。
核心代码C的问答
- 疑惑:WinStationSendMessageW()怎么使用?
答:WinStationSendMessageW()的声明如下:
typedef BOOLEAN(_stdcall *WINSTASENDMSG)(HANDLE, ULONG, PWSTR, ULONG, PWSTR, ULONG, ULONG, ULONG, PULONG, BOOLEAN);
具体可以参考,RpcWinStationSendMessage()的官方文档。
核心代码D的问答
- 疑惑:NtRaiseHardError()怎么使用?
答:NtRaiseHardError()是一个使用了_stdcall调用约束的函数,同时具有6个参数。
下面是它的定义:
NTSTATUS NtRaiseHardError(NTSTATUS ErrorStatus,
ULONG NumberOfParameters,
ULONG UnicodeStringParameterMask,
PULONG_PTR Parameters,
ULONG ValidResponseOptions,
PULONG Response);
ErrorStatus:内核错误状态(不过这个好像名不副实了0x50000018是一个很典型的消息状态)。
NumberOfParameters:Parameters数组的大小。
UnicodeStringParameterMask:Parameters的类型标志。
Parameters:指向Parameters数组。
ValidResponseOptions:设置Response返回值的有效性。
Response:函数结果的返回。
我们现在仅在探讨这个函数在MessageBox()中的应用,过深的内容就不在这里具体的解释,这已经严重超纲了。
示例代码
- NtRaiseHardError():
if((hDllNtdll = LoadLibrary(TEXT("ntdll.dll"))) != NULL) {
NtRaiseHardError = (NTRAISEHARD)GetProcAddress(hDllNtdll, "NtRaiseHardError");
RtlInitUnicodeString = (RTLINITUNCODE)GetProcAddress(hDllNtdll, "RtlInitUnicodeString");
RtlInitUnicodeString(&uniText, TEXT("Hello World!"));
RtlInitUnicodeString(&uniTitle, TEXT("Test"));
params[0] = (ULONG_PTR)&uniText;
params[1] = (ULONG_PTR)&uniTitle;
params[2] = (ULONG_PTR)MB_OKCANCEL;
params[3] = (ULONG_PTR)5000;
NtRaiseHardError(0x50000018, 4, 3, params, 1, &ulResponse);
}
在消息盒子的显示为目的的情况下NtRaiseHardError(),ErrorStatus使用的是0x50000018。在成员数量为4同时mask为3 的情况下,param数组的四个成员分别为正文指针,标题指针,按钮风格和等待时间。
- WinStationSendMessageW():
if((hDllWINSTA = LoadLibrary(TEXT("winsta.dll"))) != NULL) {
WinStationSendMessageW = (WINSTASENDMSG)GetProcAddress(hDllWINSTA, "WinStationSendMessageW");
WinStationSendMessageW( NULL,
WTSGetActiveConsoleSessionId(),
(PWSTR)TEXT("Test"),
lstrlen(TEXT("Test")) * sizeof(TCHAR),
(PWSTR)TEXT("Hello World!"),
lstrlen(TEXT("Hello World!")) * sizeof(TCHAR),
MB_OKCANCEL,
5,
&ulResponse,
FALSE
);
}
因为方便操作并没有在服务程序中执行,会话ID选择了当前的活动会话。
运行结果
-
NtRaiseHardError():
窗口正常显示,并在10s后消失,为了方便截图,这里的时间有所延长。 -
WinStationSendMessageW():
窗口正常显示,并在10s后消失,为了方便截图,这里的时间有所延长。
0x03 总结
咕咕咕,原定计划是要把SodtModalMessageBox()也包含在这一部分中的,但是出于这个函数实在是太长了,不太合适整合在一起,便把它独立了出来顺便偷个小懒。
!!刚刚开始发布文章,有很多不清楚的地方,如果有什么不对或者违反规定的地方,请第一时间联系作者哦。