本专栏上一篇文章带领大家编写了第一个Windows程序,并且带领大家了解了Windows消息机制,如果大家还是不了解的话,可以到我的另一篇专栏中深入学习一下windows消息,Windows消息循环的原理,点击这里就可以查看啦。
我们在逆向别人的程序的时候,首先肯定要找到这个程序的入口,其次,当我们点击了某个按钮的时候,程序(回调函数)到底做了什么事情?程序入口识别,定位回调函数是我们必备的技能。这篇文章来带领大家找一下程序的入口,定位回调函数,并且带领大家了解ESP寻址方式,顺带再来复习一下压栈方式。
一.Win32程序入口识别
我们在使用OllyDbg,x64dbg等工具进行动态调试的时候,工具给我们的断点是PE文件结构中的EntryPoint,并不是真正的程序入口,在程序入口和EntryPoint之间,还有很多操作系统需要做的事,而我们很多时候并不需要关心,那我们就来找找真正的程序入口:
我们在编写一个Win32应用程序的时候,我们都会以WinMain
函数作为入口点,那么在我们逆向程序的时候,也需要找到这个函数,也就是程序真正的入口了,我们先来复习一下WinMain
函数,因为我们定位应用程序入口的时候,就需要WinMain函数的参数来帮助我们定位应用程序入口。
int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd );
参数解释:
- hInstance:应用程序的当前实例的句柄,我们在程序中接收,输出后发现,其实这个就是ImageBase
hPrevInstance:应用程序的上一个实例的句柄。此参数始终为NULL。
lpCmdLine:应用程序的命令行,不包括程序名称,实际上,我们在启动应用程序的时候,是可以给应用程序传一个参数的,而lpCmdLine就用于接收这个参数,我们一般不会用到。
nShowCmd:控制程序的显示方式。
大家肯能对这里定义主函数前的APIENTRY不太了解,实际上这就是做了一个改名而已,我们来看看:
我们在VS中,选定APIENTRY,F12直接看看它的定义:
我们发现APIENTRY是对WINAPI改了一个名字,我们追根到底,看看WINAPI到底是什么:
我们发现这里的WINAPI实际上是一种调用约定,我们在讲PE的时候,提到过,这里我也记得不是很清楚了,我们来复习一下几种调用约定:
调用约定
这里讲到的调用约定都是在汇编,C语言中我们常见的调用约定,也是我们逆向的时候最常见到的:
_cdecl
:
参数压栈顺序:从左至右入栈
平衡堆栈方式:调用者自行清理堆栈_sedcall
:
参数压栈顺序:从右至左入栈
平衡堆栈方式:自身清理堆栈,调用者不用清理堆栈_fastcall
:
参数压栈顺序:ECX/EDX传送前两个参数,剩下的从左至右入栈
平衡堆栈方式:自身清理堆栈,调用者不用清理堆栈
其中,
_fastcall
压栈是最快,而且节省内存的一种调用约定(参数不超过2个的时候),而且我们发现,_cdecl
这种调用约定和_fastcall
是不用我们手动去清理堆栈的。这也就是效率高的原因。
我们知道了WinMian
函数需要四个参数,并且其中一个为当前应用程序实例句柄,那么我们就大概知道:在WinMain
函数之前,需要获取当前应用程序的实例句柄,我们根据这个,再结合四个参数,就能很容易找到应用程序真正的入口了,而且这个入口离EntryPoint不远:
我们来到OllyDbg中来找一下:
很明显这里自动断点是EntryPoint,而且是一个call那我们单步一下,看会不会进入这个函数,发现进入了,那我们就需要在这个函数中去找:
我这个程序是进入了,所以我在这个函数中去找:
进入call,往后翻一下,很快就看到了GetModuleHandleW
函数,这个函数功能就是获取当前应用程序实例句柄,那么既然第二个参数找到了,应用程序真正的入口也不远了,这里注意找入口的时候严格是4个参数,结合各种汇编代码确定是4个参数,那么这个函数大概率就是入口函数了:
这里我找到的入口函数:
这里是压入了四个参数,而且在这个函数中,OllyDbg自动解析出了很多Kener32的函数,所以我确定这个就是应用程序入口函数。
二.定位回调函数
既然我们找到了应用程序入口函数,那么找回调函数就比较容易了:
我们在注册窗口的时候,需要一个窗口类,而这个窗口类中,就包含了回调函数的地址,那么现在我们主要的任务就是找到注册窗口的函数,在注册窗口函数中,我们只传入了一个参数,就是窗口类的指针,那么我们在找到注册窗口函数后,找到这个指针参数,这个指针的第二个成员,就是回调函数地址了,我们跟进去就是回调函数了。
可以看到OllyDbg已经帮我们解析出了注册窗口函数,在这个函数之前,只压入了一个参数(eax)那么eax中存的值,肯定就是这个指针了,我们打开数据窗口,将eax指向的地址输入进去,就可以看到窗口类了:
那么第二个成员,就是回调函数地址了,我们跟进去,就是回调函数了。
三.浅析ESP寻址方式
这里的ESP寻址方式和EBP寻址方式差不多,但是这里的ESP中存储的地址,会随时变更,所以要根据ESP寻址方式,必须要单步执行到最近的入栈后查看数据。
四.具体事件的处理定位
在找到了回调函数之后,我们就要找具体的事件是怎样处理的了(点击按钮,点击鼠标,按下键盘等动作)。
我们在定义回调函数的时候,传入了4个参数:
LRESULT CALLBACK WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
而我们在做消息处理的时候,我们是根据参数uMsg
来区分到底是哪种消息的。而uMsg是第二个参数,使用ESP寻址就是[esp+8]
,我们直接在回调函数开始的位置下断点即可。
这里如果直接下断点的话,由于Windows消息会很多,所以应用程序会频繁地停在这里,所以我们必须指定条件断点,指定消息,当处理这个消息的时候,断在这里,我们就能清楚地知道,当应用程序收到这个消息的时候,做了哪些处理:
这里我指定的是当键盘KEY被按下的时候,程序断在这里:
好了,掌握了这些,相信大家在逆向程序的时候会如鱼得水。
今天的分享就到这里,如果文章中出现了错误或者是我个人理解不到位的地方,还请大家指出来,我会非常虚心地学习,希望大家共同进步!!!