分享
 
 
 

突破C++的虚拟指针--C++程序的缓冲区溢出攻击

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

backend注:本文来自Phrack56期的《SMASHING C++ VPTRS》。正如大多数国外黑客的文章,技术原理及应用都讲得比较详细,但所提供的源代码似乎总是会存在不大不小的问题。这也许是因为他们觉得应该让读者自己去研究和调试,以更好地掌握这些技术。或许以后我也会这样做。;)

测试环境:

操作系统:Red Hat 6.1 (i386)

内核版本:Kernel 2.2.14

内核补丁:None Non-executable stack patch (by Solar Design)

C++编译器:gcc

---[[ 前言 ]]--------------------------------------

到目前为止,我所掌握的缓冲区溢出程序都是针对C编程语言的。虽然C语言编程在UNIX系统中几乎无处不在,但越来越多的C++程序也开始出现了。对于大多数情况,C语言的溢出技术对于C++语言也是适用的,但C++的面向对象的特性也导致了新的缓冲区溢出技术。下面以x86 Linux系统和C++ GNU编译器为平台进行分析。

---[[ 基础--简单的C++程序 ]]--------------------------------------

我不愿在这里浪费时间讲解太多的C++语言基础。如果你对C++或面向对象编程技术一无所知,请先找本这方面的书籍看看。在继续往下看之前,请确认你已经掌握或了解以下C++术语:

1、Class(类)

2、Object(对象)

3、Method(方法)

4、Virtual(虚拟)

5、Inherit(继承)

6、Derivative(派生)

接着,把下面的两个程序看完,确认你了解每条语句的含义和作用:

// bo1.cpp

// C++基础程序

#include <stdio.h>

#include <string.h>

class MyClass

{

private:

char Buffer[32];

public:

void SetBuffer(char *String)

{

strcpy(Buffer, String);

}

void PrintBuffer()

{

printf("%s\n", Buffer);

}

};

void main()

{

MyClass Object;

Object.SetBuffer("string");

Object.PrintBuffer();

}

===========================================================

// bo2.cpp

// 有缓冲区溢出漏洞的常见C++程序

#include <stdio.h>

#include <string.h>

class BaseClass

{

private:

char Buffer[32];

public:

void SetBuffer(char *String)

{

strcpy(Buffer,String);// 存在缓冲区溢出漏洞

}

virtual void PrintBuffer()

{

printf("%s\n",Buffer);

}

};

class MyClass1:public BaseClass

{

public:

void PrintBuffer()

{

printf("MyClass1: ");

BaseClass::PrintBuffer();

}

};

class MyClass2:public BaseClass

{

public:

void PrintBuffer()

{

printf("MyClass2: ");

BaseClass::PrintBuffer();

}

};

void main()

{

BaseClass *Object[2];

Object[0] = new MyClass1;

Object[1] = new MyClass2;

Object[0]->SetBuffer("string1");

Object[1]->SetBuffer("string2");

Object[0]->PrintBuffer();

Object[1]->PrintBuffer();

}

以下是bo2.cpp编译后的运行结果:

[backend@isbase test]> ./bo2

MyClass1: string1

MyClass2: string2

[backend@isbase test]>

再一次提醒,在继续往下看时,确信你读懂了上面的程序,特别是对象虚拟(virtual)方法PrintBuffer()。与SetBuffer()方法不同,PrintBuffer方法必须在基类BaseClass的派生类MyClass1和MyClass2中声明并实现。这使得SetBuffer与PrintBuffer方法在运行时的处理会有所不同。

---[[ C++的虚拟指针(Virtual PoinTeR,VPTR)]]--------------------------------------

我们知道,虚拟方法与非虚拟方法的一个不同之处是,非虚拟方法的调用是在编译时确定(通常称为“静态绑定”),而虚拟方法的调用却是在程序时确定的(通常称为“动态绑定”)。下面以上例中的BaseClass基类及其派生类为例,对动态绑定的机制做一些解释。

编译器在编译时首先检查BaseClass基类的声明。在本例,编译器首先为私有变量Buffer(字符串型)保留32个字节,接着为非虚拟方法SetBuffer()计算并指定相应的调用地址(静态绑定处理),最后在检查到虚拟方法PrintBuffer()时,将做动态绑定处理,即在类中分配4个字节用以存放该虚拟方法的指针。结构如下:

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV

说明: B 变量Buffer占用。

V 虚拟方法指针占用。

这个指针通常被称为“VPTR”(Virtual Pointer),它指向一个“VTABLE”结构中的函数入口之一。每一个类都有一个VTABLE。如下图所示:

Object[0]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV

=+==

|

+------------------------------+

|

+--> VTABLE_MyClass1: IIIIIIIIIIIIPPPP

Object[1]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBWWWW

=+==

|

+------------------------------+

|

+--> VTABLE_MyClass2: IIIIIIIIIIIIQQQQ

说明: B 变量Buffer占用。

V 指向VTABLE_MyClass1的VPTR指针占用。

W 指向VTABLE_MyClass2的VPTR指针占用。

I 其它用途的数据

P MyClass1对象实例的PrintBuffer()方法的地址指针。

Q MyClass2对象实例的PrintBuffer()方法的地址指针。

我们可以发现,VPTR位于进程内存中Buffer变量之后。即当调用危险的strcpy()函数时有可能覆盖VPTR的内容!

根据rix的研究测试,对于Windows平台上的Visual C++ 6.0,VPTR位于对象的起始位置,因此这里提到的技术无法产生作用。这点与GNU C++有很大的不同。

---[[ 剖析VPTR ]]--------------------------------------

在Linux下当然是使用GDB来分析了:

[backend@isbase test]> gcc -o bo2 bo2.cpp

[backend@isbase test]> gdb bo2

GNU gdb 4.18

Copyright 1998 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB.Type "show warranty" for details.

This GDB was configured as "i386-redhat-linux"...

(gdb) disassemble main

Dump of assembler code for function main:

0x8049400 <main>: push %ebp

0x8049401 <main+1>: mov%esp,%ebp

0x8049403 <main+3>: sub$0x8,%esp

0x8049406 <main+6>: push %edi

0x8049407 <main+7>: push %esi

0x8049408 <main+8>: push %ebx

0x8049409 <main+9>: push $0x24

0x804940b <main+11>:call 0x804b580 <__builtin_new>

0x8049410 <main+16>:add$0x4,%esp

0x8049413 <main+19>:mov%eax,%eax

0x8049415 <main+21>:mov%eax,%ebx

0x8049417 <main+23>:push %ebx

0x8049418 <main+24>:call 0x804c90c <__8MyClass1>

0x804941d <main+29>:add$0x4,%esp

0x8049420 <main+32>:mov%eax,%esi

0x8049422 <main+34>:jmp0x8049430 <main+48>

0x8049424 <main+36>:call 0x8049c3c <__throw>

0x8049429 <main+41>:lea0x0(%esi,1),%esi

0x8049430 <main+48>:mov%esi,0xfffffff8(%ebp)

0x8049433 <main+51>:push $0x24

0x8049435 <main+53>:call 0x804b580 <__builtin_new>

0x804943a <main+58>:add$0x4,%esp

0x804943d <main+61>:mov%eax,%eax

0x804943f <main+63>:mov%eax,%esi

0x8049441 <main+65>:push %esi

0x8049442 <main+66>:call 0x804c8ec <__8MyClass2>

0x8049447 <main+71>:add$0x4,%esp

0x804944a <main+74>:mov%eax,%edi

0x804944c <main+76>:jmp0x8049455 <main+85>

0x804944e <main+78>:mov%esi,%esi

0x8049450 <main+80>:call 0x8049c3c <__throw>

0x8049455 <main+85>:mov%edi,0xfffffffc(%ebp)

0x8049458 <main+88>:push $0x804cda2

0x804945d <main+93>:mov0xfffffff8(%ebp),%eax

0x8049460 <main+96>:push %eax

0x8049461 <main+97>:call 0x804c930 <SetBuffer__9BaseClassPc>

0x8049466 <main+102>: add$0x8,%esp

0x8049469 <main+105>: push $0x804cdaa

---Type <return> to continue, or q <return> to quit---

0x804946e <main+110>: mov0xfffffffc(%ebp),%eax

0x8049471 <main+113>: push %eax

0x8049472 <main+114>: call 0x804c930 <SetBuffer__9BaseClassPc>

0x8049477 <main+119>: add$0x8,%esp

0x804947a <main+122>: mov0xfffffff8(%ebp),%edx

0x804947d <main+125>: mov0x20(%edx),%eax

0x8049480 <main+128>: add$0x8,%eax

0x8049483 <main+131>: mov0xfffffff8(%ebp),%edx

0x8049486 <main+134>: push %edx

0x8049487 <main+135>: mov(%eax),%edi

0x8049489 <main+137>: call *%edi

0x804948b <main+139>: add$0x4,%esp

0x804948e <main+142>: mov0xfffffffc(%ebp),%edx

0x8049491 <main+145>: mov0x20(%edx),%eax

0x8049494 <main+148>: add$0x8,%eax

0x8049497 <main+151>: mov0xfffffffc(%ebp),%edx

0x804949a <main+154>: push %edx

0x804949b <main+155>: mov(%eax),%edi

0x804949d <main+157>: call *%edi

0x804949f <main+159>: add$0x4,%esp

0x80494a2 <main+162>: xor%eax,%eax

0x80494a4 <main+164>: jmp0x80494d0 <main+208>

0x80494a6 <main+166>: jmp0x80494d0 <main+208>

0x80494a8 <main+168>: push %ebx

0x80494a9 <main+169>: call 0x804b4f0 <__builtin_delete>

0x80494ae <main+174>: add$0x4,%esp

0x80494b1 <main+177>: jmp0x8049424 <main+36>

0x80494b6 <main+182>: push %esi

0x80494b7 <main+183>: call 0x804b4f0 <__builtin_delete>

0x80494bc <main+188>: add$0x4,%esp

0x80494bf <main+191>: jmp0x8049450 <main+80>

0x80494c1 <main+193>: jmp0x80494c8 <main+200>

0x80494c3 <main+195>: call 0x8049c3c <__throw>

0x80494c8 <main+200>: call 0x8049fc0 <terminate__Fv>

0x80494cd <main+205>: lea0x0(%esi),%esi

0x80494d0 <main+208>: lea0xffffffec(%ebp),%esp

0x80494d3 <main+211>: pop%ebx

0x80494d4 <main+212>: pop%esi

0x80494d5 <main+213>: pop%edi

---Type <return> to continue, or q <return> to quit---

0x80494d6 <main+214>: leave

0x80494d7 <main+215>: ret

0x80494d8 <main+216>: nop

0x80494d9 <main+217>: nop

0x80494da <main+218>: nop

0x80494db <main+219>: nop

0x80494dc <main+220>: nop

0x80494dd <main+221>: nop

0x80494de <main+222>: nop

0x80494df <main+223>: nop

End of assembler dump.

(gdb)

以下是对该程序汇编代码的解释:

0x8049400 <main>: push %ebp

0x8049401 <main+1>: mov%esp,%ebp

0x8049403 <main+3>: sub$0x8,%esp

0x8049406 <main+6>: push %edi

0x8049407 <main+7>: push %esi

0x8049408 <main+8>: push %ebx

构建堆栈。为Object[]数组保留8个字节(即两个4字节指针地址),则Object[0]的指针存放在0xfffffff8(%ebp),Object[1]的指针存放在0fffffffc(%ebp)。接着保存寄存器。

0x8049409 <main+9>: push $0x24

0x804940b <main+11>:call 0x804b580 <__builtin_new>

0x8049410 <main+16>:add$0x4,%esp

首先调用__builtin_new,在堆(heap)中分配0x24(36字节)给Object[0],并将其首地址保存到EAX寄存器中。这36字节中前32字节是Buffer变量的,后4字节由VPTR占用。

0x8049413 <main+19>:mov%eax,%eax

0x8049415 <main+21>:mov%eax,%ebx

0x8049417 <main+23>:push %ebx

0x8049418 <main+24>:call 0x804c90c <__8MyClass1>

0x804941d <main+29>:add$0x4,%esp

将对象的首地址压栈,然后调用__8MyClass1函数。这其实是MyClass1对象的构造函数(constructor)。

(gdb) disassemble __8MyClass1

Dump of assembler code for function __8MyClass1:

0x804c90c <__8MyClass1>:push %ebp

0x804c90d <__8MyClass1+1>:mov%esp,%ebp

0x804c90f <__8MyClass1+3>:push %ebx

0x804c910 <__8MyClass1+4>:mov0x8(%ebp),%ebx

寄存器EBX现在存放着指向分配的36个字节的指针(在C++语言中,称之为"This"指针)。

0x804c913 <__8MyClass1+7>:push %ebx

0x804c914 <__8MyClass1+8>:call 0x804c958 <__9BaseClass>

0x804c919 <__8MyClass1+13>: add$0x4,%esp

首先调用基类BaseClass的构造函数。

(gdb) disassemble __9BaseClass

Dump of assembler code for function __9BaseClass:

0x804c958 <__9BaseClass>: push %ebp

0x804c959 <__9BaseClass+1>: mov%esp,%ebp

0x804c95b <__9BaseClass+3>: mov0x8(%ebp),%edx

寄存器EDX现在存放着指向分配的36个字节的指针("This"指针)。

0x804c95e <__9BaseClass+6>: movl $0x804e01c,0x20(%edx)

将0x804e01c存放到EDX+0x20(=EDX+32)。让我们看看该0x804e01c地址内存数据:

(gdb) x 0x804e01c

0x804e01c <__vt_9BaseClass>:0x00000000

可以看到这个存放到EDX+0x20(即该对象的VPTR位置)的地址是基类BaseClass的VTABLE地址。

现在回到MyClass1对象的构造函数:

0x804c91c <__8MyClass1+16>: movl $0x804e010,0x20(%ebx)

将0x804e010存放到EBX+0x20(即VPTR)。同样让我们看看该0x804e010地址内存数据:

(gdb) x 0x804e010

0x804e010 <__vt_8MyClass1>: 0x00000000

现在,我们知道VPTR被改写了,再在它的内容是MyClass1对象的VTABLE地址。当返回到main()函数时寄存器EAX中存放着该对象在内存中的指针。

0x8049420 <main+32>:mov%eax,%esi

0x8049422 <main+34>:jmp0x8049430 <main+48>

0x8049424 <main+36>:call 0x8049c3c <__throw>

0x8049429 <main+41>:lea0x0(%esi,1),%esi

0x8049430 <main+48>:mov%esi,0xfffffff8(%ebp)

将得到的地址指针赋予Object[0]。然后程序对Object[1]进行同样的处理,只不过返回的地址不同罢了。在经过以上对象初始化处理后,将执行以下指令:

0x8049458 <main+88>:push $0x804cda2

0x804945d <main+93>:mov0xfffffff8(%ebp),%eax

0x8049460 <main+96>:push %eax

将0x804cda2和Object[0]的值压栈。观察一下0x804cda2的内容:

(gdb) x/s 0x804cda2

0x804cda2 <_IO_stdin_used+30>: "string1"

可知该地址存放了将要通过基类BaseClass的SetBuffer函数拷贝到Buffer中的字符串"string1"。

0x8049461 <main+97>:call 0x804c930 <SetBuffer__9BaseClassPc>

0x8049466 <main+102>: add$0x8,%esp

调用基类BaseClass的SetBuffer()方法。注意到这种SetBuffer方法的调用是“静态绑定”(因为它不是虚拟方法)。对Object[1]的处理也是一样的。

为了验证这两个对象在运行时都被正确地初始化,我们将要设置如下断点:

0x8049410: 获得第一个对象的地址。

0x804943a: 获得第二个对象的地址。

0x804947a: 检验对象的初始化是否正确。

(gdb) break *0x8049410

Breakpoint 1 at 0x8049410

(gdb) break *0x804943a

Breakpoint 2 at 0x804943a

(gdb) break *0x804947a

Breakpoint 3 at 0x804947a

现在运行这个程序:

St

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有