【WINAPI】MessageBox细解(一)

0x00 简介

0.1 关于

开始学习WIN32API的时候,写过的第一个程序。就是使用MessageBox输出那个经典的HelloWorld!。但好像我对MessageBox的了解可能也只是皮毛的一点点。我决定重新回顾这个开始,重新认识一下它们。

0.2 工具

操作系统:Windows10。
调试器:Ollydbg以及IDA。
编译器:VS2019

0x01 程序对象

1.1 程序源码

此代码部分内容,仅适用于user32.dll(版本:10.0.19041.610)

#include <Windows.h>
#include "resource.h"

#define	MSGBOX				0//设置预编译选项

VOID CALLBACK MsgBoxCallback(LPHELPINFO lpHelpInfo);//消息盒子帮助回调函数

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) {
#ifdef MSGBOXIND
	MSGBOXPARAMS		mbparams;
#endif


#ifdef MSGBOXEX//消息盒子更改语言为英文
	MessageBoxEx(NULL, TEXT("Hello World!"), TEXT("Test"), MB_OKCANCEL, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US));

#elif defined(MSGBOXIND)//消息盒子更改语言为英文,并添加帮助按钮和用户自定义图标。
	memset(&mbparams, 0, sizeof(MSGBOXPARAMS));
	mbparams.cbSize					= sizeof(MSGBOXPARAMS);
	mbparams.dwStyle				= MB_OKCANCEL | MB_USERICON | MB_HELP;

	mbparams.lpszText				= TEXT("Hello World!");
	mbparams.lpszCaption			= TEXT("Test");

	//设置自定义图标
	mbparams.hInstance				= hInstance;
	mbparams.lpszIcon				= TEXT("USERICON");

	//设置帮助回文ID和回调函数
	mbparams.dwContextHelpId		= 0x100;
	mbparams.lpfnMsgBoxCallback		= MsgBoxCallback;

	//设置显示语言
	mbparams.dwLanguageId			= MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);

	MessageBoxIndirect(&mbparams);

#else//普通消息盒子
	MessageBox(NULL, TEXT("Hello World!"), TEXT("Test"), MB_YESNOCANCEL);
	
#endif

	return 0;
}

//帮助回调函数
VOID CALLBACK MsgBoxCallback(LPHELPINFO lpHelpInfo) {
	switch(lpHelpInfo->dwContextId) {
	case 0x1:
		MessageBox(NULL, TEXT("这是0x1的帮助"), TEXT("帮助"), MB_OK);
		break;

	case 0x100:
		MessageBox(NULL, TEXT("这是0x100的帮助"), TEXT("帮助"), MB_OK);
		break;

	default:
		MessageBox(NULL, TEXT("这是默认帮助"), TEXT("帮助"), MB_OK);
		break;
	}
}

使用的需要修改为对应的预编译值。

1.2 运行结果

  1. MessageBox:
    MessageBox
  2. MessageBoxEx:MessageBoxEx
  3. MessageBoxIndirect:MessageBoxIndirect

1.3 函数解释

从来不做微软文档的搬运工,有需要可以看看下面的几个链接。
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxw

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxexw

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxindirectw

0x2 函数细解

2.1 准备

经查阅微软文档或者通过逆向工具可以获知这个函数在user32.dll中。

本例使用的user32.dll(版本:10.0.19041.546)

MessageBox有两个版本分别是MessageBoxA多字节版和MessageBoxW和宽字符版(后文简称为A版和W版)。本文均使用W版进行说明,若无特殊说明的A版大部分内部进行了一个字符串转换在交由W版进行处理。

2.2 查看导出函数

将user32.dll加载到IDA中,查看导出表。MessageBox_Export
很快就能发现MessageBox,MessageBoxEx和MessageBoxIndirect三个函数两个版本的导出。

值得一提的是圈起来的MessageBoxTimeout,查阅微软官网,但找不到相关文档。这是一个微软未公开的导出函数,先放置一会,很快我们就会再次遇到它了。

2.3 静态分析

2.3.1 程序源码

#include <Windows.h>
#include "resource.h"

#define	MSGBOXSP_TIME		0//设置预编译选项

typedef struct _MSGBOXDATA {
	MSGBOXPARAMSW		mbparams;
	BYTE				unknown[0X68 - sizeof(MSGBOXPARAMSW)];
}MSGBOXDATA, *LPMSGBOXDATA;

typedef int (WINAPI *MSGTIMEPROC)(HWND, LPCWSTR, LPCWSTR, UINT, WORD, DWORD);
typedef int (_fastcall *MSGWORKERPROC)(LPMSGBOXDATA);

VOID CALLBACK MsgBoxCallback(LPHELPINFO lpHelpInfo);//消息盒子帮助回调函数

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) {
	HMODULE				hDllUser32;
	
#ifdef MSGBOXSP_TIME
	MSGTIMEPROC			MessageBoxTimeout;

#elif defined(MSGBOXSP_WORKER)
	MSGBOXDATA			mbdata;
	MSGBOXPARAMSW		mbparams;
	MSGWORKERPROC		MessageBoxWorker;

#endif

#ifdef MSGBOXSP_TIME
	if((hDllUser32 = LoadLibrary(TEXT("user32.dll"))) != NULL) {
		MessageBoxTimeout = (MSGTIMEPROC)GetProcAddress(hDllUser32, "MessageBoxTimeoutW");

		MessageBoxTimeout(NULL, TEXT("Hello World!"), TEXT("Test"), MB_OKCANCEL,
						  MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), 3000);

	}

#elif defined(MSGBOXSP_WORKER)
	if((hDllUser32 = LoadLibrary(TEXT("user32.dll"))) != NULL) {
		//这个是10.0.19041.610的user32偏移,昨天系统自动更新了,凑合着吧。
		MessageBoxWorker = (MSGWORKERPROC)((DWORD)hDllUser32 + (DWORD)0x0007E5BD);

		memset(&mbparams, 0, sizeof(MSGBOXPARAMSW));
		mbparams.cbSize					= sizeof(MSGBOXPARAMSW);
		mbparams.dwStyle				= MB_OKCANCEL | MB_USERICON | MB_HELP;

		mbparams.lpszText				= TEXT("Hello World!");
		mbparams.lpszCaption			= TEXT("Test");

		//设置自定义图标
		mbparams.hInstance				= hInstance;
		mbparams.lpszIcon				= TEXT("USERICON");

		//设置帮助回文ID和回调函数
		mbparams.dwContextHelpId		= 0x1;
		mbparams.lpfnMsgBoxCallback		= MsgBoxCallback;

		//设置显示语言
		mbparams.dwLanguageId			= MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);

		memset(&mbdata, 0, sizeof(MSGBOXDATA));
		memcpy(&mbdata, &mbparams, sizeof(MSGBOXPARAMSW));

		MessageBoxWorker(&mbdata);
	}
	
#endif

	return 0;
}

//帮助回调函数
VOID CALLBACK MsgBoxCallback(LPHELPINFO lpHelpInfo) {
	switch(lpHelpInfo->dwContextId) {
	case 0x1:
		MessageBox(NULL, TEXT("这是0x1的帮助"), TEXT("帮助"), MB_OK);
		break;

	case 0x100:
		MessageBox(NULL, TEXT("这是0x100的帮助"), TEXT("帮助"), MB_OK);
		break;

	default:
		MessageBox(NULL, TEXT("这是默认帮助"), TEXT("帮助"), MB_OK);
		break;
	}
}

程序包含两个部分,分别是MessageBoxTimeout和MessageBoxWorker的调用。

2.3.2 MessageBoxW()

.text:69E7EFA0 ; int __stdcall MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
.text:69E7EFA0                 public _MessageBoxW@16
.text:69E7EFA0 _MessageBoxW@16 proc near               ; DATA XREF: .text:69E03429↑o
.text:69E7EFA0                                         ; .text:off_69E99558↓o
.text:69E7EFA0
.text:69E7EFA0 hWnd            = dword ptr  8
.text:69E7EFA0 lpText          = dword ptr  0Ch
.text:69E7EFA0 lpCaption       = dword ptr  10h
.text:69E7EFA0 uType           = dword ptr  14h
.text:69E7EFA0
.text:69E7EFA0                 mov     edi, edi
.text:69E7EFA2                 push    ebp
.text:69E7EFA3                 mov     ebp, esp
.text:69E7EFA5                 cmp     ?gfEMIEnable@@3HA, 0 ; int gfEMIEnable
.text:69E7EFAC                 jz      short loc_69E7EFD0
.text:69E7EFAE                 mov     eax, large fs:18h
.text:69E7EFB4                 mov     edx, offset ?gdwEMIThreadID@@3PAXA ; void * gdwEMIThreadID
.text:69E7EFB9                 mov     ecx, [eax+24h]
.text:69E7EFBC                 xor     eax, eax
.text:69E7EFBE                 lock cmpxchg [edx], ecx
.text:69E7EFC2                 test    eax, eax
.text:69E7EFC4                 jnz     short loc_69E7EFD0
.text:69E7EFC6                 mov     ?gpReturnAddr@@3PAXA, 1 ; void * gpReturnAddr
.text:69E7EFD0
.text:69E7EFD0 loc_69E7EFD0:                           ; CODE XREF: MessageBoxW(x,x,x,x)+C↑j
.text:69E7EFD0                                         ; MessageBoxW(x,x,x,x)+24↑j
.text:69E7EFD0                 push    0FFFFFFFFh
.text:69E7EFD2                 push    0
.text:69E7EFD4                 push    [ebp+uType]
.text:69E7EFD7                 push    [ebp+lpCaption]
.text:69E7EFDA                 push    [ebp+lpText]
.text:69E7EFDD                 push    [ebp+hWnd]
.text:69E7EFE0                 call    _MessageBoxTimeoutW@24 ; MessageBoxTimeoutW(x,x,x,x,x,x)
.text:69E7EFE5                 pop     ebp
.text:69E7EFE6                 retn    10h
.text:69E7EFE6 _MessageBoxW@16 endp

地址69E7EFD0~69E7EFE0就是MessageBoxW的核心代码了。

值得一提的是过去版本的user32.dll,我甚至见过没有上面那一段的,着手分析,看到了那一连串熟手的交换比较线程ID后,便没有了继续的兴趣。
如果你有兴趣,那加油吧。建议想了解的可以先查看一下DLL入口点的代码。

回到核心部分,这一段调用了一个上面那个MessageBoxTimeout。
MessageBoxTimeout函数的资料
经百度百科查证:
https://baike.baidu.com/item/MessageBoxTimeout/7486390?fr=aladdin

编写测试代码:

if((hDllUser32 = LoadLibrary(TEXT("user32.dll"))) != NULL) {
		MessageBoxTimeout = (MSGTIMEPROC)GetProcAddress(hDllUser32, "MessageBoxTimeoutW");

		MessageBoxTimeout(NULL, TEXT("Hello World!"), TEXT("Test"), MB_OKCANCEL,
						  MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), 3000);

}

运行结果:
MessageBoxTimeout
编写测试代码并执行,按钮语言发生变化3秒后窗口自动关闭。

2.3.3 MessageBoxExW()

这还不简单,不懂得往前看!
复制一下反汇编结果。跳过,跳过。

.text:69E7EB00 ; int __stdcall MessageBoxExW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType, WORD wLanguageId)
.text:69E7EB00                 public _MessageBoxExW@20
.text:69E7EB00 _MessageBoxExW@20 proc near             ; DATA XREF: .text:69E03410↑o
.text:69E7EB00                                         ; .text:off_69E99558↓o
.text:69E7EB00
.text:69E7EB00 hWnd            = dword ptr  8
.text:69E7EB00 lpText          = dword ptr  0Ch
.text:69E7EB00 lpCaption       = dword ptr  10h
.text:69E7EB00 uType           = dword ptr  14h
.text:69E7EB00 wLanguageId     = word ptr  18h
.text:69E7EB00
.text:69E7EB00                 mov     edi, edi
.text:69E7EB02                 push    ebp
.text:69E7EB03                 mov     ebp, esp
.text:69E7EB05                 push    0FFFFFFFFh
.text:69E7EB07                 push    dword ptr [ebp+wLanguageId]
.text:69E7EB0A                 push    [ebp+uType]
.text:69E7EB0D                 push    [ebp+lpCaption]
.text:69E7EB10                 push    [ebp+lpText]
.text:69E7EB13                 push    [ebp+hWnd]
.text:69E7EB16                 call    _MessageBoxTimeoutW@24 ; MessageBoxTimeoutW(x,x,x,x,x,x)
.text:69E7EB1B                 pop     ebp
.text:69E7EB1C                 retn    14h
.text:69E7EB1C _MessageBoxExW@20 endp

2.3.4 MessageBoxIndirectW()

打开MessageBoxIndirectW(),发现了一个不通过MessageBoxTimeoutW()创建消息盒子的函数了,而是使用了更底层的MessageBoxWorker()。

废话不多说,上反汇编代码:

.text:69E7EC70 ; int __stdcall MessageBoxIndirectW(const MSGBOXPARAMSW *lpmbp)
.text:69E7EC70                 public _MessageBoxIndirectW@4
.text:69E7EC70 _MessageBoxIndirectW@4 proc near        ; DATA XREF: .text:69E0341A↑o
.text:69E7EC70                                         ; .text:off_69E99558↓o
.text:69E7EC70
.text:69E7EC70 var_70          = _MSGBOXDATA ptr -70h
.text:69E7EC70 var_4           = dword ptr -4
.text:69E7EC70 lpmbp           = dword ptr  8
.text:69E7EC70
.text:69E7EC70                 mov     edi, edi
.text:69E7EC72                 push    ebp
.text:69E7EC73                 mov     ebp, esp
.text:69E7EC75                 and     esp, 0FFFFFFF8h
.text:69E7EC78                 sub     esp, 70h
.text:69E7EC7B                 mov     eax, ___security_cookie
.text:69E7EC80                 xor     eax, esp
.text:69E7EC82                 mov     [esp+70h+var_4], eax
.text:69E7EC86                 push    esi
.text:69E7EC87                 mov     esi, [ebp+lpmbp]
.text:69E7EC8A                 lea     eax, [esp+74h+var_70]
.text:69E7EC8E                 push    edi             ; struct _MSGBOXDATA *
.text:69E7EC8F                 push    68h ; 'h'       ; Size
.text:69E7EC91                 push    0               ; Val
.text:69E7EC93                 push    eax             ; Dst
.text:69E7EC94                 call    _memset
.text:69E7EC99                 add     esp, 0Ch
.text:69E7EC9C                 lea     edi, [esp+78h+var_70]
.text:69E7ECA0                 cmp     ?gfEMIEnable@@3HA, 0 ; int gfEMIEnable
.text:69E7ECA7                 push    0Ah
.text:69E7ECA9                 pop     ecx
.text:69E7ECAA                 rep movsd
.text:69E7ECAC                 jz      short loc_69E7ECD0
.text:69E7ECAE                 mov     eax, large fs:18h
.text:69E7ECB4                 mov     edx, offset ?gdwEMIThreadID@@3PAXA ; void * gdwEMIThreadID
.text:69E7ECB9                 mov     ecx, [eax+24h]
.text:69E7ECBC                 xor     eax, eax
.text:69E7ECBE                 lock cmpxchg [edx], ecx
.text:69E7ECC2                 test    eax, eax
.text:69E7ECC4                 jnz     short loc_69E7ECD0
.text:69E7ECC6                 mov     ?gpReturnAddr@@3PAXA, 1 ; void * gpReturnAddr
.text:69E7ECD0
.text:69E7ECD0 loc_69E7ECD0:                           ; CODE XREF: MessageBoxIndirectW(x)+3C↑j
.text:69E7ECD0                                         ; MessageBoxIndirectW(x)+54↑j
.text:69E7ECD0                 lea     ecx, [esp+78h+var_70] ; struct _MSGBOXDATA *
.text:69E7ECD4                 call    ?MessageBoxWorker@@YGHPAU_MSGBOXDATA@@@Z ; MessageBoxWorker(_MSGBOXDATA *)
.text:69E7ECD9                 mov     ecx, [esp+78h+var_4]
.text:69E7ECDD                 pop     edi
.text:69E7ECDE                 pop     esi
.text:69E7ECDF                 xor     ecx, esp
.text:69E7ECE1                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:69E7ECE6                 mov     esp, ebp
.text:69E7ECE8                 pop     ebp
.text:69E7ECE9                 retn    4
.text:69E7ECE9 _MessageBoxIndirectW@4 endp

地址:69E7EC86 ~ 69E7ECAA 和 69E7ECD0 ~ 69E7ECD4,是本函数的核心代码。

MSGBOXDATA是微软未公开的一个结构,大小为0x68字节。
MSGBOXPARAMS是微软已经公开的一个结构,大小为0x28字节。

程序把MSGBOXDATA置为0后把MSGBOXPARAMS的内容复制到MSGBOXDATA的头部,最后直接传给了MessageBoxWorker()。

虽然暂时不知道MSGBOXDATA的全部内容,但是可以肯定的是MSGBOXDATA开头部分一定是一个MSGBOXPARAMSW结构。而且不存在A版的MSGBOXDATA。
我们可以先把脑海里的MSGBOXDATA用C语言表示出来。如下:

typedef struct _MSGBOXDATA {
	MSGBOXPARAMSW		mbparams;
	BYTE				unknown[0X68 - sizeof(MSGBOXPARAMSW)];
}MSGBOXDATA, *LPMSGBOXDATA;

MessageBoxWorker()是微软的一个未导出函数,从反汇编的结果可以看出是一个使用了_fastcall调用约束的函数,参数只有一个指向MSGBOXDATA的指针,返回值同MessageBox()。综上可得:

typedef int (_fastcall *MSGWORKERPROC)(LPMSGBOXDATA);

如何获取MessageBoxWorker()的函数地址?

一个导出函数的地址我们可以通过GetProcAddress()获取,但是未导出的函数该怎么处理?
只需要使用 模块基址(等于模块句柄)+ 函数偏移 就可以得到函数地址。

一种能在一定范围内提高兼容性的方案:

  1. 获取user32的模块句柄。
  2. 从模块句柄开始搜索MessageBoxWorker()的某个特征。
  3. 得到MessageBoxWorker()的偏移并与模块句柄相加。

附:在MessageBoxTimeoutW()和MessageBoxIndirectW()内部调用了这个函数且上一条指令都是lea ecx,[ebp+x]指令。

这个文章里由于已经进行了静态且不需要什么兼容性,就直接获取函数偏移。

测试程序:

if((hDllUser32 = LoadLibrary(TEXT("user32.dll"))) != NULL) {
		//这个是10.0.19041.610的user32偏移,昨天系统自动更新了,凑合着吧。
		MessageBoxWorker = (MSGWORKERPROC)((DWORD)hDllUser32 + (DWORD)0x0007E5BD);

		memset(&mbparams, 0, sizeof(MSGBOXPARAMSW));
		mbparams.cbSize					= sizeof(MSGBOXPARAMSW);
		mbparams.dwStyle				= MB_OKCANCEL | MB_USERICON | MB_HELP;

		mbparams.lpszText				= TEXT("Hello World!");
		mbparams.lpszCaption			= TEXT("Test");

		//设置自定义图标
		mbparams.hInstance				= hInstance;
		mbparams.lpszIcon				= TEXT("USERICON");

		//设置帮助回文ID和回调函数
		mbparams.dwContextHelpId		= 0x1;
		mbparams.lpfnMsgBoxCallback		= MsgBoxCallback;

		//设置显示语言
		mbparams.dwLanguageId			= MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);

		memset(&mbdata, 0, sizeof(MSGBOXDATA));
		memcpy(&mbdata, &mbparams, sizeof(MSGBOXPARAMSW));

		MessageBoxWorker(&mbdata);
}

运行结果:
MessageBoxWorker
运行结果我用了嵌入汇编,请勿在意。只是懒得重新截图而已。
图标成功被更改,帮助按钮成功使用。

0x3 总结

以上就是第一部分的全部内容,本来这篇文章我是打算一次性全部发布的,我也写了一个WORD文档但是发现全部分析下来实在太长了,只好分成多部分进行了,其他部分就期待后面的发布吧。

注意: 细心的人一定发现了前面的程序有个挺明显的BUG,但为了防止有人没有发现,在这里说明一下,本文除了MessageBoxEx外没有一个程序成功更改了按钮文本语言。

原因是什么呢?后面的部分会说明的。欢迎各位与我交流哦。

!!第一次发布文章,有很多不清楚的地方,如果有什么不对或者违反规定的地方,请第一时间联系作者哦。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值