棧溢出介紹


 

棧溢出介紹

 

零、前言:

在打pwnable.kr的passcode題目的時候,發現了自己存在一些基礎薄弱,需要補充回來,這是棧溢出的筆記。

 

一、進程內存:

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

1、代碼區:

存儲着被轉入的執行的二進制代碼,處理器會到這個區域獲取指令並執行。

 

2、數據區:

用來存儲局部變量

 

3、堆區:

進程可以在堆區中動態請求一定大小的內存,並在用完之后歸還給堆區。

動態分配和回收是堆區的特點。

 

4、棧區:

用於動態的存儲函數之間的調用關系。以保證被調用函數在返回時恢復到主函數中繼續執行。

 

二、棧:棧和系統棧

棧:棧是一種數據結構,是一種先入后出的數據表,按照一定的規則進行添加和刪除數據。

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

 

三、溢出:

在達緩沖區的數據向小緩沖區復制的過程中,由於沒有注意小緩沖區的邊界,導致小緩沖區滿了,從而覆蓋了和小緩沖區相鄰內存區域的其他數據而引起的內存問題。

 

四、X86通用寄存器:

1、

 

 

PUSH:壓入

POP:彈出,刪除

MOV:復制

SUB: sub,esp xxx  即預留xxx字節

 

2、

 

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

 

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

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

 

3、

EAX:累加器,多種加法乘法指令的默認寄存器

EBX:基地址寄存器,在內存尋址是存放基地址

ECX:計數器,重復(REP)前綴指令和LOOP指令的內定計數器

EDX:被用來放整數除法產生的余數

ESI/EDI:源/目標索引寄存器,在很多字符串操作指令中,DS:ESI指向源串,ES:EDI指向目標串。

 

五、函數調用過程:

1、步驟:

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

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

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

(4)棧幀調整:

1)保存當前棧幀狀態,以被后面恢復本棧幀使用(push ebp)

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

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

 

2、圖例:

1)普通C程序的內存布局:

Text:

包含要執行的程序代碼

Data:

包含程序需要的全局數據、資源等

Stack:

包含函數的輸入參數,返回地址以及保存函數的局部變量等。

Stack是后進先出的結構。隨着函數的調用,它在內存中(從高地址到低地址)向下尋址。

Heap:保存所有動態分配的內存。每當用malloc(動態分配內存)分配獲取內存指針時,這個地址就是從堆中分配的。

 

2)重點關注三大寄存器:EBP(棧底)、ESP(棧頂)、EIP(指向)

當執行一個函數的時候,相關參數以及局部變量等都會被記錄在ESP、EBP中間的區域。

一旦函數執行完畢,相關棧幀就會從堆棧中彈出,然后從預先保存好的上下文中進行恢復,以便保持堆棧平衡。

CPU必須要知道函數調用完了后的EIP指向(要去哪里執行),這需要堆棧彈出的過程中進行EIP賦指。

 

3)main函數調用func()函數的程序:

main函數調用func函數,程序運行后,依次將所有的參數壓入棧中,

func函數執行完成后,相應的棧幀依次彈出,此時存儲返回值的地址被加載到EIP寄存器中,以繼續執行main函數中剩余的部分。

目的就是要控制這個返回值,劫持func函數返回到指定的惡意代碼中區。

3、例子A:

(1)代碼:

 

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 void function2(){  5     printf("Execution flow changed\n");  6 }  7 void function1(char *str){  8     char buffer[5];  9  strcpy(buffer,str); 10 } 11 void main(int argc,char *argv[]){ 12     function1(argv[1]); 13     printf("%s\n","Executed normally"); 14 }

 

 

編譯:

1 root@kali:~/test# gcc -g -fno-stack-protector -z execstack -o test1 test1.c

 

1)-g:將調試信息記憶符號等編譯到程序中

2)-fno -stack-protestor:關閉堆棧保護機制

3)-z execstack:打開堆棧可執行機制,即關閉堆棧執行保護

 

運行與崩潰:

 

(2)審計調試:

PS:strcpy函數沒有對參數進行檢查邊界大小。

 

gdb調試:

1)使用list顯示源代碼,然后在strcpy和函數返回的地方下斷點

 

2)反復運行測試,變換參數內容,找到崩潰的地址

EBP和ESP地址

 

strcpy執行中的EBP和ESP地址

 

strcpy執行過后的EBP和ESP

 

3)找到EIP地址

 注意:EIP在EBP的下方,即

 

4)找到function2的開始地址:

 

(3)利用:

1)利用思路:

 

 

2)利用payload:

A: 相對於EBP(找到頭暈)

B:相對於ESP(找到頭暈+1)

C:直接地址相加:

 

 

1 (gdb) run $(python -c 'print "A" *17 + "\xb9\x11\x40\x00"')

 

 

 

4、例子B:

 

(1)代碼:

 

 

(2)函數調用過程:

1、壓入寄存器eax,ecx,edx等

2、壓入參數arg2,arg1

3、壓人返回地址eip

4、壓入ebp

5、壓入temp

6、壓入buffer

7、壓入ebx,esi和edi等寄存器

 

(3)利用:

1)思路:

由於局部變量buffer的長度是4個字節,arg1參數可控,

而strcpy函數並沒有判斷參數的長度,

所以傳入的arg1的長度大於4個字節時,多出來的字節將會依次覆蓋掉局部變量,ebp,返回地址等

通過精心構造arg1的值,就可以將返回地址覆蓋為任意想要的值,挑到shellcode。

2)利用:

 

 

5、例子C:修改鄰接變量

函數的局部變量在棧中一個挨着一個,如果這些局部比辦理中有數組之類的緩沖區,

並且程序中存在數組越界的缺陷,那么越界的數組元素就有可能破壞棧中相鄰變量的值,

甚至破壞棧中保存的ebp值、eip值等重要數據。 

 

(1)代碼:

 

 1 #include <stdio.h>
 2 #include <string.h>
 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 }

 

(2)審計:

1)main函數輸入password,調用verify_password函數驗證輸入的password是否等於1234567,相等返回0,否則返回1。

 

2)strcpy函數存在漏洞和buffer[8],構造利用條件

 

3)利用緩沖區溢出,修改值,返回0,完成驗證。

思路:在verify_password棧幀中

authenticated位於buffer[8]的下方

authenticated是int型,在內存中占4個字節

buffer[8]占8個字節

控制buffer[]填滿8個字節,然后越界1個字節,緩沖區溢出,使得原authenticated的1覆蓋為0,返回通過。 

 

6、例子C:修改函數返回地址

 控制buffer[8]越界,覆蓋其他值,修改eip

 

六、溢出關鍵函數:

1、輸入:

gets

scanf

vscanf

2、輸出:

sprintf

3、字符串:

strcpy

strcat

bcopy

 

4、等

 

七、棧溢出步驟:

1、尋找危險函數:

2、確定填充長度:計算所能操作的地址和所要覆蓋的地址的距離長度

(1)方法:

1)相對於棧底地址

2)相對於棧頂地址

3)直接地址

 

(2)覆蓋:

1)覆蓋函數返回地址

2)覆蓋變量內容

3)覆蓋bss段某變量內容:bss段(指用來存放程序中未初始化的全局變量的一塊內存區域,bss段屬於靜態內存分配。)

4)等

 

3、利用:

當完全控制這個程序后,使用一個直接存在指令地址來覆蓋這個返回地址后,CPU將會轉而執行我們的指令。

在uinx/Linux系統中,指令可以執行一個shell,這個shell將會獲得和被溢出程序相同的權限。

如該程序是root權限,那么就會獲得root shell

 

八、參考鏈接:

https://www.cnblogs.com/Donoy/p/5690402.html

https://www.sohu.com/a/226035403_268160

https://www.jianshu.com/p/58d03dd3680a

https://blog.csdn.net/aemperor/article/details/47310593

https://blog.csdn.net/guiguzi1110/article/details/77663046

 

 


免責聲明!

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



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