0%

17-Win32

Win32

第一章

编码的发展

  1. ascii
  2. gb2312
  3. unicode

win32宽字符

宽字符,win底层都是用宽字符实现的

练习

练习使用带w的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// WChar.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <locale>

int main(int argc, char* argv[])
{
setlocale(LC_ALL, "");
// TODO: Place code here.
wchar_t wStr[] = L"中国";
wprintf(L"%s\n", wStr);
int length = wcslen(wStr);
wchar_t wAim[10];
wcscpy(wAim,wStr);
wcscat(wAim,wStr);
int result = wcscmp(wAim,wStr);
unsigned short* test = wcsstr(wAim, wStr);
return 0;
}

了解WinMain参数

  • hInstance is something called a “handle to an instance” or “handle to a module.” The operating system uses this value to identify the executable (EXE) when it is loaded in memory. The instance handle is needed for certain Windows functions—for example, to load icons or bitmaps.
  • hPrevInstance has no meaning. It was used in 16-bit Windows, but is now always zero.
  • pCmdLine contains the command-line arguments as a Unicode string.
  • nCmdShow is a flag that says whether the main application window will be minimized, maximized, or shown normally.

The function returns an int value. The return value is not used by the operating system, but you can use the return value to convey a status code to some other program that you write.

第二章

win32初次调试

这里查文档直接用在线的,msdn文档太大了,懒得下载了

可以看到这里errorCode为1400,查文档为

调试打印语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//.cpp
void __cdecl OutputDebugStringF(const char *format, ...)
{
va_list vlArgs;
char *strBuffer = (char*)GlobalAlloc(GPTR, 4096);

va_start(vlArgs, format);
_vsnprintf(strBuffer, 4096 - 1, format, vlArgs);
va_end(vlArgs);
strcat(strBuffer, "\n");
OutputDebugStringA(strBuffer);
GlobalFree(strBuffer);
return;
}

//.h
#ifdef _DEBUG
#define DbgPrintf OutputDebugStringF
#else
#define DbgPrintf
#endif

windows事件和消息

系统消息队列与应用程序消息队列:

消息

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

Members

1
hwnd

Type: HWND

A handle to the window whose window procedure receives the message. This member is NULL when the message is a thread message.

1
message

Type: UINT

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

1
wParam

Type: WPARAM

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

1
lParam

Type: LPARAM

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

1
time

Type: DWORD

The time at which the message was posted.

1
pt

Type: POINT

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

1
lPrivate

左上角是(0,0) 假设为1024*768,右上角为(1024,0)

1
2
3
4
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT, *PPOINT, *NPPOINT, *LPPOINT;

练习

  1. 创建一个窗口程序,学习如何查询文档
  2. 查一下Windows有多少种消息,概要了解一下每个消息的作业.
  3. WNDCLASS wndclass = { 0 };
    和 WNDCLASS wndclass;区别

练习1

由于vs studio封装的太好,新建项目啥都做好了,这里自己删掉自己做一遍

设计思路

  1. 系统/用户触发的某个动作

  2. 系统将这些信息存储到MSG结构体中

  3. 系统将该消息存储到相关应用程序的消息队列中

  4. while(GetMessage(&Msg,NULL,0,0))  
    {                            
        TranslateMessage(&Msg);//翻译消息      
        DispatchMessage(&Msg);//分派消息        
    }                        
    <!--11-->
    

这里大部分需要自己查文档

练习2

消息太多了自己查看下吧

练习3

1
WNDCLASS wndclass = { 0 };
1
2
3
4
5
6
7
8
9
10
00DB1A8E  xor         eax,eax  
00DB1A90 mov dword ptr [ebp-54h],eax
00DB1A93 mov dword ptr [ebp-50h],eax
00DB1A96 mov dword ptr [ebp-4Ch],eax
00DB1A99 mov dword ptr [ebp-48h],eax
00DB1A9C mov dword ptr [ebp-44h],eax
00DB1A9F mov dword ptr [ebp-40h],eax
00DB1AA2 mov dword ptr [ebp-3Ch],eax
00DB1AA5 mov dword ptr [ebp-38h],eax
00DB1AA8 mov dword ptr [ebp-34h],eax

不加没有初始化,也就是随机数值,跟学结构体那会类似

第三章

win32入口识别

win32入口为4个参数,在开始时候我们已经学过了,在这里我们需要自己从汇编角度识别win32入口

image-20200325120252920

看到这里,便是win32入口了,理由如下

  1. GetModuleHandleA这里刚好要获取程序基地址,因为win32入口有个参数就是hInstance
  2. 这里刚好为4个参数,开头push 0xA,pop eax,push eax这里只是还是相当于push了一个参数,接下来是push [local.25]和push esi,后面那个push esi是为了GetModuleHandleA的参数的,最后获取到的返回值放入eax,同时push进去,这里还可以从我们认为的入口跟进去看看

image-20200325120701432

这里看到确实是0x10,这是__stdcall,从右往左压参数,然后内平栈

esp寻址

我们原来编译的都是debug,通常外面的都是发布版本,所以我们编译成release进行调试

image-20200325120842663

先说点题外话,vs sutdio编译的跟vc6编译的结果不怎么一致,自己可以测试,我这里用vc6重新编译了一份进行调试

这里跟进我们的winMain发觉不跟以前的一样了,这里是esp寻址

esp寻址最大的变化就是esp一直会改变,所以需要运行时才能确定具体位置,esp+0xc这种

这里可以看到一句关键的,mov dword ptr ss:[esp],eax

这里是最初的名称赋值,所有才有后面

1
2
3
4
5
6
7
004010A8  |.  8D4C24 08     lea ecx,dword ptr ss:[esp+0x8]
004010AC |. 52 push edx ; /pWndClass = 6E695720
004010AD |. C74424 54 040>mov dword ptr ss:[esp+0x54],0x4 ; |
004010B5 |. C74424 3C 001>mov dword ptr ss:[esp+0x3C],Win32_1.00401000 ; |
004010BD |. 894C24 5C mov dword ptr ss:[esp+0x5C],ecx ; |
004010C1 |. 897424 48 mov dword ptr ss:[esp+0x48],esi ; |Win32_1.00400000
004010C5 |. FF15 B0504000 call dword ptr ds:[<&USER32.RegisterClassA>] ; \RegisterClassA

这里可以看到有趣的是传参跟以往不一样了,注意,你看RegisterClassA这里,他是先push一个edx,然后才mov的,由于RegisterClassA是单参数的,可以确定edx便是函数的参数,而其余部分我们跟随edx看看

image-20200325185815133

看堆栈窗口,我们edx传的是地址,然后他4次mov都给edx指向的结构体赋了值,这个结构体相信你们都知道是谁了

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct tagWNDCLASSA {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
} WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;

这里是10个成员,而堆栈窗口也是10个成员

窗口回调函数的定位

这里回调函数从上一节结构体分析可以直接接下来,回调函数便是结构体第二个成员,跟过去看看

image-20200325190251332

可以看到这里便是回调函数了

具体事件的处理的定位

事件是一直在处理的,所以回调函数会一直被调用,这时候我们需要的便是条件断点了

image-20200325190429321

编辑条件,输入

1
[esp+0x8] == code

code替换为相应的宏对应的数值,随便举个例子便是

1
#define WM_KEYDOWN                      0x0100

将code替换为0x100便是在键盘按下的时候下断,或者替换为WM_KEYDOWN也是可以的,他会识别宏,到这里便分析完成了,接下来是练习

练习

这个逆向跟前面的练习很类似,很容易找到关键

image-20200325211818222

也就是0x41,0x46,0x67转换为字符为‘A’ ‘F’ ‘g’ ,最有意思的是,我发觉键盘无论怎么按,key down是不会识别小写字母的,查看官方文档

真有意思,Virtual-Key code居然没有小写字母,所以无论我怎么按都是无法得到‘g’的,还有个就是不能按shift+英文字母,因为他会获取到shift先,在获取的英文字母,单次中断无法响应两次的结果

最后发觉是数字7可以达到目的

逆向到这暂时告一段落

第四章

按钮是什么

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Win32_1.cpp : Defines the entry point for the application.
//

#include "stdafx.h"

HINSTANCE hAppInstance;
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
) ;
void CreateButton(HWND hwnd);

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
hAppInstance = hInstance;
//窗口类名
TCHAR className[] = "My First Window";
//创建窗口对象
WNDCLASS wndclass= { 0 };
wndclass.hbrBackground = (HBRUSH)COLOR_MENU; // Window BackGround
wndclass.lpfnWndProc = WindowProc; // Message Deal
wndclass.lpszClassName = className;// class name
wndclass.hInstance = hInstance;// hInstance

//注册该窗口对象
RegisterClass(&wndclass);

//创建窗口
HWND hwnd = CreateWindow(
className, //class name
TEXT("我的第一个窗口"),//window title
WS_OVERLAPPEDWINDOW, //window
10, //X
10, //Y
600,// width
300, //height
NULL, // hwndparent
NULL, // hwndmenu
hInstance, //application hwnd
NULL //addtional data
);
//whether create or not
if (hwnd == NULL)
return 0;
CreateButton(hwnd);
// show window
ShowWindow(hwnd, SW_SHOW);

// Message loop
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return 0;
}


void CreateButton(HWND hwnd)
{
HWND hwndPushButton;
HWND hwndCheckBox;
HWND hwndRadio;

hwndPushButton = CreateWindow(
TEXT("button"),
TEXT("普通按钮"),
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON|BS_DEFPUSHBUTTON,
10,10,
80,20,
hwnd,
(HMENU)1001,
hAppInstance,
NULL);
hwndCheckBox = CreateWindow (
TEXT("button"),
TEXT("复选框"),
//WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BS_AUTOCHECKBOX,
WS_CHILD | WS_VISIBLE | BS_CHECKBOX |BS_AUTOCHECKBOX ,
10, 40,
80, 20,
hwnd,
(HMENU)1002, //子窗口ID
hAppInstance,
NULL);

hwndRadio = CreateWindow (
TEXT("button"),
TEXT("单选按钮"),
//WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON | BS_AUTORADIOBUTTON,
WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON ,
10, 70,
80, 20,
hwnd,
(HMENU)1003, //子窗口ID
hAppInstance,
NULL);

}


LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{

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 %x %x\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);
}

按钮其实就是一个窗口

按钮事件的处理

这里进行调试代码

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
void CreateButton(HWND hwnd)
{
HWND hwndPushButton;
HWND hwndCheckBox;
HWND hwndRadio;

hwndPushButton = CreateWindow(
TEXT("button"),
TEXT("普通按钮"),
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON|BS_DEFPUSHBUTTON,
10,10,
80,20,
hwnd,
(HMENU)1001,
hAppInstance,
NULL);

TCHAR szBuffer[0x20];
GetClassName(hwndPushButton, szBuffer, 0x20);
WNDCLASS wc;
GetClassInfo(hAppInstance, szBuffer, &wc);
OutputDebugStringF("-->%s\n",wc.lpszClassName);
OutputDebugStringF("-->%x\n",wc.lpfnWndProc);

hwndCheckBox = CreateWindow (
TEXT("button"),
TEXT("复选框"),
//WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BS_AUTOCHECKBOX,
WS_CHILD | WS_VISIBLE | BS_CHECKBOX |BS_AUTOCHECKBOX ,
10, 40,
80, 20,
hwnd,
(HMENU)1002, //子窗口ID
hAppInstance,
NULL);

hwndRadio = CreateWindow (
TEXT("button"),
TEXT("单选按钮"),
//WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON | BS_AUTORADIOBUTTON,
WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON ,
10, 70,
80, 20,
hwnd,
(HMENU)1003, //子窗口ID
hAppInstance,
NULL);

}

image-20200326201911760

image-20200326201955447

注意,如果这里不理解,无法继续,因为按钮单击事件由父窗口WinProc处理

这里宏定义为WM_COMMAND,而具体窗口编号由wParam传递

image-20200326202224716

消息堆栈

这里已经分析过了,这是由于传参的原因

image-20200326202719632

不再具体分析,调用回调函数的时候,就是这个栈结构,所以wParam就在第三个参数里,uMsg为事件编号,WM_COMMAND对应的是0x111

image-20200326202955858

这里看堆栈,0x3E9我点的是第一个按钮,所以编号为这个

按钮事件处理逻辑定位

具体处理某个按钮可以这么做

  1. 获取该按钮id
  2. 找到回调函数
  3. 下条件断点

具体如下:

ollydbg点w可以获取到窗口id

image-20200326203142661

这里三个id都有,然后找到回调函数,已经找到过,下条件断点

image-20200326203255101

这里要两个参数,一个是wParam,一个是uMsg,uMsg作为按钮事件定位,wParam作为具体按钮定位

练习

这里找出三个按钮不同之处,

image-20200326223825654

我发觉除了id号不同好像没什么区别了

image-20200326223626478

第五章

资源文件,创建对话框,文本框,按钮

通过DialogBox 创建对话框

至于资源文件部分,自己看视频吧

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
62
63
64
65
66
67
68
69
70
// Win32_2.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "resource.h"
BOOL CALLBACK DialogProc(
HWND hwndDlg, // handle to dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
return 0;
}



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 btn_Login :
{
MessageBox(NULL,TEXT("btn_Login"),TEXT("OK"),MB_OK);
//1.获取文本框句柄
HWND Username = GetDlgItem(hwndDlg, txt_Username);
HWND Password = GetDlgItem(hwndDlg, txt_Password);
//2.获取文本框内容
TCHAR szUserBuff[0x50];
TCHAR szPasswordBuff[0x50];
GetWindowText(Username,szUserBuff,0x50);
GetWindowText(Password,szPasswordBuff,0x50);

return TRUE;
}
case btn_Cancel:

//MessageBox(NULL,TEXT("btn_Cancel"),TEXT("Cancel"),MB_OK);

EndDialog(hwndDlg, 0);

return TRUE;
}
break ;
}

return FALSE ;
}

这里需要注意的是

1
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);

这个函数,以及

1
2
3
4
 GetDlgItem(hwndDlg, txt_Password);
//2.获取文本框内容
TCHAR szUserBuff[0x50];
TCHAR szPasswordBuff[0x50]; GetWindowText(Username,szUserBuff,0x50);

难度不大

对话框回调函数的定位

编译成release自己调试,这里需要注意,断点需要下好,不然会一直卡在文本框,或者先不点进去文本框

常规找法

这里常规可以找到

  1. 找win32入口
  2. 找到DialogBox
  3. 他的参数有个回调函数
  4. 跟随条件断点就可以了

image-20200326234757705

非常规找法

前面学过,创建一个窗口的话,不指定回调函数的话,他会使用系统默认的回调函数,而如果我们指定了回调函数的话,他就会从系统指定的回调函数中将控制权交换给我们自己的回调函数,理解这个过程的话就简单多了,首先加载起来,打开w界面

image-20200326235045121

这里可以看到ClsProc,我们ClsProc便是系统指定的回调函数,我们可以直接对其下断

image-20200326235220200

直接切换断点,然后运行便可以断下了

又或者,消息断点,这里注意,消息断点不是下WM_COMMAND,我们原来下WM_COMMAND是因为在主窗口里下的断点,而我们现在是在Button里下的消息断点,所以该是什么就是什么

image-20200326235452221

这里选择202 WM_LBUTTONUP也就是鼠标左键按起后,接下来才是关键,运行,断下

image-20200326235652127

这里断下在系统dll部分,我们要返回我们的代码段,我们学过pe结构,代码通常是放在.text段,所以,我们在.text段下个内存访问断点,访问代码时候便断下,就可以回到程序领空了,点m

image-20200326235759800

下内存访问断点,运行,这里断下了

image-20200326235909030

注意堆栈窗口,我们点的login,wParam应该为0x3EC,而这里明显不是,我们单步,追到系统领空后再次直接运行,返回后

image-20200327000323081

这里便是了,wParam是0x3EC

同时,其实消息断点就是条件断点

image-20200327001438518

这里延伸下,这明显就是ollydbg返回到程序领空的方法啊,alt+f9的方法

未来写调试器的那一天可能会用到

练习

练习1

似乎很简单,就是应用刚刚学到的知识,找到Find Me 1,2,3对应的回调函数

image-20200327000902975

Find me 1

image-20200327000712634

Find me 2

image-20200327000852706

练习2

逆向获得正确的账号和密码

image-20200327001026968

应该不难,同样用非常规找回调

image-20200327001352323

找到回调后先下个条件断点吧,然后取消掉其余的

这里注意下,他的退出变成了0x10的编号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0040105C  |.  8D7C24 0C     lea edi,dword ptr ss:[esp+0xC]
00401060 |. 83C9 FF or ecx,-0x1
00401063 |. 33C0 xor eax,eax
00401065 |. F2:AE repne scas byte ptr es:[edi]
00401067 |. F7D1 not ecx
00401069 |. 49 dec ecx
0040106A |. 83F9 03 cmp ecx,0x3
0040106D |. 75 20 jnz short ReverseT.0040108F
0040106F |. 8D7C24 5C lea edi,dword ptr ss:[esp+0x5C]
00401073 |. 83C9 FF or ecx,-0x1
00401076 |. F2:AE repne scas byte ptr es:[edi]
00401078 |. F7D1 not ecx
0040107A |. 49 dec ecx
0040107B |. 83F9 05 cmp ecx,0x5

算法部分,第一部分长度为3,第二部分长度为5就可以通过,似乎不难,不过挺有趣的

这个居然是strlen的优化编译代码,真有趣

第六章

本文作者:NoOne
本文地址https://noonegroup.xyz/posts/be13089f/
版权声明:转载请注明出处!