3.3 main函数的识别
有了3.2节的知识作为基础,识别VC++6.0正常编译的程序的main函数的过程就非常简单。
识别main函数的原理如同识别一个人。要对一个人进行识别,首先是观察此人,然后找出他的身体和面貌上的特征,将这些特征情况与自己所认识的人的特征相匹配,从而断定出此人的身份。那么,VC++6.0下的main函数都有哪些特征呢?从代码清单3-1中可以总结出main函数有如下特征是:它有3个参数,分别为命令行参数个数、命令行参数信息和环境变量信息,而且它是启动函数中唯一的具有3个参数的函数。同理,WinMain也是启动函数中唯一的具有4个参数的函数。
在VC++6.0中,main函数被调用前要先调用的函数如下:
GetVersion()
_heap_init()
GetCommandLineA()
_crtGetEnvironmentStringsA()
_setargv()
_setenvp()
_cinit()
这些函数调用结束后就会调用main函数。根据main函数调用的特征,会将3个参数压入栈内作为函数的参数。
OllyDBG在加载程序时直接暂停在应用程序的入口处,而不会直接定位到main函数处,需要分析者手动查找定位。通过main函数的特性查找到所在的位置,如代码清单3-4所示。
代码清单3-4 OllyDBG反汇编信息
;省略部分代码
;OllyDBG识别出的函数名称为GetCommandLineA
00401210|.FF15 38514200 call dword ptr ds:[<&KERNEL32.GetCommand>
;得到命令行参数
00401216|.A3 444F4200 mov dword ptr ds:[424F44],eax
;根据main函数特性,此处为函数_crtGetEnvironmentStringsA()调用
0040121B|.E8 E0240000 call ProgramE.00403700
00401220|.A3 BC354200 mov dword ptr ds:[4235BC],eax
;根据main函数特性,此处为函数_setargv()调用
00401225|.E8 C61F0000 call ProgramE.004031F0
;根据main函数特性,此处为函数_setenvp()调用
0040122A|.E8 711E0000 call ProgramE.004030A0
;根据main函数特性,此处为函数_cinit()调用
0040122F|.E8 8C1A0000 call ProgramE.00402CC0
00401234|.8B0D 00364200 mov ecx, dword ptr ds:[423600]
0040123A|.890D 04364200 mov dword ptr ds:[423604],ecx
00401240|.8B15 00364200 mov edx, dword ptr ds:[423600]
;压栈传参,环境变量信息
00401246|.52 push edx
00401247|.A1 F8354200 mov eax, dword ptr ds:[4235F8]
;压栈传参,命令行参数信息
0040124C|.50 push eax
0040124D|.8B0D F4354200 mov ecx, dword ptr ds:[4235F4]
;压栈传参,命令行参数个数
00401253|.51 push ecx
;此处为main函数的调用处,跟进到函数中便是main函数的实现代码流程
00401254|.E8 ACFDFFFF call ProgramE.00401005
识别出代码清单3-4中的GetCommandLineA()函数后,对应前面讨论的main函数特性继续向下寻找。为了准确识别main函数,可以考察传递参数的个数,如果具有3个参数,便是main函数的调用,双击即可进入main函数的实现中。
IDA下的main函数识别更为简便,它会直接分析出main函数所在的位置,并显示出来。那么,如何使用IDA分析启动函数mainCRTStartup呢?只要在函数窗口中找到mainCRTStartup所在的位置,双击便可进入函数实现中,如图3-4所示。
图 3-4 IDA分析查找启动函数mainCRTStartup