VC++开发的程序,在调试时总是从main或者WinMain开始的。初学编程时老师一定告诉过我们这是程序的入口,也就是程序的第一条指令执行处。这个认识其实是错误的,在他们之前还要更早的调用者。
在应用程序被加载时,操作系统会分析执行文件内的数据,分配相关资源,读取文件中的代码和数据适合的内存单元,然后才是执行入口代码,入口代码其实并不是main或WinMain,通常是mainCRTStartup,wmainCRTStartup,WinMainCRTStartup,具体由编译选项而定。
了解VC++6.0的启动函数
VC++6.0在控制台和对多字节编码环境下的启动函数为mainCRTStartup,由系统库KERNEL32.dll负责调用。在mainCRTStartup中再调用main函数。我们通过编译器提供的Call Stack观察在main函数启动之前系统进行了哪些操作。
图中调用了5个函数,两个NTDLL,一个KERNEL32,一个mainCRTStartup,一个main,VC++提供了mainCRTStartup()函数的源码,该函数完成了一下调用:
1.GetVersion函数:获取当前运行平台的版本号。
2._heap_init函数:用于初始化堆空间。在函数实现中使用HeapCreate申请堆空间
3.GetCommandLineA函数:获取命令行参数信息的首地址
4._crtGetEnvironmentStringA函数:获取环境变量信息的首地址
5._setargv函数:此函数根据GetCommandLineA获取命令行参数信息的首地址并进行参数分析
6._setenvp函数:此函数根据_crtGetEnvironmentStringA函数获取环境变量信息的首地址进行分析。
7._cinit函数:用于全局变量数据和浮点数寄存器的初始化。
main函数识别
在OllyDBG中加载一个程序进行分析,OllyDBG会自动识别GetCommandLineA函数,往下依次是_crtGetEnvironmentStringA,_setargv,_setenvp,_cinit
CPU Disasm
Address Hex dump Command Comments
0040122B |. E8 502B0000 CALL 00403D80
00401230 |. FF15 4CD14200 CALL DWORD PTR DS:[<&KERNEL32.GetCommand ; [KERNEL32.GetCommandLineA
00401236 |. A3 24C64200 MOV DWORD PTR DS:[42C624],EAX
0040123B |. E8 20290000 CALL 00403B60 ; [ACM05.00403B60
00401240 |. A3 6CAC4200 MOV DWORD PTR DS:[42AC6C],EAX
00401245 |. E8 06240000 CALL 00403650 ; [ACM05.00403650
0040124A |. E8 B1220000 CALL 00403500 ; [ACM05.00403500
0040124F |. E8 CC1E0000 CALL 00403120 ; [ACM05.00403120
00401254 |. 8B0D A8AC4200 MOV ECX,DWORD PTR DS:[42ACA8]
0040125A |. 890D ACAC4200 MOV DWORD PTR DS:[42ACAC],ECX
00401260 |. 8B15 A8AC4200 MOV EDX,DWORD PTR DS:[42ACA8]
00401266 |. 52 PUSH EDX
00401267 |. A1 A0AC4200 MOV EAX,DWORD PTR DS:[42ACA0]
0040126C |. 50 PUSH EAX
0040126D |. 8B0D 9CAC4200 MOV ECX,DWORD PTR DS:[42AC9C]
00401273 |. 51 PUSH ECX
00401274 |. E8 8CFDFFFF CALL 00401005
IDA则会更清楚的反应调用情况