目前先只考虑VC编译器系列的。目前我的想法是这样的,从CRT0.C 里的启动函数入手,寻找特征。我在坛子了看到过一篇文章:
库函数的快速鉴别和识别技术 ,但这文章讲的是如何对已有的函数build签名文件,并没有说如何识别main()函数,里面有句
“为了方便用户,我们试图尽最大可能识别main()函数。鉴别这个函数的算法因编译器不同而不同、因程序不同而不同。(DOS/OS2/Windows/GUI/Console...)。
该算法作为一个文本字符串被写入一个特征文件中。遗憾的是,我们还不能自动产生该算法。
”
即对于IDA,识别main()不是用一个用sig之类的来定位的,而是用了其他方法。
目前我研究了一下crt0.c 的代码,研究了几个VC编译器生成EXE反汇编后的代码,我写了一个空程序,然后反汇编:
#include <windows.h>
#include <tchar.h>
INT32 WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, PTSTR pcmdLine, INT32 nShowCmd)
{
return 0;
}
这样里面几乎都是运行库函数的代码了。
所有VC版本,都有五个版本的crt0.c代码,crt0.c, wcrt0.c, wincrt0.c, wwincrt0.c,
dllcrt0.c, dllcrt0.c先不考虑,只考虑四个跟EXE相关的。本质上它们的最终代码实现都
是在crt0.c 中,其余三个只是定义了一下宏然后include 了一下crt0.c,例如wcrt0.c:
/***
*wincrt0.c - C runtime Windows EXE start-up routine
*
* Copyright (c) 1993-1997, Microsoft Corporation. All rights reserved.
*
*Purpose:
* This is the actual startup routine for Windows apps. It calls the
* user's main routine WinMain() after performing C Run-Time Library
* initialization.
*
*******************************************************************************/
#define _WINMAIN_
#include "crt0.c"
vc2005 和 vc2008 的crt0.c 几乎完全相同,VC6的跟它们不同的多一点。这三个crt0.c中
的__tmainCRTStartup的API调用序列为:
VC6:
ANSI 版本
GetVersion
GetCommandLineA
GetStartupInfoA
GetModuleHandleA
WinMain
UNICODE 版本
GetVersion
__crtGetCommandLineW
GetStartupInfoW
GetModuleHandleA
wWinMain
VC8:
ANSI 版本
GetStartupInfoA
InterlockedCompareExchange
InterlockedExchange
WinMain
UNICODE 版本
GetStartupInfoW
InterlockedCompareExchange
InterlockedExchange
wWinMain
VC9:(跟VC8完全一样)
ANSI 版本
GetStartupInfoA
InterlockedCompareExchange
InterlockedExchange
WinMain
UNICODE 版本
GetStartupInfoW
InterlockedCompareExchange
InterlockedExchange
wWinMain
VC8 VC9 与 VC6 的区别:
VC8 VC9 都没有调用GetModuleHandle,而是用一个常量0x00400000h代替了hInstance;
VC8 VC9 都没有调用GetCommandLine来获得命令行参数,而是用一个二级指针存放命令行
首地址的指针,运行直接可以得到命令行参数。
VC8 VC9 入口点的代码为:
call __security_init_cookie
jmp __tmainCRTStartup
VC8 VC9 与 VC6 生成的EXE文件,在"PE"签名的前32字节有一个字符串"Rich"(估计是某位微软牛人的姓氏)
VC8 与 VC9 的区别:
没区别,从CRT到WinMain之间的代码反汇编出来是完全一样的。
以上是目前掌握的信息,下面简述我的寻找WinMain的方法,此方法并没有利用上面提到的
所有信息,
对于VC6, 首先得到入口点,然后在0xD0范围内搜索
push eax ; nShowCmd
push [ebp+lpCmdLine] ; lpCmdLine
push esi ; hPrevInstance
push esi ; lpModuleName
call ds:GetModuleHandleA
push eax ; hInstance
call _WinMain@16 ; WinMain(x,x,x,x)
因为VC6 的CRT0.c中调用WinMain是这样的:
mainret = WinMain(GetModuleHandleA(NULL),NULL,...);
对于VC8 VC9 同样先得到入口点,入口点代码如下:
call __security_init_cookie
jmp __tmainCRTStartup
得到jmp后的地址,来到__tmainCRTStartup开始处,搜索如下代码:
push eax ; nShowCmd
push esi ; lpCmdLine
push 0 ; hPrevInstance
push 400000h ; hInstance
call _WinMain@16 ; WinMain(x,x,x,x)
四个连续的push指令,和一个常数0x400000,是很重要的特征。
目前思路还很初级,再怎么想觉得无法有新的突破,希望高手路过给点建议
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)