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