iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 5
0
Security

CTF30系列 第 5

CTF 5: pwn2 (Part 1) (Pwn, TamuCTF2018)

解說

這一題一樣會拿到一個可以連線的網址跟一個 Binary。

偵查階段

如果試著執行的話,會發現這支程式會把你輸入的訊息給顯示回來:

TQ0KVeb.png

我們試著先用 ltrace 來看看這程式呼叫的 system call,而不要先用 gdb
ltrace 是一支可以動態追蹤對動態函式庫呼叫的程式。

Fg9Rtd6.png

有一點幫助,但似乎沒那麼大。但至少知道會有什麼 system call 出現。我們改回用 gdb 來看看。

DvpS0Co.png

直接拆 main() 的話,會發現裡面只有一開始呼叫的 setvbufputs。沒有 gets。在 main+69 時呼叫了 echo,我們來看看內容。

8toIzPP.png

如果注意看的話,會發現在呼叫 gets 附近時,完全沒有對輸入做長度的檢查。不過這樣也幹不了什麼,因為我們的重點是要拿到 flag。

不如查一下程式裡有什麼函式好了 ...

b1CCRp6.png

info functions 可以直接列出程式中所有函式以及對應的位置。通常很難找到想要的函式,但這次很幸運,我們在一開始就看到一個名稱很有趣的函式:print_flag

順便看一下 print_flag 的內容。看起來裡面在印 flag 時,用了某種迴圈,最後再用 putchar 把內容寫到畫面上。

ChyxGYt.png

理論

這時候我們知道我們的目的:

  1. 透過未檢查長度的 gets 做某種溢位攻擊
  2. 用某種方法,強迫程序執行 print_flag

要做第二步,就必須先瞭解一個函式是怎麼進入和返回的。在用 gdb 做「測試」的時候,如果用 info registers 看看暫存器的話,會看到這樣的輸出:

5PLqH1h.png

暫存器主要要看 Calling Conventions - OSDev Wiki 等資料才會知道其用途,但我們這次只看 EIPEBPESP

在講 EBP / ESP 之前,我們必須先解釋程式的記憶體分佈。程式所有的記憶體,會分為三個部位:

  • 代碼 (code)
  • heap
  • stack

其中 stack 是從程式的記憶體區域的最下往上長,越新的數值,記憶體位置越低(小)。代碼是在記憶體最上方(通常代碼的大小也不會變),而 heap 則是緊接在 code 下方。

stack 其實是由多個 stack frame 組成的。假設我們用函式 A 呼叫函式 B,此時 stack 中會有兩個 stack frame。A 的 stack frame 會在 B 之下。而每個 stack frame 大概會長得像這樣:

---stack 頂端, frame B---- <-- 位置 = ESP
        B 的本地變數
        B 的本地變數
        B 的本地變數
         saved ebp
  執行完後的返回地址 (EIP)
-------------------------
          B 的參數
          B 的參數
---以下是 stack frame A--- <-- 位置 = EBP
        A 的本地變數
        A 的本地變數
        A 的本地變數
         saved ebp
  執行完後的返回地址 (EIP)
-------------------------
          A 的參數
          A 的參數
            ...

EBP 指向 Frame 的最底部。而 ESP 指向 Frame 的最上面。EIP 會指向下一條要執行的命令(例如圖中是 main+69,這是因為我目前斷點下在該指令上,該指令尚未執行)。
因此,若我們可以從本地變數溢位出去,在對的時間蓋掉 EIP,就可以改變程式的執行路徑。

於是我們目前有幾種可能的方法:

  • gdb 改掉 eip
  • 用溢位的方法把 eip 蓋掉
  • 用某種方法解出 print_flag (如果 flag 有寫在程式內的話)

不過理論上,我們是不能控制執行環境的,故方法 1 無效。只能走方法 2。

我們在下一篇會說行動階段會怎麼做。


上一篇
CTF 4: pwn1 (TAMUctf 18)
下一篇
CTF 5: pwn2 (Part 2) (Pwn, TamuCTF2018)
系列文
CTF3030

尚未有邦友留言

立即登入留言