win32窗口程序分析

1.分析消息的附加参数
例如:为了查看程序处理了哪些消息
 
在回调函数中调用输出函数,在控制台中输出消息的值;
 
结果:可以看到处理了消息7f、88、31f等消息
 
例如分析7f消息;
在vc6中可以选中任意一个消息类型宏,按F12进入宏的定义头文件中;
 
在头文件中找到7f对应的消息宏;
可以看到是WM_GETICON
 
在msdn中搜索WM_GETICON:
 
可以查msdn来分析消息的参数;
例如:分析WM_CREATE消息
    通过查msdn文档可以看到该消息的wParam没使用,lParam是一个指向CREATESTRUCT结构的指针;
    然后分析这个结构,将lparam强转为CREATESTRUCT*型,然后就可以获取到消息带的参数;
 
2.win32程序入口识别
1)入口函数WinMain
int CALLBACK WinMain(                        //CALLBACK表示__stdcall
            HINSTANCE hInstance,            //相当于imagebase
            HINSTANCE hPrevInstance,        //永远都是NULL不用管
            LPSTR lpCmdLine,                //命令行启动程序时后面接的参数用这个来读
            int nCmdShow                    //以什么方式显示:最大化、最小化、隐藏
){
    //...
}

WinMain有4个参数,并且是内平栈;

内平栈的压栈顺序是从右到左;因此最后入栈的参数为hInstance;
hInstance为程序的ImageBase;所以在WinMain函数的call前会push一个ImageBase;
ImageBase可以用kernel32.dll的GetModuleHandleA来获取;可以在MSDN中用该函数名来搜索获取函数详细信息;
函数的返回值一般保存在eax中;也就是说,如果一个call前面有GetModuleHandleA,并且push eax,很有可能是WinMain;
找到可能是WinMain的call后,需要跟进去确认;
看是否确实有4个参数,4个参数的内平栈函数应该结尾是 ret 10;
还需要排除是__fastcall;也就是要确认ECX/EDX没有被用来传参;
 
2)vc6编译release版
因为市面上的软件大多以release版发布,需要学会分析release版的程序;
编译成release版:
    build->setActive configuration->工程名-win32 Release
 
3)找winmain实例
用od打开测试程序HelloWin32.exe;
 
找到可能是winmain的call;call前面有push一个GetModuleHandleA的返回值
 
选中call的那一行,按enter键进入函数内部查看;(按enter键程序并不会运行到call内部)
首先查看ecx和edx;可以看到这两个寄存器是在函数内部赋值的,说明没有用来传递参数;因此排除了是__fastcall;
 
往下找到返回的ret;
可以看到retn 10说明是内平栈__stdcall;
每个参数占4个字节;十六进制的10为4个参数;
 
然后查看函数的注释;
很明显是winmain中表示窗口特征的WNDCLASS结构;
 
综上所述,可以确定这个call就是WinMain;
 
3.ESP寻址的特点
1)关于esp寻址
在调用一个函数时,首先需要提升堆栈;
目的是在栈中分配一定的空间用来存放函数的临时变量;
 
如果是debug版的程序;
    会先原来的ebp保存到栈中    ->push ebp
    再将ebp移动到esp处            ->mov ebp,esp
    然后esp提升                        ->sub esp,40h
    再然后用一堆cccccccc填充提升的堆栈区;
    如果找参数一般就是用[ebp+8]找第一个参数;
 
release版的程序提升堆栈时不会移动ebp,而是直接将esp提升;
这样就导致了一个问题,不能再用[ebp+8]之类的寻址方式来找函数的参数了;  
而需要通过esp来寻址;
例如:上图中的函数,找第一个参数寻址:[esp+58];esp提升了54,加上4个字节的函数返回地址;
esp寻址比ebp寻址要更加复杂:
    ebp不会变而esp每有一次入栈的push就会加4,因此寻址相同的参数是就需要多减4个字节;
 
2)od的esp寻址技巧
每次要计算esp+58之类的很麻烦;
为了减少计算,可以双击od堆栈窗口的地址,转为相对栈底的偏移
 
 
因为esp有push时会变动,不方便观察;
可以锁定堆栈;
右键单击堆栈窗口-》Lock stack;这样即使有push也不会改变堆栈,方便观察;
 
3)od写注释
如果在od中找到了特定的一行,可以加注释来记录;
双击目标行的注释栏 -》在弹出的框中写上注释点确认即可;
 
4.窗口回调函数的定位
在分析win32程序时,最主要想知道程序是怎么处理消息的;
例如怎么处理鼠标右键单击之类的;
如何处理消息是在回调函数中定义的;
因此首先要找到回调函数;
 
1)思路
在Win32程序中,回调函数的地址存放在WNDCLASS结构的属性lpfnWndProc中;
WNDCLASS结构通过RegisterClass(&wndclass)函数注册到窗口;
也就是说,找到了RegisterClass函数,就能知道它往里面注册的结构的内容,回调函数的地址也就确定了;
 
2)实例
od打开目标程序,找到RegisterClass
 
按F8单步执行到push edx的下一行;
edx中很可能就是参数,参数就是WNDCLASS结构的地址;
在寄存器栏中找到edx的值,右键单击选Follow in Stack追进去看;
 
可以看到此时的WNDCLASS还是一个空的结构,需要函数执行完才能填满;
 
需要分析这一段的堆栈值的变化;
右键单击堆栈窗口-》Lock stack锁定堆栈;
然后按F8单步执行;
 
WNDCLASS结构:
typedef struct _ color=#ffffff>WNDCLASS {
    UINT       style;
    WNDPROC    lpfnWndProc;
    int        cbClsExtra;
    int        cbWndExtra;
    HINSTANCE  hInstance;
    HICON      hIcon;
    HCURSOR    hCursor;
    HBRUSH     hbrBackground;
    LPCTSTR    lpszMenuName;
    LPCTSTR    lpszClassName;
} color=#ffffff>WNDCLASS, *PWNDCLASS;
对比WNDCLASS结构,可知结构的第2个成员就是回调函数地址;
也就是说回调函数地址为401100;
 
然后右键单击堆栈窗口,选解除堆栈锁定;
右键点击回调函数地址-》选Follow in Disassembler;跟到反汇编;
 
找到了回调函数第一行,下断点;
     
5.具体事件处理的定位
1)条件断点
在回调函数处下断点,每当有消息需要处理时,就会停下;
但是,消息太多了,很难找到需要分析的事件;
可以用条件断点来解决这一问题;
 
例如:分析鼠标右键单击时程序作了什么事
找到右键单击的事件宏:WM_LBUTTONDOWN;
 
点击上方的B查看断点窗口(点C回到分析窗口);
选中回调函数地址的断点,右键单击-》Edit condition;来设置断点条件;
 
条件为:传入的消息参数为WM_LBUTTONDOWN;
[esp+8]存放的就是参数中带的消息id;
    因为消息id是回调函数的第二个参数;根据内平栈的规则第二个参数倒数第二个入栈;
    断点下在回调函数开始处,还没开始提升堆栈,此时的栈顶esp的值为函数的返回地址;
    [esp+4]就是倒数第一个入栈的参数;以此类推,[esp+8]就是倒数第二个入栈的参数,也就是消息id;
 
 
 
 

猜你喜欢

转载自www.cnblogs.com/ShiningArmor/p/12021760.html