1-2 Windows下启动函数(真正的入口函数) 之 寻找入口函数与_security_init_cookie

来源:互联网 发布:基金知乎 编辑:程序博客网 时间:2024/05/16 17:18

上一节详细介绍了/MT /MD之间的区别。在这一节中,我们首先要找到win32程序真正的启动函数。

win32程序分为两种:

1.控制台(/SUBSYSTEM:CONSOLE )

2.GUI(/SUBSYSTEM:WINDOWS)


首先看控制台版本的:

写一段最简单的,或者就直接使用编译器参数的默认main函数,如下:

// EntryFunction.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"int _tmain(int argc, _TCHAR* argv[]){return 0;}

1.使用/MD选项

F9下断到main函数的标签处,F11后,在Call Stack中回溯到_tmainCRTStartup(),当前文件为crtexe.c。如下:



2.使用/MT选项

F9下断到main函数的标签处,F11后,在Call Stack中回溯到_tmainCRTStartup(),当前文件为crt0.c。如下:



也就是说,不同的运行时链接方式,其实现代码也是不同的。但是其入口点函数的名称是相同的,都是_tmainCRTStartup。这便是Windows下的启动函数(真正的入口函数)。


下面我们分别讨论,这两者在入口函数中都干了些什么。

首先看_security_init_cookie,这个函数在两个实现文件中是一样的,代码如下:

/****__security_init_cookie(cookie) - init buffer overrun security cookie.**Purpose:*       Initialize the global buffer overrun security cookie which is used by*       the /GS compile switch to detect overwrites to local array variables*       the potentially corrupt the return address.  This routine is called*       at EXE/DLL startup.**Entry:**Exit:**Exceptions:********************************************************************************/void __cdecl __security_init_cookie(void){    UINT_PTR cookie;    FT systime={0};    LARGE_INTEGER perfctr;    /*     * Do nothing if the global cookie has already been initialized.  On x86,     * reinitialize the cookie if it has been previously initialized to a     * value with the high word 0x0000.  Some versions of Windows will init     * the cookie in the loader, but using an older mechanism which forced the     * high word to zero.     */    if (__security_cookie != DEFAULT_SECURITY_COOKIE#if defined (_M_IX86)        && (__security_cookie & 0xFFFF0000) != 0#endif  /* defined (_M_IX86) */       )    {        __security_cookie_complement = ~__security_cookie;        return;    }    /*     * Initialize the global cookie with an unpredictable value which is     * different for each module in a process.  Combine a number of sources     * of randomness.     */    GetSystemTimeAsFileTime(&systime.ft_struct);#if defined (_WIN64)    cookie = systime.ft_scalar;#else  /* defined (_WIN64) */    cookie = systime.ft_struct.dwLowDateTime;    cookie ^= systime.ft_struct.dwHighDateTime;#endif  /* defined (_WIN64) */    cookie ^= GetCurrentThreadId();    cookie ^= GetCurrentProcessId();#if _CRT_NTDDI_MIN >= NTDDI_VISTA #if defined (_WIN64)    cookie ^= (((UINT_PTR)GetTickCount64()) << 56);#endif  /* defined (_WIN64) */    cookie ^= (UINT_PTR)GetTickCount64();#endif  /* _CRT_NTDDI_MIN >= NTDDI_VISTA  */    QueryPerformanceCounter(&perfctr);#if defined (_WIN64)    cookie ^= (((UINT_PTR)perfctr.LowPart << 32) ^ perfctr.QuadPart);#else  /* defined (_WIN64) */    cookie ^= perfctr.LowPart;    cookie ^= perfctr.HighPart;#endif  /* defined (_WIN64) */    /*     * Increase entropy using ASLR relocation     */    cookie ^= (UINT_PTR)&cookie;#if defined (_WIN64)    /*     * On Win64, generate a cookie with the most significant word set to zero,     * as a defense against buffer overruns involving null-terminated strings.     * Don't do so on Win32, as it's more important to keep 32 bits of cookie.     */    cookie &= 0x0000FFFFffffFFFFi64;#endif  /* defined (_WIN64) */    /*     * Make sure the cookie is initialized to a value that will prevent us from     * reinitializing it if this routine is ever called twice.     */    if (cookie == DEFAULT_SECURITY_COOKIE)    {        cookie = DEFAULT_SECURITY_COOKIE + 1;    }#if defined (_M_IX86)    else if ((cookie & 0xFFFF0000) == 0)    {        cookie |= ( (cookie|0x4711) << 16);    }#endif  /* defined (_M_IX86) */    __security_cookie = cookie;    __security_cookie_complement = ~cookie;}

 _security_init_cookie(使用/GS选项后提供)的作用是通过初始化一个全部变量用来检测局部缓冲区溢出和对函数返回地址潜在的修改(这可能导致程序执行流程改变,以至于执行不安全的shellcode)。

下图是使用了/GS选项后的函数栈帧:

L         ARG2参数2 ARG1参数1 COOKIE VARcookie变量 EBPEBP栈指针HRET函数返回地址    
看到这个布局,就可以猜到,如果cookie被改变了(因为人为的缓冲区溢出导致),那么很有可能函数的返回地址被改变了,也可能ebp改变了。这样便能检测危险所在。

现在继续分析_security_init_cookie的代码,看看其到底做了些什么:

这个security cookie是一个已经预先定义好的全局变量(定义与gs_cookie.c中):

 * The global security cookie.  This name is known to the compiler. * Initialize to a garbage non-zero value just in case we have a buffer overrun * in any code that gets run before __security_init_cookie() has a chance to * initialize the cookie to the final value. */DECLSPEC_SELECTANY UINT_PTR __security_cookie = DEFAULT_SECURITY_COOKIE;DECLSPEC_SELECTANY UINT_PTR __security_cookie_complement = ~(DEFAULT_SECURITY_COOKIE);

虽然这个值已经预先被定义好,但是在_security_init_cookie函数中会有一次机会继续对其重新赋值。

如果cookie已经初始化过,就不继续处理,但是如果在32位下,其高字节为0x0000,则需要对其进行重新赋值(获得一个高度随机的值),因为这个值在进程初始化后不会再发生变化。


现在看看security cookie是怎么被编译器安排并使用的。编译器会在可能发生栈缓冲区溢出的函数时,定义一个全局cookie,它位于局部变量和返回地址之间,测试代码如下:

// EntryFunction.cpp : Defines the entry point for the console application.//#include "stdafx.h"#include <string.h>int _tmain(int argc, _TCHAR* argv[]){char szBuffer[5];strcpy( szBuffer, "hello world" );return 0;}

其中,szBuffer会在strcpy后发生溢出,反汇编代码如下:

int _tmain(int argc, _TCHAR* argv[]){00EE1990  push        ebp 00EE1991  mov         ebp,esp  00EE1993  sub         esp,0D4h  00EE1999  push        ebx  00EE199A  push        esi  00EE199B  push        edi  00EE199C  lea         edi,[ebp-0D4h]  00EE19A2  mov         ecx,35h  00EE19A7  mov         eax,0CCCCCCCCh  00EE19AC  rep stos    dword ptr es:[edi]  00EE19AE  mov         eax,dword ptr ds:[00F53084h]  ;将全局变量cookie保存到eax00EE19B3  xor         eax,ebp   ;将eax与ebp异或后保存到eax 00EE19B5  mov         dword ptr [ebp-4],eax  ;将异或的结果保存到ebp-4char szBuffer[5];strcpy( szBuffer, "hello world" );00EE19B8  push        0F3FD34h  00EE19BD  lea         eax,[szBuffer]  00EE19C0  push        eax  00EE19C1  call        _strcpy (0EDF758h)  00EE19C6  add         esp,8  return 0;00EE19C9  xor         eax,eax  }00EE19CB  push        edx  00EE19CC  mov         ecx,ebp  00EE19CE  push        eax  00EE19CF  lea         edx,ds:[0EE19FCh]  00EE19D5  call        @_RTC_CheckStackVars@8 (0EDF5E6h)  00EE19DA  pop         eax  00EE19DB  pop         edx  00EE19DC  pop         edi  00EE19DD  pop         esi  00EE19DE  pop         ebx  00EE19DF  mov         ecx,dword ptr [ebp-4]  ;将ebp-4中的值(之前cookie与ebp异或后的值)保存到ecx00EE19E2  xor         ecx,ebp ;将ecx与ebp异或并保存到ecx中(如果ebp被破坏了,那么ecx中存放的应该与之前的cookie不同,否则相同)00EE19E4  call        @__security_check_cookie@4 (0EDF1DBh)  ;fastcall 使用ecx传递参数(ecx存放了计算得出的cookie值)  00EE19E9  add         esp,0D4h  00EE19EF  cmp         ebp,esp  00EE19F1  call        __RTC_CheckEsp (0EDFEA1h)  00EE19F6  mov         esp,ebp  00EE19F8  pop         ebp  00EE19F9  ret  }
在进入函数时在ebp-4中保存cookie xor ebp

在退出函数时在ecx中保存[ebp-4] xor ebp


调用_security_check_cookie函数,代码如下:

    /* x86 version written in asm to preserve all regs */    __asm {        cmp ecx, __security_cookie00B4AFD0  cmp         ecx,dword ptr ds:[0BB3084h] ;比较ecx中的值是否与全局cookie相同        jne failure00B4AFD6  jne         failure (0B4AFDAh);不同则跳转到failure标签(_report_gsfailure函数)中        rep ret /* REP to avoid AMD branch prediction penalty */00B4AFD8  rep ret ;相同则直接返回,说明栈帧是没有被破坏的failure:        jmp __report_gsfailure00B4AFDA  jmp         ___report_gsfailure (0B3F645h)  

通过_security_check_cookie函数便能知道ebp是否发生变化了。这是栈帧被破坏的一个标志。如果ebp发生变化了,则会调用__report_gsfailure显示错误。

现在分别看看在/MD和/MT下CRT入口函数的区别:

1./MT

__tmainCRTStartup代码如下:(crt0.c中)

__declspec(noinline)int__tmainCRTStartup(         void         ){        int initret;        int mainret=0;        int managedapp;#ifdef _WINMAIN_        _TUCHAR *lpszCommandLine = NULL;        WORD showWindowMode = 0;#ifndef _KERNELX        showWindowMode = __crtGetShowWindowMode();        __set_app_type(_GUI_APP);#endif  /* _KERNELX */#else /* _WINMAIN_ */  #ifndef _KERNELX        __set_app_type(_CONSOLE_APP);   ;设置当前程序类型:1.console 2.gui#endif  /* _CRT_APP */#endif  /* _WINMAIN_ */        /*         * Determine if this is a managed application         */        managedapp = check_managed_app();  ;检查是否为托管程序        if ( !_heap_init() )                /* initialize heap */   ;初始化堆(这个在vs2012之前和之后版本不一样)            fast_error_exit(_RT_HEAPINIT);  /* write message and die */        if( !_mtinit() )                    /* initialize multi-thread */  ;初始化多线程环境            fast_error_exit(_RT_THREAD);    /* write message and die */        /* Enable buffer count checking if linking against static lib */        _CrtSetCheckCount(TRUE);        /*         * Initialize the Runtime Checks stuff         */#if defined (_RTC)        _RTC_Initialize();#endif  /* defined (_RTC) */        /*         * Guard the remainder of the initialization code and the call         * to user's main, or WinMain, function in a __try/__except         * statement.         */        __try {            if (_ioinit() < 0)                fast_error_exit(_RT_LOWIOINIT);  /* write message and die */#if !defined (_KERNELX)            /* get wide cmd line info */            _tcmdln = (_TSCHAR *)GetCommandLineT(); ;获取命令行参数            /* get wide environ info */            _tenvptr = (_TSCHAR *)GetEnvironmentStringsT();  ;获取环境变量            if ( _tsetargv() < 0 )                _amsg_exit(_RT_SPACEARG);            if ( _tsetenvp() < 0 )                _amsg_exit(_RT_SPACEENV);#endif  /* !defined (_CRT_APP) */            initret = _cinit(TRUE);                  /* do C data initialize */   ;执行全局数据和浮点寄存器的初始化            if (initret != 0)                _amsg_exit(initret);#ifdef _WINMAIN_#if !defined (_KERNELX)            lpszCommandLine = _twincmdln();#endif /* _KERNELX */            mainret = _tWinMain( (HINSTANCE)&__ImageBase,    ;调用gui的WinMain函数                                 NULL,                                 lpszCommandLine,                                 showWindowMode                                );#else   /* _WINMAIN_ */#if !defined (_KERNELX)            _tinitenv = _tenviron;            mainret = _tmain(__argc, _targv, _tenviron);  ;调用console的main函数#else  /* !defined (_KERNELX) */            mainret = _tmain(0, NULL, NULL);#endif  /* !defined (_KERNELX) */#endif  /* _WINMAIN_ */            if ( !managedapp )                exit(mainret);            _cexit();        }        __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )        {            /*             * Should never reach here             */            mainret = GetExceptionCode();            if ( !managedapp )                _exit(mainret);            _c_exit();        } /* end of try - except */        return mainret;}

大致的流程步骤如下:

1.设置应用程序类型,判断是否为托管程序

2.初始化堆

3.初始化多线程环境

4.初始化全局数据和浮点寄存器

5.获取命令行参数和环境变量

6.调用main/WinMain函数


分别深入源码;

1.设置应用程序类型,判断是否为托管程序

主要看判断是否为托管程序,跟进check_managed_app():

/****check_managed_app() - Check for a managed executable**Purpose:*       Determine if the EXE the startup code is linked into is a managed app*       by looking for the COM Runtime Descriptor in the Image Data Directory*       of the PE or PE+ header.**Entry:*       None**Exit:*       1 if managed app, 0 if not.**Exceptions:********************************************************************************/static int __cdecl check_managed_app (        void        ){        PIMAGE_DOS_HEADER pDOSHeader;        PIMAGE_NT_HEADERS pPEHeader;        pDOSHeader = &__ImageBase;        if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE)        {            return 0;        }        pPEHeader = (PIMAGE_NT_HEADERS) ((BYTE *) pDOSHeader + pDOSHeader->e_lfanew);        if (pPEHeader->Signature != IMAGE_NT_SIGNATURE)        {            return 0;        }        if (pPEHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)        {            return 0;        }        /* prefast assumes we are overflowing __ImageBase */#pragma warning(push)#pragma warning(disable:26000)        if (pPEHeader->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)        {            return 0;        }#pragma warning(pop)        return pPEHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress != 0;}

原理很简单,通过判断PE节表中的最后一项IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR,它保存了.net的一些高级结构IMAGE_COR20_HEADER.


2.初始化堆

_heap_init这个函数在vs2012之前的版本和之后的版本中实现是不一样的,在讨论这个前可以先推荐一个关于堆的文章(http://msdn.microsoft.com/en-us/library/ms810466.aspx).


先看vs2008版本的代码:

/****_heap_init() - Initialize the heap**Purpose:*       Setup the initial C library heap.**       NOTES:*       (1) This routine should only be called once!*       (2) This routine must be called before any other heap requests.**Entry:*       <void>*Exit:*       Returns 1 if successful, 0 otherwise.**Exceptions:*       If heap cannot be initialized, the program will be terminated*       with a fatal runtime error.********************************************************************************/int __cdecl _heap_init (        int mtflag        ){#if defined _M_AMD64 || defined _M_IA64        // HEAP_NO_SERIALIZE is incompatible with the LFH heap        mtflag = 1;#endif  /* defined _M_AMD64 || defined _M_IA64 */        //  Initialize the "big-block" heap first.        if ( (_crtheap = HeapCreate( mtflag ? 0 : HEAP_NO_SERIALIZE,                                     BYTES_PER_PAGE, 0 )) == NULL )            return 0;#ifndef _WIN64        // Pick a heap, any heap        __active_heap = __heap_select();        if ( __active_heap == __V6_HEAP )        {            //  Initialize the small-block heap            if (__sbh_heap_init(MAX_ALLOC_DATA_SIZE) == 0)            {                HeapDestroy(_crtheap);                _crtheap=NULL;                return 0;            }        }#ifdef CRTDLL        else if ( __active_heap == __V5_HEAP )        {            if ( __old_sbh_new_region() == NULL )            {                HeapDestroy( _crtheap );                _crtheap=NULL;                return 0;            }        }#endif  /* CRTDLL */#elif defined _M_AMD64 || defined _M_IA64        {            // Enable the Low Fragmentation Heap for AMD64 and IA64 by default            // It's the 8 byte overhead heap, and has generally better            // performance charateristics than the 16 byte overhead heap,            // particularly for apps that perform lots of small allocations            ULONG HeapType = 2;            HeapSetInformation(_crtheap, HeapCompatibilityInformation,                               &HeapType, sizeof(HeapType));        }#endif  /* defined _M_AMD64 || defined _M_IA64 */        return 1;}


0 0