這個實驗從開始到完成大概花了三天的時間,由於我們還沒有學習編譯原理、匯編語言等課程,為了完成這個實驗我投機取巧了太多,看了網上很多的解題方法,為了更加深入學習編譯反編譯,覺得需要從頭開始好好梳理一下。這個系列的博客我將按照拆彈個數一個個的分析,應該會有七篇。。。。。。
給出對應於7個階段的7篇博客
phase_1 https://www.cnblogs.com/wkfvawl/p/10632044.html
phase_2 https://www.cnblogs.com/wkfvawl/p/10636214.html
phase_3 https://www.cnblogs.com/wkfvawl/p/10651205.html
phase_4 https://www.cnblogs.com/wkfvawl/p/10672680.html
phase_5 https://www.cnblogs.com/wkfvawl/p/10703941.html
phase_6 https://www.cnblogs.com/wkfvawl/p/10742405.html
secret_phase https://www.cnblogs.com/wkfvawl/p/10745307.html
解題前准備
Step1:將下載的炸彈包拷貝到Linux主機上;
Step2::使用tar -xvf “bomb名”進行解壓;
解壓后生成3個文件:
1)README:炸彈所屬的用戶信息;
2)bomb:二進制炸彈文件;
3)bomb.c:二進制炸彈文件的框架源文件,供解題者參考。
Step3:使用objdump -d bomb對二進制炸彈進行反匯編,並將其保存到一個文本文件中。
注:
1、這里將反匯編生成的文件重定向到asm.txt,后續的解題過程均通過分析該文件進行。
2、本例所有的分析過程均在vim中進行,大家可以使用自己熟悉的工具。
1 phase_1
phase_1要求輸入一個字符串,二進制炸彈會判斷輸入的字符串是否與目標字符串相等。
觀察框架源文件bomb.c:
從上可以看出:
1、首先調用了read_line()函數,用於輸入炸彈秘鑰,輸入放置在char* input中。
2、調用phase_1函數,輸入參數即為input,可以初步判斷,phase_1函數將輸入的input字符串與程序內部的炸彈秘鑰進行比較。
因此下一步的主要任務是從asm.txt中查找在哪個地方調用了readline函數以及phase_1函數。
1.1 尋找並分析調用phase_1函數的代碼
打開asm.txt,在其中搜索phase_1:
從上圖可以看出一些信息:
1、第330行:調用了read_line函數;read_line的返回結果(char* input)放置在eax(累加器)寄存器中。(從函數返回的結果一般都放置在eax寄存器中)
2、第331行:將read_line函數的返回結果放置在當前esp(棧指針寄存在)指針指向的棧頂。
3、第332行:在邏輯地址0x8048b47位置調用了phase_1函數。同時也說明了phase_1函數的入口地址為0x8048c00。
4、結合前面bomb.c的分析,從上可以看出第331行,是在為調用phase_1准備參數,我們可以分析出此時函數調用棧的情況:
5、從上面可以看出,phase_1函數入口在虛擬地址0x8048c00,下一步需要分析phase_1函數。
1.2 phase_1函數分析
在asm.txt中尋找8048c00(或者繼續尋找phase_1)。
從上圖可以看出一些信息:
1、第378行:sub $0x1c, %esp,將函數棧空間擴展了0x1c字節(28個字節)
2、第379行:將0x804a3ec 放置到了esp+4的地方。
3、第381/382行:將input的內容放置到了esp的地方。注:20(%esp)正好是棧中存放input的內容。
4、第383行:調用strings_not_equal函數。
5、顯然,第379行以及第381/382行是在為調用strings_not_equal函數准備參數。在調用strings_not_equal函數之前(即382行執行之后,383行執行之前),
函數棧幀變成如下:
6、第384行:test %eax %eax,是對eax寄存器里的內容(string_not_equal函數的返回內容)進行位與操作,如果為0,則置zf標志(零標志)為1;
7、第385行:是一個je指令,je指令判斷zf標志(零標志)為1時(也即strings_not_equal函數返回的是0的情況下),跳轉到phase_2 + 0x20的地方,即0x8048c20的地方,說明炸彈拆除成功。否則,call 804939b <explode_bomb>,顧名思義,是爆炸炸彈,即拆除炸彈失敗。
8、從上面的分析來看,上圖中顯示的棧幀中,esp的內容是輸入的字符串的首地址,而esp + 4的內容是0x804a3ec,應該是在程序中保存的被比較的字符串(即拆彈字符串)的首地址,而按照strings_not_equal的名字來看,如果是不等,則返回1,等則返回0。如果等,代表輸入的拆彈字符串是正確的。
C語言偽代碼:
int32_t strings_not_equal(int32_t a1, int32_t a2); void explode_bomb(int32_t a1, int32_t a2); void phase_1(int32_t a1) { int32_t eax2; int32_t v3; eax2 = strings_not_equal(a1, "Why make trillions when we could make... billions?"); if (eax2 != 0) { explode_bomb(v3, a1); } return; }
所以下一步應該在運行的時候,查看0x804a3ec地址的內容,這即是我們要輸入的拆彈字符串。
但為進一步判斷我們上面的分析,下面再大致分析一下strings_not_equal函數。
1.3 strings_not_equal函數分析
根據上面的代碼,可以看出strings_not_equal函數的地址在0x80490ba的地方。搜索80490ba或者strings_not_equal。
執行第762 - 765行之后,函數棧幀為:
注意:
1、第766行,將esp + 0x14的內容(input(輸入字符串首地址))送入到了ebx寄存器,第767行,將esp + 0x18的內容(0x804a3ec)送入到了esi寄存器。驗證了我們前面所介紹的0x804a3ec地址所在的地方應該是拆彈字符串所在的首地址。
2、768-770行:求input字符串的長度,結果送入到edi寄存器。
3、771-772行:求0x804a3ec字符串的長度,結果保存在eax寄存器中。
4、773行:將1送入edx,通過后面的分析,可以知道edx存放的是返回結果,也即默認返回結果為1,即不等。
5、774-775行:比較edi和eax的內容,即input字符串與0x804a3ec為首地址的字符串長度進行比較,如果不等,則跳轉到strings_not_equal + 0x63的地方:0x80490ba + 0x63 = 0x804911d(此地的指令是將edx的內容送入到eax,並返回,注意第773行,edx的內容被賦值為1),也即返回1,代表兩個字符串不等。
6、后面的匯編代碼,是逐一比較兩個字符串的內容,如果相等,則返回0,如果不等則返回1。
綜合前面的分析,以C語言來表示strings_not_equal,其大致含義是:
int32_t string_length(signed char* a1); int32_t strings_not_equal(signed char* a1, signed char* a2) { signed char* ebx3; signed char* esi4; int32_t eax5; int32_t eax6; int32_t edx7; int32_t eax8; int32_t eax9; ebx3 = a1; esi4 = a2; eax5 = string_length(ebx3); eax6 = string_length(esi4); edx7 = 1; if (eax5 != eax6) { addr_0x804911d_2: return edx7; } else { eax8 = (int32_t)(uint32_t)(unsigned char)*ebx3; if (*(signed char*)&eax8 == 0) { edx7 = 0; goto addr_0x804911d_2; } else { if (*(signed char*)&eax8 == *esi4) { do { ++ebx3; ++esi4; eax9 = (int32_t)(uint32_t)(unsigned char)*ebx3; if (*(signed char*)&eax9 == 0) break; } while (*(signed char*)&eax9 == *esi4); goto addr_0x8049118_8; } else { edx7 = 1; goto addr_0x804911d_2; } } } edx7 = 0; goto addr_0x804911d_2; addr_0x8049118_8: edx7 = 1; goto addr_0x804911d_2; }
以上C語言代碼基本和匯編代碼相對應,可以對照理解。
1.4 尋找拆彈字符串
使用objdump --start-address=0x804a3ec -s bomb,即可查看以0x804a3ec開頭的段信息。下圖是一個示例,我們可以看出0x804a3ec開頭的字符串,正是前面找到的拆彈字符串!
從這里我們也可以看出,所有直接硬編碼進入代碼的字符串,以只讀數據的形式存放在只讀數據段中。