突破GS

针对缓冲区溢出覆盖函数返回地址这一特征,微软在编译程序时候使用了一个很酷的安全编译选项—GS。/GS 编译选项会在函数的开头和结尾添加代码来阻止对典型的栈溢出漏洞(字符串缓冲区)的利用。当应用程序启动时,程序的 cookie(4 字节(dword),无符号整型)被计算出来(伪随机数)并保存在 加载模块的.data 节中,在函数的开头这个 cookie 被拷贝到栈中,位于 EBP 和返回地址的正前方(位于返回地址和局部变量的中间)。

[局部变量 ] [cookie] [保存的EBP] [保存的返回地址] [参数]

在函数的结尾处,程序会把这个 cookie 和保存在.data 节中的 cookie 进行比较。 如果不相等,就说明进程栈被破坏,进程必须被终止。

image-20240213211728720

为了演示覆盖虚表指针这种技术,将使用下面的代码:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "stdafx.h"

#include "windows.h"

class TestClass

{

public:

void __declspec(noinline) test1(char* src)

{

char buf[8];

strcpy(buf, src);

test2(); //调用虚函数test2

}

virtual void __declspec(noinline) test2()

{

}

};

int main()

{

char str[8000];

LoadLibrary(_T("Netfairy.dll"));

TestClass test;

test.test1("AAAABBBBCCCCDDD");

return 0;

}

TestClass对象在 main 函数的堆栈中分配空间,并在 main 函数中被调用,然后对象test被做为参数传递给存在 漏洞的成员函数 test1(如果把大于 8 字节的字符串拷贝到 buf,buf 就会被溢出。)。 完成拷贝后,一个虚函数会被执行,因为前边的溢出,堆栈中指向虚函数表的指针可能已经被覆盖,这样 就可以把程序的执行流重定向到 shellcode 中。

用Olldbg载入程序,查看test1函数的代码

image-20240213212349339

test1函数是受到GS保护的函数,在

00401006 A1 18304000 mov eax, dword ptr ds:[test.__security_cookie]

0040100B 33C5 xor eax, ebp

0040100D 8945 FC mov dword ptr ss:[ebp-4], eax

设置安全cookie。在

0040105E E8 54000000 call test.__security_check_cookie

进行检验,如果栈中的cookie被覆盖,那么程序将直接退出。但是我们注意到,在调用校验函数的时候,test1函数先调用了test2函数

00401057 FFD0 call eax ; test.TestClass::test2

image-20240213212456073

而test2是虚函数,所以我们可以覆盖保存在栈中的虚表指针,间接跳到我们的shellcode。我们先执行到

**00401050 8B10 mov edx, dword ptr ds:[eax]**观察此时的eax为0x0018FF40,这个地址保存着虚表指针。

image-20240213212735125

再执行到 00401057 FFD0 call eax ;test.TestClass::test2

image-20240213212900377

当输入“AAAABBBBCCCCDDD”时,刚刚开始覆盖到返回地址。如果我们输入很多字符的时候,多到恰好能覆盖虚表指针那么我们就能控制程序。我们可以计算出多少字符能够覆盖到虚表指针

X=0x0018ff40-0x0018dfe4=0x1f60,十进制就是8028。我们可以试一下,把test.test1(“AAAABBBBCCCCDDD”);中的AAAABBBBCCCCDDD改为8028个A。重新编译,用OD调试,执行到

00401057 FFD0 call eax

image-20240213213531916

可以看到8028个A刚好能覆盖到虚表指针。接下来就是构造利用了。找一个地址,这个地址保存的值指向我们的A。我们最好在没有开启ASLR的模块找,Netfairy.dll就是一个不错的选择。很快,我用Olldbg的搜索功能找到了一个

image-20240213213630519

0x500295A2保存的0x0018E1E8指向我们的AAAAAA…。下来我们把虚表指针覆盖为0x500295A2,把8028个A替换为我们的shellcode,不足的用\x90补充。务必记住,把shellcode放在0x0018E1E8之后,否则利用失败。

尽管堆栈中的 cookie 被破坏了,但我们依然劫持了 E I P(因为我们溢出了虚函数表指针,并控制了 eax),从而控制了程序的流程,执行了我们的shellcode。