給出對應於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
1 phase_6
phase_6要求輸入6個1~6的數,這6個數不能重復。phase_6根據用戶的輸入,將某個鏈表按照用戶的輸入的值(進行某種計算后)進行排序,如果最終能排成降序,則解題成功。
phase_6主要考察學生對C語言指針、鏈表以及結構的機器級表示的掌握程度。
觀察框架源文件bomb.c:
從上可以看出:
1、首先調用了read_line()函數,用於輸入炸彈秘鑰,輸入放置在char* input中。
2、調用phase_6函數,輸入參數即為input,可以初步判斷,phase_6函數將輸入的input字符串作為參數。
因此下一步的主要任務是從asm.txt中查找在哪個地方調用了readline函數以及phase_6函數。
1.1 尋找並分析調用phase_6函數的代碼
打開asm.txt,尋找phase_6函數。
和phase_1類似分析:
1、當前棧的位置存放的是read_line函數讀入的一串輸入;
2、phase_6的函數入口地址為0x8048e81。
此時的函數棧為:
1.2 phase_6分析
在asm.txt中繼續尋找phase_6,或者尋找0x8048e81,找到phase_6函數入口:
584-591行:初始化函數棧幀,然后調用read_six_numbers函數。調用之后,從input中讀取了6個數num[0] ~ num[5](read_siz_numbers函數分析參見前面),位於esp+0x10 ~esp+0x24,此時函數棧幀如下圖所示:
2、592行:0 --> esi
3、593-597行:判斷(esp + esi*4 + 0x10)是否小於等於6以及大於等於1,如果不滿足,則引爆炸彈(625行);也即輸入的數(esi=0時,為num[0],esi=1時,為num[1]......)應該大於等於1,同時小於等於6。注意,比較時,先將該值減1(594行),然后與5進行比較(595行),比較時用的jbe(無符號整數比較),也即,如果該輸入值小於1,減1之后變成一個很大的無符號數(負數),肯定是大於5的。因此這幾行就實現了判斷num[esi] >=1 && num[esi] <=6。(這應該是編譯器做的優化)
4、598行:esi += 1
5、599-602行:esi與6進行比較,如果等於,則意味着6個數已經比較完畢,跳轉到8048ef7。
6、603行:如果602行沒有跳轉,也即6個數還沒有判斷完畢,則繼續執行,將esi賦值給ebx
7、604-607行:判斷num[ebx]是否與num[esi-1]相等,如果相等,則引爆炸彈;
8、608-610行:ebx+=1,然后判斷ebx是否小於等於5,如果是,則跳轉到8048ec1,即604行,也即跳轉到第7步。
9、611行:跳轉到8048e9f,進行num[esi]的比較(注意num[esi]在第608行加1)
10、綜合以上分析,可以判斷出以上代碼的作用是:
1)判斷每個輸入的數應小於等於6,大於等於1;
2)num[i]不等於它的后續的每個數;
3)也即輸入的6個數,應是1/2/3/4/5/6,但順序不一定。
使用類c語言描述:
for (i = 0; i < 6; i++) { if ((num[i] < 1) || (num[i] > 6)) { explode_bomb(); } for (j = i + 1; j < 6; j++) { if (num[i] == num[j]) { explode_bomb(); } } }
14、622行(0x8048fa2<phase_6+0x90>)- 625行:ebx保存到esi(mov %ebx, %esi),將esp + ebx*4 + 0x10的內容(當ebx=0時,為num[0],當ebx=1時,為num[1]......)與1相比較,如果 esp + ebx*4 + 0x10 <= 1(624行),則跳轉到 8048ee6 <phase_6+0x65>(625行),否則繼續執行626行。(根據前面分析,ecx為輸入的num的值,僅且僅當ecx=1時,執行這個625行跳轉語句,跳轉到8048ee6 <phase_6+0x65>。
15、617行(0x8048f91<phase_6+0x7f>):當ecx=1時,會執行該條語句,將0x804c174送入到edx。查看0x804c174地址的內容(objdump --start-address=0x804c174 -s bomb):
16、618行:將edx內容送入到esp + esi*4 + 0x28。
17、619-621行:ebx += 1,然后與6相比較,如果等於6,則跳轉到0x8048f0e 。
18、如果不等於6,則繼續執行622行,對於本文,即跳轉到第14步,前面分析了ecx=1的情況,如果ecx不等於1,則應繼續執行626-628行。
19、626-628行:eax賦值為1,edx賦值為0x804c174,跳轉到8048eda <phase_6+0x59>。(第612行)
20、612-615行:這是一個循環。判斷ecx(num[ebx])是否等於eax,如果不是,則將edx + 8的內容送入到edx,然后繼續判斷, edx +8的內容應該是指向的是一個地址。如果相等,則跳轉到 8048eda <phase_6+0x59>(從第612行繼續執行)
21、根據前面的分析,13~20步的代碼,是根據處理后的num值(參見第10步分析),將相關信息壓棧(從esp+28開始壓棧):(注意:IA32是小端方式)
1)當num[i] == 1時,將0x804c174(node1)壓入到esp + 0x28 + i * 4;
2)當num[i] == 2時,將0x804c180(node2)壓入到esp + 0x28 + i * 4;
3)當num[i] == 3時,將0x804c18c(node3)壓入到esp + 0x28 + i * 4;
4)當num[i] == 4時,將0x804c198(node4)壓入到esp + 0x28 + i * 4;
5)當num[i] == 5時,將0x804c1a4(node5)壓入到esp + 0x28 + i * 4;
6)當num[i] == 6時,將0x804c1b0(node6)壓入到esp + 0x28 + i * 4;
7)觀察壓入棧的內容,每個內容地址實際上是指向12(例如:0x804c180-0x804c174)字節的一段數據,該數據的末尾又是指向一個地址,因此,可以判斷0x804c174開始的地方指向的是一個鏈表(但這些鏈表的存空間是連續分配的),每個節點包括12個字節,其中最后一個是指向下一個的指針,猜測每個節點的定義:
struct node { int d1;//尚不清楚含義,以4個字節的int暫替 int d2;//尚不清楚含義,以4個字節的int暫替 struct node* next; }
6個節點,分別為:
node1 = {0x6d, 0x01, 0x804c180}; (&node1 = 0x804c174)
node2 = {0x69, 0x02, 0x804c18c }; (&node2 = 0x804c180)
node3 = {0x3b2, 0x03, 0x804c198}; (&node3 = 0x804c18c)
node4 = {0x299, 0x04, 0x804c1a4}; (&node4 = 0x804c198)
node5 = {0xc7, 0x05, 0x804c1b0}; (&node5 = 0x804c1a4)
node6 = {0x285b, 0x06, 0}; (&node6 = 0x804c1b0)
鏈接關系為:
node1 --> node2 --> node3 --> node4 --> node5 --> node6 --> 0
8)假設當前6個num的值為6/5/4/3/2/1,則經過6次循環后,函數棧幀如下圖所示。
注:后面分析,均假設6個num的值為6/5/4/3/2/1。
22、以上操作結束,則跳轉到 8048f0e <phase_6+0x8d>(第629行,參見第17步分析)。
23、629(8048fb9 <phase_6+0xa7>)- 676行:
1)629行:0x28(%esp)的內容(num[0]這個值指向的節點的地址) --> ebx
2)630行:esp+0x2c --> eax,esp+0x2c這個地址的內容為num[1]這個值對應的節點的地址
3)631行:esp + 0x40 --> esi,esp + 0x40,根據后面的分析,這個值是作為“哨兵”,防止訪問越界
4)632行:ebx --> ecx(此時ebx以及ecx都是num[0]這個值指向的節點的地址)
5)633行:eax所指向的地址的內容(num[1]這個值對應的節點的地址)--> edx
6)634行:將edx的內容賦值給8(%ecx)的地址,注意,此時ecx為num[0]指向的節點的地址,8(%ecx)正好是num[0]這個值所對應的next,即node6.next = &node5
7)635行:eax += 4,即eax所指向的地址的內容變成了num[2]所指向的節點的地址;
8)636行:將eax與esi(哨兵)相比較,如果等於,則說明循環結束,跳轉到 8048f2c <phase_6+0xab>,如果不是,繼續執行。
9)638行:edx --> ecx:根據前面分析,edx為num[1]值指向的節點的地址。
10)639行:跳轉到8048f1c <phase_6+0x9b>,即第633行,可以轉到上面第5)步繼續執行,注意,此時edx為num[1]值指向的節點地址,eax的內容為num[2]值指向的節點的地址,即node5.next = &node4。
11)如此循環,最后的結果是:
node6.next = &node5,node5.next = &node4,node4.next = &node3,node3.next = &node2,node2.next = &node1
12)如果以上都做完,跳轉到8048fd7 <phase_6+0xc5>(677行)繼續。
13)640行:將0賦值給8(%edx)指向的地址,此時edx為node1的地址,即將node1.next=0;
14)顯然,以上步驟,根據num的值重新構成了一個鏈表,此時的鏈接關系變成了:
node6 --> node5 --> node4 --> node3 --> node2 --> node1 --> 0。(注:以上分析均是基於6個num的值為6/5/4/3/2/1)
24、641 - 行:
1)641行:5 --> esi
2)642行:將ebx+8這個地址的內容送給eax,注意, ebx為node6的地址,ebx+8為node6->next這個值的地址,這個地址的內容即為node5的地址。也即eax的內容為node5的地址。
3)643行:將eax指向的地址的內容賦值為eax,也即eax的內容為node5.d1
4)644行:將node5.d1與ebx指向的地址的內容相比較;(顯然,此處是整數的比較,因此,也可以判斷struct node中第一個元素應該是int),此時ebx的內容為node6的地址,node6的地址的內容為node6.d1,即node5.d1與node6.d1相比較。
5)645-646行:如果node6.d1 >= node5.d1,則跳轉到8048f46 <phase_6+0xc5>,否則引爆炸彈
6)647行:ebx的內容變為其指向的節點的next,即ebx=node6-next,指向了node5
7)648行:esi-= 1
8)649行:如果esi不為0,則跳轉到642行,按以上的2)繼續分析,應注意,此處ebx的值為node5的地址了。
9)顯然,此時會判斷node5.d1是否大於等於node4.d1,如果是,則繼續,如果不是,則引爆炸彈
10)后續會依次判斷node4.d1是否大於等於node3.d1,node3.d1是否大於等於node2.d1,......,綜合起來,就是判斷按照num值排序之后的節點是否降序排列,如果是,則解題成功,如果不是,則引爆炸彈。
1.3 phase_6結果分析
根據以上分析,phase_6的功能:
1)phase_6定義了一個包含6個節點的鏈表,每個節點中包含兩個整型(d1,d2),以及指向下一個節點的指針;6個節點依次的鏈接順序為node1->node2->node3->node4->node5->node6
2)要求用戶輸入6個數,這6個數應為1~6,而且不能重;為便於以后說明,假設這6個數為6/5/4/3/2/1。
3)按照num[i]的值重新排列鏈表,此時鏈表變為:
node6->node5->node4->node3->node2->node1
4)判斷以上鏈表是否降序排列(按分量d1),如果是,則拆彈成功,否則,引爆炸彈。
也即phase_6會給出一個鏈表,鏈表中的節點的d1分量含有一個整數值,需要用戶輸入一個序列號,按照這個順序重新排列鏈表中的節點,如果鏈表是按照降序排列,則輸入的這個序列號是正確的。
對於前面的炸彈,其初始化的節點值為:
node1 = {0x6d, 0x01, 0x804c180}; (&node1 = 0x804c174)
node2 = {0x69, 0x02, 0x804c18c }; (&node2 = 0x804c180)
node3 = {0x3b2, 0x03, 0x804c198}; (&node3 = 0x804c18c)
node4 = {0x299, 0x04, 0x804c1a4}; (&node4 = 0x804c198)
node5 = {0xc7, 0x05, 0x804c1b0}; (&node5 = 0x804c1a4)
node6 = {0x285b, 0x06, 0}; (&node6 = 0x804c1b0)
顯然,使得這個鏈表按降序排列的序列是:3 4 6 5 1 2,因此,輸入的序列號應為:3 4 6 5 1 2,此即為本關答案。