深入GS保护机制

2018/12/09 21:25
阅读数 25

大多数溢出漏洞产生的原因是由于数组越界,导致溢出。首先要明白溢出漏洞这个我在很早前就写过烂大街的文章了

我们知道大部分的溢出攻击主要是覆盖程序函数的返回地址那么看完再讲讲GS的工作流程=检测某些覆盖函数的返回地址、异常处理程序地址(SEH)或者类型参数的缓冲区溢出。在执行缓冲区溢出时会有安全检查GS 缓冲区。 GS 缓冲区可以是下列之一:

+++++++++++++++++++++++++++++

char buffer[20];

int buffer[20];

struct { int a; int b; int c; int d; } myStruct;

struct { int a; char buf[20]; };

但是,以下语句不声明 GS 缓冲区。 前两个声明包含指针类型的元素。 第三个和第四个语句声明的数组太小。 第五个语句声明一个结构,其大小在 x86 平台不超过 8 个字节。

+++++++++++++++++++++++++++++

char *pBuf[20];

void *pv[20];

char buf[4];

int buf[2];

struct { int a; int b; };

 

一个数组,大于 4 个字节,超过两个元素,并且具有不是指针类型的元素类型。一种数据结构,其大小超过 8 个字节,不包含指针。通过使用分配的缓冲区_alloca函数。任何类或结构,其中包含 GS 缓冲区。

例如,以下语句声明 GS 缓冲区。

从MSDN的官宣可以知道什么情况下可以申明GS,什么情况下是不可以。

GS编译器选项要求使用 COOKIE 的任何函数运行之前初始化安全 COOKIE。 在进入 EXE 或 DLL,必须立即初始化安全 COOKIE。如果使用默认 VCRuntime 入口点会自动完成: mainCRTStartup,wmainCRTStartup,WinMainCRTStartup,wWinMainCRTStartup,或 _DllMainCRTStartup。如果使用备用的入口点,必须通过调用手动初始化的安全 cookie __security_init_cookie这个就是后话了,当科普。

那么为了进一步了解GS,动手测试一下吧。

新建项目那么编译之前我们先关闭其他的一些机制,只开启GS的安全检查。

 

 

开启GS

 

 

+++++++++++++++++++++++++++++

void fun(const char * D)

{

    char buf[10];

    strcpy_s(buf, D);

}

int _tmain(int argc, _TCHAR* argv[])

{

    fun("aaaaaaaaaaaaaaa");

    return 0;

}

+++++++++++++++++++++++++++++

运行崩溃,载入Debugger。

查看堆栈空间

 

载入的15个字节的A,但是会发现真正入栈的只有9个A而多余都被舍去。

用可以看到有这么一行。

 

用OD分析Security_Check_Cookie流程

可以看到在执行我们的函数之前先CALL了一个地址。

 

CALL正是Security-cookie,下面分析流程。

00412EA0 >  55              push    ebp

00412EA1    8BEC            mov     ebp,esp

00412EA3    83EC 14         sub     esp,0x14

00412EA6    C745 F4 0000000>mov     dword ptr ss:[ebp-0xC],0x0

00412EAD    C745 F8 0000000>mov     dword ptr ss:[ebp-0x8],0x0

00412EB4    813D 00804100 4>cmp     dword ptr ds:[__security_cookie],0xBB40E64E

00412EBE    74 1F           je      short 00412EDF

00412EC0    A1 00804100     mov     eax,dword ptr ds:[__security_cookie]

00412EC5    25 0000FFFF     and     eax,0xFFFF0000

00412ECA    74 13           je      short 00412EDF

00412ECC    8B0D 00804100   mov     ecx,dword ptr ds:[__security_cookie]

00412ED2    F7D1            not     ecx

00412ED4    890D 04804100   mov     dword ptr ds:[__security_cookie_complement],ecx

00412EDA    E9 9A000000     jmp     00412F79

00412EDF    8D55 F4         lea     edx,dword ptr ss:[ebp-0xC]

00412EE2    52              push    edx

从反汇编窗口可以看出进行了一个比较然后取COOKIE之后存储然后生成一个新的COOKIE那么要生成新的COOKIE,函数首先会查询当前系统的时间也就是GetSystemTimeAsFileTime然后XOR生成新的保存至eax这是第一部分要做的事

 

 

第二部分GetCurrentThreadId开始获取了进程标识符然后XOR作为COOKIE的第二部分。

TEB第一个指向SHE,第二个指向了自身ID。

 

第三部分组成就是GetCurrentProcessId由线程标识符XOR了。

 

第四部分通过GetTickCount获取执行的毫秒数XOR作为COOKIE的第四部分。

003115D1  |.  8945 FC       mov     [local.1],eax

003115D4  |.  FF15 04203100 call    dword ptr ds:[<&KERNEL32.GetCurrentThreadId>]        ; [GetCurrentThreadId

003115DA  |.  3145 FC       xor     [local.1],eax

003115DD  |.  FF15 08203100 call    dword ptr ds:[<&KERNEL32.GetCurrentProcessId>]       ; [GetCurrentProcessId

003115E3  |.  3145 FC       xor     [local.1],eax

003115E6  |.  8D45 EC       lea     eax,[local.5]

003115E9  |.  50            push    eax                                                  ; /pPerformanceCount

003115EA  |.  FF15 0C203100 call    dword ptr ds:[<&KERNEL32.QueryPerformanceCounter>]   ; \QueryPerformanceCounter

最后通过QueryPerformanceCounter获取性能计算器值异或XOR为第五部分的COOKIE。

 

当strcpy函数跑完后会进行一个COOKIE值比较,不相等就跳到了IsProcessorFeaturePresent。

跟完后发现他在函数执行之前就把COOKIE取出来PUSH到了堆栈当中,用于函数执行后的比较。

 

基本上门清了一下流程,当然GS不是万能的在某种情况下是不受保护的。官方也说明了:

什么是不受保护

/GS编译器选项不能防止所有缓冲区溢出安全攻击。 例如,如果在对象中有一个缓冲区和 vtable,缓冲区溢出可能会损坏 vtable。即使使用 /GS,始终应该尝试编写安全代码没有缓冲区溢出。

MSDN已经告诉我们了,一个虚表函数就可以绕过GS.

从现存的资料有这么几种方式:

1、攻击S.E.H

1、通过猜测cookies值绕过

2、通过覆盖虚函数指针绕过

3、等

这里我先拿简单的通过覆盖虚函数指针来举例,剩下的几种方法我会从下篇文章中一一列举。

那么就定义一个虚表函数来尝试绕过GS保护机制

// GS.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

#include <string.h>

#include <windows.h>

 

class TestGS

{

public:

    void  test(char* src)

    {

       char buf[8];

       strcpy(buf, src);

       test2(); //调用

    }

    void  test2()//虚函数

    {

    }

};

int main()

{

    TestGS test;

    test.test("AAAAAAAAAAAAAAAAAAAA");

    return 0;

}

shellcode

/*LPVOID Memory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

memcpy(Memory, buf, sizeof(buf));

((void(*)())Memory)();*/

//unsigned char buf[] =

//"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"

//"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"

//"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"

//"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"

//"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"

//"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"

//"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"

//"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"

//"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"

//"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"

//"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"

//"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"

//"\x00\x53\xff\xd5\x6e\x6f\x74\x65\x70\x61\x64\x2e\x65\x78\x65"

//"\x00";

编译后运行崩溃,用ICE进行调试。

 

 

第一个CALL 生成Security-cookie

跟第二个CALL进去,从中会遇到很多函数别去管他找到入口。

 

 

再跟进到Strcpy函数。

 

 

使用JE指令来依次压栈,这里插入个题外话可以去跟一下各种复制字符串到地址空间的汇编代码,收获会有很多。这里发一下我自己跟的。

strcpy

00401000  /$  55            push    ebp

00401001  |.  8BEC          mov     ebp,esp

00401003  |.  83EC 08       sub     esp,0x8

00401006  |.  33C0          xor     eax,eax

00401008  |.  EB 06         jmp     short 00401010

0040100A  |   8D9B 00000000 lea     ebx,dword ptr ds:[ebx]

00401010  |>  8A88 20214000 /mov     cl,byte ptr ds:[eax+0x402120]

00401016  |.  8D40 01       |lea     eax,dword ptr ds:[eax+0x1]

00401019  |.  84C9          |test    cl,cl

0040101B  |.^ 75 F3         \jnz     short 00401010

0040101D  |.  8BE5          mov     esp,ebp

0040101F  |.  5D            pop     ebp

00401020  \.  C2 0400       retn    0x4

 

scanf

706AEA32 >  55              push    ebp

706AEA33    8BEC            mov     ebp, esp

706AEA35    8B4D 08         mov     ecx, dword ptr [ebp+8]

706AEA38    FF49 04         dec     dword ptr [ecx+4]

706AEA3B    78 0C           js      short 706AEA49

706AEA3D    8B01            mov     eax, dword ptr [ecx]

706AEA3F    0FB610          movzx   edx, byte ptr [eax]

706AEA42    40              inc     eax

706AEA43    8901            mov     dword ptr [ecx], eax

706AEA45    8BC2            mov     eax, edx

还有像fopen等等。

当我们进入到Strcpy函数后单步走的注意力看着堆栈的变化

 

然后当我们Strcpy函数执行完后可以看到我们的虚函数TEST2的CALL

当mov edx,dword ptr ds:[eax] 获取到EAX的地址后我们就可以覆盖到虚函数指针的地址然后JMP我们的shellcode。具体计算我们可以使用:

虚表指针地址-字符串堆栈地址=要覆盖的字数

从中的COOKIE在虚表函数后再进行校验,而我们的SHELLCODE早就执行完了。

但这种方式在实际过程中对使用者技术难度相对要求稍微高一点。就到这吧动手的活留给大家琢磨了,关于shellcode的生成搞web就不陌生了,可以使用自己写的代码然后取机器码无非就是自己动手累一点了,我比较喜欢用现成的metasploit了。

 

剩下的几种方式会在下节文章中列举,Thanks。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部