栈Cookie,也叫安全Cookie。我们先来看一看栈Cookie的实现机制,使用的程序为1.2节中的example_2,重点在函数get_print()部分。还记得在1.2节中我们为了演示栈溢出,去掉了编译设置中的“缓冲区安全检查(GS)”选项,我们先看看未启用该选项时的get_print()函数代码:
图78 未启用GS的get_print()函数代码
然后,启用该选项,看启用该选项之后的get_print()函数代码:
图79 启用GS的get_print()函数代码
函数开头部分和结尾部分多了上面标注的几行代码,结尾还多了一个函数调用。先看看开头多出来的三行代码:
/*****************************************************************************/
MOV EAX,DWORD PTR DS:[416000]
XOR EAX,EBP
MOV DWORD PTR SS:[EBP-4],EAX
/*****************************************************************************/
这三行代码的操作是从取数据段0x416000处的值,然后与当前的EBP进行异或操作,并将结果保存到栈中EBP-4的位置,回想1.1中的图,EBP-4的位置应该就是保存的EBP上面的那个(即紧邻EBP)。也就是说,这段代码往保存的EBP和局部变量区中间插入了一个值,现在栈变成了这个样子:
图80 加Cookie之后的栈
接下来看结尾多出来的三行代码:
/*****************************************************************************/
MOV ECX,DWORD PTR SS:[EBP-4]
XOR ECX,EBP
CALL example_.00411014
/*****************************************************************************/
操作是将保存的Cookie值取处到ECX,然后再与当前EBP异或,然后调用example_.00411014函数。这是做什么呢?EBP是没有变化的,因此,若保存的Cookie值也没有变化,则与EBP异或将为原值(即从DS:[416000]取出来的值)。所以,example_.00411014应该是一个检查Cookie是否发生变化的函数,通过ECX的值来判断Cookie值是否被修改,从而做一些操作。
究竟是做什么操作,我们不再往下跟,但从1.2节中程序的表现来看,应该是引发了一个异常。
这就是Cookie的工作原理了,在栈上的局部变量区和保存的返回地址(EIP)之间保存一个值,如果发生了栈溢出,则该值会被修改,从而验证不通过,引发栈溢出异常。这个Cookie是通过保存于数据段中的一个值与EBP异或得到的。
下面,我们在开启了GS时的栈中看一看该Cookie值(输入10个A,不超过缓冲区大小):
图81 Cookie值
这就是位于EBP和局部变量之间的Cookie值。
我们重启程序再来看一看:
图82 Cookie值
它的值变了!这说明DS:[416000]处的值是一个随机值,每次运行程序都不同。这样,使得Cookie值难以预测。但好消息是,它的位置是固定的。因此,Cookie的作用相当明确,就是保护下面的返回地址不被修改。
启用了栈Cookie之后,会进行Cookie值的检查,因此,绕过的方法之一,就是绕过检查的步骤。这种方法实际上我们前面已经用到了,由于栈Cookie检查在函数最后,因此提前引起一次异常,利用SEH就可以绕过Cookie的检查。当然,依据具体的程序,还有很多其它的方式,具体请看这篇专门讲栈Cookie的文章:Defeating the Stack Based Buffer Overflow Prevention Mechanism of Microsoft Windows 2003 Server, David Litchfield。