CTF必備技能丨Linux Pwn入門教程——棧溢出基礎


這是一套Linux Pwn入門教程系列,作者依據i春秋Pwn入門課程中的技術分類,並結合近幾年賽事中出現的一些題目和文章整理出一份相對完整的Linux Pwn教程。

課程回顧>>Linux Pwn入門教程第一章:環境配置

更多Pwn視頻課程:https://www.ichunqiu.com/courses/pwn?from=weixin

本系列教程僅針對i386/amd64下的Linux Pwn常見的Pwn手法,如棧,堆,整數溢出,格式化字符串,條件競爭等進行介紹,所有環境都會封裝在Docker鏡像當中,並提供調試用的教學程序,來自歷年賽事的原題和帶有注釋的python腳本。

教程中的題目和腳本若有使用不妥之處,歡迎各位大佬批評指正。

今天是Linux Pwn入門教程第二章:棧溢出基礎,閱讀用時約10分鍾。

函數的進入與返回

要想理解棧溢出,首先必須理解在匯編層面上的函數進入與返回。首先我們用一個簡單執行一次回顯輸入的程序hello開始。用IDA加載hello,定位到main函數后我們發現這個程序的邏輯十分簡單,調用函數hello獲取輸入,然后輸出“hello,”加上輸入的名字后退出。使用F5看反匯編后的C代碼可以非常方便的看懂邏輯。

 

我們選中IDA-View窗口或者按Tab鍵切回到匯編窗口,在main函數的call hello一行下斷點,開啟32位的Docker環境,啟動調試服務器后直接按F9進行調試。

 

如圖,這是當前IDA的界面。在這張圖中我們需要重點注意到的東西有棧窗口,EIP寄存器,EBP寄存器和ESP寄存器。

首先我們可以看到EIP寄存器始終指向下一條將要執行的指令,也就是說如果我們可以通過某種方式修改EIP寄存器的值,我們就可以控制整個程序的執行,從而“pwn”掉程序(要驗證這一點,我們可以在EIP后面的數字上點擊右鍵選擇Modify value.......把數值改成080484DE然后F9繼續執行,從而跳過call hello一行)。

剩下的東西都和棧相關。顧名思義,棧就是一個數據結構中的棧結構,遵循先入后出的規則。這個棧的最小單位是函數棧幀,一個函數棧幀的結構如圖所示:

 

 

棧的生長方式是向低地址生長,也就是說這張圖的方向和IDA中棧窗口的方向是一樣的,越往上地址值越小。同樣的,新入棧的棧幀在IDA的窗口中會把原來的棧幀“壓”在下面。

ESP和EBP兩個寄存器負責標定當前棧幀的范圍。圖中標黑的部分即為實際上ESP和EBP中間的最大區域(為了方便講解,我們把EIP和參數也列入一個函數的函數棧幀)。

圖中的局部變量和參數很好理解,但EBP和EIP又是什么意思呢?我們回到IDA調試窗口。按照程序的邏輯,接下來應該是執行call hello這行指令調用hello這個函數,函數執行完后回到下一行的mov eax, 0,其地址為080484DE.然后我們再把當前ESP和EBP的值記下來(受地址空間隨機化ASLR的影響,每台電腦每次運行到此處的ESP和EBP值不一定相同),然后按F7進入hello函數。

 

如圖,執行完call hello這一行指令后發生了如下改變。由此我們可以得知call指令是可以改變EIP“始終指向下一條指令地址”的行為的,且call指令會把call下一條指令地址壓棧。我們可以理解為call hello等價於push eip; mov eip, [hello]。所以我們的第一個問題“棧幀中的EIP是什么意思”的回答就是:棧幀中的EIP是call指令的下一條指令的地址,我們繼續F8單步執行。

 

 

 

如圖,通過依次執行三條指令,程序為hello函數開辟了新的棧幀,同時把原來的棧幀,即執行了call hello函數的main函數的棧幀的棧底EBP保存到棧中。繼續往下執行到read函數,然后隨便輸入一些比較有標志性的內容,比如12345678,我們就會發現存儲輸入的局部變量buf就在這片新開辟的棧幀中。

 

我們已經接觸到了棧幀的開辟與被使用情況,接下來我們再通過調試繼續學習棧幀的銷毀。繼續F8到leave一行,此時我們會發現棧幀再次回到了剛執行完sub esp, 18h的狀態。

執行完leave一行指令后棧幀被銷毀,整體狀態回到了call hello執行前的狀態。即leave指令相當於add esp, xxh; mov esp, ebp; pop ebp

 

 

再次F8,發現EIP指向了call hello的下一行指令,同時棧中保存的EIP值被彈出,棧頂地址+4. 即retn等同於pop eip

 

此時hello函數代碼執行完畢,控制流程返回到了調用hello函數的main函數中。

棧溢出實戰

通過上一節的調試,我們大概理解了函數棧的初始化和銷毀過程。我們發現隨着我們的輸入變多,輸入的內容離棧上保存的EIP地址越來越近,那么我們可不可以通過輸入修改掉棧上的EIP地址,從而在retn指令執行完后“pwn”掉程序呢?我們按Ctrl+F2結束掉當前的調試,再試一次。為了節約時間,這回我們直接把斷點下在hello函數里的call _read一行。

啟動調試,程序中斷后界面如下:

 

通過觀察read函數的參數和棧中的保存的EIP地址,我們計算出兩者的偏移是0x16個字節,也就是說輸入0x16=22個字節的數據,我們的輸入就會和棧中的EIP“接上”,輸入22+4=26個字節,我們的輸入就會覆蓋掉EIP。那么我們構造payload為‘A’*22+‘B’*4

即AAAAAAAAAAAAAAAAAAAAAABBBB,根據我們的推測,在EIP寄存器指向retn指令所在地址時,棧頂應該是‘BBBB’。即retn執行完之后,EIP里的值將不再是圖中框起來的080484DE,而是42424242(BBBB的ASCII值),按F8使IDA掛起,在docker環境中輸入payload:

 

棧中的EIP果然按照我們的推測被修改成42424242了。顯然,這是一個非法的內存地址,它所在的內存頁此時對我們來說並沒有訪問權限,所以我們運行完retn后程序將會報錯。

 

選擇OK,繼續F8並且選擇將錯誤傳遞給系統,這個進程接收到信號后將會結束,調試結束。我們通過一個程序本身的bug構造了一個特殊輸入結束掉了它。

結合pwntools打造一個遠程代碼執行漏洞exp

通過上一節的內容,我們已經可以做到遠程使一個程序崩潰。不要小看這個成果。如果我們能挖掘到安全軟件或者系統的漏洞從而使其崩潰,我們就可以讓某些保護失效,從而使后面的入侵更加輕松。當然,我們也不應該滿足於這個成果,如果可以繼續擴大這個漏洞的利用面,制造一個著名的RCE(遠程代碼執行),為所欲為,豈不是更好?

當然,CTF中的絕大部分pwn題也同樣需要通過暴露給玩家的一個IP地址和端口號的組合,通過對端口上運行的程序進行挖掘,使用挖掘到的漏洞使程序執行不該執行的代碼,從而獲取到flag,這也是我們學習的目標。

為了降低難度,我在編寫hello這個小程序的時候已經預先埋了一個后門——位於0804846B的名為getShell的函數。

 

如圖,這個函數唯一的作用就是調用system("/bin/sh")打開一個bash shell,從而可以執行shell命令與系統本身進行交互。

 

正常的程序流程並不會調用這個函數,所以我們將會利用上一節中發現的漏洞劫持程序執行流程,從而執行getShell函數。

首先我們把hello的IO轉發到10001端口上。

 

然后我們從Docker環境中獲取其IP地址(我的是172.17.0.2,不同環境下可能不同)

然后在kali中啟動python,導入pwntools庫並且打開一個與Docker環境10001端口(即hello程序)的連接。

 

此時我們可以像上一篇文章一樣打開IDA進行附加調試,在這里我就不再次演示了。從上一節的分析我們知道payload的組成應該是22個任意字符+地址。但是我們要怎么把16進制數表示的地址轉換成4個字節的字符串呢?

我們可以選用structs庫,當然pwntools提供了一個更方便的函數p32( )(即pack32位地址,同樣的還有unpack32位地址的u32( )以及不同位數的p16( ),p64( )等等),所以我們的payload就是22*'A'+p32(0x0804846B)。

 

由於讀取輸入的函數是read,我們在輸入時不需要以回車作為結束符(printf,getc,gets等則需要),我們使用代碼io.send(payload)向程序發送payload。

 

由於我在這里沒有設置IDA附加調試,顯然程序也不會被斷點中斷,那么這個時候hello回顯我們的輸入之后應該成功地被payload劫持,跳轉到getShell函數上了。為了與被pwn掉的hello進行交互,我們使用io.interactive( )

 

可以看到我們已經成功地pwn掉了這個程序,取得了其所在環境的控制權。為了增加一點氣氛,我們在/home下面放了一個flag文件。讓我們來看一下flag:

 

如圖,我們成功的做出了第一個pwn題。為了加深對棧溢出的理解,我選了幾個真實的CTF賽題作為作業,注意不要將思維固定在獲取shell上哦。

課后例題和練習題非常重要,小伙伴請務必下載練習。后台回復“課后練習題”即可獲得練習文檔!

以上是今天的內容,大家看懂了嗎?后面我們將持續更新Linux Pwn入門教程的相關章節,希望大家及時關注。

 


免責聲明!

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



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