运行时错误检查(/RTC)编译选项及实现原理
2014-04-10 20:55
609 查看
运行时错误检查(/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
哈哈。
运行时错误检查(/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
哈哈。
相关文章推荐
- 运行时错误检查(/RTC)编译选项及实现原理(转)
- 运行时错误检查(/RTC)编译选项及实现原理
- 运行时错误检查(/RTC)编译选项及实现原理
- 运行时错误检查(/RTC)编译选项及实现原理
- 运行时错误检查(/RTC)编译选项及实现原理 .
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC) - [C/C++]
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- MVC数据验证原理及自定义ModelValidatorProvider实现无编译修改验证规则和错误信息
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- C++反汇编揭秘2 – VC编译器的运行时错误检查(RTC)
- 内存数据库内核开发 工作日志(内存索引实现原理)(附红黑树实现清晰完整直接可编译运行代码)(十)