Win32 第一章 编码的发展 ascii gb2312 unicode win32宽字符 宽字符,win底层都是用宽字符实现的
练习 练习使用带w的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include "stdafx.h" #include <locale> int main (int argc, char * argv[]) { setlocale(LC_ALL, "" ); 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 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 ; } #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 Type: HWND
A handle to the window whose window procedure receives the message. This member is NULL when the message is a thread message.
Type: UINT
The message identifier. Applications can only use the low word; the high word is reserved by the system.
Type: WPARAM
Additional information about the message. The exact meaning depends on the value of the message member.
Type: LPARAM
Additional information about the message. The exact meaning depends on the value of the message member.
Type: DWORD
The time at which the message was posted.
Type: POINT
The cursor position, in screen coordinates, when the message was posted.
左上角是(0,0) 假设为1024*768,右上角为(1024,0)
1 2 3 4 typedef struct tagPOINT { LONG x; LONG y; } POINT, *PPOINT, *NPPOINT, *LPPOINT;
练习 创建一个窗口程序,学习如何查询文档 查一下Windows有多少种消息,概要了解一下每个消息的作业. WNDCLASS wndclass = { 0 }; 和 WNDCLASS wndclass;区别 练习1 由于vs studio封装的太好,新建项目啥都做好了,这里自己删掉自己做一遍
设计思路
系统/用户触发的某个动作
系统将这些信息存储到MSG结构体中
系统将该消息存储到相关应用程序的消息队列中
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入口
看到这里,便是win32入口了,理由如下
GetModuleHandleA 这里刚好要获取程序基地址,因为win32入口有个参数就是hInstance这里刚好为4个参数,开头push 0xA,pop eax,push eax这里只是还是相当于push了一个参数,接下来是push [local.25]和push esi,后面那个push esi是为了GetModuleHandleA的参数的,最后获取到的返回值放入eax,同时push进去,这里还可以从我们认为的入口跟进去看看
这里看到确实是0x10,这是__stdcall,从右往左压参数,然后内平栈
esp寻址 我们原来编译的都是debug,通常外面的都是发布版本,所以我们编译成release进行调试
先说点题外话,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看看
看堆栈窗口,我们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个成员
窗口回调函数的定位 这里回调函数从上一节结构体分析可以直接接下来,回调函数便是结构体第二个成员,跟过去看看
可以看到这里便是回调函数了
具体事件的处理的定位 事件是一直在处理的,所以回调函数会一直被调用,这时候我们需要的便是条件断点了
编辑条件,输入
code替换为相应的宏对应的数值,随便举个例子便是
1 #define WM_KEYDOWN 0x0100
将code替换为0x100便是在键盘按下的时候下断,或者替换为WM_KEYDOWN也是可以的,他会识别宏,到这里便分析完成了,接下来是练习
练习 这个逆向跟前面的练习很类似,很容易找到关键
也就是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 #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; wndclass.lpfnWndProc = WindowProc; wndclass.lpszClassName = className; wndclass.hInstance = hInstance; RegisterClass(&wndclass); HWND hwnd = CreateWindow( className, TEXT("我的第一个窗口" ), WS_OVERLAPPEDWINDOW, 10 , 10 , 600 , 300 , NULL , NULL , hInstance, NULL ); if (hwnd == NULL ) return 0 ; CreateButton(hwnd); ShowWindow(hwnd, SW_SHOW); 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 , 10 , 40 , 80 , 20 , hwnd, (HMENU)1002 , hAppInstance, NULL ); hwndRadio = CreateWindow ( TEXT("button" ), TEXT("单选按钮" ), WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON , 10 , 70 , 80 , 20 , hwnd, (HMENU)1003 , 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 , 10 , 40 , 80 , 20 , hwnd, (HMENU)1002 , hAppInstance, NULL ); hwndRadio = CreateWindow ( TEXT("button" ), TEXT("单选按钮" ), WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON , 10 , 70 , 80 , 20 , hwnd, (HMENU)1003 , hAppInstance, NULL ); }
注意,如果这里不理解,无法继续,因为按钮单击事件由父窗口WinProc处理
这里宏定义为WM_COMMAND,而具体窗口编号由wParam传递
消息堆栈 这里已经分析过了,这是由于传参的原因
不再具体分析,调用回调函数的时候,就是这个栈结构,所以wParam就在第三个参数里,uMsg为事件编号,WM_COMMAND对应的是0x111
这里看堆栈,0x3E9我点的是第一个按钮,所以编号为这个
按钮事件处理逻辑定位 具体处理某个按钮可以这么做
获取该按钮id 找到回调函数 下条件断点 具体如下:
ollydbg点w可以获取到窗口id
这里三个id都有,然后找到回调函数,已经找到过,下条件断点
这里要两个参数,一个是wParam,一个是uMsg,uMsg作为按钮事件定位,wParam作为具体按钮定位
练习 这里找出三个按钮不同之处,
我发觉除了id号不同好像没什么区别了
第五章 资源文件,创建对话框,文本框,按钮 通过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 #include "stdafx.h" #include "resource.h" BOOL CALLBACK DialogProc ( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) ;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, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch (uMsg) { case WM_INITDIALOG : return TRUE ; case WM_COMMAND : switch (LOWORD (wParam)) { case btn_Login : { MessageBox(NULL ,TEXT("btn_Login" ),TEXT("OK" ),MB_OK); HWND Username = GetDlgItem(hwndDlg, txt_Username); HWND Password = GetDlgItem(hwndDlg, txt_Password); TCHAR szUserBuff[0x50 ]; TCHAR szPasswordBuff[0x50 ]; GetWindowText(Username,szUserBuff,0x50 ); GetWindowText(Password,szPasswordBuff,0x50 ); return TRUE; } case btn_Cancel: EndDialog(hwndDlg, 0 ); return TRUE; } break ; } return FALSE ; }
这里需要注意的是
1 DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL , DialogProc);
这个函数,以及
1 2 3 4 GetDlgItem(hwndDlg, txt_Password); TCHAR szUserBuff[0x50 ]; TCHAR szPasswordBuff[0x50 ]; GetWindowText(Username,szUserBuff,0x50 );
难度不大
对话框回调函数的定位 编译成release自己调试,这里需要注意,断点需要下好,不然会一直卡在文本框,或者先不点进去文本框
常规找法 这里常规可以找到
找win32入口 找到DialogBox 他的参数有个回调函数 跟随条件断点就可以了
非常规找法 前面学过,创建一个窗口的话,不指定回调函数的话,他会使用系统默认的回调函数,而如果我们指定了回调函数的话,他就会从系统指定的回调函数中将控制权交换给我们自己的回调函数,理解这个过程的话就简单多了,首先加载起来,打开w界面
这里可以看到ClsProc,我们ClsProc便是系统指定的回调函数,我们可以直接对其下断
直接切换断点,然后运行便可以断下了
又或者,消息断点,这里注意,消息断点不是下WM_COMMAND,我们原来下WM_COMMAND是因为在主窗口里下的断点,而我们现在是在Button里下的消息断点,所以该是什么就是什么
这里选择202 WM_LBUTTONUP也就是鼠标左键按起后,接下来才是关键,运行,断下
这里断下在系统dll部分,我们要返回我们的代码段,我们学过pe结构,代码通常是放在.text段,所以,我们在.text段下个内存访问断点,访问代码时候便断下,就可以回到程序领空了,点m
下内存访问断点,运行,这里断下了
注意堆栈窗口,我们点的login,wParam应该为0x3EC,而这里明显不是,我们单步,追到系统领空后再次直接运行,返回后
这里便是了,wParam是0x3EC
同时,其实消息断点就是条件断点
这里延伸下,这明显就是ollydbg返回到程序领空的方法啊,alt+f9的方法
未来写调试器的那一天可能会用到
练习 练习1 似乎很简单,就是应用刚刚学到的知识,找到Find Me 1,2,3对应的回调函数
Find me 1
Find me 2
练习2 逆向获得正确的账号和密码
应该不难,同样用非常规找回调
找到回调后先下个条件断点吧,然后取消掉其余的
这里注意下,他的退出变成了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/ 版权声明 :转载请注明出处!