Loading... ##### GS安全编译选项的保护原理 针对缓冲区溢出时覆盖函数返回地址这一特征,微软在编译时使用了一个很酷的安全编译选项——GS。 ![image.png](http://47.117.131.13/usr/uploads/2021/11/3989655410.png) GS编译选项为每个函数调用增加了一些额外的数据和操作,用以检测栈中的溢出。 * 在所有函数调用发生时,向栈帧内压入一个额外的随机DWORD,这个随机数被称作"canary",但如果使用IDA反汇编的话,你会看到IDA会将这个随机数注为"Security Cookie"。 * Security Cookie位于EBP之前,系统还将在.data的内存区域中存放一个Security Cookie的副本,如图10.1.2所示。 * 当栈中发生溢出时,Security Cookie将首先被淹没,之后才是EBP和返回地址。 * 在函数返回之前,系统将执行一个额外的安全验证操作,被称做Security check。 * 在Security Check的过程中,系统将比较栈帧中原先存放的Security Cookie和.data中副本的值,如果两者不吻合,说明栈帧中的Security Cookie已经被破环,即栈中发生了溢出。 ![image.png](http://47.117.131.13/usr/uploads/2021/11/2616342821.png) 当检测到栈中发生溢出时,系统将进入异常处理流程,函数不会被被正常返回,ret指令也不会被执行,如图10.1.3所示。 ![image.png](http://47.117.131.13/usr/uploads/2021/11/999091405.png) ###### 调用A函数时 ![image.png](http://47.117.131.13/usr/uploads/2021/11/3702525934.png) 按照数字的顺序依次入栈 ``` // 当前函数栈空间 6.A函数内部局部变量(包含缓冲区) 5.异常处理代码入口地址 //如果A函数设置了异常处理 4.安全cookie //如果编译器加GS选项 3.ebp(上层) //push ebp; mov ebp,esp; sub esp,xxx; 开辟新的栈帧空间 2.返回地址 1.调用A函数所需的参数 -----ebp //上层函数的ebp(上层) ``` 缓冲区溢出的原理简单来说就是,未对输入局部变量(缓冲区)的长度作判断,以至于一直往下填充,直到覆盖EBP,覆盖函数返回地址。这样函数返回后,跳转到的就是指定的代码区域执行,从而执行恶意代码。 GS的存在使得通过上述原理修改返回地址时,肯定要填充security cookie的内容。 ###### 在A函数结束时 ![image.png](http://47.117.131.13/usr/uploads/2021/11/3474634166.png) ###### __security_check_cookie __secutiry_check_cookie函数用来检查security cookie的值是否被改变,从而而判断是否发生了缓冲区溢出。 ![image.png](http://47.117.131.13/usr/uploads/2021/11/2703816365.png) 如果堆栈中的security cookie的值和初始的__security_cookie的值一致的话,那么函数正常退出。 否则就会执行错误处理程序failure。 ###### GS作用与例外 但是额外的数据和操作带来的直接后果就是系统性能的下降,为了将对性能的影响降到最小,编译器在编译程序的时候并不是对所有的函数都应用GS,以下情况不会应用GS。 1. 函数不包含缓冲区; 2. 函数被定义为具有变量参数列表; 3. 函数使用无保护的关键字标记; 4. 函数在第一个语句中包含内嵌汇编代码; 5. 缓冲区不是8字节类型且大小不大于4个字节。 **有例外就有机会,我们后边介绍一种利用这些例外突破GS的情况。** 当然微软的工程师也发现了这个问题,因此他们为了在性能与安全之间找到一个平衡点,在Virtual Studio 2005 SP1引入了一个新的安全标识: `#pragma strict _gs_check(on) //为下边的函数强制启用GS` 通过添加#pragma strict _gs_check(on)可以对任意类型的函数添加Security Cookie。通过设置该标识,可以对不符合GS保护条件的函数添加GS保护。 除了在返回地址前添加Security Cookie外,在Visual Studio 2005及后续版本还使用了**变量重排技术**,在编译时根据局部变量的类型对变量在栈帧中的位置进行调整,将字符串变量移动到栈帧的高地址。这样可以防止该字符串溢出时破环其它的局部变量。同时还会将指针参数和字符串参数赋值到内存中低地址,防止函数参数被破环。 ![image.png](http://47.117.131.13/usr/uploads/2021/11/2856738399.png) 从图10.1.4中可以看出,不启用GS时,如果变量Buff发生溢出变量i、返回地址、函数参数arg等都会被覆盖,而启用GS后,变量Buff被重新调整到栈帧的高地址,因此当Buff溢出时不会影响变量i的值,虽然函数参数arg还是会被覆盖,但是由于重新会在栈帧低地址处保存参数的副本,所以Buff的溢出也不会影响到传递进来的函数参数。 通过GS安全编译选项,操作系统能够在运行中有效地检测并阻止绝大多数栈溢出的攻击。要想硬对硬是很难成功的。让我们再来看看Security Cookie产生的细节。 * 系统以.data节的第一个双字作为Cookie的种子,或称原始Cookie(所有函数的Cookie 都用这个DWORD生成)。 * 在程序每次运行时Cookie的种子都不同,因此种子有很强的随机性。 * 在栈帧初始化以后系统用ESP异或种子,作为当前函数的Cookie,以此作为不同函数之间的区别,并增加Cookie的随机性。 * 在函数返回前,用ESP还原出(异或)Cookie的种子。 若想要在程序运行时预测出Cookie而突破出GS机制基本上是不可能的。 但GS编译选项不可能一劳永逸地彻底遏制所有类型的缓冲区溢出攻击。下边是微软内部对GS为产品所提供的安全保护的看法: 1. 修改栈帧中函数返回地址的经典攻击将被GS机制有效遏制; 2. 基于改写函数指针的攻击,对C++虚函数的攻击,GS机制仍然很难防御; 3. 针对异常处理机制的攻击,GS很难防御; 4. GS是对栈帧的保护机制,因此很难预防堆溢出的攻击。 ##### 利用未被保护的内存突破GS 我们知道,并不是所有的函数都会被保护,所以我们就可以利用其中一些未被保护的函数绕过GS的保护。例如,下边这一段代码,由于vulfuction中不包含4字节以上的缓冲区,所以即便GS处于开启状态,这个函数也是不受保护的。 ``` #include"stdafx.h" #include"string.h" int vulfuction(char * str) { char arry[4]; strcpy(arry,str); return 1; } int _tmain(int argc, _TCHAR* argv[]) { char* str="yeah,the fuction is without GS"; vulfuction(str); return 0; } ``` ![image.png](http://47.117.131.13/usr/uploads/2021/11/3810704447.png) ##### 覆盖虚函数突破GS 回想一下GS机制,程序只有在函数返回时,才去检查Security Cookie,而在这之前是没有任何检查措施的。换句话说如果我们可以在程序检查Security Cookie之前劫持程序流程的话,就可以实现对程序的溢出,而C++的虚函数恰恰给我们提供了这么一个机会(对于虚函数溢出的原理可以参考[这篇文章](http://www.wublub.cn/index.php/archives/465/))。 通过以下代码来演示和分析一下如何利用虚函数来绕过GS机制。 ``` #include<string.h> #pragma warning(disable:4996) class GSVirtual { public: void gsv(const char* src) { char buf[200]; strcpy(buf, src); vir(); } virtual void vir() {} }; int main() { GSVirtual test; test.gsv( "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\0" ); return 0; } ``` 说明:shellcode中头部的0x7C992B04为"pop edi pop esi retn"指令的地址,不同版本的系统中该地址可能不同。 对实验思路和代码简要解释如下。 1. 类GSVirtual中的gsv函数存在典型的溢出漏洞; 2. 类GSVirtual中包含一个虚函数vir; 3. 当gsv函数中的buf变量发生溢出的时候有可能会影响到虚表指针,如果我们可以控制虚表指针,将其指向我们的可以控制的内存空间,就可以在程序调用虚函数时控制程序的流程。 为了能够精确的淹没虚函数表,我们需要搞清楚变量与虚表指针在内存中的详细布局,通过前面的分析可以知道当函数gsv传入参数的长度大于200字节时,变量buff就会被溢出。 待写...... 最后修改:2021 年 11 月 12 日 04 : 20 PM © 允许规范转载