• 我们在哪一颗星上见过 ,以至如此相互思念 ;我们在哪一颗星上相互思念过,以至如此相互深爱
  • 我们在哪一颗星上分别 ,以至如此相互辉映 ;我们在哪一颗星上入睡 ,以至如此唤醒黎明
  • 认识世界 克服困难 洞悉所有 贴近生活 寻找珍爱 感受彼此

windows编程:一切都要从Hello Word说起!

windows编程 云涯 4年前 (2020-05-25) 1850次浏览

0x01 debug与release版本的区别与联系

Debug 版本:
/MDd /MLd 或 /MTd 使用 Debug runtime library(调试版本的运行时刻函数库)
/Od 关闭优化开关
/D “_DEBUG” 相当于 #define _DEBUG,打开编译调试代码开关(主要针对assert函数)
/ZI 创建 Edit and continue(编辑继续)数据库,这样在调试过程中如果修改了源代码不需重新编译
/GZ 可以帮助捕获内存错误
/Gm 打开最小化重链接开关,减少链接时间

Release 版本:
/MD /ML 或 /MT 使用发布版本的运行时刻函数库
/O1 或 /O2 优化开关,使程序最小或最快
/D “NDEBUG” 关闭条件编译调试代码开关(即不编译assert函数)
/GF 合并重复的字符串,并将字符串常量放到只读内存,防止被修改

IDA在debug版本无法将main(不仅仅)函数识别出来,但是release版本就可以识别main函数。images0x02 C++的_tmain()和main()的区别

main()是标准C++的函数入口,头文件<iostream.h>。标准C++的程序入口点函数,默认字符编码格式ANSI,函数签名为: int main()或者int main(int argc, char* argv[]);
_tmain()是微软操作系统(windows)提供的对unicode字符集和ANSI字符集进行自动转换用的程序入口点函数,头文件<stdio.h>、<tchar.h>。 函数签名为: int _tmain(int argc, TCHAR *argv[]) 。

当你程序当前的字符集为unicode时,int _tmain(int argc, TCHAR *argv[])会被翻译成 int wmain(int argc, wchar_t *argv[])
当你程序当前的字符集为ANSI时,int _tmain(int argc, TCHAR *argv[])会被翻译成 int main(int argc, char *argv[])。

  1、main()是WINDOWS的控制台程序(32BIT)或DOS程序(16BIT),  
2、WinMain()是WINDOWS的GUI程序,
3、 wmain()是UNICODE版本的main(),wmain也是main的另一個别名,是为了支持二个字节的语言环境
4、_tmain()是个宏,如果是UNICODE则他是wmain()否则他是main()

0x03 C++main带参数与不带参数的区别

main参数形式:

//没参数的:
int main () {...};
//有参数的:
int main (int argc, char** argv) {...};
int main (int argc, char* argv[]) {...};

argc参数是命令行参数的数目,argv是指向参数的指针所构成的数组

没有参数只能执行二进制文件,有了参数可以带有参数进行运行,比如解析txt文档,1.txt就可以,否则没办法运行解析txt。

0x04 在进入main之前程序都干了什么

在调试时总是从main或WinMain函数开始,这就让开发者误以为它们是程序的第一条指令执行处,这个认识其实是错误的。main或者WinMain也是一个函数,也需要一个调用者。在它们被调用之前,编译器其实做了很多事情,所以main或WinMain应该是“语法规定的用户入口”,而不是“应用程序入口”。

在应用程序被操作系统加载时,操作系统会分析执行文件内的数据,分配相关资源,读取执行文件中的代码和数据到合适的内存单元,然后才是执行入口的代码。

入口代码其实并不是main或WinMain,通常是mainCRTStartup、wmainCRTStartup、WinMainCRTStartup或wWinMainCRTStartup,具体视编译选项而定。

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

在开发过程中,VC++也允许程序员自己指定入口。

了解VC++ 6.0的启动函数

VC++ 6.0在控制台和多字节编码环境下的启动函数为mainCRTStartup,有系统库KERNEL32.dll负责调用。在mainCRTStartup(启动函数)中再调用main函数。

启动函数的工作流程如下:

  • GetVersion函数:获取当前运行平台版本号,或者MS-DOS版本信息。
  • _heap_init函数:用户初始化堆空间。
  • GetCommandLineA函数:获取命令行信息的首地址。
  • _crtGetEnvironmentStringsA函数:获取环境变量的首地址。
  • _setargv函数:此函数根据GetCommandLineA获取命令行参数西悉尼的首地址并进行参数分析,将分离出的参数的个数保存在全局变量_argc中,将分析的每个命令行参数的首地址存在数组中,并将这个字符指针数组的首地址保存在全局变量_argv中。这样就得到了命令行参数的个数以及命令行参数信息。
  • _setenvp函数:此函数根据_crtGetEnvironmentStringsA函数获取环境变量的首地址并进行分析,将得到的每条环境变量字符的首地址存放在字符指针数组中,并将这个数组的首地址存放在全局变量env中。

得到main函数所需的三个参数信息后,当调用main函数时,便可以将_argc,_argv,env这三个全局变量作为参数,以栈传参方式传递到main函数中。

  • _cinit函数:用于全局数据和浮点寄存器的初始化。

 

VC编译起版本不同,mainCRTStartup函数也可能有所不同,高版本VC会是_tmainCRTStartup。

总结:程序启动调用启动函数(mainCRTStartup等),传入main函数所需要的三个参数,最后调用main函数。

重新指定入口函数后,将直接从KERNEL32中调用重新指定的入口函数,而不会经过maminCRTStartup。但是因为没有调用maminCRTStartup函数,堆空间是没有初始化的,当使用堆空间时,程序会报错并崩溃。

程序启动都经历了什么

  • 运行程序MyApp.exe时,shell(比如windows下的Explorer.exe)调用CreateProcess激活一个MyApp.exe进程。
  • CreateProcss创建了一个进程内核对象,操作系统的加载程序为进程分配一个4GB的虚拟地址空间,CreateProcess加载exe文件,分析文件头(具体格式见PE文件格式分析)以识别文件的运行环境,根据文件头决定由那个环境进行加载操作。
  • 然后把程序MyApp.exe所占用的磁盘空间作为虚拟内存映射到这个4GB的虚拟地址空间中。一般情况下,会映射到虚拟地址空间中0X00400000的位置。

加载一个应用程序的时间比一般人所设想的要少,因为加载一个PE文件并不是把这个文件整个一次性的从磁盘读到内存中,而是简单的做一个内存映射,映射一个大文件和映射一个小文件所花费的时间相差无几。当然,真正执行文件中的代码时,操作系统还是要把存在于磁盘上的虚拟内存中的代码交换到物理内存(RAM)中。但是,这种交换也不是把整个文件所占用的虚拟地址空间一次性的全部从磁盘交换到物理内存中,操作系统会根据需要和内存占用情况交换一页或多页。当然,这种交换是双向的,即存在于物理内存中的一部分当前没有被使用的页也可能被交换到磁盘中。

  • 接着,系统在内核中创建进程对象和主线程对象以及其它内容。
  • 然后操作系统的加载程序搜索PE文件中的引入表,加载所有应用程序所使用的动态链接库。对动态链接库的加载与对应用程序的加载完全类似。
  • 再接着,操作系统执行PE文件首部所指定地址处的代码,开始应用程序主线程的执行。
  • 主线程执行会先调用调用启动函数(mainCRTStartup等),传入main函数所需要的三个参数,最后调用main函数。

0x05 怎么识别main函数

识别main函数最重要是找特征,也就是在main被调用前的函数,以及main的三个参数。

IDA可以直接看出来。OD就要参照IDA以及特征观察。

images

0x06 总结

在逆向时,要始终关注主功能函数,不要过多陷入系统底层。多编程,多逆向,了解哪些是编译器生成的,哪些是程序独有的。


云涯历险记 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:windows编程:一切都要从Hello Word说起!
喜欢 (0)