1. 程式人生 > 其它 >棧溢位漏洞原理詳解與利用

棧溢位漏洞原理詳解與利用

0x01 前言

和我一樣,有一些計算機專業的同學可能一直都在不停地碼程式碼,卻很少關注程式是怎麼執行的,也不會考慮到自己寫的程式碼是否會存在棧溢位漏洞,藉此機會我們一起走進棧溢位。

0x02 程式是怎麼執行的

在瞭解棧溢位之前我們先了解一下程式執行過程

程式的執行過程可看作連續的函式呼叫。當一個函式執行完畢時,程式要回到call指令的下一條指令繼續執行,函式呼叫過程通常使用堆疊實現

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
 test1(1);
 test2(2);
 test3(3);
 return 0;
}
int test1(int test1){
int a = 6;
 printf("1");
 return 1;
}
int test2(int test2){
 printf("2");
 return 2;
}
int test3(int test3){
 printf("3");
 return 3;
}

編譯成32位可執行檔案,放在ollydbg中就行除錯,來詳細看一下執行過程

因為程式的執行可以看做一個一個函式的執行(main函式也一樣),因此我們挑選其中一個即可,在test1()函式設定斷點

F7單步除錯第一步mov dword ptr ss:[esp],0x1,進行傳參,簡潔明瞭。

第二步call mian.00401559,進入test(),這裡我們關注一下esp和棧頂值,將該指令的下一條指令的地址進行壓棧,既然有壓棧那麼就會有出棧,這就與函式中的retn指令形成呼應。

第三步push ebp,就是把ebp的值進行壓棧,那麼這個ebp是什麼呢?有什麼用呢?

EBP叫做擴充套件基址指標暫存器(extended base pointer) ,裡面放一個指標,該指標指向系統棧最上面一個棧幀的底部,用於C執行庫訪問棧中的區域性變數和引數。那麼這一步的意義就是:儲存舊棧幀中的幀基指標以便函式返回時恢復舊棧幀

第四步,mov ebp,esp,將esp的值放在ebp中,我們再來了解一下什麼是esp?

ESP(Extended Stack Pointer)為擴充套件棧指標暫存器,是指標暫存器的一種,用於存放函式棧頂指標,指向棧的棧頂(下一個壓入棧的活動記錄的頂部),也就是它不停在變,剛才提到的ebp指向棧底,在函式內部執行過程中是不變。

那麼我們再看一下這一步的作用:

從第三步可以知道esp儲存的值是舊棧幀中的幀基指標,而esp值棧頂指標,隨時都在變,因此為了函式結束後能恢復,把esp值(外層函式棧底地址)儲存在本函式棧底ebp中。簡而言之,將內部函式ebp的值作為地址,它存放外函式的ebp的值。這一步在末尾也存在逆向指令leave。

第五步是sub esp,0x28,開闢該函式的區域性變數空間

緊接著第六步mov dword ptr ss:[ebp-0xC],0x6,給變數a一個大小是0xC的空間,並且賦值。

然後就是傳參字元1的ascii碼,呼叫printf函式,把返回值放到eax。

我們重點來看leave指令,可以發現ebp的值恢復了,esp的值也變了,相當於mov esp,ebp;pop ebp

最後執行retn指令,至此一個函式執行完畢,esp和eip的值都被改變,相當於pop eip,然後程式繼續執行。EIP是指令暫存器,存放當前指令的下一條指令的地址。CPU該執行哪條指令就是通過EIP來指示的

0x03 棧溢位

分析完這一過程,相信大家對函式是怎麼執行的應該明朗了,那麼我們言歸正傳,繼續聊一下棧溢位。首先我們先看一下什麼是棧?

棧可以看作是一個漏斗,棧底地址大,棧頂地址小,然後在一個儲存單元中,按照由小到大進行儲存,它的目的是賦予程式一個方便的途徑來訪問特定函式的區域性資料,並從函式呼叫者那邊傳遞資訊。

棧溢位屬於緩衝區溢位,指的是程式向棧中某個變數中寫入的位元組數超過了這個變數本身所申請的位元組數,因而導致與其相鄰的棧中的變數的值被改變。

另外,我們也不難發現,發生棧溢位的基本前提是:程式必須向棧上寫入資料、寫入的資料大小沒有被良好地控制。引用一個例子來了解一下棧溢位

#include <stdio.h>
#include <string.h>
void success() { puts("You Hava already controlled it."); }
void vulnerable() {
 char s[12];
 gets(s);
 puts(s);
 return;
}
int main(int argc, char **argv) {
 vulnerable();
 return 0;
}

很顯然符合以上兩個條件,gets()成為突破口我們在主函式處下斷點,執行和除錯

lea eax,dword ptr ss:[ebp-0x14] 這時開闢一個空間給變數,也即是s,如圖所示

我們想執行sucess()函式,要怎麼辦呢?

執行完vulnerable()函式後,會還原ebp,改變esp的值(leave),然後retn,也就是pop eip,然後CPU根據eip指標指向的指令繼續執行。

我們能抓到的點就是控制eip,怎麼控制?通過控制棧頂的值,那麼棧頂的值是什麼?棧頂的值是進入該函式時儲存的下一條指令的地址。這裡提一點,進入函式,要儲存兩個值:下一條命令的地址、EBP舊棧幀的幀基指標,只有這樣才能完全恢復。

此時我們可以構造payload,來控制我們要控制的地方,棧中儲存EBP值的儲存單元的上一個儲存單元,也就是圖中的儲存address的儲存單元

我們先試驗一下輸入0x14 *'A'+BBBB+0000,發生的變化

很好,按照我們的預想進行(python -c 'print "A"* 0x18+p32(0x00401520)') 就可以達到棧溢位的效果

0x04 尾記

還沒有入門,只是個人的見解,如有錯誤,希望各位大佬指出。

高階棧溢位技術—ROP實戰(學習ROP概念及其思路,瞭解高階棧溢位時需要注意的事項,並掌握解決方法)

合天智匯:合天網路靶場、網安實戰虛擬環境