运行时错误检查(/RTC)编译选项及实现原理

来源:互联网 发布:linux 启动initramfs 编辑:程序博客网 时间:2024/06/06 13:00


运行时错误检查(/RTC)编译选项及实现原理

 

作者:童磊(magictong

 

环境:VS2005

前因后果:debug居然编不过!!!这里准备说4个例子,都是为了说明debug版本对于调试是很重要的,很多问题在调试版本下都会提前暴露出来。

 

注意:随意调整优化,可调试选项可能会遇到下面的编译错误:

Command line error D8016 : '/O2' and '/RTC1' command-line options are incompatible

 

http://msdn.microsoft.com/zh-cn/library/8wtf2dfz(v=vs.80).aspx

1/GS

名称:缓冲区安全检查(http://msdn.microsoft.com/zh-cn/library/8dbf701c(v=vs.80).aspx)。

编译选项位置:C/C++à Code Generationà Buffer Security Check

说明:缓冲区溢出安全检测,要强调的是,该编译选项并不是对每一个函数都设置安全cookies。编译器首先会判断一个函数是否是属于“潜在危险”的函数,例如是否在函数的堆栈上分配了字符串数组等等,就可以作为一个特征(编译器怎么判断的偶没研究过o(_)o)。只有被编译器判断是存在“潜在危险”的函数之后,编译器才会在这个函数里面使用安全cookie检测。但是要注意,它不能预防所有的安全漏洞。

原理:

#include "stdafx.h"

#include <string.h>

void Function(const char*);

 

int _tmain(int argc, _TCHAR* argv[])

{

        char pBuf[] = "aaaaaaaaaaaa";

        Function(pBuf);

        return 0;

}

 

void Function(const char* pBuf)

{

        char szBuf[10];

        strcpy(szBuf, pBuf);

}

void Function(const char* pBuf)

{

004017E0 push        ebp 

004017E1 mov         ebp,esp

004017E3 sub         esp,10h

004017E6 mov         eax,dword ptr [___security_cookie (403000h)]

004017EB xor         eax,ebp

004017ED mov         dword ptr [ebp-4],eax

     char szBuf[10];

     strcpy(szBuf, pBuf);

004017F0 mov         eax,dword ptr [ebp+8]

004017F3 push        eax 

004017F4 lea         ecx,[ebp-10h]

004017F7 push        ecx 

004017F8 call        strcpy (401010h)

004017FD add         esp,8

}

00401800 mov         ecx,dword ptr [ebp-4]

00401803 xor         ecx,ebp

00401805 call        __security_check_cookie (401000h)

0040180A mov         esp,ebp

0040180C pop         ebp 

0040180D ret

 

__security_check_cookie声明:

void __declspec(naked) __fastcall __security_check_cookie(UINT_PTR cookie)

 

00401000 cmp        ecx,dword ptr [___security_cookie (403000h)]

00401006 jne         failure (40100Ah)

00401008 rep ret         

0040100A jmp        __report_gsfailure(4012BFh)

备注:

在以下情况中,编译器不会对易受攻击的参数提供安全保护:

a.函数不包含缓冲区。

b.如果未启用优化 ( /O 选项(优化代码))

c.函数具有可变参数列表 (...)

d.函数标记为naked (C++)

e.函数的第一行语句包含内联程序集代码。

f.如果仅通过在缓冲区溢出事件中不太可能利用的方式使用参数。

 

2/ RTCu

名称:未初始化变量使用检查

编译选项位置:C/C++à Code Generationà Basic Runtime Checks

说明:这是一个动态的警告,编译的时候也会有警告,不过这个是动态的警告,更直观。

原理:

void Function(const char* pBuf)

{

        int a;

        int b;

        b = a;

}

        通过额外使用一个变量来跟踪某个变量是否初始化,下面的代码里面那个额外的跟踪变量放在[ebp-49h]里面。

0042D759 mov         byte ptr [ebp-49h],0

0042D75D cmp         byte ptr [ebp-49h],0

0042D761 jne         Function+20h (42D770h)

0042D763 push        offset  (42D77Dh)

0042D768 call        @ILT+1935(__RTC_UninitUse) (42B794h)

0042D76D add         esp,4

 

 

把代码稍微改一改:

void Function(const char* pBuf)

{

        int a;

        int b;

 

        if (pBuf)

        {

                  a = 100;

        }

 

        b = a;

}

很明显那个分支里面,给a赋值之前更改了那个跟踪变量的值(修改为1),这样那个警告框就不会再弹出来了。

0042D72D cmp         dword ptr [pBuf],0

0042D731 je          Function+1Eh (42D73Eh)

0042D733 mov         byte ptr [ebp-49h],1

0042D737 mov         dword ptr [a],64h

0042D73E cmp         byte ptr [ebp-49h],0

0042D742 jne         Function+31h (42D751h)

0042D744 push        offset  (42D75Eh)

0042D749 call        @ILT+1935(__RTC_UninitUse) (42B794h)

0042D74E add         esp,4

 

3/ RTCs

名称:堆栈检查

编译选项位置:C/C++à Code Generationà Basic Runtime Checks

说明:该选项主要做三件事情:

(1)Debug模式下把stack上的变量全部初始化为0xcc(使用这个值是因为0xcc对应汇编代码int 3,而且这个值很大容易引起程序员的注意),检查未初始化的问题

(2)、检查数组越界

(3)、检查ESP是否被破坏。

原理:

0x0D4 = 0x35 * 4

void Function(const char* pBuf)

{

        char szBuf[10];

        strcpy(szBuf, pBuf);

}

00411470 push        ebp 

00411471 mov         ebp,esp

00411473 sub         esp,0D4h

00411479 push        ebx 

0041147A push        esi 

0041147B push        edi 

0041147C lea         edi,[ebp-0D4h]

00411482 mov         ecx,35h

00411487 mov         eax,0CCCCCCCCh

0041148C rep stos    dword ptr es:[edi]

 

同样是上面的那一小段代码,在strcpy执行完之后,插入了如下一段代码:

0042D6B8 push        edx 

0042D6B9 mov         ecx,ebp

0042D6BB push        eax 

0042D6BC lea         edx,[ (42D6E8h)]

0042D6C2 call        @ILT+1480(@_RTC_CheckStackVars@8) (42B5CDh)

 

_RTC_CheckStackVars函数里面对数组的前后两端进行了对比看是否和0CCCCCCCCh相等,如果不相等会报错,用这种方法判断数组是否越界。

0042E140 push        ebp 

0042E141 mov         ebp,esp

0042E143 push        ecx 

0042E144 push        ebx 

0042E145 push        esi 

0042E146 push        edi 

0042E147 xor         edi,edi

0042E149 mov         esi,edx

0042E14B cmp         dword ptr [esi],edi

0042E14D mov         ebx,ecx

0042E14F mov         dword ptr [i],edi

0042E152 jle         _RTC_CheckStackVars+58h (42E198h)

0042E154 mov         eax,dword ptr [esi+4]

0042E157 mov         ecx,dword ptr [eax+edi]

0042E15A add         eax,edi

0042E15C cmp         dword ptr [ecx+ebx-4],0CCCCCCCCh

0042E164 jne         _RTC_CheckStackVars+34h (42E174h)

0042E166 mov         edx,dword ptr [eax+4]

0042E169 add         edx,ecx

0042E16B cmp         dword ptr [edx+ebx],0CCCCCCCCh

0042E172 je          _RTC_CheckStackVars+48h (42E188h)

0042E174 mov         eax,dword ptr [esi+4]

0042E177 mov         ecx,dword ptr [eax+edi+8]

0042E17B mov         edx,dword ptr [ebp+4]

0042E17E push        ecx 

0042E17F push        edx 

0042E180 call        _RTC_StackFailure (42B82Ah)

0042E185 add         esp,8

0042E188 mov         eax,dword ptr [i]

0042E18B add         eax,1

0042E18E add         edi,0Ch

0042E191 cmp         eax,dword ptr [esi]

0042E193 mov         dword ptr [i],eax

0042E196 jl          _RTC_CheckStackVars+14h (42E154h)

0042E198 pop         edi 

0042E199 pop         esi 

0042E19A pop         ebx 

0042E19B mov         esp,ebp

0042E19D pop         ebp 

0042E19E ret             

 

        再看最后一小段代码,通过检测esp的值来判断堆栈是否平衡,首先把esp加上初始化时减出的值,然后和ebp对比,正常情况下应该是相等的(看看初始化的代码这很好理解):

0042D6D6 add         esp,0D8h

0042D6DC cmp         ebp,esp

0042D6DE call        @ILT+3445(__RTC_CheckEsp) (42BD7Ah)

0042D6E3 mov         esp,ebp

0042D6E5 pop         ebp 

0042D6E6 ret

 

_RTC_CheckEsp极其简单就下面两句:

0042D9E0 jne         esperror (42D9E3h)

0042D9E2 ret

 

构造这样一个函数测试一下:

void Function(const char* pBuf)

{

        char szBuf[10];

        strcpy(szBuf, pBuf);

        __asm push ebx

}

 

 

4/RTCc

名称:数据转换检查

编译选项位置:C/C++à Code Generationà Smaller Type Check

说明:在数据赋值时,如果把一个较长的数据类型赋值给一个较小的数据类型,很有可能发生数据截断的问题,这个选项就是用来在发生数据截断时提示程序员的。(注意的是这里是动态检测的,只有确实会丢失数据时才报错)

原理:

void Func2()

{

        int nSrc = 100;

        char chDes = 'a';

        chDes = nSrc;

}

0042D679 mov         dword ptr [nSrc],64h

0042D680 mov         byte ptr [chDes],61h

0042D684 mov         ecx,dword ptr [nSrc]

0042D687 call        @ILT+4935(@_RTC_Check_4_to_1@4) (42C34Ch)

0042D68C mov         byte ptr [chDes],al

 

        一堆这种函数,哈哈。

0042C338 jmp         _RTC_Check_2_to_1 (42D760h)

0042C33D jmp         _RTC_Check_8_to_1 (42E520h)

0042C342 jmp         _RTC_Check_8_to_2 (42E560h)

0042C347 jmp         _RTC_Check_8_to_4 (42E790h)

0042C34C jmp         _RTC_Check_4_to_1 (42DE60h)

0042C351 jmp         _RTC_Check_4_to_2 (42DF50h)

 

        我们还是来看看_RTC_Check_4_to_1:

0042DE60 push        ebp 

0042DE61 mov         ebp,esp

0042DE63 push        ebx 

0042DE64 mov         ebx,ecx

0042DE66 mov         eax,ebx

0042DE68 and         eax,0FFFFFF00h

0042DE6D je          _RTC_Check_4_to_1+24h (42DE84h)

0042DE6F cmp         eax,0FFFFFF00h

0042DE74 je          _RTC_Check_4_to_1+24h (42DE84h)

0042DE76 mov         eax,dword ptr [ebp+4]

0042DE79 push        1   

0042DE7B push        eax 

0042DE7C call        _RTC_Failure (42C1EEh)

0042DE81 add         esp,8

0042DE84 mov         al,bl

0042DE86 pop         ebx 

0042DE87 pop         ebp 

0042DE88 ret 

        逻辑还是很简单的,先判断int的高三个字节是不是都是0,如果都是0说明没超出范围,直接跳到末尾;再检查高三个字节是不是全是1,如果是说明是个一个负数也没超出范围,直接跳转到末尾,其他情况直接报错。

 

备注:

        如果你确信要这么做,确实要进行数据截断(譬如取数据的高位低位的),请按上面错误MSGBOX里面说明的做,写这个玩意的哥么想得很周到啊o(_)o哈哈。

0 0
原创粉丝点击