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


本文首發於“合天智匯”公眾號,作者:threepwn

 

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()函數設置斷點

1.png

 

 

 

 

 

 

 

 

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

 

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

2.png

 

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

EBP叫做擴展基址指針寄存器(extended base pointer) ,里面放一個指針,該指針指向系統棧最上面一個棧幀的底部,用於C運行庫訪問棧中的局部變量和參數。那么這一步的意義就是:保存舊棧幀中的幀基指針以便函數返回時恢復舊棧幀

3.png

 

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

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

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

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

4.png

 

第五步是sub esp,0x28,開辟該函數的局部變量空間

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

然后就是傳參字符1的ascii碼,調用printf函數,把返回值放到eax。

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

5.png

 

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

6.png

 

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()成為突破口我們在主函數處下斷點,運行和調試

7.png

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

8.png

我們想執行sucess()函數,要怎么辦呢?

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

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

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

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

9.png

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

10.png

 

0x04 尾記


 

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

參考:

https://en.wikipedia.org/wiki/Stack_buffer_overflow

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/stackoverflow-basic-zh/

http://hetianlab.com/cour.do?w=1&c=CCID31b0-fe03-4277-8e2f-504c4960d33f


免責聲明!

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



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