Main()函数的前世今生

        在开始分析程序之前,我们第一个要解决的问题,就是如何定位到main函数,想要从二进制逆向的角度分析出main函数,就必须要了解正向的代码下main函数的所有的细节和特

征。毕竟逆向的本质就是正向。

程序真正的入口

VS C++开发的程序在调试时总是从main或WinMain函数开始,这就让开发者误认为它们是程序的第一条指令执行处,这个认知其实是错误的。

main或WinMain函数需要有一个调用者,在它们被调用前,编译器其实已经做了很多事情,所以main或WinMain是“语法规定的用户入口”,而不是“应用程序入口”。

应用程序被操作系统加载时,操作系统会分析执行文件内的数据,并分配相关资源,读取执行文件中的代码和数据到合适的内存单元,然后才是执行入口代码,入 口 代 码 其 实 并 不 是 main 或 WinMain 函 数 , 通 常 是mainCRTStartup 、 wmainCRTStartup 、 WinMainCRTStartup 或wWinMainCRTStartup , 具体视编译选项而定 。

  • mainCRTStartup和wmainCRTStartup是控制台环境下多字节编码和 Unicode 编 码 的 启 动 函 数
  • WinMainCRTStartup 和wWinMainCRTStartup则是Windows环境下多字节编码和Unicode编码的启动函数。

VS2019的启动函数

VS C++ 在 控 制 台 和 多 字 节 编 码 环 境 下 的 启 动 函 数 为 mainCRTStartup , 由 系 统 库 KERNEL32.dll 负 责 调 用 , 在 mainCRTStartup中再调用main函数。使用VS2019进行调试时,入 口断点总是停留在main函数的首地址处。可以利用VS2019的栈回溯功能分析调用main函数的栈信息。

mainCRTSartup函数之前的高级源码无法查看

mainCRTSartup --  _scrt_common_main()  --  _scrt_common_main_seh()  --  invoke_main()  --  main()

main函数的识别

特征如下:

  • 有3个参数,分别是命令行参数个数、命令行参数信息和环境变量信息,而且main函数是启动函数中唯一具有3个参数的函数。同理,WinMain也是启动函数中唯一具有4个参数的函数。
  • main函数返回后需要调用exit函数,结束程序根据main函数调用的特征,找到入口代码第一次调用exit函数处,离exit最近的且有3个参数的函数通常就是main函数。

调用main()堆栈

样例代码

#include <stdio.h>


int main()
{
	printf("Hello World!");

	return 0;
}

通过VS2019查看main()函数调用的堆栈空间

转到mainCRTStartup()的反汇编,这是C++程序执行的第一个函数

mainCRTStartup先调用__scrt_common_main函数

转到__scrt_common_main()的反汇编

在反汇编代码中,__scrt_common_main第二个调用函数是__scrt_common_main_seh

之后__scrt_common_main_seh调用invoke_main()

转到invoke_main()的反汇编,发现调用 _main()函数

回到源代码

这就到main函数了,这里就到了main函数了,这里main函数有三个参数,分别是

  • __argc是参数个数
  • __argv是参数列表
  • 最后一个是环境指针
里面的每一个参数都是单独的一个函数

可以得出规律

  1. mainCRTStartup调用__scrt_common_main
  2. __scrt_common_main调用__scrt_common_main_seh,对应汇编是在第二个call上
  3. __scrt_common_main_seh调用invoke_main,对应在汇编的特征如下:
    1. int const main_result = invoke_main();源码是赋值给一个变量
    2. 这个函数参数是0个,后面会跟exit函数,exit函数的参数 是 invoke_main 的返回值
    3. call指令后面必有一个mov [ebp-0xXXX],eax
  4. invoke_main调用main

x64dbg定位main函数

进入程序入口,调用mainCRTStartup函数

调用__scrt_common_main函数

调用_scrt_common_main_seh

调用invoke_main比较复杂,先分析一下它的特征,在scrt_common_main_seh

会把invoke_main的返回值给main_result
int const main_result = invoke_main();


返回值会伴随一个exit 和 一个main_result,必然有一个ret
if (!__scrt_is_managed_app())
   exit(main_result);

return main_result;

定位invoke_main方法:

  1. 找到第一个ret
  2. 定位到call XXXX
  3. call指令后面必有一个mov [ebp-0xXXX],eax

定位ret

定位invoke_main()

在这里会看到四个函数调用,最后一个才是 main 函数

定位到main()函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dao-道法自然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值