security cookie

在微软的c/c++ 编译器中,增加了对于栈溢出进行检测的参数 “/GS”,在调试shellcode 的时候,发现vs2005 产生的code 和 vc6 产生的code 有些不同,才让我注意到这个问题。

     写了这样的一个测试程序:

     void foo(const char * datas)

     {

       char szbuf[32];

        strcpy(szbuf, datas);

     }

     汇编的代码如下:

00411F70  push        ebp 

00411F71  mov         ebp,esp

00411F73  sub         esp,0ECh

00411F79  push        ebx 

00411F7A  push        esi 

00411F7B  push        edi 

00411F7C  lea         edi,[ebp-0ECh]

00411F82  mov         ecx,3Bh

00411F87  mov         eax,0CCCCCCCCh ;0x0c is equal to the machine code of int3

00411F8C  rep stos    dword ptr es:[edi]

00411F8E  mov         eax,dword ptr [___security_cookie (419004h)]

00411F93  xor         eax,ebp

00411F95  mov         dword ptr [ebp-4],eax

00411F98  mov         eax,dword ptr [ebp+8]

00411F9B  push        eax 

00411F9C  lea         ecx,[ebp-28h]

00411F9F  push        ecx 

00411FA0  call        @ILT+625(_strcpy) (411276h)

00411FA5  add         esp,8 ; strcpy's call conversation is __cdecl, so the caller should help callee cleanup stack

00411FA8  push        edx 

00411FA9  mov         ecx,ebp ; pass the stack top by ecx

00411FAB  push        eax 

00411FAC  lea         edx,[ (411FD8h)] ; pointer to a _RTC_framedesc structure

00411FB2  call        @ILT+195(@_RTC_CheckStackVars@8) (4110C8h)

00411FB7  pop         eax 

00411FB8  pop         edx 

00411FB9  pop         edi 

00411FBA  pop         esi 

00411FBB  pop         ebx 

00411FBC  mov         ecx,dword ptr [ebp-4]

00411FBF  xor         ecx,ebp

00411FC1  call        @ILT+45(@__security_check_cookie@4) (411032h)

00411FC6  add         esp,0ECh

00411FCC  cmp         ebp,esp

00411FCE  call        @ILT+430(__RTC_CheckEsp) (4111B3h)

00411FD3  mov         esp,ebp

00411FD5  pop         ebp 

00411FD6  ret             

00411FD7  nop             

00411FD8  db          01h 

00411FD9  db          00h 

00411FDA  db          00h 

00411FDB  db          00h 

00411FDC  db          e0h 

00411FDD  db          1fh 

00411FDE  db          41h 

00411FDF  db          00h 

00411FE0  db          d8h 

00411FE1  db          ffh 

00411FE2  db          ffh 

00411FE3  db          ffh 

00411FE4  db          20h 

00411FE5  db          00h 

00411FE6  db          00h 

00411FE7  db          00h 

00411FE8  db          ech 

00411FE9  db          1fh 

00411FEA  db          41h 

00411FEB  db          00h 

00411FEC  db          62h 

00411FED  db          75h 

00411FEE  db          66h 

00411FEF  db          66h 

00411FF0  db          65h 

00411FF1  db          72h 

00411FF2  db          00h 

    从程序可以看出在函数被调用之后,在正确的把ebp压入stack 之后,把当前的esp存储到ebp 中,在往下就是这个函数体自身需要的stack 空间了,微软的编译器会在debug 版本的程序中,在每一个函数的堆栈顶分配一定数目的空间,把int3的机器码写入,这样做的目的是可以防止程序跑飞,如果程序跑飞,且eip落入这个空间,那么取出的指令都是int 3,从而能程序被中断。

    ___security_cookie 变量是一个 UINT_PTR 类型的数值,这个数值应该被编译到bufferoverflowU.lib 这个库中,数值的初始值定义如下:

#ifdef _WIN64

#define DEFAULT_SECURITY_COOKIE 0x00002B992DDFA232

#else  /* _WIN64 */

#define DEFAULT_SECURITY_COOKIE 0xBB40E64E

#endif  /* _WIN64 */

DECLSPEC_SELECTANY UINT_PTR __security_cookie = DEFAULT_SECURITY_COOKIE;

    函数在取到该值之后,会和当前的ebp 相异或(xor),异或的值保持在stack 的顶部。在函数的体被执行完成之后,__security_check_cookie 会被调用,来检测security cookie 是否被修改过,__security_check_cookie 函数需要一个参数,不过这个参数没有使用,函数中,只是把ecx 的值和 _security_cookie 的值相比较,看看是否相同,如果不相同则认定为stack overflow。从上面的汇编代码可以看出在调用_security_check_cookie 之前,就把_security_cookie 的值取到ecx 中,且与ebp 进行异或(xor)操作。这样通常能够很好的防范stack 的overflow,通常的exploit code 都要覆盖函数的返回地址,而覆盖过程中写入的stack 的值是基本不会等于(__security_cookie & ebp ) 的值。

    其实在调用 _security_check_cookie 函数之前,还调用了函数 _RTC_CheckStackVars,这个函数的原型是:

    void   __fastcall _RTC_CheckStackVars(void *_Esp, _RTC_framedesc *_Fd);

    其中_RTC_framedesc 结构为 :

    typedef struct _RTC_framedesc {

        int varCount;

        _RTC_vardesc *variables;

    } _RTC_framedesc;

    _RTC_vardesc 的结构为:RTC 可能是run time check 的缩写,而 vardesc 可能是 variable descritpion 的缩写

     typedef struct _RTC_vardesc {

        int addr;

        int size;

        char *name;

    } _RTC_vardesc;

  _RTC_CheckStackVars 的汇编代码如下:

004122D0  push        ebp 

004122D1  mov         ebp,esp

004122D3  push        ecx 

004122D4  push        ebx 

004122D5  push        esi 

004122D6  push        edi 

004122D7  xor         edi,edi

004122D9  mov         esi,edx

004122DB  cmp         dword ptr [esi],edi ;比较是否有数组变量

004122DD  mov         ebx,ecx             ; 需要比较的堆栈顶是通过ecx 传递进来的,把这个值保持到 ebx

004122DF  mov         dword ptr [i],edi  ; i 应该是记录当前比较了几个 _RTC_vardesc

004122E2  jle         _RTC_CheckStackVars+58h (412328h)  ;如果没有,则不必检测了,程序跳转退出

004122E4  mov         eax,dword ptr [esi+4]              ;eax 指向_RTC_vardesc 结构

004122E7  mov         ecx,dword ptr [eax+edi]            ;ecx 为 _RTC_vardesc.addr 的值

004122EA  add         eax,edi

004122EC  cmp         dword ptr [ecx+ebx-4],0CCCCCCCCh   ; 根据addr 算出堆栈中的一个地址,addr 的来历不详

004122F4  jne         _RTC_CheckStackVars+34h (412304h)  ; 如果不是初始化的 0xCCCCCCCC ; 报错

004122F6  mov         edx,dword ptr [eax+4]

004122F9  add         edx,ecx

004122FB  cmp         dword ptr [edx+ebx],0CCCCCCCCh

00412302  je          _RTC_CheckStackVars+48h (412318h)

00412304  mov         eax,dword ptr [esi+4]

00412307  mov         ecx,dword ptr [eax+edi+8]

0041230B  mov         edx,dword ptr [ebp+4]

0041230E  push        ecx 

0041230F  push        edx 

00412310  call        _RTC_StackFailure (411127h)

00412315  add         esp,8

00412318  mov         eax,dword ptr [i]

0041231B  add         eax,1

0041231E  add         edi,0Ch

00412321  cmp         eax,dword ptr [esi]

00412323  mov         dword ptr [i],eax

00412326  jl          _RTC_CheckStackVars+14h (4122E4h)

00412328  pop         edi 

00412329  pop         esi 

0041232A  pop         ebx 

0041232B  mov         esp,ebp

0041232D  pop         ebp 

0041232E  ret   

    

        从函数的汇编代码可以看出虽然 CheckStackVars 需要两个参数,但是函数并没有使用这两个参数,而实际需要检测的数值,是通过edx、ecx 两个寄存器传递的,edx 指向一个_RTC_framedesc 的结构,ecx 指向函数的堆栈的顶部,可见编译器在每一个函数的结束处添加了一个_RTC_framedesc 的对象。

       _RTC_framedesc 结构,好在只是记录函数中用到的数组变量的情况。上面的 _RTC_framedesc 值为:

       _RTC_framedesc.varCount = 1;

       _RTC_framedesc.variables = 0x00411fe0

       _RTC_vardesc.addr = 0xFFFFFFD8  /* -40 */

       _RTC_vardesc.size = 0x00000020 /* buffer size is : 0x20 */

       _RTC_vardesc.name = 0x00411fec /* poiner to string 'buffer' */

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/free2o/archive/2008/06/20/2570168.aspx

posted @ 2010-05-09 15:07  麒麟子MrKylin  阅读(832)  评论(0编辑  收藏  举报