分享

__RTC_CheckEsp 等 C Rumtime问题

 quasiceo 2013-12-05

/RTCs开关

这个开关是用来检查和Stack相关的问题:

1.     Debug模式下把Stack上的变量初始化为0xcc,检查未初始化的问题

2.     检查数组变量的Overrun

3.     检查ESP是否被毁坏

Debug模式下初始化变量为0xcc

假设我们有下面的代码:

void func()

{

    int a;

    int b;

    int c;

}

对应的汇编代码如下:

func@@YAXXZ PROC                         ; func, COMDAT

 

; 38   : {

 

      push  ebp

      mov   ebp, esp

      sub   esp, 228                      ; 000000e4H

      push  ebx

      push  esi

      push  edi

      lea   edi, DWORD PTR [ebp-228]

      mov   ecx, 57                             ; 00000039H

      mov   eax, -858993460                     ; ccccccccH

      rep stosd

 

; 39   :     int a;

; 40   :     int b;

; 41   :     int c;

; 42   :

; 43   : }

 

      pop   edi

      pop   esi

      pop   ebx

      mov   esp, ebp

      pop   ebp

      ret   0

func@@YAXXZ ENDP

1.     sub esp, 228:s编译器为栈分配了228个byte

2.     接着3个push指令保存寄存器

3.     Lea edi, DWORD PTR [ebp-228]一直到repstosd指令是初始化从ebp-228开始写57个0xcccccccc,也就是57*4=228个0xcc,正好填满之前sub esp, 228所分配的空间。这段代码会把所有的变量初始化为0xcc。

选择0xcc是有一定理由的:

1.     0xcc不同于一般的初始化值,人们一般倾向于把变量初始化为0, 1, -1等比较简单的值,而0xcc一般情况下足够大,而且是负数,容易引起注意,而且一般变量的值很有可能不允许是0xcc,比较容易造成错误

2.     0xcc = int 3,如果作为代码执行,则会引发断点异常,比较容易引起注意

 

检查数组变量的Overrun

假设我们有下面的代码:

void func

{

    char buf[104];

    scanf("%s", buf);

 

    return 0;

}

在scanf调用之后,会执行下面的代码:

      mov   ecx, ebp

      push  eax

      lea   edx, DWORD PTR $LN5@wmain

      call  @_RTC_CheckStackVars@8

这段代码会调用_RTC_CheckStackVars@8函数会在数组的开始和结束的地方检查0xcccccccc有否被破坏,如果是,则报告错误。_RTC_CheckStackVars由于代码过长这里就不给出了,这个函数主要是利用编译器保存的数组位置和长度信息,检查数组的开头和结尾:

$LN5@func:

      DD    1

      DD    $LN4@func

$LN4@func:

      DD    -112                          ; ffffff90H

      DD    104                           ; 00000068H

      DD    $LN3@func

$LN3@func:

      DB    98                            ; 00000062H

      DB    117                           ; 00000075H

      DB    102                           ; 00000066H

      DB    0

$LN5@func纪录了数组的个数,而$LN4@func保存了数组的偏移量ebp - 112和数组的长度104,而$LN3@func则保存了变量的名称(0x62, 0x75, 0x66, 0 = “buf”)。

检查ESP

ESP的错误很有可能是由调用协定的mistach造成,或者Stack本身没有平衡。编译器会在调用其他函数和在函数Prolog和Epilog(开始和结束代码)的时候插入对ESP的检查:

1.     在调用其他外部函数的时候:

假设我们有下面的代码:

 

printf( "%d", 1 );

对应的汇编代码如下:

      mov   esi, esp

      push  1

      push  OFFSET ??_C@_02DPKJAMEF@?$CFd?$AA@

      call  DWORD PTR __imp__printf

      add   esp, 8

      cmp   esi, esp

      call  __RTC_CheckEsp

可以看到检查的代码非常简单直接,把ESP保存在ESI之中,当调用printf,平衡堆栈之后,检查esp和esi的是否一致,然后调用__RTC_CheckESP,__RTC_CheckESP代码也很简单:

_RTC_CheckEsp:

00412730  jne         esperror (412733h)

00412732  ret             

esperror:

……

00412744  call        _RTC_Failure (411195h)

……

00412754  ret 

 

如果不一致,跳转到esperror标号报告错误。

 

2.     函数返回的时候:

以下面的代码为例:

void func()

{

    __asm

    {

        push eax

    }

}

Func函数故意push eax来破坏堆栈的平衡性,对应的汇编代码如下:

func@@YAXXZ PROC                         ; func, COMDAT

 

; 38   : {

 

      push  ebp

      mov   ebp, esp

      sub   esp, 192                      ; 000000c0H

      push  ebx

      push  esi

      push  edi

      lea   edi, DWORD PTR [ebp-192]

      mov   ecx, 48                             ; 00000030H

      mov   eax, -858993460                     ; ccccccccH

      rep stosd

 

; 39   :     __asm

; 40   :     {

; 41   :         push eax

 

      push  eax

 

; 42   :     }

; 43   : }

 

      pop   edi

      pop   esi

      pop   ebx

      add   esp, 192                      ; 000000c0H

      cmp   ebp, esp

      call  __RTC_CheckEsp

      mov   esp, ebp

      pop   ebp

      ret   0

func@@YAXXZ ENDP

 

在函数的初始化代码中,func会将ebp保存在Stack中,并且把当前esp保存在ebp中。

func@@YAXXZ PROC                         ; func, COMDAT

      push  ebp

      mov   ebp, esp

 

关键的检查代码在后面,当func函数恢复了堆栈之后,堆栈会恢复到之前刚保存esp到ebp的那个状态,这个时候ebp必然等于esp,否则出错

      cmp   ebp, esp

      call  __RTC_CheckEsp

      mov   esp, ebp

      pop   ebp

      ret   0

func@@YAXXZ ENDP

出错的时候显示的对话框如下:

OK,这次就写到这里。下面几篇文章预定会写到下面这些内容:

1.     /GS & Security Cookie

2.     Calling Conventions

3.     Name Mangling

4.     Structured Exception Handling

5.     Passing by Reference

6.     Member functions

7.     Object layout

8.     Virtual functions

9.     Virtual Inheritance

10.   C++ Exceptions

11.   Templates

敬请关注。

 

作者:      ATField
Blog:      http://blog.csdn.net/atfield

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多