【转载】栈溢出原理及实现
緩沖區(qū)溢出
-
在大緩沖區(qū)的數(shù)據(jù)向小緩沖區(qū)復(fù)制的過(guò)程中,由于沒(méi)注意小緩沖區(qū)的邊界,“撐爆”了較小的緩沖區(qū),從而沖掉了和小緩沖區(qū)相鄰內(nèi)存區(qū)域的其他數(shù)據(jù)而引起的內(nèi)存問(wèn)題。
無(wú)論什么計(jì)算機(jī)架構(gòu),進(jìn)程使用的內(nèi)存都可以按照功能大致分為4個(gè)部分:
(1)代碼區(qū):這個(gè)區(qū)域存儲(chǔ)著被裝入的執(zhí)行的二進(jìn)制代碼,處理器會(huì)到這個(gè)區(qū)域取指并執(zhí)行。
(2)數(shù)據(jù)區(qū):用于存儲(chǔ)局部變量。
(3)堆區(qū):進(jìn)程可以在堆區(qū)中動(dòng)態(tài)的請(qǐng)求一定大小的內(nèi)存,并在用完之后歸還個(gè)堆區(qū)。動(dòng)態(tài)分配和回收是堆區(qū)的特點(diǎn)。
(4)棧區(qū):用于動(dòng)態(tài)的存儲(chǔ)函數(shù)之間的調(diào)用關(guān)系。以保證被調(diào)用函數(shù)在返回時(shí)恢復(fù)到母函數(shù)中繼續(xù)執(zhí)行。
棧與系統(tǒng)棧
-
棧:指的是一種數(shù)據(jù)結(jié)構(gòu),是一種先入后出的數(shù)據(jù)表。系統(tǒng)棧:指的是內(nèi)存中的棧,由系統(tǒng)自動(dòng)維護(hù),他用于實(shí)現(xiàn)高級(jí)語(yǔ)言中的函數(shù)調(diào)用。
- 系統(tǒng)棧:指的是內(nèi)存中的棧,由系統(tǒng)自動(dòng)維護(hù),他用于實(shí)現(xiàn)高級(jí)語(yǔ)言中的函數(shù)調(diào)用。
函數(shù)調(diào)用過(guò)程
當(dāng)函數(shù)被調(diào)用時(shí),系統(tǒng)棧會(huì)為這個(gè)函數(shù)新開辟一個(gè)棧幀,并把它壓入棧中,這個(gè)棧幀的內(nèi)存空間被它所屬的函數(shù)獨(dú)占,正常情況下是不會(huì)和別的函數(shù)共享的。
函數(shù)調(diào)用大致包括以下幾個(gè)步驟:
-
(1)參數(shù)入棧:將參數(shù)從右向左依次壓入系統(tǒng)棧。
-
(2)返回地址入棧:將當(dāng)前代碼區(qū)調(diào)用的下一條指令地址壓入棧中,供函數(shù)返回時(shí)繼續(xù)執(zhí)行。
-
(3)代碼區(qū)跳轉(zhuǎn):處理器從當(dāng)前代碼區(qū)跳到被執(zhí)行函數(shù)入口。
-
(3)棧幀調(diào)整:1.保存當(dāng)前棧幀狀態(tài),已被后面恢復(fù)本棧幀使用(push ebp)
? ? ? ? ? ? ? ? ? ? ?2.將當(dāng)前棧幀切換到新的棧幀(mov ebp,esp)
? ? ? ? ? ? ? ? ? ? ?3.給新棧幀分配空間(把ESP減去所需空間大小,抬高棧頂)
例如:
對(duì)于_stdcall?調(diào)用約定,函數(shù)調(diào)用時(shí)用到的指令序列如下:
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ?????????;調(diào)用前 push 參數(shù)3;???????? ;假設(shè)該函數(shù)有3個(gè)參數(shù),將從右向左依次入棧 push 參數(shù)2; push 參數(shù)1; call? 函數(shù)地址;????? ; 該指令同時(shí)完成兩件事:(a)向棧中壓入當(dāng)前指令在內(nèi)存中???????????????????????????? ?????????????????????????????????????????;的位置,及保存返回地址 ?????????????????????????????????????????;(b)跳轉(zhuǎn)到函數(shù)地址 push? ebp mov? ebp,esp sub?? esp,xxx |
類似的函數(shù)返回步驟:
-
(1)保存返回值,通常將函數(shù)返回值保存在寄存器eax中。
-
(2)彈出當(dāng)前棧幀,恢復(fù)上一個(gè)棧幀
?
| 1 2 3 4 5 | add? esp,xxx ; 降低棧頂,回收當(dāng)前棧幀 pop? ebp????? ; 將上一個(gè)棧幀底部位置ebp恢復(fù) retn???????? ; 這個(gè)指令有兩個(gè)作用 (a)彈出當(dāng)前棧頂元素,即彈出棧幀中的返回地址, ?????????????????????????????????;至此,棧幀恢復(fù)。 ????????????????????????;(b)讓處理器跳轉(zhuǎn)到彈出的返回地址,恢復(fù)調(diào)用前的代碼 |
寄存器與函數(shù)棧幀
每一個(gè)函數(shù)獨(dú)占自己的棧幀空間。當(dāng)前運(yùn)行的函數(shù)的棧幀總在棧頂。Win32系統(tǒng)提供兩個(gè)特殊的寄存器用于標(biāo)識(shí)位于系統(tǒng)的棧頂端的棧幀。
-
(1)ESP:棧指針寄存器,其內(nèi)存中是一個(gè)指針,該指針永遠(yuǎn)指系統(tǒng)棧最上面的一個(gè)棧幀的棧頂。
-
(2)EBP:基址指針寄存器,其內(nèi)存中是一個(gè)指針,該指針永遠(yuǎn)指向系統(tǒng)棧最上面的要給棧幀的底部。
-
(3)EIP:指令寄存器,其內(nèi)存中是一個(gè)指針,該指針永遠(yuǎn)指向下一條等待執(zhí)行的指令地址。
函數(shù)調(diào)用約定
| 調(diào)用約定 | _cdecl | _fastcall | _stdcall |
| 參數(shù)入棧順序 | 右→左 | 右→左 | 右→左 |
| 恢復(fù)平衡的位置 | 調(diào)用者 | 函數(shù)本身 | 函數(shù)本身 |
?
修改鄰接變量
通過(guò)上面的知識(shí)我們知道,函數(shù)的調(diào)用細(xì)節(jié)和棧中的數(shù)據(jù)分布情況,函數(shù)的局部變量在棧中一個(gè)挨著一個(gè)排著,如果這些局部變量中有數(shù)組之類的緩沖區(qū),并且程序中存在數(shù)
組越界的缺陷,那么越界的數(shù)組元素就有可能破壞棧中相鄰變量的值,甚至破壞棧幀中保存的ebp的值、返回地址等重要數(shù)據(jù)。
下面舉個(gè)例子,來(lái)說(shuō)明一下破壞棧內(nèi)局部變量對(duì)程序安全性的影響:
1 #include <IOSTREAM>2 using namespace std;3 #define PASS_WORD "1234567"4 5 int verify_password(char* password)6 {7 int authentitated;8 char buffer[8];9 authentitated = strcmp(password,PASS_WORD); 10 strcpy(buffer,password); 11 return authentitated; 12 } 13 14 int main() 15 { 16 int valid_flag = 0; 17 char password[1024] = {0}; 18 while (1) 19 { 20 printf("please input password:"); 21 scanf("%s",password); 22 valid_flag = verify_password(password); 23 if(valid_flag) 24 { 25 printf("incorrect password!\r\n"); 26 } 27 else 28 { 29 printf("Congratulation! you have passed the verification!\r\n"); 30 } 31 } 32 return 0; 33 }
?
當(dāng)我們輸入的是qqqqqqq,上述代碼verify_password棧幀布局:
? ? ? ? ? ? ? ? ? ? ? ??
由此可知,在verify_password棧幀中,局部變量authenticated,位于緩沖區(qū)buffer[8]的下方,authenticated是int型,在內(nèi)存中占4個(gè)字節(jié),所以,如果能讓buffer數(shù)組越界,就能夠影
響到authenticated。在程序中,authenticated為0表示驗(yàn)證成功,為1表示驗(yàn)證失敗,我們通過(guò)讓buffer數(shù)組越界,達(dá)到修改authenticated值得目的。
通過(guò)我們輸入可以造成緩沖區(qū)溢出,導(dǎo)致authenticated的值被修改。所以當(dāng)我們輸入8個(gè)字符,第9個(gè)字符,作為結(jié)尾的NULL字符,將剛好寫到authenticated內(nèi)存的低位上去,導(dǎo)致
authenticated由?0x00000001?變?yōu)?x00000000,驗(yàn)證通過(guò)。
修改函數(shù)返回地址
上述的的修改鄰接變量的方法是很有用的,但是這種漏洞利用對(duì)代碼的環(huán)境要求相對(duì)苛刻,更強(qiáng)大、更通用的攻擊通過(guò)緩沖區(qū)溢出改寫的目標(biāo)往往不是一個(gè)變量,而是瞄準(zhǔn)棧幀的
最下方的EBP和函數(shù)返回地址等棧幀狀態(tài)。
也就是說(shuō),我們繼續(xù)增加輸入的字符串長(zhǎng)度,超出buffer[8]邊界,一次淹沒(méi)authenticated、前棧幀EBP、返回地址。也就是說(shuō),控制好字符串長(zhǎng)度就可以讓字符串中相應(yīng)的位置字符
的ASCII碼覆蓋這些棧幀的狀態(tài)。
當(dāng)我們輸入一個(gè)足夠長(zhǎng)的字符串是,程序崩潰,這是由于字符串足夠的長(zhǎng),淹沒(méi)了程序的返回地址,我們知道,當(dāng)我們程序執(zhí)行完畢之后,在執(zhí)行retn指令時(shí),棧頂恰好就是源程
序的返回地址,”retn”指令會(huì)把這個(gè)地址pop,彈入eip寄存器中,之后跳轉(zhuǎn)到這個(gè)地址去執(zhí)行。
??
程序崩潰的原因是因?yàn)?#xff0c;函數(shù)返回地址裝入eip?,但是eip由于緩沖區(qū)溢出,淹沒(méi)了,將值改變,程序執(zhí)行找不到對(duì)應(yīng)地址的指令,導(dǎo)致程序崩潰。但是,如果我們給出一個(gè)有效
的地址,就可以讓處理器跳轉(zhuǎn)到任意的代碼去執(zhí)行,也就是說(shuō),我們可以通過(guò)淹沒(méi)返回地址從而控制程序的執(zhí)行。
下面舉個(gè)例子,通過(guò)緩沖區(qū)溢出,淹沒(méi)eip,修改eip寄存器,從而控制程序執(zhí)行:
?
1 #include <IOSTREAM>2 using namespace std;3 #define PASS_WORD "1234567"4 int verify_password(char* password)5 {6 int authentitated;7 char szBuffer[8];8 authentitated = strcmp(password,PASS_WORD);9 strcpy(szBuffer,password); 10 return authentitated; 11 } 12 int main() 13 { 14 int valid_flag = 0; 15 char password[1024] = {0}; 16 FILE* fp ; 17 fp=fopen("password.txt","rw+"); 18 19 if(fp==NULL) 20 { 21 exit(0); 22 } 23 24 fscanf(fp,"%s",password); 25 valid_flag = verify_password(password); 26 27 if(valid_flag) 28 { 29 printf("incorrect password!\r\n"); 30 } 31 else 32 { 33 printf("Congratulation! you have passed the verification!\r\n"); 34 } 35 fclose(fp); 36 37 getchar(); 38 return 0; 39 }通過(guò)OD分析可得:
沒(méi)有淹沒(méi)時(shí),verify_password棧幀如下:
? ? ??
當(dāng)淹沒(méi)之后,verify_password棧幀如下:
? ? ??
eip已經(jīng)被修改,成功。很開心!
當(dāng)我們可以利用棧溢出這一漏洞,修改eip,我們就可以干一些更牛的事情,讓進(jìn)程執(zhí)行輸入的數(shù)據(jù)的代碼。
? ?下面舉個(gè)例子,通過(guò)我們向password里添加一些機(jī)器指令,實(shí)現(xiàn)彈MessageBox。
?
1 #include <IOSTREAM>2 #include <Windows.h>3 using namespace std;4 #define PASS_WORD "1234567"5 int verify_password(char* password)6 {7 int authentitated;8 char szBuffer[44];9 authentitated = strcmp(password,PASS_WORD); 10 strcpy(szBuffer,password); 11 return authentitated; 12 } 13 14 int main() 15 { 16 int valid_flag = 0; 17 char password[1024] = {0}; 18 FILE* fp ; 19 fp=fopen("password.txt","rw+"); 20 21 HMODULE h = LoadLibrary("user32.dll"); 22 printf("%x\r\n",h); 23 //0x77760000 24 //0x000774C0 25 //0x777D74C0 //MessageBox地址 26 //0x0018FA88 //buffer 的地址 27 28 if(fp==NULL) 29 { 30 exit(0); 31 } 32 fscanf(fp,"%s",password); 33 valid_flag = verify_password(password); 34 35 if(valid_flag) 36 { 37 printf("incorrect password!\r\n"); 38 } 39 else 40 { 41 printf("Congratulation! you have passed the verification!\r\n"); 42 } 43 fclose(fp); 44 return 0; 45 }?
直接同過(guò)buffer中寫入代碼,這次的例子中,buffer定義的足夠大,就是為了能將我們自己彈窗的代碼完整的存放在里面,當(dāng)我們執(zhí)行拷貝,棧溢出,淹沒(méi)了棧幀,將返回地址設(shè)
置為buffer的首地址,此時(shí),當(dāng)函數(shù)棧retn之后,到返回地址繼續(xù)執(zhí)行,這就實(shí)現(xiàn)了我們的目的。
通過(guò)OD分析可得,在沒(méi)發(fā)生拷貝前,沒(méi)有棧溢出時(shí),verify_password棧幀如下:
在發(fā)生拷貝后,產(chǎn)生了棧溢出,verify_password棧幀如下:
在retn之后,eip指向buffer的基地址,進(jìn)行程序的向下執(zhí)行:
?最終彈出對(duì)話框:
?
本文的實(shí)現(xiàn),主要是通過(guò)參考《0day安全_軟件漏洞分析技術(shù)(第二版)》進(jìn)行學(xué)習(xí),本文中的代碼實(shí)現(xiàn)如下,點(diǎn)擊下載:
? ?http://files.cnblogs.com/files/Donoy/%E6%A0%88%E6%BA%A2%E5%87%BA%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E7%8E%B0.zip
?
轉(zhuǎn)載于:https://www.cnblogs.com/wh4am1/p/6623039.html
總結(jié)
以上是生活随笔為你收集整理的【转载】栈溢出原理及实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Windows: 在系统启动时运行程序、
- 下一篇: bzoj2957:楼房重建