棧溢出原理與實現


緩沖區溢出

  • 在大緩沖區的數據向小緩沖區復制的過程中,由於沒注意小緩沖區的邊界,“撐爆”了較小的緩沖區,從而沖掉了和小緩沖區相鄰內存區域的其他數據而引起的內存問題。

無論什么計算機架構,進程使用的內存都可以按照功能大致分為4個部分:

  (1)代碼區:這個區域存儲着被裝入的執行的二進制代碼,處理器會到這個區域取指並執行。

  (2)數據區:用於存儲局部變量。

  (3)堆區:進程可以在堆區中動態的請求一定大小的內存,並在用完之后歸還個堆區。動態分配和回收是堆區的特點。

  (4)棧區:用於動態的存儲函數之間的調用關系。以保證被調用函數在返回時恢復到母函數中繼續執行。

棧與系統棧

  • 棧:指的是一種數據結構,是一種先入后出的數據表。系統棧:指的是內存中的棧,由系統自動維護,他用於實現高級語言中的函數調用。

  • 系統棧:指的是內存中的棧,由系統自動維護,他用於實現高級語言中的函數調用。

函數調用過程

  當函數被調用時,系統棧會為這個函數新開辟一個棧幀,並把它壓入棧中,這個棧幀的內存空間被它所屬的函數獨占,正常情況下是不會和別的函數共享的。

  函數調用大致包括以下幾個步驟:

  • (1)參數入棧:將參數從右向左依次壓入系統棧。

  • (2)返回地址入棧:將當前代碼區調用的下一條指令地址壓入棧中,供函數返回時繼續執行。

  • (3)代碼區跳轉:處理器從當前代碼區跳到被執行函數入口。

  • (3)棧幀調整:1.保存當前棧幀狀態,已被后面恢復本棧幀使用(push ebp

                         2.將當前棧幀切換到新的棧幀(mov ebp,esp

                         3.給新棧幀分配空間(把ESP減去所需空間大小,抬高棧頂)

例如:

  對於_stdcall 調用約定,函數調用時用到的指令序列如下:

 

          ;調用前


 push 參數3;         ;假設該函數有3個參數,將從右向左依次入棧
 push 參數2;
 push 參數1;
 call  函數地址;      ; 該指令同時完成兩件事:(a)向棧中壓入當前指令在內存中                             

                                          ;的位置,及保存返回地址

                                          ;(b)跳轉到函數地址

 push  ebp
 mov  ebp,esp
 sub   esp,xxx

 

  類似的函數返回步驟:

  • (1)保存返回值,通常將函數返回值保存在寄存器eax中。

  • (2)彈出當前棧幀,恢復上一個棧幀

 

 add  esp,xxx ; 降低棧頂,回收當前棧幀
 pop  ebp      ; 將上一個棧幀底部位置ebp恢復
 retn         ; 這個指令有兩個作用 (a)彈出當前棧頂元素,即彈出棧幀中的返回地址,
                                  ;至此,棧幀恢復。
 		                 ;(b)讓處理器跳轉到彈出的返回地址,恢復調用前的代碼

  

  寄存器與函數棧幀

  每一個函數獨占自己的棧幀空間。當前運行的函數的棧幀總在棧頂。Win32系統提供兩個特殊的寄存器用於標識位於系統的棧頂端的棧幀。

  • (1)ESP:棧指針寄存器,其內存中是一個指針,該指針永遠指系統棧最上面的一個棧幀的棧頂。

  • (2)EBP:基址指針寄存器,其內存中是一個指針,該指針永遠指向系統棧最上面的要給棧幀的底部。

  • (3)EIP:指令寄存器,其內存中是一個指針,該指針永遠指向下一條等待執行的指令地址。

  函數調用約定

調用約定

_cdecl

_fastcall

_stdcall

參數入棧順序

右→左

右→左

右→左

恢復平衡的位置

調用者

函數本身

函數本身

 

  修改鄰接變量

  通過上面的知識我們知道,函數的調用細節和棧中的數據分布情況,函數的局部變量在棧中一個挨着一個排着,如果這些局部變量中有數組之類的緩沖區,並且程序中存在數

 組越界的缺陷,那么越界的數組元素就有可能破壞棧中相鄰變量的值,甚至破壞棧幀中保存的ebp的值、返回地址等重要數據。

  下面舉個例子,來說明一下破壞棧內局部變量對程序安全性的影響:

  

 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 }

 

  當我們輸入的是qqqqqqq,上述代碼verify_password棧幀布局:

                        

  由此可知,在verify_password棧幀中,局部變量authenticated,位於緩沖區buffer[8]的下方,authenticatedint型,在內存中占4個字節,所以,如果能讓buffer數組越界,就能夠影

 響到authenticated在程序中,authenticated0表示驗證成功,為1表示驗證失敗,我們通過讓buffer數組越界,達到修改authenticated值得目的。

  通過我們輸入可以造成緩沖區溢出,導致authenticated的值被修改。所以當我們輸入8個字符,第9個字符,作為結尾的NULL字符,將剛好寫到authenticated內存的低位上去,導致

 authenticated0x00000001 變為 0x00000000,驗證通過。

  修改函數返回地址

  上述的的修改鄰接變量的方法是很有用的,但是這種漏洞利用對代碼的環境要求相對苛刻,更強大、更通用的攻擊通過緩沖區溢出改寫的目標往往不是一個變量,而是瞄准棧幀的

 最下方的EBP和函數返回地址等棧幀狀態。

  也就是說,我們繼續增加輸入的字符串長度,超出buffer[8]邊界,一次淹沒authenticated、前棧幀EBP、返回地址。也就是說,控制好字符串長度就可以讓字符串中相應的位置字符

 的ASCII覆蓋這些棧幀的狀態。

  當我們輸入一個足夠長的字符串是,程序崩潰,這是由於字符串足夠的長,淹沒了程序的返回地址,我們知道,當我們程序執行完畢之后,在執行retn指令時,棧頂恰好就是源程

 序的返回地址,”retn”指令會把這個地址pop,彈入eip寄存器中,之后跳轉到這個地址去執行。

  

  程序崩潰的原因是因為,函數返回地址裝入eip ,但是eip由於緩沖區溢出,淹沒了,將值改變,程序執行找不到對應地址的指令,導致程序崩潰。但是,如果我們給出一個有效

 的地址,就可以讓處理器跳轉到任意的代碼去執行,也就是說,我們可以通過淹沒返回地址從而控制程序的執行。

  下面舉個例子,通過緩沖區溢出,淹沒eip,修改eip寄存器,從而控制程序執行:

 

 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 }

  通過OD分析可得:

  沒有淹沒時,verify_password棧幀如下:

      

  當淹沒之后,verify_password棧幀如下:

      

  eip已經被修改,成功。很開心! 

  當我們可以利用棧溢出這一漏洞,修改eip,我們就可以干一些更牛的事情,讓進程執行輸入的數據的代碼。

    下面舉個例子,通過我們向password里添加一些機器指令,實現彈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 }

 

   直接同過buffer中寫入代碼,這次的例子中,buffer定義的足夠大,就是為了能將我們自己彈窗的代碼完整的存放在里面,當我們執行拷貝,棧溢出,淹沒了棧幀,將返回地址設

 置為buffer的首地址,此時,當函數棧retn之后,到返回地址繼續執行,這就實現了我們的目的。

   通過OD分析可得,在沒發生拷貝前,沒有棧溢出時,verify_password棧幀如下:

   

   在發生拷貝后,產生了棧溢出,verify_password棧幀如下:

   

   在retn之后,eip指向buffer的基地址,進行程序的向下執行:

  

    最終彈出對話框:

 

   本文的實現,主要是通過參考《0day安全_軟件漏洞分析技術(第二版)》進行學習,本文中的代碼實現如下,點擊下載:

     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

   


免責聲明!

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



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