安裝的流程和之前一樣,就不在贅述了。而本作業的題目說明在 attacklab.pdf 。
本作業一共有 5 題。分別對具有不同安全漏洞的兩個程式攻擊,而透過本次作業可以學到:
GDB
和 OBJDUMP
等除錯工具cookie.txt
: 一個8位16進制數,作為攻擊的特殊標誌符ctarget
: 程式注入攻擊的目標文件rtarget
: ROP 攻擊目標文件farm.c
: 在 ROP 攻擊中作為 gadgets
的產生源hex2row
: 將16進制數轉換為攻擊字符phase_1
本題會執行一段程式碼 test
函式,原本在第 4 行 getbuf
函式執行結束後會回到第 5 行 printf
。但現在希望可以跳到令一個完全不相關的函式 touch1
。
因此先觀察 getbuf
函式。一開始它將堆疊指標減 40 (0x28
),接著呼叫 Gets
。
$ 3. Target Programs:
The function Gets is similar to the standard library function gets—it reads a string from standard input (terminated by ‘\n’ or end-of-file) and stores it (along with a null terminator) at the specified destination.
得知 Gets
函式和標準庫的 gets
是一樣不會檢查輸入長度,因此就知道 Gets
的字串緩衝區只有 40 個 byte,而這 40 個後面緊接的就是回傳地址,因此我們可以將堆疊畫成以下樣子,既然它沒有邊界檢查,就代表我只要輸入 41 ~ 48
個數據就可以改寫寫回地址。
那要蓋過去的地址就為 touch1
函式的初始地址,可以看到是 0x4017c0
。
現在要攻擊程式了,首先要把剛剛得到的答案寫入 sol_1.txt
檔案裡。注意到我們機器是使用 Little-Endian ,因此要把地址反過來寫。
接著利用 hex2row
將這個 16 進制的文字檔轉成攻擊字符,再使用 ctarget
將攻擊字符注入目標文件。本題就成功攻擊了。
phase_2
第二題一樣希望從 test
再執行 getbuf
後跳到 touch
函式。但不同的是,touch2
函式有一個參數 val
要和 cookie 值一樣。
那想當然就要先去看 cookie 這個值存放在哪裡阿。透過下面可以看到 cookie 是存在 0x202ce2 + rip
的記憶體位置。而我們要的輸入值則是存在 rdi
。因此這邊就知道我們必須要將 rdi
暫存器輸入成和 0x202ce2 + rip
記憶體位置存放的值一樣。
可以在 cookie.txt 中看到 0x202ce2 + rip
存放的值為 0x59b997fa
。
因此可以開始設計一套攻擊流程。首先,由上題可以知道 buffer 的 40 ~ 47 個 byte 可以更改回傳地址,但我們不能直接更改成 touch2
的地址,因為 rdi
還未更改到。所以我們可以在 buffer 區寫一段程式碼,並將 40 ~ 47 的位置指向這段程式,最後在回傳回 touch2
的地址。
而這段程式要做的是有以下三件:
rdi
設置為 0x59b997fa
touch2
的地址可以在 asm_2.s
寫成:
接著執行以下流程
現在需要知道指向堆棧的地址在哪裡,可以用以下 gdb
操作獲得 rsp
在執行 bufget
的值。因此可以獲得值為 0x5561dc78
。
我們可以先來考慮整個堆疊的狀態
而整題的流程如下
getbuf
的回傳值被改成 0x5561dc78
,因此回傳到堆疊底部rdi
(函式第一個參數) 改成 val
值touch2
的地址touch2
地址不但成功進入 touch2
,也更改了 rdi
的值。
打開 asm_2.d
可以看到組合語言的 16 進制表示
所以最後將答案寫入 sol_2.txt
成功通過第二關
phase_3
第三題和第二題的架構類似,不同的是 touch3
函式的參數是一個字串。它呼叫 hexmatch
函式來比較字串。
首先先來觀察一下 touch3
和 hexmatch
的組合語言
我們要先理解它的比較邏輯,從上題得知,cookie 存放的記憶體地址為 0x59b997fa
,從 touch3
函式作為參入輸入進 hexmatch
的 val
變數。接著使用 sprintf
函式將 0x59b997fa
以 8 位的 16 進制存放在字串 s
,最後和我們目標要注入的 sval
做比較,注意到存入只存 8 位,但比較 9 位,是因為字串預設有結束字符 '\0'
,因此我們要注入的資料型態為 0x59b997fa
轉成字串後,再將根據每個字元轉成 ASCII 代碼型態。
字串 | '5' | '9' | 'b' | '9' | '9' | '7' | 'f' | 'a' | '\0' |
---|---|---|---|---|---|---|---|---|---|
ASCII code | 35 | 39 | 62 | 39 | 39 | 37 | 66 | 61 | 0 |
那能不能直接像第二題一樣把值放入 rdi
就好呢,我們來考慮一下文件中提到建議。
When functions hexmatch and strncmp are called, they push data onto the stack, overwriting portions of memory that held the buffer used by getbuf. As a result, you will need to be careful where you place the string representation of your cookie.
在第二題的流程中,從 getbuf
跳到 touch2
就直接比較數值了。但在第三題中,則是先呼叫 hexmatch
再呼叫 strncmp
,而這兩個函式都會對堆疊進行覆寫,如果我們照第二題的方式將上面得到的數值放入 rdi
,它會在調用這兩個函式時被改掉而導致不能和我們預期的數值一樣。
那到底要把數值存到哪裡才不會被覆寫,可以考慮以下幾個條件。
push
,可以計算從執行我們注入的程式到執行 strncmp
之前操作了幾次。可以看到 touch3
執行 1 次、hexmatch
執行 3 次,將堆疊向下減了 32 。也就是說從 0x5561dc78 + 0x20 = 0x5561dc98
的位置都會被覆寫。0x5561dca0
是存放堆疊底層,使其執行我們注入的程式。因此得到的結論就是 0x5561dc78
到 0x5561dca0
都不能存放,所以要放在比 0x5561dca0
高 8 位,也就是 0x5561dca8
的位置。
剩下的步驟就和第二題類似了。要注入的程式如下
對應的堆疊就如下圖所示:
完整流程:
getbuf
的回傳值被改成 0x5561dc78
,因此回傳到堆疊底部rdi
(函式第一個參數) 改成 0x5561dca8
,也就是存放資料的地址touch3
的地址touch3
地址touch3
後使用 push
一次hexmatch
後使用 push
三次0x5561dc78
到 0x5561dca0
的區域都被複寫strncmp
,rdi
的記憶體位置沒被複寫,因此比較成功將注入程式轉成 16 進制
就可以寫本題的答案啦
最後執行,成功通過第三關
第四、五題將堆疊指標隨機化並且使一些部份的堆疊不可執行。這導致我們沒辦法向前三題一樣直接將想執行的程式注入到堆疊區。因此要使用 ROP 技術。
它的核心思想就是要利用已存在的程式,利用巧妙的切割,使其變成完全不一樣的程式,下面舉個例子:
這是一個簡單的程式,它的組合語言的 byte 形式為 c7 07 d4 48 89 c7 c3
。但如果我們只保留 d4 48 89 c7 c3
呢
可以看到就便完全不一樣的行為了。因此我們就可以利用這點來攻擊。可以先使用 objdump -d ./rtarget > rtarget.s
反彙編。並在裡面找到
由上面知道這個函式的地址為 0x4019b5
,我們就可以計算出第三個數值開始的位置為 0x4019b5 + 0x3 = 0x4019b8
,可以將這個地址 push 進堆疊中,當 ret 的時候就會跳到這裡。而這段指令的最後面為 c3
也就是 ret
指令。因此可以將多個攻擊程式依次放入堆疊中,這樣每次 ret 就會照我們的想法執行攻擊程式了。就如下圖所示,gadget 就是我們切割出來的程式碼。
phase_4
第四題要求使用 ROP 來做 phase_2
。第二題原本是
0x59b997fa
放入 rdi
touch2
而現在我們因為不知道堆疊地址,因此無法將其直接給 rdi
,但還是可以利用 getbuf
會寫堆疊的特性,將 0x59b997fa
放入堆疊後,在利用 pop
出來。所以流程變成
pop
將堆疊中的數值抓出來給某暫存器mov
將暫存的暫存器給 rdi
touch2
接著就要在 rtarget.s
中湊出對應的 gadget 。可以對應教材的表格來尋找。
在裡面找到回傳值為 rax
。
因此我們可以這樣湊
再次去 rtarget.s
尋找,就可以找到以下兩個。但是其實其他還有很多,但其他不行的原因是因為可以看到 addval_219
的第 5 個數值之後為 58 90 c3
,其中 58
和 c3
是我們想要的 popq
和 ret
,而 90
則是 NOP ,這也就表示若不是 90
則會導致整個程式錯誤,而 setval_426
也是如此,對應 48 89 c7 90 c3
。
而 addval_219
對應的地址為 0x4019ab
, setval_426
對應的地址為 0x4019c5
。
最後要將其依次放入堆疊中,注意若是 gadget 則是要放指令地址。
popq
對應地址 0x4019ab
movq
對應地址 0x4019c5
touch2
函式對應地址 0x4017ec
堆疊的樣子就變成
完整流程:
getbuf
的回傳值被改成 popq
的地址popq
將現在 rsp
的值給 rax
,rsp
再加 8,執行 ret
回傳回 movq
的地址movq
將 rax
賦值給 rdi
,執行 ret
回傳回 touch2
的地址將其寫入 sol_4.txt
最後執行,攻擊成功
phase_5
第五題一樣要使用 ROP 來實作 phase_3
。由於堆疊已經隨機化了,我們無法直接取得 cookie 所在地址。因此可以利用返回時的堆疊指標 + 一段偏移量。
而偏移量要如何取得,考慮到本題的目標
rdi
要是返回的 rsp
+ 偏移量touch3
因此可以設計一系列暫存器操作流程將上面步驟做完,在看使用到堆疊多少空間後,在堆疊後一個地方放上 cookie 值。而偏移量就是第一個 gadget 到最後一個的距離。
但我們還需要考慮兩件事
rsp
和偏移量分別放入 rdi
和 rsi
,最後在把 rax
賦值給 rdi
(第一個參數)。pop
出來開始操作,根據上述第一點將 rsp -> rdi 、 偏移量給 rsi
rsp
取出: movq rsp rax
(48 89 e0)rax
賦值給 rdi
: movq rax rdi
(48 89 c7)pop
出偏移量給 rax
: popq rax
(58)eax
賦值給 edx
: movl eax edx
(89 c2)edx
賦值給 ecx
: movl edx ecx
(89 d1),注意到多了一個 (38 c9) ,查表為 cmpb %cl %cl
不影響程式行為。ecx
賦值給 esi
: movl ecx esi
(89 ce)lea (%rdi,%rsi,1),%rax
rax
賦值給 rdi
: movq rax rdi
(48 89 c7)touch3
函式我們可以將 cookie 的值放在這些操作的上面,可以來考慮偏移量是多少了。在第一步將 rsp
取出時,堆疊指標其實是指著更上面的空間的,就如堆疊圖所示,從堆疊指標算到 cookie 地址一共 9 個空間,偏移量就是 9 * 8 = 0x48
。
完整流程:
getbuf
的回傳值被改成 gadget 的地址rdi
touch3
將答案寫入 sol_5.txt
最後執行,最後一題就攻擊成功啦