這一題一樣會拿到一個可以連線的網址跟一個 Binary。
如果試著執行的話,會發現這支程式會把你輸入的訊息給顯示回來:
我們試著先用 ltrace
來看看這程式呼叫的 system call,而不要先用 gdb
。ltrace
是一支可以動態追蹤對動態函式庫呼叫的程式。
有一點幫助,但似乎沒那麼大。但至少知道會有什麼 system call 出現。我們改回用 gdb
來看看。
直接拆 main()
的話,會發現裡面只有一開始呼叫的 setvbuf
、puts
。沒有 gets
。在 main+69
時呼叫了 echo
,我們來看看內容。
如果注意看的話,會發現在呼叫 gets
附近時,完全沒有對輸入做長度的檢查。不過這樣也幹不了什麼,因為我們的重點是要拿到 flag。
不如查一下程式裡有什麼函式好了 ...
info functions
可以直接列出程式中所有函式以及對應的位置。通常很難找到想要的函式,但這次很幸運,我們在一開始就看到一個名稱很有趣的函式:print_flag
順便看一下 print_flag
的內容。看起來裡面在印 flag 時,用了某種迴圈,最後再用 putchar
把內容寫到畫面上。
這時候我們知道我們的目的:
gets
做某種溢位攻擊print_flag
要做第二步,就必須先瞭解一個函式是怎麼進入和返回的。在用 gdb
做「測試」的時候,如果用 info registers
看看暫存器的話,會看到這樣的輸出:
暫存器主要要看 Calling Conventions - OSDev Wiki 等資料才會知道其用途,但我們這次只看 EIP
、EBP
、ESP
。
在講 EBP
/ ESP
之前,我們必須先解釋程式的記憶體分佈。程式所有的記憶體,會分為三個部位:
其中 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。
我們在下一篇會說行動階段會怎麼做。