轉載請注明出處:https://www.cnblogs.com/ustca/p/11694127.html
二進制炸彈任務描述
拓展:緩沖區溢出攻擊
"二進制炸彈包含若干個階段,每個階段需要輸入特定的字符串,所有輸入正確則炸彈被排除,否則….."
拆彈的任務也就是找出這些字符串將字符串記錄到solution.txt文件中,用換行區別不同階段的字符串,
Linux環境下可按下列方式驗證拆彈結果:
主要方法
objdump反匯編與gdb調試。
分析流程
已知數據有編譯好的二進制可執行文件bomb,也就是反匯編目標文件,以及bomb.c文件,用於輔助理解代碼。
查看bomb.c可知程序利用phase_*函數(*為1~6) 檢查輸入字符串是否合法,不合法就引爆炸彈。
bomb.c沒有給出phase的源碼,我們實際的任務就是逆向出每個phase的檢查規則,構造出合法字符串。
首先使用反匯編得到bomb的匯編文件,
以下將逐個分析phase1-phase6。
phase1【字符串對比】
<phase_1>匯編段前三行申請了8字節棧空間,
movl指令在高4字節存入了0x80499a8處的一份數據,隨后的兩條mov指令將我們提供的phase1的參數存入了棧的低4字節,
緊跟着一條call指令,跳轉到<strings_not_equal>處,顯然是在進行字符串對比,
通過分析我們可以得到,phase_1實際上是將輸入參數與0x80499a8處存儲的數據做對比,
在匯編文件中,數據段的內容並沒有包含,所以我們需要通過gdb斷點輸出該處的存儲數據,
斷點處輸出可以得到0x80499a8處的數據,即phase_1的字符串答案I turned the moon into something I like to call a Death Star.
phase2【循環數字】
從8048bbe行可以看出,phase_2的執行與讀取6個數字有關,那么輸入也應該是6個數字。
從8048bc3、8048bf0、8048bf3行可以得到phase_2的執行,進行了3次循環,也就是6個輸入數字進行了3次對比。
有一個誤區是,一開始去分析了read_six_numbers段的匯編碼,沒有得到什么有用信息,
由此可知后面對phase的分析,主要基於phase段的代碼,其中涉及到的一些子操作只要按照字面意思理解即可。
分析循環語句中的8048bcc到8048bdd行,可以看到edx寄存器先從棧中讀取了一個數字,
而后eax寄存器在索引+3(8048bd6行)的情況下,也從棧中讀取了一個數字,
之后edx寄存器與eax寄存器進行了對比。
從已經分析到的信息可知,phase_2輸入6個數字,對比只循環了3次,而對比的兩個數字都在棧中(與phase_1不同)且下標相差3,可以猜測phase_2是將用戶輸入的6個數字中的前3個數字與后3個數字做了對比,下面通過gdb進行調試:
為了避免巧合,測試數據將數字設置成兩組五位數,phase_2通過,證明phase_2確實是在對輸入的數字進行索引加3的對比檢查。
phase3【case分支】
有了phase1和phase2的經驗,直接從8048c36行開始分析,根據<sscanf@plt>可以得到,phase_3的參數應該是調用了C語言中sscanf函數來輸入,sscanf讀取格式化的字符串中的數據。隨后的mov、cmpl、jg、call四條指令中出現了對比操作與<explode_bomb>的調用,顯然是在對輸入數據進行分析判斷。cmpl語句將寄存器eax與常數1進行對比,若不大於1則爆炸,而eax寄存器中存放的是上一次操作的返回值,查看sscanf函數定義可以得知,返回值為int類型,返回的是讀取到的有效數據個數。
接下來,測試phase_3需要的參數個數,首先給兩個輸入參數:
得到sscanf函數返回值為2,接下來我們再給三個輸入參數:
給了三個輸入參數,但sscanf函數返回值仍然為2,由此可知,sscanf函數只讀取兩個格式化字串,也就是phase_3需要兩個輸入參數。
8048c49行將輸入的參數存入eax寄存器,用gdb調試可以得到存入的是第一個輸入參數,參數1與常數7進行對比跳轉,若大於7則跳轉到爆炸指令,可知參數1取值范圍為0-7。8048c5f行根據eax寄存器的內容進行直接尋址跳轉,8048c58行在計算跳轉地址時,用到了地址0x80499ec,且以4*%edx作為偏移量,查看反匯編得到的匯編碼,並沒有0x80499ec行,gdb調試輸出可以得到:
顯然該段是數據段,存儲了case跳轉表,而跳轉地址正是phase_3代碼段的8個地址入口。
不同的入口地址,分別給-0x8(%ebp)賦予了不同的常數值。
再讀取參數2,將參數2與case段中的賦值進行比較,相等則通過。
0x3d4=980;0x2a3=675;0x23e=574;0x14a=330;0x1b3=435;0x2e5=741;0x154=340;0x9c=156;
所以有6組可用輸入:0 980; 1 675; 2 574; 3 330; 4 435; 5 741; 6 340; 7 156;
phase4【遞歸求冪】
有了phase3的經驗,可以看出phase_4需要一個輸入參數。
test指令判斷輸入參數是否為0,可知輸入參數>0,隨后將輸入參數傳遞給func4函數並執行。
func4執行的返回值與常數$0x1cb91(117649D)對比,相等則通過,可以得知func4是對輸入參數進行了運算,且運算結果應該等於111649。接下來分析func4函數,反推輸出111649的輸入值應該是多少。
快速分析func4的功能:func4匯編段中出現了調用自身的call指令(0x8048cd9),顯然func4函數是在進行遞歸運算。
再尋找遞歸結束條件,發現遞歸出口在0x48cd0行。每層遞歸參數值-1,最后一層遞歸返回1后,匯編代碼執行到0x8048cde,該處匯編代碼對下一層遞歸的返回值進行7倍乘,然后將值返回給上一層,由此可以推導出func4的C代碼——7的n次冪。
log(7,117649)=6,phase_4輸入6通過。
phase5【靜態鏈表】
匯編碼前一部分已經很熟悉了,可以看出phase_5的輸入大於1個參數,下面用3個參數去測試,若sscanf返回值不等於3,則需要2個參數。
phase_5的輸入參數確實是兩個。
通過gdb調試,可以得到phase_5還是對參數1進行操作。
直接找到匯編碼的主體,jne返回調用8048d98明顯是循環,循環體之前是在做一些初始化操作。
循環判斷條件是用返回值eax(也就是-0x14(%ebp))與常數15對比,而incl指令每次循環自增,在循環體內並沒有其他地方使用該計數變量。
所以此處邏輯應該是類似while循環的循環體,當滿足eax等於15則跳出循環。
先不分析循環內部,當跳出循環之后,cmpl指令將循環計數變量與10對比,等於10則通過,說明前面的循環體應該在第10次時使eax第一次滿足等於15的條件跳出。
繼續往下觀察匯編碼可以發現,當-0xc(%ebp)與-0x18(%ebp)相等時則phase_5完全通過,-0x18(%ebp)是我們輸入的第二個參數(通過讀之前的匯編碼或gdb可以看出來),而-0xc(%ebp)在0x8048dab處出現過,在循環的每次都做了+=操作。
綜合以上分析,得以得到,循環主體做的工作應該是,每次循環都對-0xc(%ebp)處參數進行+=操作,當-0x14(%ebp)等於15則跳出循環。而我們需要滿足的條件是,循環應該進行10次,且最終累加得到的-0xc(%ebp)應該等於我們輸入的第二個參數。那么,我們輸入的第一個參數應該是什么范圍呢?
往回分析匯編碼,可以看到0x8048d7c行有一個and指令,效果是%16,而操作數正是-0x14(%ebp),這個操作數很眼熟,正是循環跳出需要等於15的那個數。說明,我們輸入的第一個參數,在循環體中會被賦予新的值。(直接分析最上面幾行初始化的匯編碼也可以得到第一第二參數,這里沒有使用該方法。phase_6也沒有分析,事實證明分析粒度太細會讓事情變得很復雜,沒有直接分析邏輯並代入來的快)
到這里我們已經得到了除循環內部以外所有信息,接下來再分析循環內部會簡單很多。
在0x8048d9e行出現了,地址結構是線性存儲,首地址是0x804a5c0,偏移4*eax。而eax又是等於一直在變化的參數一,結構又與鏈表有關(以上一結點的值作為當前結點的索引),所以循環體內部應該是在進行靜態鏈表的遍歷操作,打印0x804a5c0地址為首的16個數(因為參數一%16有16個值)驗證猜測:
輸出值的范圍正好遍布0-15,且從第17個數開始明顯超出了合理數值,證實了靜態鏈表的猜想。
假設參數一%16等於6,那么循環一次參數一就變成了15,若參數一%16等於14,則循環兩次變成15。
那么拆除phase_5的任務就變成了,從誰開始索引,需要索引10次才使參數一變成15,且參數二等於循環過程中(不包括初始值)所有參數一的累加和。列表推導一下,可以得到初始參數的低八位應該等於13,循環十次累加和為69。
phase6【尋找靜態鏈表的降序索引】
phase6的分析踩了不少坑,一開始去直接分析匯編碼,但是匯編碼很長而且到處跳轉,實在進行不下去。之后又去gdb調試,過程中出現了不少變量,也越查越亂。最后去看了下之前幾次拆除炸彈一直忽略的初始化部分,發現phase_6設置了72字節的棧,說明phase_6中用來操作的變量比較多。最后索性代入參數逐步調試,直接將棧打印出來,這樣有了輸入和輸出分析中間的過程會簡單一點。
代碼主要分為四段,下面逐步分析:
【第一部分】
根據圖示,大體結構是進行了兩重循環,循環操作的對象是線性結構存儲的一系列參數,通過gdb調試或者代碼分析可以得出實際上就是我們輸入的六個數值。每層循環下標只索引到5,循環體的內層循環是在將外層循環索引的值與后面索引的值逐個對比。
再看一下第一部分需要我們滿足的條件,首先外層循環要求 0<輸入參數<=6,內存循環要求從參數1開始,每個參數都要與其后的參數不同。也就是6個輸入參數要兩兩不同,且在1-6之間,那么只能是1,2,3,4,5,6的某一種排列。
【第二部分】
還是兩層循環,這里外層遍歷每一個輸入參數,輸入6 5 4 3 2 1參數,通過gdb調試,發現經過第二部分后棧內數據發生了以下變化:
多出了六個地址,每個距離相差0xc,gdb輸出一下這六個地址對應的值:
匯編碼0x8048e68行與0x8048e74行表示,每次循環都在通過-0xc(%ebp)對那六個地址賦值,最后gdb輸出的-0xc(%ebp)也確實與六個地址值的最后一個相等。對匯編碼繼續分析,或者改動六個數字的排列方式按照上面的方式將棧打印,最終可以得到那六個地址值與1-6是一一對應的:
6—0x0804a600=274,5—0x0804a60c=920,4—0x0804a618=100,
3—0x0804a624=313,2—0x0804a630=586,1—0x0804a63c=125。
所以,第二部分的作用就是根據輸入的六個參數,給出一組對應順序的地址,地址指向6個數字。
【第三部分】
代碼運行過第三部分之后,棧內數據沒有任何改變,而且第三段也不存在指向爆炸的跳轉,直接去分析第四部分。
【第四部分】
循環執行5次,1處匯編碼是在對比兩個線性存儲的數字,也就是之前第二部分添加的那六個中的數字。
2處的操作和phase5的靜態鏈表索引很像,每次加0x8向后索引。
8048ee0行的跳轉指令又顯示,每次循環需要當前索引值大於等於后一個索引值。
到這一步,我們已經有了足夠的信息,可以猜測第三部分應該是在對第二部分獲得的六個數初始化索引鏈接,而第四部分驗證的要求是,前一個索引數要大於后一個索引數,也就是125、586、313、100、920、274降序排列。而我們要給出的,則是降序的索引。總結下來,phase_6的功能為,當程序按照我們輸入的六個參數去生成六個對應的地址時,這六個地址對應的值要滿足降序排列。所以答案為:5 2 3 6 1 4。
至此程序需要的六個輸入我們都已經分析出來,炸彈也成功拆除。
----------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------