Windows下程序入口函数与_security_init_cookie

win32程序分为两种:

1.控制台(/SUBSYSTEM:CONSOLE )

2.GUI(/SUBSYSTEM:WINDOWS)


首先看控制台版本的:

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

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // EntryFunction.cpp : 定义控制台应用程序的入口点。  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5.   
  6.   
  7. int _tmain(int argc, _TCHAR* argv[])  
  8. {  
  9.     return 0;  
  10. }  

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,这个函数在两个实现文件中是一样的,代码如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /*** 
  2. *__security_init_cookie(cookie) - init buffer overrun security cookie. 
  3. * 
  4. *Purpose: 
  5. *       Initialize the global buffer overrun security cookie which is used by 
  6. *       the /GS compile switch to detect overwrites to local array variables 
  7. *       the potentially corrupt the return address.  This routine is called 
  8. *       at EXE/DLL startup. 
  9. * 
  10. *Entry: 
  11. * 
  12. *Exit: 
  13. * 
  14. *Exceptions: 
  15. * 
  16. *******************************************************************************/  
  17.   
  18. void __cdecl __security_init_cookie(void)  
  19. {  
  20.     UINT_PTR cookie;  
  21.     FT systime={0};  
  22.     LARGE_INTEGER perfctr;  
  23.   
  24.     /* 
  25.      * Do nothing if the global cookie has already been initialized.  On x86, 
  26.      * reinitialize the cookie if it has been previously initialized to a 
  27.      * value with the high word 0x0000.  Some versions of Windows will init 
  28.      * the cookie in the loader, but using an older mechanism which forced the 
  29.      * high word to zero. 
  30.      */  
  31.   
  32.     if (__security_cookie != DEFAULT_SECURITY_COOKIE  
  33. #if defined (_M_IX86)  
  34.         && (__security_cookie & 0xFFFF0000) != 0  
  35. #endif  /* defined (_M_IX86) */  
  36.        )  
  37.     {  
  38.         __security_cookie_complement = ~__security_cookie;  
  39.         return;  
  40.     }  
  41.   
  42.   
  43.     /* 
  44.      * Initialize the global cookie with an unpredictable value which is 
  45.      * different for each module in a process.  Combine a number of sources 
  46.      * of randomness. 
  47.      */  
  48.   
  49.     GetSystemTimeAsFileTime(&systime.ft_struct);  
  50. #if defined (_WIN64)  
  51.     cookie = systime.ft_scalar;  
  52. #else  /* defined (_WIN64) */  
  53.     cookie = systime.ft_struct.dwLowDateTime;  
  54.     cookie ^= systime.ft_struct.dwHighDateTime;  
  55. #endif  /* defined (_WIN64) */  
  56.   
  57.     cookie ^= GetCurrentThreadId();  
  58.     cookie ^= GetCurrentProcessId();  
  59.   
  60. #if _CRT_NTDDI_MIN >= NTDDI_VISTA   
  61. #if defined (_WIN64)  
  62.     cookie ^= (((UINT_PTR)GetTickCount64()) << 56);  
  63. #endif  /* defined (_WIN64) */  
  64.     cookie ^= (UINT_PTR)GetTickCount64();  
  65. #endif  /* _CRT_NTDDI_MIN >= NTDDI_VISTA  */  
  66.   
  67.     QueryPerformanceCounter(&perfctr);  
  68. #if defined (_WIN64)  
  69.     cookie ^= (((UINT_PTR)perfctr.LowPart << 32) ^ perfctr.QuadPart);  
  70. #else  /* defined (_WIN64) */  
  71.     cookie ^= perfctr.LowPart;  
  72.     cookie ^= perfctr.HighPart;  
  73. #endif  /* defined (_WIN64) */  
  74.   
  75.     /* 
  76.      * Increase entropy using ASLR relocation 
  77.      */  
  78.     cookie ^= (UINT_PTR)&cookie;  
  79.   
  80. #if defined (_WIN64)  
  81.     /* 
  82.      * On Win64, generate a cookie with the most significant word set to zero, 
  83.      * as a defense against buffer overruns involving null-terminated strings. 
  84.      * Don't do so on Win32, as it's more important to keep 32 bits of cookie. 
  85.      */  
  86.     cookie &= 0x0000FFFFffffFFFFi64;  
  87. #endif  /* defined (_WIN64) */  
  88.   
  89.     /* 
  90.      * Make sure the cookie is initialized to a value that will prevent us from 
  91.      * reinitializing it if this routine is ever called twice. 
  92.      */  
  93.   
  94.     if (cookie == DEFAULT_SECURITY_COOKIE)  
  95.     {  
  96.         cookie = DEFAULT_SECURITY_COOKIE + 1;  
  97.     }  
  98. #if defined (_M_IX86)  
  99.     else if ((cookie & 0xFFFF0000) == 0)  
  100.     {  
  101.         cookie |= ( (cookie|0x4711) << 16);  
  102.     }  
  103. #endif  /* defined (_M_IX86) */  
  104.   
  105.     __security_cookie = cookie;  
  106.     __security_cookie_complement = ~cookie;  
  107.   
  108. }  

 _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中):

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1.  * The global security cookie.  This name is known to the compiler.  
  2.  * Initialize to a garbage non-zero value just in case we have a buffer overrun  
  3.  * in any code that gets run before __security_init_cookie() has a chance to  
  4.  * initialize the cookie to the final value.  
  5.  */  
  6.   
  7. DECLSPEC_SELECTANY UINT_PTR __security_cookie = DEFAULT_SECURITY_COOKIE;  
  8.   
  9. DECLSPEC_SELECTANY UINT_PTR __security_cookie_complement = ~(DEFAULT_SECURITY_COOKIE);  

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

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


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

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // EntryFunction.cpp : Defines the entry point for the console application.  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5.   
  6. #include <string.h>  
  7.   
  8.   
  9. int _tmain(int argc, _TCHAR* argv[])  
  10. {  
  11.     char szBuffer[5];  
  12.   
  13.     strcpy( szBuffer, "hello world" );  
  14.     return 0;  
  15. }  

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

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. int _tmain(int argc, _TCHAR* argv[])  
  2. {  
  3. 00EE1990  push        ebp   
  4. 00EE1991  mov         ebp,esp    
  5. 00EE1993  sub         esp,0D4h    
  6. 00EE1999  push        ebx    
  7. 00EE199A  push        esi    
  8. 00EE199B  push        edi    
  9. 00EE199C  lea         edi,[ebp-0D4h]    
  10. 00EE19A2  mov         ecx,35h    
  11. 00EE19A7  mov         eax,0CCCCCCCCh    
  12. 00EE19AC  rep stos    dword ptr es:[edi]    
  13. 00EE19AE  mov         eax,dword ptr ds:[00F53084h]  ;将全局变量cookie保存到eax  
  14. 00EE19B3  xor         eax,ebp   ;将eax与ebp异或后保存到eax   
  15. 00EE19B5  mov         dword ptr [ebp-4],eax  ;将异或的结果保存到ebp-4  
  16.     char szBuffer[5];  
  17.   
  18.     strcpy( szBuffer, "hello world" );  
  19. 00EE19B8  push        0F3FD34h    
  20. 00EE19BD  lea         eax,[szBuffer]    
  21. 00EE19C0  push        eax    
  22. 00EE19C1  call        _strcpy (0EDF758h)    
  23. 00EE19C6  add         esp,8    
  24.     return 0;  
  25. 00EE19C9  xor         eax,eax    
  26. }  
  27. 00EE19CB  push        edx    
  28. 00EE19CC  mov         ecx,ebp    
  29. 00EE19CE  push        eax    
  30. 00EE19CF  lea         edx,ds:[0EE19FCh]    
  31. 00EE19D5  call        @_RTC_CheckStackVars@8 (0EDF5E6h)    
  32. 00EE19DA  pop         eax    
  33. 00EE19DB  pop         edx    
  34. 00EE19DC  pop         edi    
  35. 00EE19DD  pop         esi    
  36. 00EE19DE  pop         ebx    
  37. 00EE19DF  mov         ecx,dword ptr [ebp-4]  ;将ebp-4中的值(之前cookie与ebp异或后的值)保存到ecx  
  38. 00EE19E2  xor         ecx,ebp ;将ecx与ebp异或并保存到ecx中(如果ebp被破坏了,那么ecx中存放的应该与之前的cookie不同,否则相同)  
  39. 00EE19E4  call        @__security_check_cookie@4 (0EDF1DBh)  ;fastcall 使用ecx传递参数(ecx存放了计算得出的cookie值)    
  40. 00EE19E9  add         esp,0D4h    
  41. 00EE19EF  cmp         ebp,esp    
  42. 00EE19F1  call        __RTC_CheckEsp (0EDFEA1h)    
  43. 00EE19F6  mov         esp,ebp    
  44. 00EE19F8  pop         ebp    
  45. 00EE19F9  ret    
  46. }  
在进入函数时在ebp-4中保存cookie xor ebp

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


调用_security_check_cookie函数,代码如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1.     /* x86 version written in asm to preserve all regs */  
  2.     __asm {  
  3.         cmp ecx, __security_cookie  
  4. 00B4AFD0  cmp         ecx,dword ptr ds:[0BB3084h] ;比较ecx中的值是否与全局cookie相同  
  5.         jne failure  
  6. 00B4AFD6  jne         failure (0B4AFDAh);不同则跳转到failure标签(_report_gsfailure函数)中  
  7.         rep ret /* REP to avoid AMD branch prediction penalty */  
  8. 00B4AFD8  rep ret ;相同则直接返回,说明栈帧是没有被破坏的  
  9. failure:  
  10.         jmp __report_gsfailure  
  11. 00B4AFDA  jmp         ___report_gsfailure (0B3F645h)    

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

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

1./MT

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

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. __declspec(noinline)  
  2. int  
  3. __tmainCRTStartup(  
  4.          void  
  5.          )  
  6. {  
  7.         int initret;  
  8.         int mainret=0;  
  9.         int managedapp;  
  10. #ifdef _WINMAIN_  
  11.         _TUCHAR *lpszCommandLine = NULL;  
  12.         WORD showWindowMode = 0;  
  13.   
  14. #ifndef _KERNELX  
  15.         showWindowMode = __crtGetShowWindowMode();  
  16.         __set_app_type(_GUI_APP);  
  17. #endif  /* _KERNELX */  
  18.   
  19. #else /* _WINMAIN_ */  
  20.   
  21.   #ifndef _KERNELX  
  22.         __set_app_type(_CONSOLE_APP);   ;设置当前程序类型:1.console 2.gui  
  23. #endif  /* _CRT_APP */  
  24.   
  25. #endif  /* _WINMAIN_ */  
  26.   
  27.         /* 
  28.          * Determine if this is a managed application 
  29.          */  
  30.         managedapp = check_managed_app();  ;检查是否为托管程序  
  31.   
  32.         if ( !_heap_init() )                /* initialize heap */   ;初始化堆(这个在vs2012之前和之后版本不一样)  
  33.             fast_error_exit(_RT_HEAPINIT);  /* write message and die */  
  34.   
  35.         if( !_mtinit() )                    /* initialize multi-thread */  ;初始化多线程环境  
  36.             fast_error_exit(_RT_THREAD);    /* write message and die */  
  37.   
  38.         /* Enable buffer count checking if linking against static lib */  
  39.         _CrtSetCheckCount(TRUE);  
  40.   
  41.         /* 
  42.          * Initialize the Runtime Checks stuff 
  43.          */  
  44. #if defined (_RTC)  
  45.         _RTC_Initialize();  
  46. #endif  /* defined (_RTC) */  
  47.         /* 
  48.          * Guard the remainder of the initialization code and the call 
  49.          * to user's main, or WinMain, function in a __try/__except 
  50.          * statement. 
  51.          */  
  52.   
  53.         __try {  
  54.   
  55.             if (_ioinit() < 0)  
  56.                 fast_error_exit(_RT_LOWIOINIT);  /* write message and die */  
  57.   
  58. #if !defined (_KERNELX)  
  59.             /* get wide cmd line info */  
  60.             _tcmdln = (_TSCHAR *)GetCommandLineT(); ;获取命令行参数  
  61.   
  62.             /* get wide environ info */  
  63.             _tenvptr = (_TSCHAR *)GetEnvironmentStringsT();  ;获取环境变量  
  64.   
  65.             if ( _tsetargv() < 0 )  
  66.                 _amsg_exit(_RT_SPACEARG);  
  67.             if ( _tsetenvp() < 0 )  
  68.                 _amsg_exit(_RT_SPACEENV);  
  69. #endif  /* !defined (_CRT_APP) */  
  70.   
  71.             initret = _cinit(TRUE);                  /* do C data initialize */   ;执行全局数据和浮点寄存器的初始化  
  72.             if (initret != 0)  
  73.                 _amsg_exit(initret);  
  74.   
  75. #ifdef _WINMAIN_  
  76.   
  77. #if !defined (_KERNELX)  
  78.             lpszCommandLine = _twincmdln();  
  79. #endif /* _KERNELX */  
  80.   
  81.             mainret = _tWinMain( (HINSTANCE)&__ImageBase,    ;调用gui的WinMain函数  
  82.                                  NULL,  
  83.                                  lpszCommandLine,  
  84.                                  showWindowMode  
  85.                                 );  
  86. #else   /* _WINMAIN_ */  
  87. #if !defined (_KERNELX)  
  88.             _tinitenv = _tenviron;  
  89.             mainret = _tmain(__argc, _targv, _tenviron);  ;调用console的main函数  
  90. #else  /* !defined (_KERNELX) */  
  91.             mainret = _tmain(0, NULL, NULL);  
  92. #endif  /* !defined (_KERNELX) */  
  93. #endif  /* _WINMAIN_ */  
  94.   
  95.             if ( !managedapp )  
  96.                 exit(mainret);  
  97.   
  98.             _cexit();  
  99.   
  100.         }  
  101.         __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )  
  102.         {  
  103.             /* 
  104.              * Should never reach here 
  105.              */  
  106.   
  107.             mainret = GetExceptionCode();  
  108.   
  109.             if ( !managedapp )  
  110.                 _exit(mainret);  
  111.   
  112.             _c_exit();  
  113.   
  114.         } /* end of try - except */  
  115.   
  116.         return mainret;  
  117. }  

大致的流程步骤如下:

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

2.初始化堆

3.初始化多线程环境

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

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

6.调用main/WinMain函数


分别深入源码;

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

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

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /*** 
  2. *check_managed_app() - Check for a managed executable 
  3. * 
  4. *Purpose: 
  5. *       Determine if the EXE the startup code is linked into is a managed app 
  6. *       by looking for the COM Runtime Descriptor in the Image Data Directory 
  7. *       of the PE or PE+ header. 
  8. * 
  9. *Entry: 
  10. *       None 
  11. * 
  12. *Exit: 
  13. *       1 if managed app, 0 if not. 
  14. * 
  15. *Exceptions: 
  16. * 
  17. *******************************************************************************/  
  18.   
  19. static int __cdecl check_managed_app (  
  20.         void  
  21.         )  
  22. {  
  23.         PIMAGE_DOS_HEADER pDOSHeader;  
  24.         PIMAGE_NT_HEADERS pPEHeader;  
  25.   
  26.         pDOSHeader = &__ImageBase;  
  27.   
  28.         if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE)  
  29.         {  
  30.             return 0;  
  31.         }  
  32.   
  33.         pPEHeader = (PIMAGE_NT_HEADERS) ((BYTE *) pDOSHeader + pDOSHeader->e_lfanew);  
  34.   
  35.         if (pPEHeader->Signature != IMAGE_NT_SIGNATURE)  
  36.         {  
  37.             return 0;  
  38.         }  
  39.   
  40.         if (pPEHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)  
  41.         {  
  42.             return 0;  
  43.         }  
  44.   
  45.         /* prefast assumes we are overflowing __ImageBase */  
  46. #pragma warning(push)  
  47. #pragma warning(disable:26000)  
  48.         if (pPEHeader->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)  
  49.         {  
  50.             return 0;  
  51.         }  
  52. #pragma warning(pop)  
  53.   
  54.         return pPEHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress != 0;  
  55. }  

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


2.初始化堆

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


先看vs2008版本的代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /*** 
  2. *_heap_init() - Initialize the heap 
  3. * 
  4. *Purpose: 
  5. *       Setup the initial C library heap. 
  6. * 
  7. *       NOTES: 
  8. *       (1) This routine should only be called once! 
  9. *       (2) This routine must be called before any other heap requests. 
  10. * 
  11. *Entry: 
  12. *       <void> 
  13. *Exit: 
  14. *       Returns 1 if successful, 0 otherwise. 
  15. * 
  16. *Exceptions: 
  17. *       If heap cannot be initialized, the program will be terminated 
  18. *       with a fatal runtime error. 
  19. * 
  20. *******************************************************************************/  
  21.   
  22. int __cdecl _heap_init (  
  23.         int mtflag  
  24.         )  
  25. {  
  26. #if defined _M_AMD64 || defined _M_IA64  
  27.         // HEAP_NO_SERIALIZE is incompatible with the LFH heap  
  28.         mtflag = 1;  
  29. #endif  /* defined _M_AMD64 || defined _M_IA64 */  
  30.         //  Initialize the "big-block" heap first.  
  31.         if ( (_crtheap = HeapCreate( mtflag ? 0 : HEAP_NO_SERIALIZE,  
  32.                                      BYTES_PER_PAGE, 0 )) == NULL )  
  33.             return 0;  
  34.   
  35. #ifndef _WIN64  
  36.         // Pick a heap, any heap  
  37.         __active_heap = __heap_select();  
  38.   
  39.         if ( __active_heap == __V6_HEAP )  
  40.         {  
  41.             //  Initialize the small-block heap  
  42.             if (__sbh_heap_init(MAX_ALLOC_DATA_SIZE) == 0)  
  43.             {  
  44.                 HeapDestroy(_crtheap);  
  45.                 _crtheap=NULL;  
  46.                 return 0;  
  47.             }  
  48.         }  
  49. #ifdef CRTDLL  
  50.         else if ( __active_heap == __V5_HEAP )  
  51.         {  
  52.             if ( __old_sbh_new_region() == NULL )  
  53.             {  
  54.                 HeapDestroy( _crtheap );  
  55.                 _crtheap=NULL;  
  56.                 return 0;  
  57.             }  
  58.         }  
  59. #endif  /* CRTDLL */  
  60. #elif defined _M_AMD64 || defined _M_IA64  
  61.         {  
  62.             // Enable the Low Fragmentation Heap for AMD64 and IA64 by default  
  63.             // It's the 8 byte overhead heap, and has generally better  
  64.             // performance charateristics than the 16 byte overhead heap,  
  65.             // particularly for apps that perform lots of small allocations  
  66.             ULONG HeapType = 2;  
  67.             HeapSetInformation(_crtheap, HeapCompatibilityInformation,  
  68.                                &HeapType, sizeof(HeapType));  
  69.         }  
  70. #endif  /* defined _M_AMD64 || defined _M_IA64 */  
  71.   
  72.         return 1;  
  73. }  
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值