編輯:關於Android編程
一個類似於破解的初級實驗。用到的gdb的指令並不多,只是基礎的使用和內存查看的指令。考的大多是匯編代碼的熟練程度和分析能力。不過有幾個函數長的讓人吐血。本著不輕易爆炸的原則,只好慢慢調。
objdump -d ./bomb > bomb.s可以看到以phase開始的八個函數,其中有1-6 6個必過關卡,還有一個defused是檢測是否觸發了進入secret的函數,而secert就是隱藏的boss了。按照順序來吧。
第一個函數的代碼很簡單,看到strings_not_equal()函數的調用就能大概知道參數應該是一個字符串。此外可以看到兩個push將參數入棧,是為strings_not_equal()准備的。根據函數名,可以知道這個函數是在比較兩個字符串是否相等,所以push的很有可能就是一個答案字符串所在地址(如果是程序自帶的變量包括字符串等都會在.rodata部分,所以壓棧時會直接壓入對應地址),另一個來自標准輸入的字符串地址。
圖1 phase_1部分語句 那麼接下來就可以進入gdb,加載bomb,在phase_1處設置斷點並運行,任意輸入一個字符串: 圖2 設置斷點並進行測試 圖3 檢查入棧的參數很明顯可以看到eax內容為來自標准輸入的參數的地址,而直接入棧的地址很有可能就是答案。直接kill掉並重新加載bomb,在0x8048b3a處設置斷點,由圖4所示代碼可以看出如果eax為0則通過。運行並輸入測試字符串:
圖4 phase_1檢測部分代碼
圖5 測試字符串運行結果
如圖 $eax==0 , 所以第一個關卡的答案就是 ”Public speaking is very easy.” 了。
觀察phase_2,可以看到如下片段:
圖6 phase_2部分代碼
入棧eax和edx可以看出是兩個地址,而read_six_numbers可以看出這個關卡的答案是6個數字。所以就用1 2 3 4 5 6 來進行測試,並read_six_number()函數運行前後對參數進行追蹤和對比:
圖7 進行phase_2測試 圖8 read_six_number()函數運行前 圖8 read_six_number()函數運行後 對比可以看到輸入的6個數字參數被存在以0xffffd0e0開始的24個字節中。接下來有如下代碼: 圖9 phase_2的第一個炸彈點 可以看到-18($ebp)是第一個數字的地址,而判斷語句就是判斷第一個數字是不是1,不是則爆炸。幸運的是測試的數字第一個數字是1,所以可以繼續運行: 圖10 運行到0x8048b73 圖11 對phase_2核心代碼的初步解釋 如圖11所示,由於ebp的值為0xffffd0f8,把輸入的6個數字看作數組num[6],則可以將所示代碼總結為以下C代碼: 圖12 phase_2的等價形式 根據圖10 和11推斷出第一個數字一定是1,根據循環算出後面的數字分別是1*2,2*3,6*4,24*5,120*6,即1 2 6 24 120 720。按照推斷出的答案進行測試,在bomb_explode函數處設置斷點,如果錯誤就可以直接Kill重新運行而不會爆炸: 圖13 測試phase_2可以看到並未觸發斷點,則結果正確。
此外從整體看phase_3可以看出有9個分段,每個分段都類似於一下片段:
圖17 phase_3重復片段
且都由一處語句控制跳轉內存:
圖18 控制跳轉語句
控制語句通過將第一個參數的值放入eax中,通過第一個值控制跳轉,進一步控制第三個參數與哪個數字比較。如果第三個參數匹配失敗就會跳到末尾的explode_bomb()中。所以可以看出是一個switch語句。整理後可以得到以下結構: 圖19 phase_3等價的switch語句 所以可以算出第三個關卡共有8個答案,即從後三行中對應每一列為一個答案。任選一個答案,在explode_bomb處設下斷點進行測試: 圖20 測試phase_3的答案 未觸發斷點,即成功通過。提示已經過了一半啦。
接下來就轉戰func4函數,可以看出它是一個遞歸函數。對於遞歸函數首先要找到特殊出口:
圖25 特殊出口 圖26 phase_4對func4()返回值的判斷 可以看到當$ebx也就是輸入的參數小於1時直接返回1。觀察phase_4函數的部分( 圖26 )可以看出返回值應該等於55(0x37)。所以還是要研究一下遞歸部分。根據以下代碼可以看到遞歸部分有兩層,且都要進行:圖27 func4的遞歸部分
不過代碼較短又只涉及到輸入的參數,所以可以直接看出等價以下C代碼:
圖28 模擬func4的遞歸代碼 經過計算,想要返回值為55,則i應該為9。測試一下結果的正確性( 還是要在explode_bomb()處設置斷點,否則錯誤就會bomb,然後就會扣分啦。。) : 圖29 phase_6測試結果因為沒有觸發explode_bomb斷點,所以phase_4就這樣被KO啦。
根據以上信息,可以大概總結出如下內存和尋址的模式:
圖47 尋址方式總結和內存變化 這個循環基本就搞清楚了,接下來是下一個單層循環,同樣由於多為內存操作,只能先用gdb單步運行來查看內存的變化: 圖48 49 循環中內寄存器和內存的變化 經過觀察發現此次循環過程的內存與上一個循環中被改變的內存有關,經過整理後可以發現如下內存變化: 圖50 第三次循環執行完畢相關內存變化可以看到這次循環是根據上一次循環更改的相關內存進行第二次的修改,間接與輸入的6個數字有關。接下來是phase_6的最後一個循環:
圖51 phase_6最後一個循環
炸彈現在才出現,可以看出來是內存間的訪問和比較,根據圖47 和圖50可以得到下面的流程模擬圖:
圖52 最後一個循環的運行模擬和參數推算
可以看到這個循環是在測試根據輸入的6個數字排好序的地址中存放的數字,輸入的6個數字應該使 0x804b230– 0x804b26c 這6個地址中的數字按照降序排列,而輸入的6個數字是在圖47所示的循環過程中控制6個地址在 0xffffd0c8 – 0xffffd0dc 中的順序 進而通過圖50所示過程控制6個地址存放在以0x804b開始的地址。所以正確輸入順序的推算應是 4 2 6 3 1 5 。如下圖所示:
圖53 推算正確結果 接下來就要測試一下推斷出的結果了,如下圖把phase_6的所有explode_bomb()的入口語句設置斷點,並在每個循環階段設置斷點: 圖54 設置斷點並輸入推算答案測試 圖55 第二個循環階段完成 如上圖,第一階段未觸發explode_bomb()函數,二階段後檢查內存發現在0xffffd0c8 -- 0xffffd0dc段的地址排列正像推算的一樣。
圖57 第三階段運行後檢測其他相關內存
第三階段後,根據圖57和58可以得出下圖所示的圖示:
圖58 內存圖示和比較順序推算
最後運行後顯示成功拆除炸彈並退出程序:
圖59 提示成功拆除炸彈並退出
圖62 異常跳過secret_phase()入口
可以看到是因為$eax的值不對導致跳過入口,由於$eax作為sscanf()函數的返回值,所以推算需要匹配兩個部分,但在sscanf()運行過程中並未手動輸入新的字符串且sscanf()的參數不一定來自標准輸入,所以需要查看之前入棧的參數來推算sscanf()的參數有哪些。
圖63 查找sscanf()的參數
可以看到模式串是數字+字符串,而收到的匹配串是一個數字9,由於並未手動輸入新串,所以排除是程序自帶的字符串,所以觀察6個關卡的答案,發現第四關的答案是9,而且第四個關卡的參數串是%d,所以有多余的字符串也不會影響匹配的個數,所以推測需要改變第四個答案。在第四關時多輸入任意一個字符串:
圖64 測試任意字符串
圖65 通過參數匹配測試
圖66 再次異常跳轉
可以看到在輸入任意字符串後,在參數匹配個數部分已經成功,但在單步執行到strings_not_equal()時由於返回的參數$eax不為0,所以再次異常跳轉,所以觀察入棧的兩個參數,發現一個字符串是標准輸入的字符串,另一個是絕對地址存放的字符串,所以推定應該在第四關輸入的字符串就是該處地址顯示的字符串,即 “9 austinpowers”。用推算的答案進行測試:
圖67 測試推算的答案
圖68成功進入secret_phase()入口
可以看到已經成功運行到了secret_phase()入口處。.首先觀察入口部分的代碼:
圖69 入口部分代碼
可以看到有兩次調用函數,第一次是read_line(),從而可以推斷出此關卡應該是一個字符串作為參數。用gdb單步調試觀察第二個調用函數的參數:
圖70 第二個調用的函數參數
可見是剛才輸入的字符串,繼續運行:
圖71發現錯誤
由於在調用第二個函數後$eax已經發生改變,所以$eax的值應該是該函數的返回值,由 cmp語句可知返回值應該為0x3e8+1,為1001。再觀察第二個函數的解析符號,為<__strtol_internal@plt>,所以推測應該為 strtol()函數。strtol()函數的參數原型為long int strtol(const char *nptr,char **endptr,int base) ,作用是將一個字符串轉換成一個長整數,又因為參數 0xa 被入棧,所以推測這個函數是將輸入的字符串轉換成十進制長整數賦給$eax作為返回值,且jbe 8048f14圖73成功通過並進入fun7()
可以看到已經成功通過,並運行到fun7()函數入口,對參數進行分析發現是輸入的數字和0x24。由於除了參數匹配的檢測fun7()中沒有其他的爆炸帶你,所以先直接完成fun7()函數回到secert_phase()函數,發現fun7()返回的值未通過測試:
圖74 fun7()函數返回值錯誤
觀察fun7()的匯編代碼:
圖75 一種完成fun7()的方式
由圖75可看到如果傳入的參數$edx為0,則直接完成函數並返回$eax,此時返回值$eax為0xffffffff = -1 ,gdb測試觀察$eax和$edx的值:圖76 查看fun7()參數
可以看到$edx的值是傳入的地址,所以首次進入時不會發生edx=0的情況。所以繼續觀察fun7()其他代碼發現又是一個雙層遞歸,但屬於分支情況,分析可得如下圖所示C模式:
圖77fun7()的三分支模式
根據三分支模式在分支處設置斷點並運行測試:圖78 設置斷點並測試
圖79 根據三分支模式尋找遞歸返回點
如圖,由於fun7()是三分支模式,除相等時直接返回0外,只有在傳入地址參數為0時才會返回-1。所以查找所有可以遞歸的地址點,直到某個地址點的左右分支均為0則為結束點。由上圖可知該遞歸模式是四層二叉樹遞歸模式,則有如下的二叉樹形態:
圖80 內存的二叉樹形態
由於secret_phase()判斷返回值應為7,所以根據三分支的返回值公式,有且僅有7 = ((0*2+1)*2+1)*2+1。所以應該為 $eax > 0x24 且$eax>0x6b 且 $eax==0x3e9,則輸入的數字應該為0x3e9,即1001。strtol()函數在轉換時會捨棄無法轉換的部分,所以1001後可接第一個字符不為0-9的其他字符串。
根據推算的結果輸入字符串進行測試:
圖81 測試secret_phase()
由圖81可以看到並未觸發secret_phase()和fun7()內的explode_bomb()入口,即破解成功。本篇文章介紹:如何使用Toolbar;自定義Toolbar;先來看一看效果,了解一下toolbar;布局文件:<android.support.v7.widget.
Android提供了實現按照秒計時的API,今天就是用這個API實現簡單的倒計時。來個布局: 對應活動中的代碼如下: pa
移植Android到不同的設備 Android允許提供不同的硬件平台和驅動支持Android的運行。硬件抽象層(Hardware Abstraction Layer)
(轉載請注明出處:http://blog.csdn.net/buptgshengod) 1.背景 在android源碼中我們能看到各種以@開頭的字符,他們大