___security_cookie机制,防止栈溢出

有关security cookie在栈保护上的研究

06.10 by flyingkisser

这里主要讨论栈,不是堆。
首先,security cookie并非windows系统自带的保护机制,并非说一个确实存在溢出漏洞的
程序,放到带security cookie保护的环境中,就不能正常溢出了。
那么,究竟是什么是security cookie呢?
我以为从广义上讲,它应该是一种保护栈的机制,提供这种保护的,是程序自己,编译进程序自己的
代码提供的,而不是系统中某个运行在黑暗角落中的线程。
因此,既然是程序自身就带上的,为了避免给程序员带来额外的负担,这份工做就交给编译器来完成了。
vc6.0的cl.exe是不带这个功能的,只有vc.net之后面版本的cl.exe才带这个功能,就所谓的/GS选项。
即用vc.net的cl编译器时,/GS选项默认就打开了。
如今,咱们知道了这个机制的提供方,那么,这个机制究竟是怎么一回事呢?
熟悉函数调用及返回先后的汇编指令的人确定很清楚,在win32平台,对于stdcall类型的函数调用,
当call指令运行完毕,当前的堆栈结构基本上是这样的:

局变2  ebp-8   低地址
局变1  ebp- 4
ebp  ebp
返回地址 ebp+4
参数1  ebp+8
参数2  ebp+c
参数3  ebp+10
参数4  ebp+14   高地址

第一列是堆栈中存放的dword的内容,第二列是用ebp做为栈地址的索引时,它对应的应该用ebp表示的值,
说得形象一点,ebp中存放着栈的一个地址(栈其实也是一片内存,ebp只是指向其中一个对当前函数内部比较
重要的地址,实际上是至关重要),栈的其它位置都是经过这个ebp来寻址的,即咱们给函数的第一个形参的
地址,就是ebp+8,第二个就是ebp+c,咱们定义的局部变量的地址,第一个局部变量是ebp-4,第二局部变量的
地址就是ebp-8,依此类推。但这也不是必定的,上面说的是理想状况,若是咱们在函数里定了一个数组,
如 char buf[8];而且是定义的第一个局部变量,那么它的地址确定就不是ebp-4,仍是ebp-8。因此,数组
比较特殊,结构体也比较特殊,其根本缘由是栈是从高地址向低地址生长的,而咱们的数组,结构体,
倒是从低向高地址生长的,二者矛盾的结果就是寻址上的微妙变化。
固然,这里为了方便说明问题,都默认定义的变量,传入的参数,都是四字节对齐的,而且一个变量一个
双字。你能够把数组理解一个4字节的char,也就是一个双字了。
话说回来,当call运行完毕,当前的堆栈结构已经给出,若是在函数里调用strcpy()往局部变量1 里考入
东西,对长度没有进行检测,那么ebp-4,ebp,ebp+4,还有后面的地址,其所在的内容都会被覆盖掉。这里
溢出就发生了,咱们控制住了ret的返回地址,而后...
嗯,为了防止这一切,新的cl编译器的/GS选项加上入所谓的"security cookie",如何加入的?在哪加入的
呢?
先看看加入"security cookie"后的call指令运行完之后,堆栈的变化。

局变2  ebp-c   低地址
局变1  ebp-8
XXXXX  ebp-4
ebp  ebp
返回地址 ebp+4
参数1  ebp+8
参数2  ebp+c
参数3  ebp+10
参数4  ebp+14   高地址

变化很明显,在ebp上面,第一个局部变量的下面,填入的一个新的值,这个值就是所谓的"security cookie"
.按照前面说的溢出过程,ebp- 4的内容被覆盖掉,即security cookie的值被修改,在函数返回,即执行ret
指令前,会call另外一个函数,这个函数就是用来对比 ebp-4的值和当时push到栈中的值是否是同样,不同的
话,就说明溢出了,而后进程被终止。
那么,你大概会产生如下几个问题:
1. 这个security cookie是如何计算出来的?
security cookie是一个双字,也能够说是一个int,其自己是保存在全局变量里的,其建立是编译器在编译
阶段就建立的,而后写入到.data段里,即在PE里就保存了这个值。
但这个值又是变化的,windows装载器完成必要的前期准备工做后(如建立进程,为栈分配内存,等待)
把 EIP设置为PE里的代码入口处,第一个执行的指令就是一个call调用,这个call调用就是用来初始化这个
cookie值的,固然,这段代码也是公开的,但没有关系,这个算法保证这个cookie值是随机的,hacker也
是不能在一个shellcode中能够猜出来的。
具体算法我不打算在此说明,感兴趣的读者能够本身编译一下再反汇编一下看看。

2.是何时填入到栈里面的?
咱们知道了这个 security cookie的计算和初始化过程,那么,它必须在函数调用时写入到ebp-4里面才有用。
因此,过去的不带这种保护的代码,在函数入口处通常是这样的:
push ebp
mov ebp,esp
sub ebp,n   ;这条指令可能不一样,不过多数状况下都是这样来为局部变量分配空间的
而后,后面就开始执行咱们的代码了,
加入这种保护后,会在sub ebp,n后面,加入一条像这样的指令:
mov     dword ptr [ebp-4],XXX
XXX就是security cookie的值,这个值保存在全局变量里,经过RVA+PE头地址,实际上也能够说成是
绝对地址来引用了。
到这里security cookie的值就写入栈了,而后在函数返回前检测一下就好了。

到了这里,你大概又会产生一个新的问题,
必须为每个函数调用都写入security cookie进行保护吗?
答案是否认的,要否则咱们的程序的执行效率会受到必定的影响,而且可能还不小。
那么,就应该存在必定的规则,何时进行这种保护,何时不须要。
其依据固然也很简单,有溢出可能的,就加入这种保护,没有溢出可能的,就不加。
那怎么样才算是有溢出可能呢?
这个是编译器进行判断的,像函数里定义了char数组,后面又用字符串操做函数进行了必定的操做,就说明
可能存在溢出。编译器在编译这个函数里的时候就加上security cookie的保护。
固然,这里还有一些其它的很具体的规则,在msdn里有更详细的描述。

还有其它一些问题没有在这里说明,能够把这些问题留给你们
1.有对付security cookie的检测的方法吗?(答案是有的,但好像都不是很优美)
2.有关security异常的异常处理函数
3./safeSEH对 SEH处理的变化程序员

相关文章
相关标签/搜索