C和C++安全编码(原书第2版)
上QQ阅读APP看书,第一时间看更新

2.6.5 栈探测仪

栈探测仪(canary)是另一种用来检测和阻止栈溢出攻击的机制。探测仪用于保护栈上的返回地址免遭通过内存的连续写操作(例如,调用strcpy()所导致的结果),而不是执行一般化的边界检查。探测仪由一个被写入被保护栈的节地址前面的“难以插入”或“难以伪造”的值构成。为了进入受保护区域,一个连续的写操作将需要覆盖这个值。探测仪在返回地址被保存后立即被初始化,并且在返回地址被访问之前立即被检测。例如,探测仪可以由4种不同的终止字符组成(CR、LF、NULL和-1)。例如,这些终止字符可以保护由于越界strcpy()调用所产生的缓冲区溢出,因为攻击者需要在他的缓冲区中包含一个空字节。探测仪可以保护由于字符串操作所产生的缓冲区溢出,但不能保护由于内存复制操作而造成的缓冲区溢出。“难以伪造”或随机的探测仪由一个32位的秘密随机数组成,每次随着程序执行它都会发生改变。在探测仪确实保密的情况下,这种方式能够很好地工作。

StackGuard、GCC的栈溢出保护器(Stack-Smashing Protector也被称为ProPolice),以及微软的Visual C++ .NET编译器中作为缓冲区溢出检测能力的那部分,都实现了探测仪。

栈缓冲区溢出检测(stack buffer overrun detection)能力在Visual Studio.NET 2002中被引入C/C++编译器,并已在后续版本更新。/GS编译器开关指示编译器添加启动代码和函数结尾和开始代码以生成和检查被放置在一个函数栈中的一个随机数。如果这个值被损坏,就调用一个处理程序函数来终止应用程序,从而减少试图利用缓冲区溢出的shellcode正确执行的机会。

注意Visual C++2005(及更高版本)对栈上的数据重新排序,也使那些数据难以预见地破坏。例子包括:

·把缓冲区移动到比nonbuffers更高的内存。这一步骤可以帮助保护驻留在栈中的函数指针。

·在运行时把指针和缓冲区参数移动到较低的内存,以减轻各种缓冲区溢出攻击。

Visual C++2010中包括对/GS的增强,它扩展了用来确定何时对一个函数应该启用/GS以及何时可以安全地将/GS优化掉的启发式。

当使用Visual C++2005的Service Pack1或更高版本时,若要充分利用增强的/GS启发式,需要在常用的头文件中加入以下指令以增加被/GS保护的函数的数量:


#pragma strict_gs_check
(on
)

在Visual C++2010中,确定哪些函数需要/GS保护的规则比它们在编译器的早期版本中的表现更积极,但strict_gs_check规则比Visual C++2010的规则更积极。即使Visual C++2010达到了很好的平衡,但在面向Internet的产品中应使用strict_gs_check。

要使用Microsoft Visual Studio中的栈缓冲区溢出检测,你应该遵循下面的要求:

·用最新版本的编译器编译代码。在写作的时候,这个版本是VC++2010(cl.exe版本16.00)。

·若使用比VC++2010早的VC++版本时,需要在常用的头文件中添加#pragma string_gs_check(on)。

·若使用VC++2010及更高版本,需要在面向互联网的产品中添加#pragma string_gs_check(on)。

·使用/GS标志进行编译。

·与用/GS编译的库进行链接。

按照目前的实现,探测仪仅用来阻止那些对“通过从栈中的缓冲区溢出来覆写栈返回地址”的漏洞利用。探测仪无法保护程序免受修改变量、对象指针或函数指针的漏洞利用,也不能阻止发生于任何位置(包括栈段在内)的缓冲区溢出。它们只有在这些缓冲区溢出事件发生后才能检测到其中某些事件。

不管是终结符探测仪还是随机探测仪都无法抵御通过直接覆写返回地址位置字节的漏洞利用[Bulba 2000]。为了解决这些直接访问的漏洞利用,StackGuard加入随机异或探测仪(Random XOR canaries)[Wagle 2003],将返回地址与该探测仪进行异或计算。当然,这种方法也只有在探测仪保持秘密的情况下才能有效保护返回地址。总之,探测仪提供了弱的运行时保护。