您的位置:首页 > 编程语言 > C语言/C++

c/c++基于cookie的数组越界检查

2014-02-12 15:54 387 查看

昨天在试着逆向一个有时间期限的LIB时,发现一些特别的检查函数,在之前的VC2003中是没有的,这些函数可谓是重量级函数。由于个人比较看不惯自己不懂的东西,出于不愤之情绪研究了下这些函数。首先在这里介绍个人认为较之其他几个更为重要的一种安全检查方式——基于Cookie的缓冲区溢出安全检查!

为了在发布版本中也能检测到缓冲区溢出,防止程序因缓冲区而受到攻击,VS2005(VC8)便增加了基于Cookie的安全检查。

在计算机领域,Cookie一词最早出现在网站开发中,是指网站的服务程序通过浏览器保存到客户端的少量数据,这些数据都是二进制的,或者是经过加密的,通常用来记录用户身份和登录情况等信息。后来这个词被泛指一方签发给另一方的认证或者标志信息。

在之前的博文中,都提到过堆栈调用及EBP,RET返回地址,还有局部变量在栈帧中存放的各种微妙关系。相信大家也有清晰的了解,如果不清晰呢,可以参见Shell Code,HOOK API这两篇。嘿嘿!

VC8编译器在编译可能发生缓冲区溢出的函数时,会定义一个很特别局部变量,如果不加分析的话,这个局部变量还真不知道代表什么意思。而且它的值又是怎么算出来的?,它有什么作用? 可能有的朋友不是没有注意到这个细节就是觉得没有必要追究它。不过个人认为熟悉了堆栈调用里面的原理会对我们的调试能力和差错能力有很明显的提高。好了,转入正题。编译器增加的这个局部变量时紧挨着EBP的存放地址的。顺序就是:VAR COOKIE EBP RET ARGS。这个Cookie的变量位于函数体内的局部变量和EBP的存放地址之间,具体表示就是:[EBP-4]。这个专门用于保存Cookie的变量被称为Cookie变量。是一个32位的无符号整数。它的值是从全局变量__security_cookie得到的。我们可以看看这个全局变量的定义:

#ifdef _WIN64

#define DEFAULT_SECURITY_COOKIE 0x00002B992DDFA232

#else

#define DEFAULT_SECURITY_COOKIE
0xBB40E64E

#endif

DECLSPEC_SELECTANY UINT_PTR __security_cookie =
DEFAULT_SECURITY_COOKIE;

DECLSPEC_SELECTANY UINT_PTR __security_cookie_complement = ~(DEFAULT_SECURITY_COOKIE);

由于我测试的机子是32位的,这里只看32位的。有朋友是64位的可以测试下。这里个定义位于gs_cookie.c文件中。文件位于编译器目录的VC/crt/src下。这里可以看到,最开始的时候这个全局变量被初始化成了0xBB40E64E。为什么是这个值,这里不是讨论的重点,还有待研究!嘿嘿。

上面说到最开始的时候被初始化为了0xBB40E64E。言外之意后面还会对它进行处理?答案是肯定的!下一次初始化是在我们程序进入主函数之前的:__tmainCRTStartup函数里。

抛开预处理和机器的条件判断,这个函数的原型如下:

int mainCRTStartup( void )

{

/*

* The /GS security cookie must be initialized before any exception

* handling targetting the current image is registered. No function

* using exception handling can be called in the current image until

* after __security_init_cookie has been called.

*/

__security_init_cookie();

return __tmainCRTStartup();

}

我的机子抛开后是调用的这个函数。现在的硬件环境大多数也是这个。这里可以看到在这里会调用__security_init_cookie()这个函数对__security_cookie变量再次初始化。这个函数也是能看到原型的,它位于gs_support.c文件中。进去看看,抛开一编译时的判断条件其原型主体部分为:

void __cdecl __security_init_cookie()

{

UINT_PTR cookie;

FT systime={0};

LARGE_INTEGER perfctr;

GetSystemTimeAsFileTime(&systime.ft_struct);

cookie = systime.ft_struct.dwLowDateTime;

cookie ^= systime.ft_struct.dwHighDateTime;

cookie ^= GetCurrentProcessId();

cookie ^= GetCurrentThreadId();

cookie ^= GetTickCount();

QueryPerformanceCounter(&perfctr);

cookie ^= perfctr.LowPart;

cookie ^= perfctr.HighPart;

__security_cookie = cookie;

__security_cookie_complement = ~cookie;

}

这里可以看出来,为了取得好的随机性,先是取出时间,异或之,然后是分别跟其他一些列具有随机性的数据(进程ID,线程ID,TickCount和性能计数器)进行异或运算。这个变量因为是全局的,在这里( mainCRTStartup启动函数)初始化后在进程过程中将不会再改变。如果想要查看这个cookie变量的值。可以再调试的时候拉出“即时窗口(Immediate)”,在里面输入__security_cookie回车就能看到了。

好了,在上面介绍完了Cookie变量的产生、初始化和作用后,下面来看看使用。

写个最简单的测试:

int main( void )

{

char a[ 20 ];

strcpy( a, "masefee" );

return 0;

}

上面说过,编译器会在可能发生缓冲区溢出的函数插入Cookie变量和安全检查。这样一个小例子足以让它检查了。他已经发现可能存在危险了。是不是很智能? - -

要看这个函数一开始怎么写入Cookie变量的,可以打断点在红色的括号处或者F11单步。这里又得在反汇编里面进行了,这里不厌其烦的从汇编里面去看问题,包括以前的文章基本跟汇编有联系。这里不是别的,只是个人认为还是很有必要从汇编的角度去了解高级语言的原理。很多是很必要的。当然这里也只能从汇编去分析这个Cookie变量的写入过程及检查过程!忍耐一下! - -

就这里这个简单的例子,DEBUG模式下反汇编如下:

0043BEF0 push ebp

0043BEF1 mov ebp,esp

0043BEF3 sub esp,0E0h

0043BEF9 push ebx

0043BEFA push esi

0043BEFB push edi

0043BEFC lea edi,[ebp-0E0h]

0043BF02 mov ecx,38h

0043BF07 mov eax,0CCCCCCCCh

0043BF0C rep stos dword ptr es:[edi]

0043BF0E mov eax,dword ptr [___security_cookie (4B7A74h)]

0043BF13 xor eax,ebp

0043BF15 mov dword ptr [ebp-4],eax

0043BF18 push offset string "masefee" (4AA938h)

0043BF1D lea eax,[ebp-1Ch]

0043BF20 push eax

0043BF21 call @ILT+3880(_strcpy) (437F2Dh)

0043BF26 add esp,8

0043BF29 xor eax,eax

0043BF2B push edx

0043BF2C mov ecx,ebp

0043BF2E push eax

0043BF2F lea edx,[ (43BF5Ch)]

0043BF35 call @ILT+3115(@_RTC_CheckStackVars@8) (437C30h)

0043BF3A pop eax

0043BF3B pop edx

0043BF3C pop edi

0043BF3D pop esi

0043BF3E pop ebx

0043BF3F mov ecx,dword ptr [ebp-4]

0043BF42 xor ecx,ebp

0043BF44 call @ILT+920(@__security_check_cookie@4) (43739Dh)

0043BF49 add esp,0E0h

0043BF4F cmp ebp,esp

0043BF51 call @ILT+7205(__RTC_CheckEsp) (438C2Ah)

0043BF56 mov esp,ebp

0043BF58 pop ebp

0043BF59 ret

这就是main函数的所有反汇编代码。首先看红色的一句指令。是将___security_cookie变量的值给取出来。然后蓝色的指令就是将取出来的Cookie全局变量与当前EBP的值进行异或运算。与EBP异或当然有好处。

1. 可以增加随机性,尽可能使不同函数的安全Cookie都不同。

2. 可以检查EBP是否被破坏,因为在函数结束检查Cookie时,还会将Cookie变量值再次与EBP异或,如果EBP的值没有变化,那么就能恢 复成原来的___security_cookie值。

这些细节地方不得不佩服微软的设计师们的缜密和扩展的思维!

上面绿色的指令便是关键的一步,它是将异或后的值存入[ebp-4]中。当前的EBP就是最开始压入EBP后ESP的值,也就是压入的EBP的地址。减4就刚好挨着一进函数时压入的EBP的地址减4。好了!Cookie变量已经在栈帧中了。

下面一步就看最后的检查部分,粉色的部分前两句指令是将Cookie变量的值重新取出来并异或还原并保存到ECX中。保存到ECX是有目的的。之后再讨论。第三句粉色的CALL就是调用__security_check_cookie 函数了。其原型非常简单:

void __declspec(naked) __fastcall __security_check_cookie(UINT_PTR cookie)

{

/* x86 version written in asm to preserve all regs */

__asm

{

cmp ecx, __security_cookie

jne failure

rep ret /* REP to avoid AMD branch prediction penalty */

failure:

jmp __report_gsfailure

}

}

这个函数位于编译器目录下的VC/crt/src/intel/secchk.c中。为了降低对可执行文件大小和运行性能的影响,这个函数直接用汇编写的。而且只有4条指令,不存在任何变量和寄存器(标志寄存器除外)的改变。因为是使用的快速调用协定。因为唯一的一个参数都是存放在ECX中,直接进行CMP比较的。红色的部分就是与全局的Cookie变量进行比较。如果相同就正常,如果不同就jmp failure报错。跳转到__report_gsfailure函数。这样既检查了EBP是否合法,又检查了Cookie变量的合法性。

好了,基本上写完了。上面留了两个点,一是mian函数里面我有标志了一句橙色的语句,这个也是一个检查。将在后面的博文中提到。二是__report_gsfailure函数的整个过程。也将在后面的博文中深入阐述。上面有什么不对的地方还望大家批评。我很希望得到大家的指点!

思考:

1. 这种模式的安全检查能够移植到我们平常的项目中的哪些地方,在我们用户代码上显示进行检查?

2. 这种产生尽可能随机的方式用于类似于生成对象的全局唯一ID或者更多的需要唯一性的数据上?

在VS2008下,函数的栈空间里如果存在数组,就会自动加上CheckStackVars检查,顾名思义,就是用来检查局部数据是否访问越界。相对来说,这种检查只能起到一定的作用,并不会所有越界访问都能检查到,根据后面的原理介绍会了解到这点。既然是检查局部的,那么在函数内定义的static类型数组或者函数外部的全局数组并不会采用此检查,既然是检查数组,那么如果函数内没有局部数组时,此检查也不会存在。

首先来看一个简单的例子,验证这个检查的存在:

void TestVars( void )

{

int bf = 0xeeeeeeee;

char array[10] = { 0 };

int bk = 0xffffffff;

strcpy( array, "masefee" );

}

int main( void )

{

TestVars();

return 0;

}

在这个例子中,存在一个数组array,这里刻意定义了另外两个变量,用于看这两个变量与数组array的内存分布情况。这样就能清晰的了解到CheckStackVars这个检查的原理。然后来看看Debug下,TestVars函数内部的3个局部变量的内存分布情况。断点打在strcpy这句上,分布如下:

ff ff ff ff
cc cc cc cccc cc cc cc
00 00 00 00 00 00 00 00 00 00cc cc cc cc cc cc
cc cc cc cc
ee ee ee ee

bk array bf

上面的关系已经很明确了,我们发现,在C++的代码中看,bf、array、bk三者在内存分布上应该是连续的,紧挨着的。但是这里并不是这样的,看看bf和array之间居然像个10个字节之远。原因在于,在VS2008的debug版本下,局部变量之间并不是连续存放在栈内存里的,而是以4字节对齐的方式,前后都会有保护字节的。这里的保护字节占4个字节,值为0xcc,很明显这是汇编指令int 3中断的代码字节。因此这里bk和bf变量前后都会有4个字节的0xcc。上面绿色的部分就是,在数组array两端也有4字节的0xcc。上面黑色加粗的部分即是,array数组一共占10字节,要以4字节对齐,所以要补两字节,因此多了两个0xcc,因此导致bf和array之间相隔10字节。上面array后面紧挨着的本应该是两个0xcc,用于补充对齐。这里故意标识到后面去了。这里这样标识的意图是为了说明CheckStackVars这个检查的原理。

好了,清楚了内存分布情况,那么CheckStackVars在什么时间执行检查的呢,在C++代码上并不能显示的看到,于是来翻翻TestVars函数的反汇编代码:

TestVars:

004113B0 push ebp

004113B1 mov ebp,esp

004113B3 sub esp,0F0h

004113B9 push ebx

004113BA push esi

004113BB push edi

004113BC lea edi,[ebp-0F0h]

004113C2 mov ecx,3Ch

004113C7 mov eax,0CCCCCCCCh

004113CC rep stos dword ptr es:[edi]

004113CE mov eax,dword ptr [___security_cookie (417004h)]

004113D3 xor eax,ebp

004113D5 mov dword ptr [ebp-4],eax

004113D8 mov dword ptr [ebp-0Ch],0EEEEEEEEh

004113DF mov byte ptr [ebp-20h],0

004113E3 xor eax,eax

004113E5 mov dword ptr [ebp-1Fh],eax

004113E8 mov dword ptr [ebp-1Bh],eax

004113EB mov byte ptr [ebp-17h],al

004113EE mov dword ptr [ebp-2Ch],0FFFFFFFFh

004113F5 push offset string "masefee" (415804h)

004113FA lea eax,[ebp-20h]

004113FD push eax

004113FE call @ILT+160(_strcpy) (4110A5h)

00411403 add esp,8

00411406 push edx

00411407 mov ecx,ebp

00411409 push eax

0041140A lea edx,[ (411438h)]

00411410 call @ILT+130(@_RTC_CheckStackVars@8) (411087h)

00411415 pop eax

00411416 pop edx

00411417 pop edi

00411418 pop esi

00411419 pop ebx

0041141A mov ecx,dword ptr [ebp-4]

0041141D xor ecx,ebp

0041141F call @ILT+25(@__security_check_cookie@4) (41101Eh)

00411424 add esp,0F0h

0041142A cmp ebp,esp

0041142C call @ILT+320(__RTC_CheckEsp) (411145h)

00411431 mov esp,ebp

00411433 pop ebp

00411434 ret

00411435 lea ecx,[ecx]

00411438 db 01h

00411439 db 00h

0041143A db 00h

0041143B db 00h

0041143C db 40h

0041143D db 14h

0041143E db 41h

0041143F db 00h

00411440 db e0h

00411441 db ffh

00411442 db ffh

00411443 db ffh

00411444 db 0ah

00411445 db 00h

00411446 db 00h

00411447 db 00h

00411448 db 4ch

00411449 db 14h

0041144A db 41h

0041144B db 00h

0041144C db 61h

0041144D db 72h

0041144E db 72h

0041144F db 61h

00411450 db 79h

00411451 db 00h

从TestVars的反汇编代码可以清楚的看到,黑色加粗的部分就是前一篇博文介绍的,在本篇注意看在strcpy调用之后,又调用了_RTC_CheckStackVars函数,这是一个什么样的函数?先来看看他的原型:

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

这是一个fastcall函数,因此两个参数都是通过寄存器进行传递的。第二个参数是一个结构体类型,再来看看这个结构体的定义:

typedef struct _RTC_framedesc

{

int varCount; // 要检查的数组的个数

_RTC_vardesc *variables; // 要检查的数组的相关信息

} _RTC_framedesc;

这个结构体定义在rtcapi.h头文件中的,_RTC_vardesc 也是一个结构体类型,看看定义:

typedef struct _RTC_vardesc

{

int addr; // 数组的首地址相对于EBP的偏移量

int size; // 数组的大小字节数

char *name; // 数组的名字

} _RTC_vardesc;

以上面的例子来填充这个结构体之后,结构体的数据就是:

_RTC_framedesc.varCount = 1;

_RTC_vardesc->addr = array - EBP; // 这里array在低地址,所以addr最终为负

_RTC_vardesc->size = 10;

_RTC_vardesc->name = "array";

好了,这下清楚了信息的存储,再回到上面的反汇编代码,在调用_RTC_CheckStackVars函数之前,注意红色粗体的一句指令,将ebp赋值给了ecx寄存器,再将411438h这个地址值赋值给了edx,由于_RTC_CheckStackVars函数是fastcall,因此通过这两个寄存器进行传递参数,而不是push操作。ecx就是保存的TestVars函数的栈帧,edx这个地址有点奇怪,本来是应该传递_RTC_framedesc结构指针的,难道这个411438h地址值就是_RTC_framedesc结构体变量所在的内存地址?从上面的反汇编代码可以看到,下面从411438h地址开始,多了一段奇怪的数据,本应该函数下面不会有这么一段数据的,在Debug下大多数情况都是0xcc填充的。咱们仔细观察下这段数据,或者直接将411438h这个地址值copy到内存窗口里看:

0x00411438
01 00 00 00 40 14 41 00
e0 ff ff ff 0a 00 00 00 4c 14 41 00
61 72 72 61 79 00

看看上面的数据,是不是就是_RTC_framedesc结构应该有的数据?答案是肯定的,红色的部分就是_RTC_framedesc.variables指针的值,指向的位置就是紧跟其后,这是编译器故意这么处理的。当然可以是其它地方。这是编译器直接把这些信息记录在代码段的,并且紧跟在所记录的函数代码之后。因此不要误认为这些信息是在程序执行期间才写进去或填充的_RTC_framedesc结构。

了解到这里,发现整个规则都是有理有据的,并且设计都是很良好的。也能又一次感受MS的伟大。呵呵,废话了!

上面既然将两个参数都给了_RTC_CheckStackVars函数,再来看看此函数内部是怎么检测的,看看此函数的反汇编:

_RTC_CheckStackVars:

00411500 mov edi,edi

00411502 push ebp

00411503 mov ebp,esp

00411505 push ecx

00411506 push ebx

00411507 push esi

00411508 push edi

00411509 xor edi,edi // 清零

0041150B mov esi,edx
// 将_RTC_framedesc结构指针赋值给esi

0041150D cmp dword ptr [esi],edi // 比较varCount是否为0,if( _Fd->varCount != 0 )

0041150F mov ebx,ecx // 将TestVars的栈帧赋值给ebx

00411511 mov dword ptr [i],edi // 这里的i应该是循环变量,将数组的个数赋值给i,i = _Fd->varCount ;

00411514 jle _RTC_CheckStackVars+58h (411558h)

00411516 mov eax,dword ptr [esi+4] // +4之后就是_RTC_framedesc.variables指针

00411519 mov ecx,dword ptr [eax+edi] // _RTC_vardesc->addr了,就是数组的首地址相对于TestVars的EBP的偏移量

0041151C add eax,edi // 将eax定位到_RTC_vardesc结构首地址

0041151E cmp dword ptr [ecx+ebx-4],0CCCCCCCCh // [ecx+ebx-4]等价于ebp-addr-4,也就是array的前面4个保护字节

00411526 jne _RTC_CheckStackVars+36h (411536h) // 如果不等于0xcccccccc就报错_RTC_StackFailure

00411528 mov edx,dword ptr [eax+4] // eax+4就是_RTC_vardesc->size,表示数组的大小

0041152B add edx,ecx // ecx当前是偏移量,加上size后就是array数组尾部相对于ebp的偏移量

0041152D cmp dword ptr [edx+ebx],0CCCCCCCCh // edx+ebx即是数组array尾部的后4个保护字节,然后比较

00411534 je _RTC_CheckStackVars+4Ah (41154Ah)

00411536 mov eax,dword ptr [esi+4] // esi+4为_RTC_framedesc.variables指针

00411539 mov ecx,dword ptr [eax+edi+8] // eax+edi+8即是_RTC_vardesc->name,用于报错提示

0041153D mov edx,dword ptr [ebp+4]

00411540 push ecx // 传入越界的数组名

00411541 push edx // 传入EBP+4的地址,此地址正是_RTC_CheckStackVars的返回地址,用于定位

00411542 call _RTC_StackFailure (4110CDh) // 调用此函数后,弹出异常MessageBox,提示哪个数组越界

00411547 add esp,8

0041154A mov eax,dword ptr [i] // 存在多个数组需要检查时有用

0041154D inc eax

0041154E add edi,0Ch // 定位到下一个_RTC_vardesc结构

00411551 cmp eax,dword ptr [esi]

00411553 mov dword ptr [i],eax

00411556 jl _RTC_CheckStackVars+16h (411516h) // 循环

00411558 pop edi

00411559 pop esi

0041155A pop ebx

0041155B mov esp,ebp

0041155D pop ebp

0041155E ret

以上过程稍微解析得有点复杂,其主要原理就是读取_RTC_vardesc结构,挨个对每个数组进行前后边界检查,如果发生更改,则调用_RTC_StackFailure函数,最后弹出错误信息框,信息如:

Run-Time Check Failure #2 - Stack around the variable 'array' was corrupted.

这里需要说明一点,如果存在多个数组需要检查时,每个数组的name是紧挨着的,同时紧接着跟在多个_RTC_vardesc结构之后,内存分布如下:

[数组个数, _RTC_vardesc地址] [ 多个_RTC_vardesc结构(数组)][ 每个数组的name]

这些位置分布都是编译器直接写在代码里的。

这样就能实现简单的边界检查了,前面提到了,这种检查只是会检查前后边界,如果在程序中越界访问,但是没有修改或者写的值就是边界检查的值0xcccccccc,那也不会检测出代码已经有越界隐患。因此最主要的还是要小心谨慎。编译器总不能为我们做所有的事情。以上过程会在栈内存里加上边界检查值,所以在Debug版本下是比较实用的。在Release下不会这么浪费空间,因此越界就显得更加危险了。

从上面的分析过程来看,可以写出_RTC_CheckStackVars函数的伪代码,如下:

typedef
struct _RTC_vardesc

{
int addr;

int size;

char* name;

} _RTC_vardesc;

typedef
struct _RTC_framedesc

{
int varCount;

_RTC_vardesc* variables;
} _RTC_framedesc;

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

{
if ( _Fd->varCount == 0 )

return;

int _RetAddr = 0;

__asm
{
mov eax, ebp
add eax, 4
mov _RetAddr, eax // 保存返回地址

}

int i = 0;

while ( i < _Fd->varCount )

{
char* pAddr = (
char* )_Esp + _Fd->variables[i].addr - 4;

if ( *(
int* )pAddr != 0xcccccccc )

__asm int 3
// _RTC_StackFailure( _RetAddr, _Fd->variables[i].name );

int ofs = _Fd->variables[i].addr + _Fd->variables[i].size;

pAddr = ( char* )_Esp + ofs;

if ( *(
int* )pAddr != 0xcccccccc )

__asm int 3
// _RTC_StackFailure( _RetAddr, _Fd->variables[i].name );

++i;
}
}

typedef struct _RTC_vardesc { int addr; int size; char* name; } _RTC_vardesc; typedef struct _RTC_framedesc { int varCount; _RTC_vardesc* variables; } _RTC_framedesc; void __fastcall _RTC_CheckStackVars( void* _Esp, _RTC_framedesc* _Fd ) { if ( _Fd->varCount
== 0 ) return; int _RetAddr = 0; __asm { mov eax, ebp add eax, 4 mov _RetAddr, eax // 保存返回地址 } int i = 0; while ( i < _Fd->varCount ) { char* pAddr = ( char* )_Esp + _Fd->variables[i].addr - 4; if ( *( int* )pAddr != 0xcccccccc ) __asm int 3 // _RTC_StackFailure(
_RetAddr, _Fd->variables[i].name ); int ofs = _Fd->variables[i].addr + _Fd->variables[i].size; pAddr = ( char* )_Esp + ofs; if ( *( int* )pAddr != 0xcccccccc ) __asm int 3 // _RTC_StackFailure( _RetAddr, _Fd->variables[i].name ); ++i; } }

这段代码可以直接通过编译,并起到相应的检查功能,上面检查失败我这里暂时使用的__asm int 3进行中断,后面的注释是真正的_RTC_CheckStackVars函数调用的错误函数,_RTC_StackFailure用于弹出错误信息和定位调试器的光标到这个返回地址。

以下代码是用于测试这段伪代码的功能:

void TestVars(
void )

{
int _EBP = 0;

__asm mov _EBP, ebp

int b = 0xeeeeeeee;

char array1[10] = { 0 };

char array2[10] = { 0 };

int c = 0xffffffff;

//array1[ 10 ] = 0;

//array2[ 10 ] = 0;

strcpy( array, "masefee" );

char* name[ 2 ] = {
"array1", "array2" };
// 存放名字

_RTC_vardesc vdesc[2];
vdesc[0].addr = ( int )array1 - _EBP;
// 求得与EBP之间的偏移, 注意:array1处于低地址, addr的值为负

vdesc[0].name = name[0]; // 名字的地址

vdesc[0].size = 10; // 数组大小

vdesc[1].addr = ( int )array2 - _EBP;

vdesc[1].name = name[1];
vdesc[1].size = 10;

_RTC_framedesc fdesc;
fdesc.varCount = 2; // 数组个数

fdesc.variables = vdesc; // 各个数组的检测信息

_RTC_CheckStackVars( ( void* )_EBP, &fdesc );

}

int main(
void )

{
TestVars();
return 0;

}

void TestVars( void ) { int _EBP = 0; __asm mov _EBP, ebp int b = 0xeeeeeeee; char array1[10] = { 0 }; char array2[10] = { 0 }; int c = 0xffffffff; //array1[ 10 ] = 0; //array2[ 10 ] = 0; strcpy( array, "masefee" ); char* name[ 2 ] = { "array1", "array2"
}; // 存放名字 _RTC_vardesc vdesc[2]; vdesc[0].addr = ( int )array1 - _EBP; // 求得与EBP之间的偏移, 注意:array1处于低地址, addr的值为负 vdesc[0].name = name[0]; // 名字的地址 vdesc[0].size = 10; // 数组大小 vdesc[1].addr = ( int )array2 - _EBP; vdesc[1].name = name[1]; vdesc[1].size = 10;
_RTC_framedesc fdesc; fdesc.varCount = 2; // 数组个数 fdesc.variables = vdesc; // 各个数组的检测信息 _RTC_CheckStackVars( ( void* )_EBP, &fdesc ); } int main( void ) { TestVars(); return 0; }

上面的代码是合法的,调用了检查函数之后没有任何的越界访问,如果要测试失败的情况,则将:

//array1[ 10 ] = 0;

//array2[ 10 ] = 0;

这两句的注释取消,就会在第二个__asm int 3出中断。

以上就是CheckStackVars的所有原理,基于这种检查机制还能发散出很多的东西,并且也可以自己实现一套规则,在一些关键的代码处设置这道检测关卡,也是非常有用的。本文到此结束,希望大家多提意见,欢迎拍砖!

我自己注释的内容

.text:0042DEE0 ; Attributes: bp-based frame

.text:0042DEE0

.text:0042DEE0 _RTC_CheckStackVars proc near ; CODE XREF: j__RTC_CheckStackVarsj

.text:0042DEE0

.text:0042DEE0 var_4 = dword ptr -4

.text:0042DEE0

.text:0042DEE0 mov edi, edi ; _RTCINTERNAL_DEPRECATED void __fastcall _RTC_CheckStackVars(void *_Esp, _RTC_framedesc *_Fd);

.text:0042DEE0 ; 参数: Ecx= ebp edx=_RTC_framedesc

.text:0042DEE0 ;

.text:0042DEE0 ; typedef struct _RTC_vardesc {

.text:0042DEE0 ; int addr;

.text:0042DEE0 ; int size;

.text:0042DEE0 ; char *name;

.text:0042DEE0 ; } _RTC_vardesc;

.text:0042DEE0 ;

.text:0042DEE0 ; typedef struct _RTC_framedesc {

.text:0042DEE0 ; int varCount;

.text:0042DEE0 ; _RTC_vardesc *variables;

.text:0042DEE0 ; } _RTC_framedesc;

.text:0042DEE2 push ebp

.text:0042DEE3 mov ebp, esp

.text:0042DEE5 push ecx

.text:0042DEE6 push ebx

.text:0042DEE7 push esi

.text:0042DEE8 push edi

.text:0042DEE9 xor edi, edi ; edi=0

.text:0042DEEB mov esi, edx ; esi=_RTC_framedesc

.text:0042DEED cmp [esi], edi ; if(_RTC_framedesc.varCount==0)

.text:0042DEEF mov ebx, ecx ; ebx=ebp

.text:0042DEF1 mov [ebp+var_4], edi ; [ebp+var_4]=局部变量i 这里 i=0

.text:0042DEF4 jle short Exit_fun ; if(_RTC_framedesc.varCount==0) return

.text:0042DEF4 ;

.text:0042DEF6

.text:0042DEF6 loc_42DEF6: ; CODE XREF: _RTC_CheckStackVars+56j

.text:0042DEF6 mov eax, [esi+4] ; eax=&_RTC_framedesc.variables

.text:0042DEF9 mov ecx, [eax+edi] ; ecx = _RTC_vardesc.addr

.text:0042DEFC add eax, edi

.text:0042DEFE cmp dword ptr [ecx+ebx-4], 0CCCCCCCCh ; if( ( 变量-4)==0xcccccccc)

.text:0042DF06 jnz short loc_42DF16

.text:0042DF08 mov edx, [eax+4] ; edx=_RTC_vardesc.size

.text:0042DF0B add edx, ecx

.text:0042DF0D cmp dword ptr [edx+ebx], 0CCCCCCCCh ; if((数组尾+4)==0xcccccccc)

.text:0042DF14 jz short loc_42DF2A

.text:0042DF16

.text:0042DF16 loc_42DF16: ; CODE XREF: _RTC_CheckStackVars+26j

.text:0042DF16 mov eax, [esi+4]

.text:0042DF19 mov ecx, [eax+edi+8]

.text:0042DF1D mov edx, [ebp+4]

.text:0042DF20 push ecx

.text:0042DF21 push edx

.text:0042DF22 call j__RTC_StackFailure

.text:0042DF27 add esp, 8

.text:0042DF2A

.text:0042DF2A loc_42DF2A: ; CODE XREF: _RTC_CheckStackVars+34j

.text:0042DF2A mov eax, [ebp+var_4]

.text:0042DF2D inc eax

.text:0042DF2E add edi, 0Ch

.text:0042DF31 cmp eax, [esi]

.text:0042DF33 mov [ebp+var_4], eax

.text:0042DF36 jl short loc_42DEF6 ; eax=&_RTC_framedesc.variables

.text:0042DF38

.text:0042DF38 Exit_fun: ; CODE XREF: _RTC_CheckStackVars+14j

.text:0042DF38 pop edi

.text:0042DF39 pop esi

.text:0042DF3A pop ebx

.text:0042DF3B mov esp, ebp

.text:0042DF3D pop ebp

.text:0042DF3E retn

.text:0042DF3E _RTC_CheckStackVars endp

.text:0042DF3E

.text:0042DF3E ; ---------------------------------------------------------------------------

.text:0042DF3F
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: