Drunkmars's Blog

win32

字数统计: 7.6k阅读时长: 31 min
2021/06/17 Share

本文记录一下自己win32的学习过程。

win32 宽字节

多字节字符

ascii

1、ASCII 码使用指定的 7 位或 8 位二进制数组合来表示 128 或 256 种可能的字符。

2、标准 ASCII 码使用 7 位二进制数来表示所有的大写和小写字母,数字 0 到 9、标点符号,以及在美式英语中使用的特殊控制字符。

3、扩展 ASCII 码允许将每个字符的第 8 位用于确定附加的 128 个特殊符号字符、外来语字母和图形符号。

GB2312

计算机发明之处及后面很长一段时间,只用应用于美国及西方一些发达国家,ASCII能够很好满足用户的需求。但是当天朝也有了计算机之后,为了显示中文,必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。天朝专家把那些127号之后的奇异符号们(即EASCII)取消掉,规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。

在这些编码里,还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。

上述编码规则就是GB2312或GB2312-80

C语言中的宽字符

宽字符的使用

“中”字的编码:

ASCII:d6 d0
UNICODE:4e 2d

这里先测试一下,正常的查表是查ASCII码表,char取一字节,所以这里x在内存中的值为D0

image-20210616213621999

使用L'中'即可让编译器查UNICODE码表,用wchar_t取两字节,查看y在内存的值为4E 2D

image-20210616214139019

宽字符串的使用

image-20210616215012176

使用拓展ASCII编码表 以00结尾

image-20210616214829289

使用UNICODE编码表 以00 00结尾

image-20210616214942489

在控制台打印

这里使用L配合wprint打印默认是输出英文所以这里显示不出来

image-20210616215841379

设置地域即可

1
2
3
#include <locale.h>

setlocale(LC_ALL,"");

image-20210616220745696

字符串长度

image-20210616222606638

字符串复制

image-20210616223420517

win32 API中的宽字符

win32api

Windows操作系统应用程序接口(Windows API),有非正式的简称法为WinAPI,是微软对于Windows操作系统中可用的核心应用程序编程接口的称法。它被设计为各种语言的程序调用,也是应用软件与Windows系统最直接的交互方式。大多数驱动程序需要对Windows系统更底层次访问接口,由所用版本的Windows的Native API来提供接口。主要是存放在 C:\WINDOWS\system32

重要dll

Kernel32.dll:最核心的功能模块,比如管理内存、进程和线程相关的函数等

User32.dll:是Windows用户界面相关应用程序接口,如创建窗口和发送消息等

GDI32.dll:全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数。比如要显示一个程序窗口,就调用了其中的函数来画这个窗口

Win32 API中的宽字符和多字节字符

Windows是使用C语言开发的,Win32 API同时支持宽字符与多字节字符

(1) 字符类型 (2) 字符串指针

char CHAR PSTR(LPSTR) 指向多字节字符串

wchar_t WCHAR PWSTR(LPWSTR) 指向宽字符串

宏 TCHAR 宏 PTSTR(LPTSTR)

这里重点说一下为什么会有TCHAR这个类型存在,首先f12跟一下TCHAR这个类型

image-20210616233547634

这里可以看到TCHAR是个char类型,但是因为这里使用的是默认的类型,即ascii码,这里如果定义使用unicode编码的话,这里的TCHAR就会是一个wchar类型

假如windows有一个内核函数NTA,windows一般会给两种方法去调用这个内核函数,一个是a版,一个是w版,但是内核使用的都是w版字符。如果使用的是a版字符,在调用之前还会有一个从a版字符到w版字符的转换过程。但是这里已经使用a版习惯了,也不好直接去转换成w版字符。所以这里微软就提出了使用TCHAR,使用起来就更加方便,如果使用ascii码,TCHAR就是char类型,如果使用unicode编码的话,TCHAR就是wchar类型

各种版本的MessageBox

这里首先定义一个MessageBox探究,f12跟进去查看

image-20210617104655908

这里看到define的MessageBox有两个,一个是MessageBoxW,一个是MessageBoxA,这里就可以印证之前的观点,windows设置了两套字符集,一个是W,一个是A,这里相当于定义了一个MessageBox的宏

image-20210617104924348

win32的入口程序

生成一个简单的win32程序,定义一个DWORDhInstance,然后进入反汇编查看x的值

image-20210617112456750

这里可以看到x的值为400000,这里很像ImageBase,为了验证猜想修改一下ImageBase观察值的变化

image-20210617112445040

修改一下ImageBase,这里要十六进制转十进制

image-20210617112832139

再进入反汇编查看x的值已经改变为500000

image-20210617112920079

在win32程序中打印信息

这里首先可以使用OutputDebugString()函数来打印信息,但是这个函数因为也是一个宏,就导致不能够加入参数信息一同进行打印

image-20210617113748486

那么这里就要使用另外一种方法,首先新建一个类

image-20210617113916378

然后在.cpp文件里面存入OutputDebugStringF()这个函数

image-20210617114028142

.h存入如下代码

image-20210617114154318

包含Tools.h文件

image-20210617114337607

即可打印参数

image-20210617114629498

GetLastError()的使用

首先还是用MessageBox()实现一个弹窗功能,但是这里修改代码之后并没有出现弹窗,也没有报错,这里就需要用到GetLastError()这个函数

image-20210617161409243

使用GetLastError()打印错误信息,可以看到这里错误序号为578,转换为十进制就是1400

image-20210617162136830

在msdn里找到GetLastError()函数进行查看

image-20210617191945755

找到1400的报错原因

image-20210617193425020

事件 消息 消息处理函数

事件与消息

事件:Windows中的事件是一个“动作”,这个动作可能是用户操作应用程序产生的,也可能是Windows自己产生的

消息:用来描述动作,具体描述动作到底做了什么事。例如:这个动作在什么时候产生、哪个应用产生的、在什么位置产生的等等

消息对应的结构体为MSG,具体结构如下

MSG结构

1
2
3
4
5
6
7
8
typedef struct tagMSG {	
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG;
  • hwnd

Handle to the window whose window procedure receives the message.

hwnd表示消息所属的窗口,这里可以理解为一个唯一的标识,一个消息一般与某个窗口相关联,在windows中HWND类型变量通常来表示窗口。

  • message

Specifies the message identifier. Applications can only use the low word; the high word is reserved by the system.

windows中消息是由一个数值进行表示的,但是数值不方便记忆,所以windows将消息对应的数值定义为WM_XXX宏(WM == Windows Message)

鼠标左键按下 WM_LBUTTONDOWN 键盘按下 WM_KEYDOWN,message就是消息类型

  • wParam

Specifies additional information about the message. The exact meaning depends on the value of the message member.

  • lParam

Specifies additional information about the message. The exact meaning depends on the value of the message member.

32位消息的特定附加信息,具体表示什么取决于message

  • time

Specifies the time at which the message was posted.

消息创建时的时间

  • pt

Specifies the cursor position, in screen coordinates, when the message was posted.

记录鼠标所在分辨率的坐标

系统消息队列与应用消息队列

当事件传入过后封装为MSG形成消息,因为这时候所有队列都是一起的,这里的结构跟之前C++提到的vector类似,但是这里是先进先出,即不是封闭的。当消息传入之后,windows首先会对这些消息通过hwnd进行分类,以区分不同应用的不同消息。

image-20210617220012819

image-20210617220327317

这里在进行应用消息队列的区分后,windows会从这个队列中取出消息,注意这里应用程序随时会进行更改,所以这里windows做的是一个取消息的循环,即从消息队列中一直取消息出来。再取消息之后,会进行消息的判断,这个判断就是判断消息所进行的操作是不是我这个应用程序所设置的一些操作(例如一个消息框,我在其他空白区域点击的时候都不会进行操作,当我点击发送这个按钮的时候,windows才会执行相应的操作),如果跟应用程序所设置的操作相同,就调用应用程序的相关函数,如果不是的话,就交付给windows进行处理。

这里消息的整个过程可以总结如下;

事件 -> MSG -> 系统消息队列 -> 应用消息队列 -> 循环取出消息 ->处理消息

image-20210617221349107

第一个图形界面程序

WNDCLASS用于创建窗口,看一下MSDN的解释

The WNDCLASS structure contains the window class attributes that are registered by the RegisterClass function.

这里说了WNDCLASS这个结构体包含了许多windows里面的类,需要用RegisterClass这个函数进行注册

看一下WNDCLASS这个结构体

WNDCLASS结构

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _WNDCLASS { 
UINT style;
WNDPROC lpfnWndProc; //窗口的消息处理函数
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance; //窗口属于的应用程序
HICON hIcon; //窗口图片标识
HCURSOR hCursor; //鼠标形状
HBRUSH hbrBackground; //窗口背景色
LPCTSTR lpszMenuName; //菜单名字
LPCTSTR lpszClassName; //结构体名字
} WNDCLASS, *PWNDCLASS;

首先定义一下结构体里面的成员,这里其他三个成员都已经定义好,但是这个WindowProc即窗口过程函数有点特殊

image-20210617224028505

WindowProc的结构如下

The WindowProc function is an application-defined function that processes messages sent to a window. The WNDPROC type defines a pointer to this callback function. WindowProc is a placeholder for the application-defined function name.

1
2
3
4
5
6
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);

然后写一个对应结构的WindowProc,代码如下:

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
{  									
switch(uMsg)
{
//窗口消息
case WM_CREATE:
{
DbgPrintf("WM_CREATE %d %d\n",wParam,lParam);
CREATESTRUCT* createst = (CREATESTRUCT*)lParam;
DbgPrintf("CREATESTRUCT %s\n",createst->lpszClass);

return 0;
}
case WM_MOVE:
{
DbgPrintf("WM_MOVE %d %d\n",wParam,lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("X Y %d %d\n",points.x,points.y);

return 0;
}
case WM_SIZE:
{
DbgPrintf("WM_SIZE %d %d\n",wParam,lParam);
int newWidth = (int)(short) LOWORD(lParam);
int newHeight = (int)(short) HIWORD(lParam);
DbgPrintf("WM_SIZE %d %d\n",newWidth,newHeight);

return 0;
}
case WM_DESTROY:
{
DbgPrintf("WM_DESTROY %d %d\n",wParam,lParam);
PostQuitMessage(0);

return 0;
}
//键盘消息
case WM_KEYUP:
{
DbgPrintf("WM_KEYUP %d %d\n",wParam,lParam);

return 0;
}
case WM_KEYDOWN:
{
DbgPrintf("WM_KEYDOWN %d %d\n",wParam,lParam);

return 0;
}
//鼠标消息
case WM_LBUTTONDOWN:
{
DbgPrintf("WM_LBUTTONDOWN %d %d\n",wParam,lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("WM_LBUTTONDOWN %d %d\n",points.x,points.y);

return 0;
}
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}

然后创建窗口,这里注意一下类名,windows系统自带了一些类名,如使用button的话就会生成一个按钮,而我们不想使用windows自带的窗口,所以在这里类名就需要填自己创建的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建窗口  							
HWND hwnd = CreateWindow(
className, //类名
TEXT("我的第一个窗口"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口外观样式
10, //相对于父窗口的X坐标
10, //相对于父窗口的Y坐标
600, //窗口的宽度
300, //窗口的高度
NULL, //父窗口句柄,为NULL
NULL, //菜单句柄,为NULL
hInstance, //当前应用程序的句柄
NULL); //附加数据一般为NULL

if(hwnd == NULL) //是否创建成功
return 0;

这里直接编译运行没有报错但是没有窗口运行起来,回头找一下原因

image-20210618111859266

上面提到过WNDCLASS这个结构体是需要定义很多个成员的,但是并不是每个成员都必须要定义,所以在之前只能定义了几个成员

image-20210618111823935

再往下走到RegisterClass这个函数,到msdn看一下定义

RegisterClass

The RegisterClass function registers a window class for subsequent use in calls to the CreateWindow or CreateWindowEx function.

Note The RegisterClass function has been superseded by the RegisterClassEx function. You can still use RegisterClass, however, if you do not need to set the class small icon.

1
2
3
ATOM RegisterClass(
CONST WNDCLASS *lpWndClass // class data
);

Parameters

  • lpWndClass

[in] Pointer to a WNDCLASS structure. You must fill the structure with the appropriate class attributes before passing it to the function.

注意看一下这个下面的定义,You must fill the structure with the appropriate class attributes before passing it to the function. ,也就是说无论用不用WNDCLASS这个结构体的成员,都要给成员附上值

那么这里直接给WNDCLASS定义成0即可

image-20210618112720123

这里查看一下WNDCLASS wndclass;WNDCLASS wndclass = {0};的区别,WNDCLASS wndclass相当于直接创建了一个结构体wndclass,并没有对结构体中的成员进行任何的操作,所以没有压栈出栈的操作,自然也没有反汇编;而WNDCLASS wndclass = {0}相当于给结构体里面的每个成员都赋初始值为0,所以有反汇编出现

image-20210618114820809

image-20210618114847274

修改之后这里就可以弹出窗口

image-20210618113043584

这里点击缩小、放大、关闭都能够有对应的操作,但是这里我们并没有写这些函数,是因为我们使用了一个windows的回调函数,我们之前在分析的时候提到过一些不是应用程序需要的操作就不会调用应用程序自己的函数,而会交给操作系统去处理,所以这里就必须要返回一个DefWindowsProc()交付给windows处理

另外一个点lpfnWndProc并不是调用了WindowsProc这个函数,而是通过一个指针指向了WindowsProc这个函数,等待着windows调用,这就称为回调函数

image-20210618113141490

image-20210618113245997

这里我们在f5运行程序之后,点击关闭我们生成的这个程序之后,发现程序并没有退出,到任务管理器里面看这个进程还在

image-20210618113708302

每一个消息都有一个对应的编号,来到回调函数f12跟进去查看

image-20210618114312461

windows消息范围说明

0 ~ WM_USER – 1系统消息

WM_USER ~ 0x7FFF自定义窗口类整数消息

WM_APP ~ 0xBFFF应用程序自定义消息

0xC000 ~ 0xFFFF应用程序字符串消息

> 0xFFFF以后 系统应用保留

WindowProc结构探究

我们知道WindowProc的结构如下所示

1
2
3
4
5
6
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
UINT uMsg, // WM_CREATE
WPARAM wParam, // not used
LPARAM lParam // creation data (LPCREATESTRUCT)
);

探究以下WM_CREATE函数中wParamlParam的用法

  • wParam

    This parameter is not used.

  • lParam

    Pointer to a CREATESTRUCT structure that contains information about the window being created.

这里可以看到wParam这个参数是不使用的,lParam这个参数指向的是一个结构体CREATESTRUCT,这里继续探究CREATESTRUCT这个结构体

创建一个结构体指针看一下结构体里面有哪些成员

image-20210619125144253

打印一下lpszclass看一下

image-20210619125301285

再探究一个函数WM_MOVE,首先打印它的id,可以看到这里id为3

image-20210619125554262

image-20210619125602231

继续msdn查看WM_MOVE的结构中的wParamlParam

  • wParam

This parameter is not used.

  • lParam

Specifies the x and y coordinates of the upper-left corner of the client area of the window. The low-order word contains the x-coordinate while the high-order word contains the y coordinate.

这里同样wParam是不使用的,lParam表示窗口的坐标,并且低位表示x坐标,高位表示y坐标

这里看到msdn已经给我们给出了使用的方法,这里直接调用一下

image-20210619132100707

写一个输出函数

image-20210619132527542

image-20210619132231065

继续探究WM_SIZE,这里跟之前不一样的是wParamWM_SIZE里面是有意义的,这里先不细说,还是先看lParam属性

image-20210619150650432

  • lParam

    The low-order word of lParam specifies the new width of the client area.

    The high-order word of lParam specifies the new height of the client area.

还是熟悉的配方,只不过这里是低位为宽,高位为高,打印一下wParamlParam属性

image-20210619152434258

image-20210619152106892

这里发现wParam属性始终为0,这里回到定义的地方看一下,当窗口最大化、最小化操作时这个值才会变化

image-20210619152330928

这里再分别把lParam属性的低位和高位分别打印出来如下图所示

image-20210619152619778

image-20210619152612517

ESP寻址 定位回调函数

关于入口函数的探究

WinMain结构

WinMain函数主要包含了以下几个成员

1
2
3
4
5
6
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
);
  • hInstance

    [in] Handle to the current instance of the application.

  • hPrevInstance

    [in] Handle to the previous instance of the application. This parameter is always NULL.

  • lpCmdLine

    [in] Pointer to a null-terminated string specifying the command line for the application, excluding the program name. To retrieve the entire command line, use the GetCommandLine function.

  • nCmdShow

    is a flag that says whether the main application window will be minimized, maximized, or shown normally.

hInstance被称为实例句柄,当它被加载到内存时,操作系统会根据这个值来识别可执行文件。hPrevInstance 本来在16位windows中使用,现在已经废弃,一直为空。IpCmdLine包含作为Unicode字符串的命令行参数。nCmdShow表示主应用程序窗口是最大化、最小化还是正常显示。

这里说下IpCmdLine的具体作用,当我们打开cmd使用程序执行命令的时候在后面执行的命令就是通过这个IpCmdLine传入

编译生成exe拿到win10中用DebugView进行运行查看

image-20210619155718107

image-20210619155911660

win32应用程序入口识别

这里首先改成release版本编译

image-20210619160259786

拖入od,这里并不是我们写的入口程序的地址

image-20210619160504252

WinMain执行之前下断点查看堆栈,发现调用WinMain()的是WinMainCRTStartup()这个函数

image-20210619160837419

od往下跟,找到GetModuleHandleA这个函数

image-20210619161810808

GetModuleHandleA

看一下MSDN里对GetModuleHandleA的描述

1
2
3
HMODULE GetModuleHandleA(
LPCSTR lpModuleName
);

The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default library extension .dll is appended. The file name string can include a trailing point character (.) to indicate that the module name has no extension. The string does not have to specify a path. When specifying a path, be sure to use backslashes (), not forward slashes (/). The name is compared (case independently) to the names of modules currently mapped into the address space of the calling process.

If this parameter is NULL, GetModuleHandle returns a handle to the file used to create the calling process (.exe file).

The GetModuleHandle function does not retrieve handles for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag. For more information, see LoadLibraryEx.

此函数用来加载模块的名称,如果参数为NULL,则返回exe的句柄

继续跟这个函数,这里的GetModuleHandleA用的是间接call,指向一个地址

image-20210619162327715

image-20210619162351805

这里跟到这个地址,发现这里又指向一个地址,地址里面存储的是KERNEL32这个dll的GetModuleHandleA这个函数

image-20210619162442174

这里点击回车跟进这个函数查看

image-20210619163533117

这里发现retn 10,也就是4个参数,那么几乎可以确定这个函数就是我们要找的入口函数

image-20210619163603546

这里往上看一个细节,这里首先把内存的值传给了ecx,再把ecx的值压入堆栈,那么这里肯定就不是寄存器传参。一般使用寄存器传参的不会有从内存赋值给ecx的操作

image-20210619164220495

ESP寻址

F2断点到函数位置

image-20210619164707475

查看堆栈里面的值,发现有四个参数,因为压栈顺序为_stdcall内平栈,从右往左入栈,那么第一个参数就是hInstanceImageBase,第二个参数为hPrevInstance永远为NULL,第三个参数为IpCmdLine为命令行参数,第四个参数为nCmdShow为最大最小化还是正常显示

这里的0019FEE4里面存的就是函数调用完成后返回的地址,即00401783

image-20210619164726395

这里注意一下,之前提升堆栈都是通过push ebp mov ebp,esp提升堆栈,但是在release版本中可能使用的是esp寻址,直接使用sub esp,0x98提升堆栈而ebp不变

这里提升堆栈过后,esp+0x98的地址存的就是函数的返回地址,如果要找第一个参数就是esp+0x9C

image-20210619165504266

所以如果使用esp寻址的话,esp的值是随时要变化的,所以在堆栈寻址的时候要时刻注意esp的变化

窗口回调函数的定位

首先是窗口回调函数的结构

1
wndclass.lpfnWndProc = WindowProc;	

wndclass是通过RegisterClass注册进去的,那么我们继续在od里面跟到RegisterClass这个函数,在push参数前面下一个断点

image-20210619175031985

F8单步跟下去,得到eax的值为0019FEC0,这个地址就是我们要找的结构

image-20210619175210746

点击右键在堆栈窗口中跟随,这里可以看到堆栈窗口中是有10个值

image-20210619175228503

WNDCLASS中,第二个成员lpfnWndProc就是我们找的回调函数,地址为004010F0

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _WNDCLASS { 
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;

image-20210619175255745

具体事件处理的定位

这里继续往下找,比如我要弄清楚一个函数的具体功能,这里就以WM_LBUTTONDOWN为例

image-20210619174334794

WM_LBUTTONDOWN对应的编号为0x0201

image-20210619180135244

在回调函数的地方下个断点,这里的[esp+0x8]就是消息的类型

image-20210619175417743

加一个条件为消息类型是WM_LBUTTONDOWN

image-20210619175403377

运行一下,当我点击右键的时候程序还是会照常运行

image-20210619175815807

当我点击左键的时候它暂停了

image-20210619175840948

F8单步往下跟就可以找到鼠标左键按钮处理的函数

image-20210619175932395

子窗口 消息处理函数

在窗口中创建按钮

添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void CreateButton(HWND hwnd)		
{
HWND hwndPushButton;
hwndPushButton = CreateWindow (
TEXT("button"),
TEXT("普通按钮"),
//WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON,
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON,
10, 10,
80, 20,
hwnd, //父窗口句柄
(HMENU)1001, //子窗口ID
hAppInstance,
NULL);
}

这里的hAppInstance本来为当前应用程序的句柄,这里把hAppInstance定义为全局变量,即HINSTANCE hAppInstance;。还有一个要注意的点是button

不能够独立存在,所以这里需要用父窗口的句柄,而且这个函数需要在窗口创建出来之后才能使用,所以这里要放在创建窗口的后面

image-20210620163232033

image-20210620163251191

这里探究一下WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_DEFPUSHBUTTON这几个参数

WS_CHILD Creates a child window. Cannot be used with the WS_POPUP style.

WS_VISIBLE Creates a window that is initially visible.

BS_PUSHBUTTON Creates a pushbutton that posts a WM_COMMAND message to the owner window when the user selects the button.

BS_DEFPUSHBUTTON Creates a button that has a heavy black border. The user can select this button by pressing the ENTER key. This style enables the user to quickly select the most likely option (the default option).

按钮事件的处理

在自己创建窗口的时候第一行应该定义一个类,但是在创建button的时候这里直接就可以使用。是因为系统已经定义好了常用的一些窗口函数

image-20210620165019695

这里我们想看一下button究竟是怎样定义的WNDCLASS,这里调用GetClassName函数找到类名存到szBuffer分配的缓冲区里

1
2
TCHAR szBuffer[0x20];				
GetClassName(hwndPushButton,szBuffer,0x20);

GetClassName

这里看一下GetClassName()这个函数

1
2
3
4
5
int GetClassName(
HWND hWnd, // handle to window
LPTSTR lpClassName, // class name
int nMaxCount // size of class name buffer
);
  • hWnd

[in] Handle to the window and, indirectly, the class to which the window belongs.

  • lpClassName

[out] Pointer to the buffer that is to receive the class name string.

  • nMaxCount

[in] Specifies the length, in TCHARs, of the buffer pointed to by the lpClassName parameter. The class name string is truncated if it is longer than the buffer.

lpClassName为out参数,指向缓冲区地址(即存放类函数),nMaxCount是给缓冲区分配的空间大小

然后查看具体信息

1
2
3
4
WNDCLASS wc;				
GetClassInfo(hAppInstance,szBuffer,&wc);
OutputDebugStringF("-->%s\n",wc.lpszClassName);
OutputDebugStringF("-->%x\n",wc.lpfnWndProc);

GetClassInfo

这里又用到一个api函数GetClassInfo()

1
2
3
4
5
BOOL GetClassInfo(
HINSTANCE hInstance, // handle to application instance
LPCTSTR lpClassName, // class name
LPWNDCLASS lpWndClass // class data
);
  • hInstance

    [in] Handle to the instance of the application that created the class. To retrieve information about classes defined by the system (such as buttons or list boxes), set this parameter to NULL.

  • lpClassName

    [in] Pointer to a null-terminated string containing the class name. The name must be that of a preregistered class or a class registered by a previous call to the RegisterClass or RegisterClassEx function. Alternatively, this parameter can be an atom. If so, it must be a class atom created by a previous call to RegisterClass or RegisterClassEx. The atom must be in the low-order word of lpClassName; the high-order word must be zero.

  • lpWndClass

    [out] Pointer to a WNDCLASS structure that receives the information about the class.

hInstance是函数的ImageBaselpClassName是前面得到的缓冲区,lpWndClass为out参数,将结构的信息写到lpWndClass所在的地址

然后再使用wc这个结构体打印信息,这里使用lpszClassNamelpfnWndProc打印地址

image-20210620171245819

继续往下探究,这里当我不点击这几个按钮所在的区域的时候,会产生消息,但是当我点击这几个按钮的时候是不会显示消息的,这里不会显示不代表没有产生消息,只是这里因为这几个按钮是子窗口,所以这里的消息不会在父窗口里的消息函数中直接输出显示

image-20210620172152989

当按钮有事件产生时,会给父窗口消息处理程序发送一个WM_COMMAND消息,我们就需要增加一个WM_COMMAND函数

也就是说在子窗口调用WinProc的时候需要用WM_COMMAND去调用父窗口的WinProc

image-20210620172504902

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
case WM_COMMAND:								
{
switch(LOWORD(wParam))
{
case 1001:
MessageBox(hwnd,"Hello Button 1","Demo",MB_OK);
return 0;
case 1002:
MessageBox(hwnd,"Hello Button 2","Demo",MB_OK);
return 0;
case 1003:
MessageBox(hwnd,"Hello Button 3","Demo",MB_OK);
return 0;
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}

image-20210620202158326

消息堆栈

F7生成release版本进行调试,找到回调函数地址为00401260

image-20210620205445014

401260处下断点,这里返回的为消息类型

image-20210620205831080

我们看一下WM_COMMANDWindowProc的结构如下所示

1
2
3
4
5
6
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
WM_COMMAND, // the message to send
WPARAM wParam, // notification code and identifier
LPARAM lParam // handle to control (HWND)
);
  • wParam

    The high-order word specifies the notification code if the message is from a control. If the message is from an accelerator, this value is 1. If the message is from a menu, this value is zero. The low-order word specifies the identifier of the menu item, control, or accelerator.

  • lParam

    Handle to the control sending the message if the message is from a control. Otherwise, this parameter is NULL.

回调函数的堆栈如图所示,我们是通过地址跳转到401260这个地址,那么esp还是在WindowProC结构处不变,所以esp+0x8就是指向的uMsg的值,即消息类型

image-20210620210029004

按钮事件处理逻辑定位

这里再去找WM_COMMAND这个消息类型

image-20210620210545289

这里我点击父窗口的时候是没有反应的

image-20210620210735200

当我点击子窗口的时候发现已经暂停

image-20210620210803068

再运行一下,有了弹窗

image-20210620210847654

这里就有一个问题,当我们点击每一个按钮的时候都会触发断点,所以我们需要想一个方法来断指定的按钮,这里就需要用到wParam

例如我只想断复选框,这里找到十六进制情况下的ID为000003EA

image-20210620211241714

再添加条件

image-20210620211411587

当我点普通按钮的时候是不断的

image-20210620211614399

当我点击复选框之后就暂停了

image-20210620211648507

资源文件 消息断点

用资源文件创建对话框

首先创建一个资源文件

image-20210620230508428

手动添加resource.h头文件

image-20210620230539325

右键在rc文件处加入Dialog并点击新建

image-20210620230655585

可以看到创建成功

image-20210620230748904

右键修改对话框的属性

image-20210620231102040

F7编译后会在resource.h头文件中定义一个dialog

image-20210620231237414

然后就可以用DialogBox()生成dialog

GetProcAddress

1
2
3
4
FARPROC GetProcAddress(
HMODULE hModule, // handle to DLL module
LPCSTR lpProcName // function name
);
  • hModule

    [in] Handle to the DLL module that contains the function or variable. The LoadLibrary or GetModuleHandle function returns this handle.

  • lpProcName

    [in] Pointer to a null-terminated string containing the function or variable name, or the function’s ordinal value. If this parameter is an ordinal value, it must be in the low-order word; the high-order word must be zero.

GetProcAddress的作用是通过hModule到导出表里找到dll,然后通过名字得到dll地址

1
2
3
4
char szDllBuffer = "kernel32.dll";
char szFunctionName = "Function";

GetProcAddress(szDllBuffer,szFunctionName);

但是我们知道在导入表里有两种导入方式,一是名称导入,二是序号导入,那么如果是序号导入的情况下我们就需要把序号强转为char*类型后再去查询,即用指针去查询

1
2
3
4
char szDllBuffer = "kernel32.dll";
char szFunctionName = "Function"; //序号为11

GetProcAddress(szDllBuffer,(char*)11);

Dialogbox

1
2
3
4
5
6
INT_PTR DialogBox(
HINSTANCE hInstance, // handle to module
LPCTSTR lpTemplate, // dialog box template
HWND hWndParent, // handle to owner window
DLGPROC lpDialogFunc // dialog box procedure
);
  • hInstance

    [in] Handle to the module whose executable file contains the dialog box template.

  • lpTemplate

    [in] Specifies the dialog box template. This parameter is either the pointer to a null-terminated character string that specifies the name of the dialog box template or an integer value that specifies the resource identifier of the dialog box template. If the parameter specifies a resource identifier, its high-order word must be zero and its low-order word must contain the identifier. You can use the MAKEINTRESOURCE macro to create this value.

  • hWndParent

    [in] Handle to the window that owns the dialog box.

  • lpDialogFunc

    [in] Pointer to the dialog box procedure. For more information about the dialog box procedure, see DialogProc.

第一个参数hInstance相当于ImageBase,第二个参数lpTemplate就是dialog需要的模板,这里就是我们之前自己手动生成的dialog,第三个参数hWndParent为父进程的句柄,如果没有父进程的话就写NULL,第四个参数lpDialogFunc就是当前窗口的消息处理函数

消息处理函数代码如下:

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
BOOL CALLBACK DialogProc(									
HWND hwndDlg, // handle to dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{

switch(uMsg)
{
case WM_INITDIALOG :

MessageBox(NULL,TEXT("WM_INITDIALOG"),TEXT("INIT"),MB_OK);

return TRUE ;

case WM_COMMAND :

switch (LOWORD (wParam))
{
case IDC_BUTTON_OK :

MessageBox(NULL,TEXT("IDC_BUTTON_OK"),TEXT("OK"),MB_OK);

return TRUE;

case IDC_BUTTON_ERROR:

MessageBox(NULL,TEXT("IDC_BUTTON_ERROR"),TEXT("ERROR"),MB_OK);

EndDialog(hwndDlg, 0);

return TRUE;
}
break ;
}

return FALSE ;
}

这里我直接使用IDD_DIALOG_MAIN这个定义好了的宏发现报错,报错原因是不能够把int类型转为char*类型。在windows里面是不允许直接使用数字去查询的,跟上面所提到的GetProcAddress()函数一样,这里也需要使用指针去查询

image-20210620233834254

image-20210620233808235

这里可以使用(char*) IDD_DIALOG_MAIN进行强转,但是这里msdn里给我们定义了一个宏方便我们进行转型

You can use the MAKEINTRESOURCE macro to create this value.

那么这里就可以使用MAKEINTRESOURCE (IDD_DIALOG_MAIN)即可

image-20210620234323295

即可弹出窗口

image-20210620234353567

使用资源文件的话可以简化为两个步骤:创建窗口和提供消息处理函数

按钮&文本框

双击资源文件,添加两个按钮,然后点击下方两个按钮即可调整为两个完全相同的按钮

image-20210620234905046

image-20210620235018754

右键点击属性进行修改

image-20210620235227154

回到resource.h发现又多了两个定义

image-20210620235550092

这里加一下两个按钮的消息处理函数

image-20210621000120585

F7编译运行看一下效果

image-20210621000150472

image-20210621000200689

继续再生成两个文本框,分别为UserNamePassWord,左边的为静态即写死了的文本框,右边的为动态即可编辑的对话框

image-20210621000821115

这里我想把UserName框和PassWord框里面的字符取出来打印,就需要以下两个步骤

  • 获取文本框句柄
  • 获取文本框内容

这里获取文本框句柄可以使用如下语句

1
HWND hEditUser = GetDlgItem(hDlg,IDC_EDIT_USER);
CATALOG
  1. 1. win32 宽字节
    1. 1.1. 多字节字符
      1. 1.1.1. ascii
      2. 1.1.2. GB2312
    2. 1.2. C语言中的宽字符
      1. 1.2.1. 宽字符的使用
      2. 1.2.2. 宽字符串的使用
      3. 1.2.3. 在控制台打印
      4. 1.2.4. 字符串长度
      5. 1.2.5. 字符串复制
    3. 1.3. win32 API中的宽字符
      1. 1.3.1. win32api
      2. 1.3.2. 重要dll
      3. 1.3.3. Win32 API中的宽字符和多字节字符
      4. 1.3.4. 各种版本的MessageBox
      5. 1.3.5. win32的入口程序
      6. 1.3.6. 在win32程序中打印信息
      7. 1.3.7. GetLastError()的使用
  2. 2. 事件 消息 消息处理函数
    1. 2.1. 事件与消息
      1. 2.1.1. MSG结构
    2. 2.2. 系统消息队列与应用消息队列
    3. 2.3. 第一个图形界面程序
      1. 2.3.1. WNDCLASS结构
    4. 2.4. windows消息范围说明
    5. 2.5. WindowProc结构探究
  3. 3. ESP寻址 定位回调函数
    1. 3.1. 关于入口函数的探究
      1. 3.1.1. WinMain结构
    2. 3.2. win32应用程序入口识别
      1. 3.2.1. GetModuleHandleA
    3. 3.3. ESP寻址
    4. 3.4. 窗口回调函数的定位
    5. 3.5. 具体事件处理的定位
  4. 4. 子窗口 消息处理函数
    1. 4.1. 在窗口中创建按钮
    2. 4.2. 按钮事件的处理
      1. 4.2.1. GetClassName
      2. 4.2.2. GetClassInfo
    3. 4.3. 消息堆栈
    4. 4.4. 按钮事件处理逻辑定位
  5. 5. 资源文件 消息断点
    1. 5.1. 用资源文件创建对话框
      1. 5.1.1. GetProcAddress
      2. 5.1.2. Dialogbox
    2. 5.2. 按钮&文本框