20155306 白皎 0day漏洞——漏洞利用原理之GS
Posted 0831j
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20155306 白皎 0day漏洞——漏洞利用原理之GS相关的知识,希望对你有一定的参考价值。
20155306 白皎 0day漏洞——漏洞利用原理之GS
一、GS安全编译选项的保护原理
1.1 GS的提出
在第二篇博客(栈溢出利用)中,我们可以通过覆盖函数的返回地址来进行攻击,面对这个重灾区,Windows在VS 7.0(Visual Studio 2003)及以后版本的Visual Studio中默认启动了一个安全编译选项——GS(针对缓冲区溢出时覆盖函数返回地址这一特征),来增加栈溢出的难度。
1.2 GS的工作原理
GS编译选项为每个函数调用增加了一些额外的数据和操作,用以检测栈中的溢出。
- 在所有函数调用发生时,向栈帧内压入一个额外的随机DWORD,随机数被称为Security Cookie。
- Security Cookie位于EBP之前,系统将在.data的内存区域中存放一个Security Cookie的副本,如下图所示:
- 当栈中发生溢出时,Security Cookie会首先被淹没,之后才是EBP和返回地址。
- 在函数返回之前,系统将执行一个额外的安全验证操作,被称作Security check。
在Security check过程中,系统将比较栈帧中原先存放的SC和存放在.data之中的SC值进行比较,如果两者不吻合,说明栈帧中的SC已经被破坏,即栈中发生溢出。
当检测到栈中发生溢出时,系统将进入异常处理流程,函数不会被正常返回,ret指令也不会被执行。如图:
但是,GS保护机制的使用带来的后果就是系统性能的下降,所以编译器并不是对所有的函数都应用GS,以下情况不会应用GS:
1. 函数不包含缓冲区
2. 函数被定义为具有变量参数列表
3. 函数使用无保护的关键字标记
4. 函数在第一个语句中包含内嵌汇编代码
5. 缓冲区不是8字节类型且大小不大于4字节
1.3 Security Cookie的生成
系统以.data节第一个双字作为Cookie的种子,或者原始Cookie(所欲函数的Cookie都用这个DWORD生成)
在程序每次运行时Cookie的种子都不用,因此种子具有很强的随机性;
在栈帧初始化以后系统用EBP异或种子,作为当前函数的Cookie,以此作为不同函数之间的区别,并增加Cookie的随机性;
在函数返回时前,用EBP还原出(异或)Cookie的种子。
当然,GS编译选项不可能一劳永逸彻底遏制所有类型的缓冲区溢出攻击,本节我们学习四种突破方法:
1.利用未被保护的内存突破GS
2. 覆盖虚函数突破GS
3.攻击异常处理突破GS
4.同时替换栈中和.data中的Cookie突破GS
二、利用未被保护的内存突破GS
原理:在前面我们我们介绍GS原理时提到,为了将GS对性能的影响降到最低,并不是所有函数都会被保护,所以我们可以利用一些未被保护的函数绕过GS的保护。
实验代码如下:
// gs1.cpp : 定义控制台应用程序的入口点。
//
#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;
}
我们在vs2008下对其进行编译后,用IDA对可执行程序反汇编时,可以看到没有任何Security Cookie的验证操作。
直接运行程序,程序会弹出异常对话框,可以看到提示说明了进行strcpy时发生了溢出,是不安全的。
二、覆盖虚函数突破CS
2.1 原理
GS机制中说明,程序只有在函数返回时,才会去检查Security Cookie,而在这之前是没有任何的检查措施。换句话说,只要我们在检查SC之前劫持程序流程,就可以实现对程序的溢出,而C++虚函数就可以起到这样的功能。
2.2实验思路及步骤
实验代码如下:
#include "stdafx.h" #include "string.h" class GSVirtual { public : void gsv(char * src) { char buf[200]; printf("begin!"); strcpy(buf, src); printf("done!"); bar(); // virtual function call } virtual void bar() { } }; int main() { GSVirtual test; test.gsv( "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90 " ); return 0; }
实验思路
- gsv中存在着典型的缓冲区溢出的漏洞函数strcpy。
- gsv中存在一个虚函数bar()。
当gsv函数中的buf变量发生溢出的时候就可能覆盖到虚表指针,倘若如果能够控制虚表指针使其指向我们可以控制的内存空间,那么就就可以运行缓冲区中相应的shellcode了。
实验步骤:
1.如代码所示,在test.gsv传入199个"x90"+1个"