Win32程序入口识别,定位回调函数,具体事件处理的定位,ESP寻址方式,压栈方式复习

简介: Win32程序入口识别,定位回调函数,具体事件处理的定位,ESP寻址方式,压栈方式复习

本专栏上一篇文章带领大家编写了第一个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语言中我们常见的调用约定,也是我们逆向的时候最常见到的:

  1. _cdecl
    参数压栈顺序:从左至右入栈
    平衡堆栈方式:调用者自行清理堆栈
  2. _sedcall
    参数压栈顺序:从右至左入栈
    平衡堆栈方式:自身清理堆栈,调用者不用清理堆栈
  3. _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被按下的时候,程序断在这里:

fc2f7e23b2a8486eb5b2c686e680b61b.png

好了,掌握了这些,相信大家在逆向程序的时候会如鱼得水。

今天的分享就到这里,如果文章中出现了错误或者是我个人理解不到位的地方,还请大家指出来,我会非常虚心地学习,希望大家共同进步!!!


相关文章
|
10月前
|
编译器 Linux C语言
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)(上)
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)
|
2月前
|
存储
hyengine 寄存器问题之传递参数和接收返回值如何解决
hyengine 寄存器问题之传递参数和接收返回值如何解决
|
3月前
|
编译器
8086 汇编笔记(六):更灵活的定位内存地址的方法
8086 汇编笔记(六):更灵活的定位内存地址的方法
|
10月前
|
编译器 C语言
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)(下)
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)
|
存储 运维 安全
基于VS调试分析 + 堆栈观察问题代码段
面对眼前两段有问题的代码,你会通过什么去解决这个问题?本文将通过调试进行逐步分析💻,带你步步观察程序的运行逻辑
21375 0
基于VS调试分析 + 堆栈观察问题代码段
|
Linux
Linux驱动开发 驱动程序的具体编写及出口入口函数解析,printk打印内核信息
Linux驱动开发 驱动程序的具体编写及出口入口函数解析,printk打印内核信息
220 0
|
前端开发
前端hook项目pc总结笔记-打印实现局部打印
前端hook项目pc总结笔记-打印实现局部打印
90 0
西门子S7-1200移动指令编程实例,移动和块移动指令、填充指令、交换 指令的作用是什么?
西门子S7-1200的移动指令包括移动和块移动指令、填充指令、交换指令。
西门子S7-1200移动指令编程实例,移动和块移动指令、填充指令、交换 指令的作用是什么?
DHL
|
存储 算法 Java
卷起来,突破35岁焦虑,动画演示CPU记录函数调用过程,进互联大厂如此简单
通过这篇文章,能够了解到 方法如何调用 、 方法执行完之后如何返回、 内存如何记录方法调用过程。方法调用和返回过程涉及到了,虚拟机栈、程序计数器、局部变量表、操作数栈、方法返回地址、动态链接 等等内容
DHL
148 0
卷起来,突破35岁焦虑,动画演示CPU记录函数调用过程,进互联大厂如此简单