Windows 安全机制---GS
一;GS基本原理
GS功能是Windows 针对栈溢出而产生得防御技术。其主要原理是在调用函数初始化一个栈帧之后将一个随机数放入栈当中,并且在“.data“节区保存一个副本。每次在执行返回地址得指令之前都需要验证一下随机值。如果发生变化,则认为产生溢出。接下来,我们通过对比看看启动GS前后栈空间得变化。
测试环境:
|
|
|
操作系统 | Windows 家庭版 |
|
编程环境 | VS 2019
|
|
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#pragma strict_gs_check(on)
int vul(const char * str) {
char arry[8];
strcpy_s(arry,str);
return 1;
}
int main() {
__asm int 3
const char* str = "qqqq";
int c = vul(str);
return 0;
}
注释:如何启动关闭GS功能如下图:
我们设定好之后,直接编译,发现代码在“__asm int 3”的位置断下来,接着我们查看“反汇编”窗口,如下代码。我们知道在断点之后的代码首先将字符串“qqqq”放入内存并且将对应的地址放入寄存器EAX当中压入栈。
008518AE int 3
008518AF mov dword ptr [str],offset string "qqqq" (0857B30h)
16: const char* str = "qqqq";
17: int c = vul(str);
008518B6 mov eax,dword ptr [str]
008518B9 push eax
008518BA call vul (085139Dh)
我们步入进入函数vul()查看。如下汇编指令:
008517C0 push ebp
008517C1 mov ebp,esp
008517C3 sub esp,0D4h
008517C9 push ebx
008517CA push esi
008517CB push edi
008517CC lea edi,[ebp-0D4h]
008517D2 mov ecx,35h
008517D7 mov eax,0CCCCCCCCh
008517DC rep stos dword ptr es:[edi]
008517DE mov eax,dword ptr [__security_cookie (085A004h)] //初始cookie值
008517E3 xor eax,ebp //初始cookie值与当前栈EBP的值进行异或
008517E5 mov dword ptr [ebp-4],eax //将异或后的值放入栈空间
008517E8 mov ecx,offset _43DCD8FE_test@cpp (085C009h)
008517ED call @__CheckForDebuggerJustMyCode@4 (085130Ch)
9: char arry[8];
10: strcpy_s(arry, str);
008517F2 mov eax,dword ptr [str]
008517F5 push eax
008517F6 lea ecx,[arry]
008517F9 push ecx
008517FA call strcpy_s<8> (08512D5h)
008517FF add esp,8
11: return 1;
00851802 mov eax,1
12: }
00851807 push edx
00851808 mov ecx,ebp
0085180A push eax
0085180B lea edx,ds:[851838h]
00851811 call @_RTC_CheckStackVars@8 (08511D1h)
00851816 pop eax
00851817 pop edx
00851818 pop edi
00851819 pop esi
0085181A pop ebx
0085181B mov ecx,dword ptr [ebp-4] //取出cookie值
0085181E xor ecx,ebp //异或处理
00851820 call @__security_check_cookie@4 (085113Bh) //调用验证函数
00851825 add esp,0D4h
0085182B cmp ebp,esp
0085182D call __RTC_CheckEsp (0851230h)
00851832 mov esp,ebp
00851834 pop ebp
00851835 ret
由上述代码,我们首先将vul() 函数分配得栈空间画出来,详细如下图:
注释:这里因为vul() 函数当中同样调用了strcpy_s()函数,所以同样会有对strcpy_s()函数得栈空间,这里不去讨论。
1.1;security_cookie值得产生
我们看到,在正常进行初始化一个栈空间的程序当中,插入了如下代码。由代码我们可以知道,首先会有一个初始的security_cookie值放入寄存器。
008517DE mov eax,dword ptr [__security_cookie (085A004h)]
008517E3 xor eax,ebp
008517E5 mov dword ptr [ebp-4],eax
通过使用工具,我们发现初始值得地址在节区“.data”范围。详细如下图:
1.2;security_cookie值的验证
我们知道,在vul()函数返回得时候,会执行针对security_cookie得验证,接下来,我们来看看如下代码。首先将放入[ebp-4]位置得security_cookie值取出并且再次与当前得栈EBP值进行异或,并且将值放入ECX当中,之后调用验证函数。
0085181B mov ecx,dword ptr [ebp-4]
0085181E xor ecx,ebp
00851820 call @__security_check_cookie@4 (085113Bh)
接下来,我们步入验证函数,看看操作了什么,这里我们看如下代码,我们发现它将ECX,与“085a004h”地址得值进行对比。对比相同则执行ret指令,即跳转会之前vul()得栈空间,如果不相同值跳转到“0851b4bh”。这里我们注意地址“085a004h”得值就是初始得security_cookie。
00851B40 cmp ecx,dword ptr [__security_cookie (085A004h)]
00851B46 bnd jne failure (0851B4Bh)
00851B49 bnd ret
总结:
在正常执行完初始化一个栈帧结束之后,会执行将“.data”节的第一个双字作为初始化cookie(具备随机性)。
然后使用初始化的种子cookie种子与当前的栈EBP进行异或运算,将值放入栈[ebp-4]的位置当中。
在函数返回之前,即执行ret指令之前,调用验证函数进行验证。
二;GS基本防护范围例外
函数不包含缓冲区。
函数被定义为具有变量参数列表。
函数使用无保护的关键字标记。
函数在第一个语句众包含内嵌汇编代码。
缓冲区不是8字节类型且大小不大于4个字节。
三;GS常见绕过手段
案例:
1;覆盖虚函数突破GS
测试Shellcode:
注释:该Shellcode来自ioiojy讲师CVE实战课程
char ShellCode[] =
"\x33\xDB" // xor ebx,ebx
"\xB7\x06" // mov bh,6
"\x2B\xE3" // sub esp,ebx
"\x33\xDB" // xor ebx,ebx
"\x53" // push ebx
"\x68\xB9\xFE\xB9\xFE" // push "哈哈"
"\x8B\xC4" // mov eax,esp
"\x53" // push ebx
"\x68\xD0\xA1\xEA\xCA"
"\x68\x20\x62\x79\x3A"
"\x68\xB2\xE2\xCA\xD4"
"\x68\xD2\xE7\xB3\xF6" // push "溢出测试 by:小晔"
"\x8B\xCC" // mov ecx,esp
"\x53" // push ebx
"\x50" // push eax
"\x51" // push ecx
"\x53" // push ebx
"\xB8\xea\x07\xd5\x77"
"\xFF\xD0" // call MessageBox
"\x53"
"\xB8\xFA\xCA\x81\x7C"
"\xFF\xD0" ; // call ExitProcess
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#pragma warning( disable : 4996) //强制允许编译器可以使用strcpy()函数
#pragma strict_gs_check(on)
class GS
{
public:
void a(char* str) {
char buf[200];
strcpy(buf, str);
v(); //虚函数
}
virtual void v() {}
};
int main() {
char ShellCode[] = "qqqqqqqqqqqqqqqqqq";
char* str = ShellCode;
GS test; //使用栈空间
test.a(str);
return 0;
}
我们先思考一下,GS防护机制在函数返回的时候才会产生效果,而如果在不返回函数就劫持程序流是否就可以绕过了呢?虚函数就提供了这样的一次机会。
我们先执行代码,看看变量与虚表指针在栈中的布局。
首先,执行代码,代码会停止在int 3 得位置,映入眼帘的是如下汇编代码:
00A51968 int 3
00A51969 mov eax,dword ptr [string "qqqqqqqqqqqqqqqqqq" (0A57B3Ch)]
00A5196E mov dword ptr [ShellCode],eax
00A51971 mov ecx,dword ptr ds:[0A57B40h]
00A51977 mov dword ptr [ebp-18h],ecx
00A5197A mov edx,dword ptr ds:[0A57B44h]
00A51980 mov dword ptr [ebp-14h],edx
00A51983 mov eax,dword ptr ds:[00A57B48h]
00A51988 mov dword ptr [ebp-10h],eax
00A5198B mov cx,word ptr ds:[0A57B4Ch]
00A51992 mov word ptr [ebp-0Ch],cx
00A51996 mov dl,byte ptr ds:[0A57B4Eh]
00A5199C mov byte ptr [ebp-0Ah],dl
21: char ShellCode[] = "qqqqqqqqqqqqqqqqqq";
22: char* str = ShellCode;
00A5199F lea eax,[ShellCode]
00A519A2 mov dword ptr [str],eax
23: GS test;
00A519A5 lea ecx,[test]
00A519A8 call GS::GS (0A510B9h)
24: test.a(str);
00A519AD mov eax,dword ptr [str]
00A519B0 push eax //压入参数
00A519B1 lea ecx,[test]
00A519B4 call GS::a (0A51069h) // 调用a()函数
单步执行,进入a()函数当中,下面便是汇编代码
00A517F0 push ebp
00A517F1 mov ebp,esp
00A517F3 sub esp,1A0h
00A517F9 push ebx
00A517FA push esi
00A517FB push edi
00A517FC push ecx
00A517FD lea edi,[ebp-1A0h]
00A51803 mov ecx,68h
00A51808 mov eax,0CCCCCCCCh
00A5180D rep stos dword ptr es:[edi]
00A5180F pop ecx //字符长度
00A51810 mov eax,dword ptr [__security_cookie (0A5A004h)]
00A51815 xor eax,ebp
00A51817 mov dword ptr [ebp-4],eax
00A5181A mov dword ptr [this],ecx
00A5181D mov ecx,offset _43DCD8FE_test@cpp (0A5C009h)
00A51822 call @__CheckForDebuggerJustMyCode@4 (0A5132Ah)
12: char buf[200];
13: strcpy(buf, str);
00A51827 mov eax,dword ptr [str]
00A5182A push eax
00A5182B lea ecx,[buf]
00A51831 push ecx
00A51832 call _strcpy (0A5120Dh)
00A51837 add esp,8
14: v(); //虚函数
00A5183A mov eax,dword ptr [this]
00A5183D mov edx,dword ptr [eax]
00A5183F mov esi,esp
00A51841 mov ecx,dword ptr [this]
00A51844 mov eax,dword ptr [edx]
00A51846 call eax
00A51848 cmp esi,esp
00A5184A call __RTC_CheckEsp (0A5124Eh)
15: }
00A5184F push edx
00A51850 mov ecx,ebp
00A51852 push eax
00A51853 lea edx,ds:[0A51880h]
00A51859 call @_RTC_CheckStackVars@8 (0A511EAh)
00A5185E pop eax
00A5185F pop edx
00A51860 pop edi
00A51861 pop esi
00A51862 pop ebx
00A51863 mov ecx,dword ptr [ebp-4]
00A51866 xor ecx,ebp
00A51868 call @__security_check_cookie@4 (0A51154h)
00A5186D add esp,1A0h
00A51873 cmp ebp,esp
00A51875 call __RTC_CheckEsp (0A5124Eh)
00A5187A mov esp,ebp
00A5187C pop ebp
00A5187D ret 4
下面我们使用OD来详细看一下,
程序进入到函数a()
2;攻击异常处理突破GS
四;参考文件
1;0day 安全:软件漏洞分析技术
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2021-4-8 17:17
被天象独行编辑
,原因: