原理
基本的棧幀結構(以 x64 的棧為例)
(圖片摘自Hello-CTF)
RBP 為棧底暫存器,RSP 為棧頂暫存器,分別記錄了棧幀中記錄資料部分的起始和終止地址。函式的臨時變數的在記憶體中的位置都是透過這兩個暫存器加減偏移確定的。
棧底分別還記錄了上一個棧幀的 RBP 的值,以及函式的返回地址。
方法:
1、找常發生棧溢位的危險函式
輸入:
gets()
:直接讀取一行,到換行符’\n’為止,同時’\n’被轉換為’\x00’;scanf(),格式化字串中的%s不會檢查長度;
vscanf()
:同上。
輸出:
sprintf()
:將格式化後的內容寫入緩衝區中,但是不檢查緩衝區長度
字串:
strcpy()
:遇到’\x00’停止,不會檢查長度,經常容易出現單位元組寫0(off by one)溢位;
strcat()
:同上。
2、確定填充長度
這一部分主要是計算我們所要操作的地址與我們所要覆蓋的地址的距離。常見的操作方法就是開啟 IDA,根據其給定的地址計算偏移。一般變數會有以下幾種索引模式:
- 相對於棧基地址的的索引,可以直接透過檢視 EBP 相對偏移獲得
- 相對應棧頂指標的索引,一般需要進行除錯,之後還是會轉換到第一種型別。
- 直接地址索引,就相當於直接給定了地址。
一般來說,我們會有如下的覆蓋需求:
- 覆蓋函式返回地址,這時候就是直接看 EBP 即可。
- 覆蓋棧上某個變數的內容,這時候就需要更加精細的計算了。
- 覆蓋 bss 段某個變數的內容。
- 根據現實執行情況,覆蓋特定的變數或地址的內容。
之所以我們想要覆蓋某個地址,是因為我們想透過覆蓋地址的方法來直接或者間接地控制程式執行流程。
這裡給出兩種方法:
1、肉眼看
看到函式距離rbp為0x30距離,加上預設RBP與下面棧禎預設距離0x8,因此總距離為0x38
2、看v4的IDA定義
點v4,上面註釋裡全寫好了
(“Saved Regs” 是 “Saved Registers” 的縮寫,意思是 “已儲存的暫存器”。)
3、找函式洩露地址
shift+F12發現/bin/sh很可疑,因此找到該函式,按空格找到地址
注意這裡最好用offset command這一行,原因是函式首地址在編譯可能發生改變,而下面這一行是不變的(已經存在了暫存器裡)
4、寫exp
from pwn import *
p=remote("1.95.36.136",2051)
payload=b'a'*(0x38)+p64(0x000000000040059A)
p.sendline(payload)
p.interactive()
最後cat flag
獲得flag的值