二进制入门学习笔记-10.win32开发/逆向基础

1.win32宽字符

1.1 什么是宽字符?

传统的ascii码(8位,最高位位0)只能表示英文字母和常用符号,但缺少了很多特殊符号,因此出现了扩展ascii码(8位,最高位为1)。但是这样依旧无法表示中文字符,因此中国人创造了GB2312码表,GB2312码表其本质上是使用两个8位存储一个中文字符,这两个连续的八位二进制串的共同特征就是最高位都是1,因此可以表示的中文字符也就是2^14个。而GB2312虽然可以表示中文字符了,但是日韩等国家的字符没办法表示。为了统一,国际上制定了UNICODE编码,Unicode中的每个字符都是16位宽而不是8位宽(收录的仅是常用字符,并不是全部的字符)。 这个unicode也就是我们所说的宽字符

1.2 宽字符使用

1.2.1 声明一个宽字符

声明宽字符的代码如下:

wchar_t x1[] = L"中国";
//2d 4e fd 56 00 00  对应的二进制内容
//使用UNICODE编码表 以00 00(\0\0)结尾           

1.2.2 在控制台打印宽字符

打印宽字符的代码如下,使用的函数为wprintf

char x[] = "中国";
wchar_t x1[] = L"中国";
printf("%s\n",x);           //使用控制台默认的编码,可以正常打印
wprintf(L"%s\n",x1);            //默认使用英文,打印出来乱码

为了避免宽字符在windows控制台中打印出乱码应该添加如下内容,告诉编译器,使用控制台默认的编码格式:

#include <locale.h>     //包含头文件 
setlocale(LC_ALL,""); //使用控制台默认的编码                      

1.3 c语言中多字符和宽字符的对比

具体不同如下图所示:

1.4 宏TCHAR

1.4.0 TCHAR简介

windows底层使用的是宽字符,但是项目当中有的人可能喜欢用CHAR,有的人喜欢用WCHAR,为了兼容,windows定义了TCHAR,当编译环境设置使用宽字符TCHAR就使用宽字符,编译环境使用多字符,TCHAR就会被当成窄字符。

1.4.1 TCHAR使用

//字符数组赋值    
CHAR cha[] = "中国";
WCHAR chw[] = L"中国";
TCHAR cht[] = TEXT("中国");
//为字符串指针赋值:
PSTR pszChar = "china"; //多字节字符
PWSTR pszWChar = L"china";//宽字符
PTSTR pszTChar = TEXT("china"); //如果项目是ASCII的 相当于"china" UNICODE 相当于L"china"

1.5 多个版本的MessageBox

因为有了多字节和宽字节的区别,所以MessageBox其实也是一个宏,详情如下:

MessageBoxA(0,"内容多字节","标题",MB_OK);
MessageBoxW(0,L"内容宽字节",L"标题",MB_OK);
MessageBox(0,TEXT("根据项目字符集决定"),TEXT("标题"),MB_OK);
//Windows提供的API 凡是需要传递字符串参数的函数,都会提供两个版本和一个宏.

2.win32程序调试方法

2.1 输出内容

win32程序没有控制台了,因此如果需要输出内容推荐使用如下方式,可以在debug窗口中打印内容:

声明一个printdebug头文件,和一个对应的cpp文件,引入头文件,使用自定义的DbgPrintf即可。

//printdebug.h文件



void __cdecl OutputDebugStringF(const char *format, ...); 
#ifdef _DEBUG  
#define DbgPrintf   OutputDebugStringF  
#else  
#define DbgPrintf  
#endif 

//以下weistdafx.h文件中的内容
// stdafx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//
#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define WIN32_LEAN_AND_MEAN     // Exclude rarely-used stuff from Windows headers
#include <windows.h>
// TODO: reference additional headers your program requires here
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
//printdebug.cpp文件
#include "printdebug.h"
#include <stdio.h>  
#include <stdarg.h>  
#include <ctype.h> 
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;  
}  

使用方法如下:

#include "printdebug.h"
......
DbgPrintf("WM_LBUTTONDOWN %d %d\n",points.x,points.y);

2.2 捕获异常码

只需在有可能出现异常的代码之后添加如下代码即可得到一个对应的异常编号,查询异常编号即可知道异常详情。

DWORD errorCode = GetLastError();

3.事件和消息

3.1 概念

3.1.1 什么是事件?

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

3.1.2 什么是消息?

消息就是用来描述上述这些“动作”的,比如,这个动作是什么时候产生的?哪个应用程序产生的?Windows为了能够准确的描述这些信息,提供了一个结构体:MSG,该结构体里面记录的事件的详细信息.

typedef struct tagMSG {     
  HWND   hwnd;      //表示消息所属的窗口
  UINT   message;       //在Windows中,消息是由一个数值来表示的,但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏(WM == Window Message).
 //例如:鼠标左键按下 WM_LBUTTONDOWN             键盘按下 WM_KEYDOWN
  WPARAM wParam;        
  LPARAM lParam;        
  //32位消息的特定附加信息,具体表示什么处决于message 
  DWORD  time;      //消息创建时的时间
  POINT  pt; //消息创建时的鼠标位置
} MSG, *PMSG;       

3.2 windows消息处理流程

如下图所示:

4.创建一个win32应用程序

1.创建一个空的win32 Application,并创建一个新的CPP文件。

2.引入windows.h库并添加win32应用入口函数,代码如下:

#include <Windows.h>
int CALLBACK WinMain(               
      HINSTANCE hInstance,              
      HINSTANCE hPrevInstance,              
      LPSTR lpCmdLine,              
      int nCmdShow              
    )           
{               

    return 0;           
}           

3.设计窗口类

//窗口的类名
PSTR className = "My First Window"; 
// 创建窗口类的对象 
WNDCLASS wndclass = {0};                        //一定要先将所有值赋值
wndclass.hbrBackground = (HBRUSH)COLOR_MENU;    //窗口的背景色
wndclass.hCursor = LoadCursor(NULL,IDC_APPSTARTING);    
wndclass.lpfnWndProc = WindowProc;              //窗口过程函数
wndclass.lpszClassName = className;             //窗口类的名字    
wndclass.hInstance = hInstance;                 //定义窗口类的应用程序的实例句柄

4.注册窗口类

// 注册窗口类  
// 参加MSDN文档RegisterClass->Parameters:
// You must fill the structure with the appropriate class attributes 
// before passing it to the function. 
RegisterClass(&wndclass);  

5.创建窗口

// 创建窗口  
HWND hwnd = CreateWindow(  
    className,              //类名
    "我的第一个窗口",      //窗口标题
    WS_OVERLAPPEDWINDOW,    //窗口外观样式  
    10,                     //相对于父窗口的X坐标
    10,                     //相对于父窗口的Y坐标
    600,                    //窗口的宽度  
    300,                    //窗口的高度  
    NULL,                   //父窗口句柄,为NULL  
    NULL,                   //菜单句柄,为NULL  
    hInstance,              //当前应用程序的句柄  
    NULL);                  //附加数据一般为NULL
if(hwnd == NULL)            //是否创建成功  
    return 0;  

6.显示窗口

// 显示窗口  
ShowWindow(hwnd, SW_SHOW);  
// 更新窗口  
//UpdateWindow(hwnd);  

7.消息循环

// 消息循环  
MSG msg;  
while(GetMessage(&msg, NULL, 0, 0))  
{  
    TranslateMessage(&msg);  
    DispatchMessage(&msg);  
}  

8.编写回调函数

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);
                break;
            }
        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);
                break;
            }
        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);
                break;
            }
        case WM_DESTROY:
            {
                //DbgPrintf("WM_DESTROY %d %d\n",wParam,lParam);
                PostQuitMessage(0);
                return 0;
                break;
            }
        //键盘消息
        case WM_KEYUP:
            {
                //DbgPrintf("WM_KEYUP %d %d\n",wParam,lParam);
                break;
            }
        case WM_KEYDOWN:
            {
                //DbgPrintf("WM_KEYDOWN %d %d\n",wParam,lParam);
                break;
            }
        //鼠标消息
        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);
                break;
            }
        default:  
            return DefWindowProc(hwnd,uMsg,wParam,lParam);
    }
    return 0;  
}  

注:

  • 1.窗口回调函数处理过的消息,必须传回0.
  • 2.窗口回调不处理的消息,由DefWindowProc来处理.
  • 3.如果想要打印信息,可参考2.1节当中的调试方法.

5.win32应用程序调试

5.1 Dbgview

如之前2.1中介绍的可以将输出显示在debug窗口中,但编译好后的exe脱离了开发环境没有debug窗口,此时就可以实用Dbgview进行显示。

5.2 win32应用入口识别

观察程序winmain入口定义如下:

int CALLBACK WinMain(                  
      HINSTANCE hInstance, //ImgBase地址
      HINSTANCE hPrevInstance,    //没用
      LPSTR lpCmdLine,     //命令行参数
      int nCmdShow    //程序运行后窗口显示方式(最大化,最小化等)
)  

调用约定为stdcall,传入四个参数,stdcall为内平栈,因此retn时一般为retn 10,而且传入参数时第四个push的参数一般是调用kernel32.GetModuleHandlerA获取的ImgBase。

5.3 找窗口回调函数的定位

先找registerClass,因为注册的时候调用了结构体WNDCLASS,而该结构体lpfnWndProc参数就是回调函数。

6.按钮

6.1 按钮的使用

先定义如下创建按钮的代码,在需要使用的窗口中只需调用即可,观察按钮的定义可以发现,其实按钮的本质就是窗口,只不过windows提前定义好了很多内容。

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

    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);                      
}                               

6.2 按钮的事件处理

6.2.1 查看按钮预定义信息的方法

调用如下代码即可查看按钮中预定义的信息

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

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

6.2.2 按钮事件的处理

按钮是一种特殊的窗体,并不需要提供单独的窗口回调函数.当按钮有事件产生时,会给父窗口消息处理程序发送一个WM_COMMAND消息,如下图所示: 如果需要处理只需要在父窗口的消息处理函数中添加如下代码即可:

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);                          
}