计算机系统实验:二进制炸弹+缓冲区炸弹 (自我学习笔记)
本來沒想寫個博客,結果得知還要驗收,發現自己全忘了,那就趁著復習的功夫再捋一遍吧>-<
一、使用工具:IDA-pro
簡單使用方式:?
1、打開IDA,open需要反匯編的exe。
選則win32debugger,process option可以傳入命令行參數(該實驗中為學號)。
2、F9會運行exe,直到斷點停止。F8 step over, F7 step into;? F5顯示C代碼(32bit的IDA有,64位沒有)
3、展示出的匯編指令為x86,采用Inter格式 ,no?ATT,通常目的操作數在前(與課內正好相反)。
4、空格鍵使得代碼在純文本<->graph之間切換。graph便于查看分支,跳轉的目的地。
F9后,右側可查看寄存器與棧的地址與對應的值,地址為32位,用16進制表示。下方有存儲器不過不常用。
5、用IDA 運行exe,在exit后,運行框直接消失;
所有答案弄完后,用命令行會方便些,便于查看所有結果。
win+r, cmd, cd 到指定文件夾,bomb.exe 001044;
或者用powershell ,在文件夾中shift+右鍵運行powershell ? .\bomb.exe 001044
二、解題思路
首先找正確通關條件。找到后向上推,尋找關鍵的控制指令,滿足xxx條件才可正確跳轉。
當然指令不一定完全按graph的順序執行,有時按圖中的路徑無法運行到想要的代碼,這時需要強行修改pc使跳轉到正確通關的位置。
理論上暫時想到強行修改pc的方式有以下幾種:
ret:? ? ? ? pc=stack[esp--]
jmp:? ? ? pc=JTA
mov?eip? xxx
三、流程
首先是一些關于讀入的函數:(僅限于大意,不求甚解,不影響流程推進即可)
readline:把輸入的一行當成字符串讀到起始目標地址。
sscanf: 把readline得到的字符串轉化為數字,返回數字個數
二進制炸彈:
第一關:? ?字符串比較
進入phase1,由explode_bomb向上推 ,-> eax==0 -> al==0 ,然后就不太好懂,必須開始推。
觀察generaterandomstring函數:
int __cdecl GenerateRandomString(int a1) {signed int v1; // edi@1int result; // eax@5v1 = 0;do{GenerateRandomNumber(2);if ( rand_div == 1 )*(_BYTE *)(v1 + a1) = 65;else*(_BYTE *)(v1 + a1) = 97;GenerateRandomNumber(26);result = v1 + a1;*(_BYTE *)(v1++ + a1) += rand_div;}while ( v1 < 10 );*(_BYTE *)(v1 + a1) = 0;return result; }?不然看出函數隨機生成了一個長度為10的字符串,起始地址為a1,并將a1[10]置為0;
回到匯編中,結合棧結構,其實a1=ebp-8 ,則[ebp-8,ebp+1]存的就是隨機生成的字符串。
由右方可知,ebx為起始地址存了輸入的字符串。
接下來的邏輯:
?起始時ecx+edx=生成字符串的起始地址(ebp-8),ecx為讀入字符串的起始地址。
至于為何ecx為首地址:
push eax?
call ? _read_line
pop?eax
IDA的邏輯中,通常傳參的方法是在call之前把參數push入棧。
我們的目的是 do [ecx]==[ecx+edx](while ecx++),直到[ecx+1]==0時達到目的。顯然輸入的字符串前10位要與生成的相同即可。? 運行完randstring()后讀取出生成的字符串即可。
通關密碼:AxOlSPKPVo (后面可以隨便追加字符)
第二關:尋找6個數滿足的一定規則
本人的_rand_div為6,進入phase_2_6:
觀察call read_six_number 上下文邏輯,得到:讀入了長度為6的int類型數組,起始地址edx=ebp-0x18
依次關注以下的分支邏輯:
a[0]=_rand_div+1=4;? ? ? ? 6個數都>0;? ? ? ? ?rep(i,2,5)a[i]=a[i-1]*a[i-2];? ?a[2]+a[3]+a[4]+a[5]>4
綜合上述條件,一組通關密碼:4 1 4 4 16 64?
第三關:
先看讀入:
call ? ?_read_line
push ? ?eax ? ? ? ? ? ? ; Src
call ? ?_phase_3
_phase_3中:
push ? ?ebx
mov ? ? ebx, [esp+4+Src]
則ebx=_readline_line返回值
然后對于每個子階段,都push ebx,即傳入的參數。
本人randomnumber修改randdiv=4,進入phase3_4
3_4中,將輸入的字符串變為了起始位置為ebp-4的int數組。
觀察分支指令,輸入的第一個整數應為rand(8)+0xDC
此后rand_div+6=第二個數
通關密碼:223 83 (后面的輸入無所謂)
第四關:
進入4_20。同上,容易得到ebp-4為讀入int數組的首地址。
觀察分支,要求返回值為1,那么只能輸入一個數!
接下來兩個分支要求輸入的一個數(記為temp)>1 且 >3E8h,那不就是>3E8h嗎,這個>1屬于沒看懂有啥意義。
接著觀察分支,要求[ebp+eax*4+var_20]==ebx。 跑一遍得到左邊==720。而ebx=func4_2([ebp-4])。 結合F5容易看出func4_2(x)=x!。
然后就是讓我百思不得其解的一段指令:
mov edx, 10624DD3h imul edx sar edx, 6 mov eax, edx shr eax, 1Fh add eax, edxF5才知道這段指令功能是eax/=1000,具體為啥一臉懵,堪比平方根倒數算法帶給我的震撼程度。
那就不求甚解了。(temp/1000)!=720?, 則輸入數字在[6000,7000)內即可.
第五關:
先輸入Y。 再輸入突防指令,字符串地址eax作為參數傳入phase_impossible。
先來看phase_impossible調用的幾個內置函數:
call ? ?__imp__GetTickCount@0 ; GetTickCount()? ,返回從操作系統啟動所經過的毫秒數,具體用處聽老師說是檢測程序運行時間不能過長,否則爆炸(不太明白,暫咕,回來補上)。不過好像對本關沒啥限制。
call ? ?__imp__IsDebuggerPresent:函數會檢測debugger是否正在工作,如在工作就炸。這意味著我們不能設斷點動態調試?其實不然。
?當指令運行到該函數上方,強行在此函數后一條指令set IP,跳過即可。(實際上要跳得更后,避開這個分支。)
按照之前找通關點的邏輯看到最后,也沒發現如何正確通關,而是全部指向bomb。那就可能涉及到指令強行修改pc。觀察_goto_buf_0,1,2,發現其作用均為 設置pc=eax;?而eax=ebp-0x100
_goto_buf_0,1,2分別對應rand()函數的返回值為0,1,2。而rand()參數為3,也就是說不論如何都會調用其中一個goto_buf。
現在要關注的是,ebp-0x100處是啥。觀察_to_hex函數。 由函數名可猜測,這是把某個東西轉化成16進制。函數有兩個參數,ebx,eax. 分析可知,ebx為輸入字符串的首地址,eax=ebp-0x100.
此時就可以盲猜結論了(不求甚解),_to_hex函數的作用是將輸入的字符串轉化為16進制機器碼,存儲于ebp-0x100處。而_goto_buf后會運行輸入的這些指令。
我們的目標是,輸入一串指令使得pc跳轉到正確通關的地方。
先找到目標pc。在main函數的graph中找到通關部分的指令:
那么pc要跳到0x00401240.
同時為了維護下棧的平衡,要把esp初始為phase_impossible之前的狀態。容易想到如下指令:
mov esp, ebp pop ebp push 0x00401240 ret?這還沒做完。_to_hex下面還有函數_check_buf_valid()來控制分支。其傳入的參數為_rand_div,ebp-0x100. F5后得到函數功能是:將從ebp-0x100開始的256個字節存儲值異或起來,判斷是否==_rand_div的低8位。
為使之相等,可得到上述指令的機器碼,后面加上補償異或值的對應數字即可。
ps:x86指令->機器碼可參考網站:?鏈接
不過注意到這里的通關提示還有彩蛋,于是找彩蛋。在左側的函數欄中成功找到_phase_secret。
指令修改為如下:
mov esp, ebp pop ebp push 0x00401240 push 0x004014F0 retupdate:不過這樣還是沒有維護棧平衡,似乎漏掉了call _phase_impossible的返回地址、、、
那就修改成如下,不返回0x00401240了,直接返回call _phase_impossible的返回地址。
mov esp, ebp pop ebp push 0x004014F0 ret最終的第五關指令:89EC5D68F0144000C3F0?
最終效果:
緩沖區炸彈:
首先與二進制炸彈不同,緩沖區的1-4關不是連續,一次程序跑完的,而是相互獨立的,也就是說要跑四次,每次都有一個密碼。具體原因可分析流程,發現關于讀入的只有 main函數->test函數->getbuf函數,然后停止到getxs函數處理讀入。
推進一遍流程,發現按照graph順序跑一遍是找不到想要的木馬的,那就必須要用到強行修改pc的方式。觀察哪些指令有可能協助pc跳轉。
一番搜尋發現唯一可疑的地方是getbuf函數的ret指令。那就先研究一下代碼邏輯和棧結構,如下:
?
| 棧地址 | 內容 | 具體值 | 操作說明 | ||||
| test返回地址 | |||||||
| 0019FEF8 | 原來的ebp | 此后ebp=此地址,記為esp0 | |||||
| 金絲雀 | 0DEADBEEF | ||||||
| …… | |||||||
| 0019FEDC | 第二關=cookie | ||||||
| getbuf返回地址 | |||||||
| 0019FED4 | esp0 | 此后ebp=此地址,記為esp1 | |||||
| esp1-12 | 輸入機器碼的起始地址 | 第三關跳到這里 | |||||
| esp1-12 | 作為參數傳入getxs | ||||||
| getxs返回地址 |
test函數到getbuf之前,大概實現了設置金絲雀,并調用chkstk函數(仔細研究發現其功能就是開辟棧空間,就是_alloca),不過chkstk對通關沒啥影響。。
進入getbuf。如上方棧結構,記esp1=0x0019FED4. 然后令eax=esp1-0xC,并作為參數傳入getxs。 隨便輸入幾次字符后可以猜測出getxs的功能 : 處理輸入的字符串,變為以eax為首地址的對應機器碼。?紅色標注的棧內容,是getbuf函數的返回地址。試想:如果我們輸入的字符串覆蓋掉了原先的返回地址,那么可以為所欲為了。
現在開始分析關卡。
第一關:
?把紅色的返回地址覆蓋為0x004011E0即可。覆蓋后有call exit,程序會自動退出。
密碼:?00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ?E0 11 40 00? (前24字節隨意)
由于小端尋址,13-16 字節要與實際值反過來。
第二關:
?
要求除了要強行把pc設置到這里來,還要使得[esp+8]==cookie。
先跑一遍得到cookie的值,然后分析esp值:
getbuf的ret指令之前,esp=0019FED4, 對應輸入字符的 13-16 字節。ret后esp+=4, trojan 2中esp-=4; 則mov ebx,[esp+8]指令中的esp=0019FED4. 那么把輸入字符的 21-24 位修改為cookie值即可。
通關密碼:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ?00 12 40 00 00 00 00 00 FE 65 C6 71?
第三關:
容易看出通關要求global value==cookie. 那我們要做的就是通過插入指令修改global value。
我的做法是,使getbuf ret到修改global value的指令。?修改global value的指令完之后,要再緊跟修改pc的指令跳到trojan 3部分。這里我采用ret ,同時往棧中塞入trojan 3地址。
通關密碼:B9 FE 65 C6 71 89 0D 04 90 40 00 C3 00 00 00 00 ?C8FE1900 50124000
首先將getbuf返回地址覆蓋為 0019FEC8,也就是讀入機器碼的起始地址。 然后前12個字節代表的指令為:
mov ecx,0x71C665FE mov [0x00409004],ecx ret注意:mov指令的目的操作數為存儲器時,源操作數必須指定長度,所以直接:mov [0x00409004],?0x71C665FE 是錯誤指令。
ret后pc跳轉到0x401250。
第四關:
與第三關唯一的不同是,第四關函數結尾不是exit, 而是ret。分析trojan 4棧結構,發現其函數是棧平衡的(即:call塞入的返回地址使esp-4 ,函數執行過程前后esp不變,ret使esp+4)。
這意味著,如果繼續延用第三關密碼,pc將ret到輸入字符的21-24字節。
那我們把21-24字節改成想要的地址就好了。我的做法是設置21-24字節為exit()函數的地址。
通關密碼:B9 FE 65 C6 71 89 0D 04 90 40 00 C3 00 00 00 00 ?C8FE1900 A0 12 40 00 70 15 40 00
ps:?00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ?E4FE1900 A0 12 40 00 ?70154000 ?B9 FE 65 C6 71 89 0D 04 90 40 00這也是個合理的通關密碼,不過字節數長了。
第0關:
老師的要求是顯示:“ 不錯哦,緩沖區溢出成功,而且getbuf返回xxxxxxxx ”。
觀察到這段話出現在test函數中,要求
(1)金絲雀不變。其實金絲雀那個地址的值一直都不變,輸入字節沒那么長到影響金絲雀的地步。但是我們觀察指令是:cmp ? ? [ebp+var_4], 0DEADBEEF。
這意味著getbuf結束后ebp要回到原來的狀態!
結合棧結構可知,設置輸入字符的13-16字節為ebp原先值即可。
(2)eax=cookie.? 類似trojan 2, 在1-12字節填充指令:mov eax ,0x71C665FE;ret 。
再修改一下返回地址即可。
通關密碼:B8 FE 65 C6 71 C3 00 00 00 00 00 00 F8 FE 19 00 ?C8FE1900 81114000
總結
以上是生活随笔為你收集整理的计算机系统实验:二进制炸弹+缓冲区炸弹 (自我学习笔记)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文件读取下载
- 下一篇: 使用AFS, Active Direct