写了这样的一个测试程序:
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' */