0%

GS绕过

GS原理

GS功能是Windows 针对栈溢出而产生得防御技术。其主要原理是在调用函数初始化一个栈帧之后将一个随机数放入栈当中,并且在“.data“节区保存一个副本。每次在执行返回地址得指令之前都需要验证一下随机值。如果发生变化,则认为产生溢出。

向栈内压入一个随机的DWORD值,这个随机数被称为canary ,IDA分析中为 Security Cookie。Security Cookie 位于 EBP 之前,系统还将在.data的内存区域中存放一个 Security Cookie的副本(如下图,图片来自该博客

image-20220719160609079

Security Cookie的生成

  • 系统以data节的第一个 DWORD值作为Cookie种子,或称为原始Cookie(所有函数的Cookie都用它生成)
  • 程序每次运行,种子都不同,具有很强的随机性
  • 在栈帧初始化以后,用ebp xor 种子 ,作为当前函数的Cookie,以此作为不同函数的区别,并增加随机性
  • 函数返回前,用ebp 还原出 Cookie 种子 ,进行比较

注意事项:
因为额外的数据和操作带来的直接后果就是系统性能的下降,为了将对性能的影响降到最
小,编译器在编译程序的时候并不是对所有的函数都应用 GS,以下情况不会应用 GS。

  • 函数不包含缓冲区。 (就不会有栈溢出)
  • 函数被定义为具有变量参数列表。
  • 函数使用无保护的关键字标记。
  • 函数在第一个语句中包含内嵌汇编代码。
  • 缓冲区不是 8 字节类型且大小不大于 4 个字节。

IDA逆向分析GS

image-20220719161335393

生成exe后分析,可以看到,把安全cookie给rax, 与 rsp异或后, 放入栈中rsp+0x90的位置 ,然后再去函数末尾看一下

image-20220719162615666

可以看到,在printf调用后, 把栈中的 cookie拿出来给rcx , rcx与rsp异或后, 调用了一个检查函数

image-20220719163211608

先把结果与.data节中的原始 cookie进行比较 ,其中主要处理函数是sub_1400013cc

image-20220719163603028

调用系统自己的异常处理函数,并传入之前的ExceptionInfo,之后获取当前进程后强制结束

image-20220719164427150

绕过GS

可以有以下几种方式,这里试着实践一下2,3两点

  • 利用未被保护的内存突破GS
  • 覆盖虚函数突破GS
  • 攻击SEH突破GS
  • 同时替换栈中和.data中的Cookie突破GS(硬刚覆盖返回地址)

攻击SEH

复习一下SEH的知识

image-20220719180756855

image-20220719180832894

SEH的链表结构如上所示, Next代表下一个SEH结构 ,Handler 代表本函数内的异常处理例程

SEH是以函数为单位,也就是一个函数只能有一个SEH结构,如果本函数内的异常处理例程没有处理这个异常,则去寻找Next记录的下一个SEH结构,如果最后都没有处理,就交给系统的默认异常处理

系统的默认异常处理, Next 为FFFFFFFF , Handler 为系统默认处理例程,也就是会给你弹一个框,并结束进程

SEH会在函数入口注册,函数出口注销,会出现有关fs:[0]的操作 , 简单点说就是把上述的SEH记录指向自己在栈中的SEH结构,然后自己再指向原来的SEH结构

1.写一个测试函数: test是一个溢出函数且注册了SEH , 把buf1 的 500字节的数据 放入 test函数中的200字节大小的缓冲区中, 此时会造成溢出,溢出后会覆盖到 out 的地址(参数地址 ebp + n),然后再次拷贝buf到out 的过程中 , 会触发非法访问,转入异常处理流程,但是此时函数并没有执行到返回,也就是没有执行到 check cookie函数, 所以可以覆盖SEH来实现绕过 GS(因此我们可以通过**(pop pop ret)**覆盖SEH来达到溢出的目的。但对于受SafeSEH保护的模块,就可能会导致exploit失效)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <Windows.h>

void __stdcall test(char* str, char* out)
{
char buf[200] = { 0 };

__try
{
strcpy(buf, str);
strcpy(out, buf);
}
__except (1)
{
printf("Error OverFlow\n");
}
}


int main(int arc, char** argv)
{
char buf1[500];
memset(buf1, 0x90, 1000);

char buf2[100] = { 0 };
test(buf1, buf2);

return 0;
}

攻击虚函数

堆栈布局:【局部变量】【cookie】【入栈寄存器】【返回地址】【参数】【虚表指针】
当把虚表指针覆盖后,由于要执行虚函数得通过虚表指针来搜索,即可借此劫持EIP

参考链接:EXP编写学习 之 绕过GS(四)