1.Win32程序入口识别
WinMain函数的四个参数:
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
//这里许多我们没见过的类型,其实都是系统定义的宏,通过F12跳转我们可以发现
//其实都是一些常见的类型宏定义出来的
//甚至有很多类型相同,但是宏定义的名称不同
第一个参数 :hInstance 表示该程序当前运行的实例的句柄,这是一个数值。当程序在 Windows 下运行时,它唯一标识运行中的实例(注意,只有运行中的程序实例,才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过 hInstance 参数传递给 WinMain 函数。
第二个参数: hPrevInstance 表示当前实例的前一个实例的句柄。通过查看 MSDN 我们可以知道,在 Win32 环境下,这个参数总是 NULL ,即在 Win32 环境下,这个参数不再起作用。
第三个参数 :lpCmdLine 是一个以空终止的字符串,指定传递给应用程序的命令行参数。 例如:在 D 盘下有一个 sunxin.txt 文件,当我们用鼠标双击这个文件时将启动记事本程序( notepad.exe ),此时系统会将 D:/sunxin.txt 作为命令行参数传递给记事本程序的 WinMain 函数,记事本程序在得到这个文件的全路径名后,就在窗口中显示该文件的内容。要在 VC++ 开发环境中向应用程序传递参数,可以单击菜单 【 Project 】→【 Settings 】,选择“ Debug ” 选项卡,在“ Program arguments ”编辑框中输入你想传递给应用程序的参数。
第四个参数: nCmdShow 指定程序的窗口应该如何显示,例如最大化、最小化、隐藏等。这个参数的值由该程序的调用者所指定,在调用ShowWindow()时可以使用到该值。
我们通过windows+R键,输入cmd打开控制台,输入D:后回车,再输入文件名+需要传递的参数abcdef,此时我们可以在DebugView上观察到WinMain函数接受到的参数就是abcdefg
win32程序入口识别:
首先我们用DTDbug所在文件夹,打开udd文件夹,清空里面的缓存,再打开exe程序
我们首先进行分析,WinMain函数有四个参数,其中第一个参数是该程序的句柄
我们发现,在此处调用了一个函数,通过函数名我们大概可以猜到是获取句柄的一个函数,我们通过查文档可知:
If the function succeeds, the return value is a handle to the specified module.
如果函数成功,返回值是指定模块的句柄。
If the function fails, the return value is NULL. To get extended error information, call GetLastError.
如果函数失败,返回值为NULL。要获取扩展的错误信息,请调用GetLastError。
并且我们发现,call下一指令是push EAX,因此我们可以知道,GetModuleHandle函数的返回值通过寄存器传参给了EAX
push EAX后,call了一个函数,因此我们可以推测这就是WinMain函数的入口,我们Enter进入,查看平栈可以知道有几个参数
这里为什么DTDbug可以知道调用函数名字?
因为导入表的存在,此时call的地址就是IAT表
我们判断参数的个数并不能完全相信call前面push了几个参数,需要到函数内部观察平栈时恢复了几个,我们发现,该函数平栈时return 10,加上该函数是 stdcall,参数是从右至左进行压栈的。
我们可以断定这个call就是调用WinMain函数的入口
2.ESP寻址的特点
ESP寻址:可直接sub ESP来提升堆栈,ESP+提升的大小就可直接访问到参数
问题:在每一次push后,ESP的位置都会发生变化,因此,想要通过ESP来寻找到参数,需要计算ESP到参数的距离。
可通过双击OD右下角的地址处可以得到偏移量
3.窗口回调函数的定位
思路:
WinMain函数的流程是我们首先创建了一个WNDCLASS结构体的对象,并且定义了属性
然后再通过RegisterWindow函数对窗口类进行注册
wndclass对象中有一个成员存储了回调函数的名称,并且该对象的地址在RegisterWindow函数中作为参数进行传递
因此我们想要找到回调函数就要先找到RegisterWindow函数的参数
我们在push ECX处下一个断点,然后运行来到断点处,通过右上角我们可以得到ECX的值,我们鼠标右键跟进堆栈
此时我们可以发现,堆栈中存储的就是wndclass对象的10个成员
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, NEAR *NPWNDCLASSA, FAR *LPWNDCLASSA;
第二个成员就是我们要找的回调函数的地址,我们Enter跟进,这里就是回调函数的入口位置
4.具体消息处理的定位
我们前面已经定位到了回调函数的内部,下面我们要定位到回调函数对于具体消息处理功能的位置
首先我们在回调函数头部下一个断点,清楚其余的断点
例如,我们需要定位对于鼠标左键消息处理的位置:
鼠标左键消息类型名是:WM_LBUTTONDOWN
设置条件[esp+8]==WM_LBUTTONDOWN
注意:这里为什么认定为是[esp+8]
因为我们定义的回调函数
LRESULT CALLBACK WindowProc( IN HWND hwnd, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam) {
他的类型是stdcall,参数压栈是从右至左进行压栈
因此在【esp+4】==hwnd
【esp+8】==uMsg
【esp+c】==wParam
【esp+10】==IParam
并且,虽然是ESP寻址,但是我们的条件是定在刚调用函数时,参数已经压栈,并且ESP还未进行提升堆栈的时候
设置完条件后,我们就可以点击运行,此时会跳出窗口,除了跟鼠标左键意外的消息,此时堆栈都不会发生变化
我们鼠标左键后,窗口消失,我们F8逐步运行来到第一个call的位置就是具体消息处理的位置了