深入理解win32(二)

阅读量477852

|

发布时间 : 2021-12-10 15:30:39

 

前言

上一节中我们初始了win32中的事件、消息以及消息处理函数,这节我们来探究一下win32的入口函数、ESP以及回调函数以及如何在od里面找到这几个结构。

 

关于入口函数

WinMain结构

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

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 ) function.
  • nCmdShowis 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进行运行查看

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,                                    //相对于父窗口的X坐标        
    10,                                    //相对于父窗口的Y坐标        
    600,                                //窗口的宽度          
    300,                                //窗口的高度          
    NULL,                                //父窗口句柄,为NULL          
    NULL,                                //菜单句柄,为NULL          
    hInstance,                            //当前应用程序的句柄          
    NULL);                                //附加数据一般为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;
}

win32应用程序入口识别

这里首先改成release版本编译

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

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

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

GetModuleHandleA

看一下MSDN里对GetModuleHandleA的描述

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,指向一个地址

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

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

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

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

 

ESP寻址

这里首先说一下ESP的概念,我们知道在汇编层面中有许多个标志寄存器,在这些标志寄存器里面ESP和EBP可以说是成对存在的

ESP:栈指针寄存器 (extended stack pointer) ,永远指向系统栈最上面一个栈帧的栈顶。
EBP:基址指针寄存器(extended base pointer), 永远指向系统栈最上面一个栈帧的栈底。

我们如果要往一个栈里面存放数据,首先需要在栈里面申请空间,然后调整esp和ebp

例如在这个地方我根据汇编语句画出堆栈图,在执行push 0x5,push 0xc和push0x9的操作的时候,esp就会相应的减少12,对应的十六进制就是10,所以ESP就从0019FEE4 – 10 = 0019FED8,也就是说如果要往栈里面存入数据,esp的值就是栈顶的地址值

F2断点到函数位置

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

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

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

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

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

 

窗口回调函数

DllMain只是在Windows系统里注册的一个回调函数(call back)

早期的SDK版本中,DllMain是叫做DllEntryPoint

DllMain 是Dll 的缺省入口函数,DLLMain 负责Dll装载时的初始化及卸载的收尾工作,每当一个新的进程或者该进程的新的线程访问 DLL 或访问DLL 的每一个进程或线程不再使用DLL时,都会调用 DLLMain。

使用 TerminateProcess 或 TerminateThread 结束进程或者线程,不会调用DLLMain。

有些DLL并没有提供DllMain函数,也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会引入一个缺省DllMain函数版本。

缺省的DllMain函数在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs 键值中。

DllMain函数的原型

BOOL  WINAPI  DllMain( HINSTANCE  hinstDLL,  DWORD  fdwReason,  LPVOID  lpvReserved );

BOOL  APIENTRY  DLLMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID  lpReserved)

{

switch(ul_reason_for_call)

{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
}

return TRUE;

}

参数:

hModule:

是动态库被调用时所传递来的一个指向自己的句柄(实际上,它是指向_DGROUP段的一个选择符);

ul_reason_for_call:

是一个说明动态库被调原因的标志。当进程或线程装入或卸载动态连接库的时候,操作系统调用入口函数,并说明动态连接

DLL_PROCESS_ATTACH
进程被调用
DLL_THREAD_ATTACH
线程被调用
DLL_PROCESS_DETACH
进程被停止
DLL_THREAD_DETACH
线程被停止

lpReserved:是一个被系统所保留的参数;

DLL_PROCESS_ATTACH
每个进程第一次调用DLL文件被映射到进程的地址空间时,传递的fdwReason参数为DLL_PROCESS_ATTACH。
这进程再次调用操作系统只会增加DLL的使用次数。
DLL_THREAD_ATTACH
进程中的每次建立线程,都会用值DLL_THREAD_ATTACH调用DllMain函数,哪怕是线程中建立线程也一样。
DLL_PROCESS_DETACH
当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的fdwReason值是DLL_PROCESS_DETACH。

◆FreeLibrary解除DLL映射(有几个LoadLibrary,就要有几个FreeLibrary)
◆进程结束而解除DLL映射,在进程结束前还没有解除DLL的映射,进程结束后会解除DLL映射。

用TerminateProcess终结进程,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。

注意:当用DLL_PROCESS_ATTACH调用DLL的DllMain函数时,如果返回FALSE,说明没有初始化成功,系统仍会用DLL_PROCESS_DETACH调用DLL的DllMain函数。

DLL_THREAD_DETACH

线程调用ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),用DLL_THREAD_DETACH来调用DllMain函数。

注意:用TerminateThread来结束线程,系统就不会用值DLL_THREAD_DETACH来调DLL的DllMain函数。

早期的SDK版本中,DllMain是叫做DllEntryPoint。其实Dll的入口函数名是可以自己定义的。

窗口回调函数的结构

wndclass.lpfnWndProc = WindowProc;

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

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

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

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

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;

 

具体事件处理

首先看一下具体事件处理的代码

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

        //子进程鼠标信息
    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);                                
}

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

WM_LBUTTONDOWN对应的编号为0x0201

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

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

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

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

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

本文由Drunkmars原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/260799

安全客 - 有思想的安全新媒体

分享到:微信
+17赞
收藏
Drunkmars
分享到:微信

发表评论

Drunkmars

溯洄从之 道阻且长

  • 文章
  • 13
  • 粉丝
  • 31

TA的文章

相关文章

热门推荐

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66