Linux pwn入門教程(1)——棧溢位基礎
原文來自:https://bbs.ichunqiu.com/thread-42241-1-1.html
0×00 函式的進入與返回
要想理解棧溢位,首先必須理解在彙編層面上的函式進入與返回。首先我們用一個簡單執行一次回顯輸入的程式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
一行)。
剩下的東西都和棧相關。顧名思義,棧就是一個資料結構中的棧結構,遵循先入後出的規則。這個棧的最小單位是函式棧幀。一個函式棧幀的結構如圖所示:
區域性變數1 |
---|
……. |
…… |
……. |
區域性變數m |
區域性變數n |
EBP |
EIP |
引數1 |
……. |
引數n |
棧的生長方式是向低地址生長,也就是說這張圖的方向和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
0×01 棧溢位實戰
通過上一節的除錯,我們大概理解了函式棧的初始化和銷燬過程。我們發現隨著我們的輸入變多,輸入的內容離棧上儲存的EIP地址越來越近,那麼我們可不可以通過輸入修改掉棧上的EIP地址,從而在retn指令執行完後“pwn”掉程式呢?我們按Ctrl+F2結束掉當前的除錯,再試一次。為了節約時間,這回我們直接把斷點下在hello函式裡的call _read
一行。
啟動除錯,程式中斷後介面如下
通過觀察read函式的引數和棧中的儲存的EIP地址,我們計算出兩者的偏移是0×16個位元組,也就是說輸入0×16=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構造了一個特殊輸入結束掉了它。
0×02 結合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上哦。
有問題大家可以留言哦也歡迎大家到春秋論壇中來耍一耍 >>>點選跳轉
相關文章
- Pwn-棧溢位
- 棧溢位基礎
- [原創]pwn棧溢位2
- 棧溢位基礎及利用
- Pwn入門筆記(二)a little棧基礎知識筆記
- 64位Linux下的棧溢位Linux
- Re:從零開始的pwn學習(棧溢位篇)
- StackOverFlowError(棧溢位)Error
- PWN(棧溢位漏洞)-原創小白超詳細[Jarvis-level0]JAR
- Linux基礎入門Linux
- [二進位制漏洞]棧(Stack)溢位漏洞 Linux篇Linux
- Linux入門學習(1基礎操作命令)Linux
- [pwn基礎]Linux安全機制Linux
- Linux入門基礎命令Linux
- Elasticsearch(1):基礎入門Elasticsearch
- Kernel pwn 基礎教程之 Heap Overflow
- 阿里大佬講解Java記憶體溢位示例(堆溢位、棧溢位)阿里Java記憶體溢位
- Dubbo入門(1) - 基礎概念
- Arduino基礎入門套件教程PDFUI套件
- Nestjs入門教程【一】基礎概念JS
- ROS基礎入門——實操教程ROS
- Android入門教程 | Fragment 基礎概念AndroidFragment
- 為什麼要學Linux作業系統?linux入門基礎教程Linux作業系統
- Java棧溢位|記憶體洩漏|記憶體溢位Java記憶體溢位
- 淺入kubernetes(1):Kubernetes 入門基礎
- 怎樣安裝Linux系統?Linux基礎教程之小白入門Linux系統安裝教程Linux
- Ninx 基礎入門補充1
- pytorch入門- tensor基礎操作(1)PyTorch
- Linux運維基礎學習怎麼樣?linux基礎入門Linux運維
- Linux程式基礎命令有哪些?Linux運維命令入門基礎Linux運維
- 【WEB基礎】HTML & CSS 基礎入門(1)初識WebHTMLCSS
- 棧溢位漏洞利用(繞過ASLR)
- Linux入門教程Linux
- Linux 學習基礎入門之Linux分割槽Linux
- Linux基礎入門(一)初識ShellLinux
- Linux入門基礎——常用命令Linux
- Python 基礎 4-1 字典入門Python
- ThinkPHP3.2 快速入門(1)基礎PHP