1. 程式人生 > >《深入理解計算機系統》實驗二 —— Bomb Lab

《深入理解計算機系統》實驗二 —— Bomb Lab

> 這是CSAPP的第二個實驗,主要讓我們理解程式碼的機器級表示,最重要的是理解每個暫存器的作用以及如何使用這些暫存器。本次的實驗內容有點晦澀難懂,對於這些內容多看下習慣就好了。 >   本次實驗中的bomb檔案中共有7個炸彈問題(6個顯式的和1個隱藏的),每條問題只有輸入正確的答案才能進入下一題,否則則會觸發爆炸。通過閱讀bomb檔案的彙編程式碼理解各個問題的運作方式,推出正確的輸入答案。隱藏的問題需要通過gdb直接呼叫解決。   我的編譯環境:Ubuntu 16.04,gcc 5.4.0。 ## 準備工作   從官網下載到實驗,解壓後一共三個檔案,具體如下圖所示。 ![3](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171124760-482969894.png)   readme中沒寫什麼有用的內容,bomb檔案是編譯完成的檔案,bomb.c是本次實驗的原始碼,開啟看下,大概瀏覽了一遍,一共有phase_1 ~ phase_6 6個炸彈,從命令列輸入的內容必須要和phase函式中的一致,否則就會爆炸退出程式。phase函式並沒有給出原始碼,所以無法得知其期望的字串是什麼。給了bomb可執行檔案,我們就把這個檔案反彙編下,從反彙編推算下其內容是什麼。   首先使用objdump -d bomb > bomb.asm命令生成反彙編檔案。 ![4](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171124958-1822589666.png)   先執行bomb檔案,提示沒有許可權,我的檔案是從windwos拷貝到Linux虛擬機器中的,所以會報這個錯誤。執行chmod +777 bomb 賦予許可權。如下圖所示。 ![1](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171125311-890510858.png)   然後隨便輸入一些內容看下會有什麼後果,如下圖所示,提示已經爆炸。 ![6](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171125708-173954375.png) ## phase_1   下面從main函式開始分析下反彙編。 ```bash 0000000000400da0
: 400da0: 53 push %rbx 400da1: 83 ff 01 cmp $0x1,%edi #if (argc == 1) 400da4: 75 10 jne 400db6 # 不相等就跳轉到400db6 400da6: 48 8b 05 9b 29 20 00 mov 0x20299b(%rip),%rax # 603748 400dad: 48 89 05 b4 29 20 00 mov %rax,0x2029b4(%rip) # 603768 相等就讀取輸入 400db4: eb 63 jmp 400e19 #跳轉到initialize_bomb 400db6: 48 89 f3 mov %rsi,%rbx 400db9: 83 ff 02 cmp $0x2,%edi #else if (argc == 2) 400dbc: 75 3a jne 400df8 #不相等跳轉到400df8 400dbe: 48 8b 7e 08 mov 0x8(%rsi),%rdi 400dc2: be b4 22 40 00 mov $0x4022b4,%esi 400dc7: e8 44 fe ff ff callq 400c10 400dcc: 48 89 05 95 29 20 00 mov %rax,0x202995(%rip) # 603768 400dd3: 48 85 c0 test %rax,%rax 400dd6: 75 41 jne 400e19 #跳轉到initialize_bomb 400dd8: 48 8b 4b 08 mov 0x8(%rbx),%rcx 400ddc: 48 8b 13 mov (%rbx),%rdx 400ddf: be b6 22 40 00 mov $0x4022b6,%esi 400de4: bf 01 00 00 00 mov $0x1,%edi #傳參 400de9: e8 12 fe ff ff callq 400c00 <__printf_chk@plt> #printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]); 400dee: bf 08 00 00 00 mov $0x8,%edi 400df3: e8 28 fe ff ff callq 400c20 #exit(8); 400df8: 48 8b 16 mov (%rsi),%rdx 400dfb: be d3 22 40 00 mov $0x4022d3,%esi 400e00: bf 01 00 00 00 mov $0x1,%edi 400e05: b8 00 00 00 00 mov $0x0,%eax #傳參 400e0a: e8 f1 fd ff ff callq 400c00 <__printf_chk@plt> #printf("Usage: %s []\n", argv[0]); 400e0f: bf 08 00 00 00 mov $0x8,%edi 400e14: e8 07 fe ff ff callq 400c20 #exit(8); 400e19: e8 84 05 00 00 callq 4013a2 #呼叫initialize_bomb(); 400e1e: bf 38 23 40 00 mov $0x402338,%edi 400e23: e8 e8 fc ff ff callq 400b10 #printf("Welcome to my fiendish little bomb. You have 6 phases with\n"); 400e28: bf 78 23 40 00 mov $0x402378,%edi 400e2d: e8 de fc ff ff callq 400b10 #printf("which to blow yourself up. Have a nice day!\n"); 400e32: e8 67 06 00 00 callq 40149e #呼叫read_line(); 400e37: 48 89 c7 mov %rax,%rdi #傳參 400e3a: e8 a1 00 00 00 callq 400ee0 #呼叫phase_1(); 400e3f: e8 80 07 00 00 callq 4015c4 #呼叫phase_defused(); 400e44: bf a8 23 40 00 mov $0x4023a8,%edi 400e49: e8 c2 fc ff ff callq 400b10 #printf("Phase 1 defused. How about the next one?\n"); 400e4e: e8 4b 06 00 00 callq 40149e #呼叫read_line(); 400e53: 48 89 c7 mov %rax,%rdi #傳參 400e56: e8 a1 00 00 00 callq 400efc #呼叫phase_2(); 400e5b: e8 64 07 00 00 callq 4015c4 400e60: bf ed 22 40 00 mov $0x4022ed,%edi 400e65: e8 a6 fc ff ff callq 400b10 400e6a: e8 2f 06 00 00 callq 40149e 400e6f: 48 89 c7 mov %rax,%rdi 400e72: e8 cc 00 00 00 callq 400f43 #呼叫phase_3(); 400e77: e8 48 07 00 00 callq 4015c4 400e7c: bf 0b 23 40 00 mov $0x40230b,%edi 400e81: e8 8a fc ff ff callq 400b10 400e86: e8 13 06 00 00 callq 40149e 400e8b: 48 89 c7 mov %rax,%rdi 400e8e: e8 79 01 00 00 callq 40100c #呼叫phase_4(); 400e93: e8 2c 07 00 00 callq 4015c4 400e98: bf d8 23 40 00 mov $0x4023d8,%edi 400e9d: e8 6e fc ff ff callq 400b10 400ea2: e8 f7 05 00 00 callq 40149e 400ea7: 48 89 c7 mov %rax,%rdi 400eaa: e8 b3 01 00 00 callq 401062 #呼叫phase_4(); 400eaf: e8 10 07 00 00 callq 4015c4 400eb4: bf 1a 23 40 00 mov $0x40231a,%edi 400eb9: e8 52 fc ff ff callq 400b10 400ebe: e8 db 05 00 00 callq 40149e 400ec3: 48 89 c7 mov %rax,%rdi 400ec6: e8 29 02 00 00 callq 4010f4 #呼叫phase_4(); 400ecb: e8 f4 06 00 00 callq 4015c4 400ed0: b8 00 00 00 00 mov $0x0,%eax 400ed5: 5b pop %rbx 400ed6: c3 retq ```   大概分析了下主函式,主要還是傳參和函式的呼叫,想要得出結果還是要看phase_1 ~ phase_6這些函式的反彙編。 ```bash 0000000000400ee0 : 400ee0: 48 83 ec 08 sub $0x8,%rsp #開闢記憶體空間 400ee4: be 00 24 40 00 mov $0x402400,%esi #傳參。這個引數很可能就是期望輸入的字串 400ee9: e8 4a 04 00 00 callq 401338 #strings_not_equal 比較兩個字串結果儲存在 %eax 400eee: 85 c0 test %eax,%eax # 測試 %eax 為0還是1 400ef0: 74 05 je 400ef7 #相等就跳轉到400ef7 400ef2: e8 43 05 00 00 callq 40143a #否則就explode_bomb,失敗 400ef7: 48 83 c4 08 add $0x8,%rsp #正常結束 400efb: c3 retq ```   第二行表示為函式開闢記憶體空間。第三行給%esi傳進了一個引數,然後就呼叫了strings_not_equal。由strings_not_equal的反彙編可以看出,這個函式是接受兩個引數的。那麼,除了%esi傳進的一個,另一個就是我們輸入的字串了。strings_not_equal比較的結果放在%eax中。接下來測試%eax為0還是1 ,如果%eax為1就表明兩個字串相等,跳轉到相等400ef7,正常結束程式。否則就跳轉到explode_bomb,失敗。 ```bash 0000000000401338 : 401338: 41 54 push %r12 40133a: 55 push %rbp 40133b: 53 push %rbx 40133c: 48 89 fb mov %rdi,%rbx # 這裡傳入了兩個引數%rdi,%rsi,這兩個引數一個是輸入的字串,另一個是期望的字串 40133f: 48 89 f5 mov %rsi,%rbp 401342: e8 d4 ff ff ff callq 40131b ```   通過以上分析,我們可以得出結論,在記憶體為0x402400的地方儲存的就是程式期望我們輸入的字串,那麼我們利用GDB工具除錯下程式碼,列印0x402400處的值看下。 ![5](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171126253-1527489991.png)   下面輸入這個字串測試下,結果顯示完全正確。 ![7](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171126640-1482251921.png) ## phase_2   下面繼續看phase_2的反彙編。 ```bash 0000000000400efc : 400efc: 55 push %rbp 400efd: 53 push %rbx 400efe: 48 83 ec 28 sub $0x28,%rsp 400f02: 48 89 e6 mov %rsp,%rsi 400f05: e8 52 05 00 00 callq 40145c #read_six_numbers 讀取6個數字 400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) #比較 1 和(%rsp)的值 400f0e: 74 20 je 400f30 #相等就繼續400f30 400f10: e8 25 05 00 00 callq 40143a #否則就呼叫explode_bomb 400f15: eb 19 jmp 400f30 400f17: 8b 43 fc mov -0x4(%rbx),%eax # %rbx-0x4 的值放到%eax 也就是上上個元素 1 400f1a: 01 c0 add %eax,%eax # 2*%eax 400f1c: 39 03 cmp %eax,(%rbx) # 比較 2*%eax (%rbx) 400f1e: 74 05 je 400f25 #相等就跳轉到400f25 400f20: e8 15 05 00 00 callq 40143a #否則就呼叫explode_bomb 400f25: 48 83 c3 04 add $0x4,%rbx 400f29: 48 39 eb cmp %rbp,%rbx 400f2c: 75 e9 jne 400f17 400f2e: eb 0c jmp 400f3c 400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx #0x4+%rsp的值放到%rbx 400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp #0x18+%rsp的值放到%rbp 400f3a: eb db jmp 400f17 400f3c: 48 83 c4 28 add $0x28,%rsp 400f40: 5b pop %rbx 400f41: 5d pop %rbp 400f42: c3 retq ```   虛擬碼: ```c // %rsp %rsp+0x4 %rsp+0x18 if((%rsp) == 1) { goto 400f30; } 400f17: %eax = %rbx-4; //%eax其實就是 %rsp %eax=2*%eax; //%rsp = 2*%rsp if((%rbx)==%eax) //要滿足兩倍關係 %rbx代表的是%rsp+0x4地址處的資料,%eax代表的是 2*(%rsp),意思就是說下一個數要是上一個數的兩倍 { %rbx=%rbx+0x4; //下一地址 +0x4 if(%rbx==%rbp) //%rbp = %rsp+0x18 %rbp儲存的是結束的條件 { return; } else { goto 400f17 } } else { explode_bomb } else { explode_bomb } 400f30: %rbx=%rsp+0x4; %rbp=%rsp+0x18; goto 400f17; ```   直接看彙編程式碼有點複雜,所以捋了下思路,寫一個虛擬碼出來會方便看一點。從反彙編的read_six_numbers可以看出,答案一定是6個數字。   程式一開始將%rsp的值和1比較,只有相等時,才會繼續進行。說明第一個數一定要是1。接著跳轉到400f30將%rsp+0x4,%rsp+0x18。跳轉到400f17,到這裡後,把%rbx的值減去了4,減完之後,%eax其實就是%rsp的值,回到了最初的狀態。接著將%eax乘以2,%rbx代表的是%rsp+0x4地址處的資料,%eax代表的是 2*(%rsp),意思就是說下一個數要是上一個數的兩倍才會跳進if語句中,否則就會爆炸。接著將%rbx+0x4,看起來像是將地址+4.指向下一個數字。這個時候再將%rbx和%rbp比較,從這裡可以判斷出%rbp很可能就是結束的條件,只有%rbx == %rbp,才會正常結束程式,而且只有這一條路可以結束正序,否則就會爆炸。   分析到這裡可以得出三個重要的結論:1.第一個數是1。2. 6個數字的關係為:後一個數是前一個數的兩倍。3.結束的條件存放在%rsp+0x18。所以根據這三個結論我們可以推斷出6個數字為:1 2 4 8 16 32。執行程式測試結果完全正確。 ![image-20201113164615181](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171126933-1634735897.png) ## phase_3 ```bash 0000000000400f43 : 400f43: 48 83 ec 18 sub $0x18,%rsp 400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # %rcx = 0xc + %rsp 第二個引數 400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # %rdx = 0x8 + %rsp 第一個引數 400f51: be cf 25 40 00 mov $0x4025cf,%esi # %esi = 0x4025cf %d %d 400f56: b8 00 00 00 00 mov $0x0,%eax # %eax = 0 400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt> 400f60: 83 f8 01 cmp $0x1,%eax # %eax 和 1 比較 輸入的數的個數要大於1 400f63: 7f 05 jg 400f6a # %eax > 0x1, 大於就跳轉到400f6a,否則explode_bomb 400f65: e8 d0 04 00 00 callq 40143a # %eax <= 0x1 explode_bomb 400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) # 0x8(%rsp) 0x7 400f6f: 77 3c ja 400fad # 0x8(%rsp)>=0x7 跳轉 explode_bomb 第一個引數大於7 爆炸 <0 400f71: 8b 44 24 08 mov 0x8(%rsp),%eax # 0x8(%rsp) < 0x7 則 %eax = 0x8(%rsp) [0,6] 400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(, %rax,8) # 跳轉表 *0x402470 + 8 * %rax *0x402470 = 0x400f7c 400f7c: b8 cf 00 00 00 mov $0xcf,%eax # %eax = 0xcf = 207 case 0 400f81: eb 3b jmp 400fbe # 400fbe 400f83: b8 c3 02 00 00 mov $0x2c3,%eax # %eax = 0x2c3 = 707 case 2 400f88: eb 34 jmp 400fbe 400f8a: b8 00 01 00 00 mov $0x100,%eax # %eax = 0x100 = 256 case 3 400f8f: eb 2d jmp 400fbe 400f91: b8 85 01 00 00 mov $0x185,%eax # %eax = 0x185 = 389 case 4 400f96: eb 26 jmp 400fbe 400f98: b8 ce 00 00 00 mov $0xce,%eax # %eax = 0xce = 206 case 5 400f9d: eb 1f jmp 400fbe 400f9f: b8 aa 02 00 00 mov $0x2aa,%eax # %eax = 0x2aa = 682 case 6 400fa4: eb 18 jmp 400fbe 400fa6: b8 47 01 00 00 mov $0x147,%eax # %eax = 0x147 = 327 case 7 400fab: eb 11 jmp 400fbe 400fad: e8 88 04 00 00 callq 40143a 400fb2: b8 00 00 00 00 mov $0x0,%eax # %eax = 0x0 = 0 400fb7: eb 05 jmp 400fbe 400fb9: b8 37 01 00 00 mov $0x137,%eax # %eax = 0x137 = 311 case 1 400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax # 0xc(%rsp) %eax 400fc2: 74 05 je 400fc9 # 0xc(%rsp)==%eax 輸入的第二個引數 和 %eax相等則解除 400fc4: e8 71 04 00 00 callq 40143a # 0xc(%rsp)!=%eax explode_bomb 400fc9: 48 83 c4 18 add $0x18,%rsp 400fcd: c3 retq ```   從第5行的0x4025cf入手,檢視後發現是%d %d ,說明輸入的是兩個整數。 ![image-20201113223724669](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171127562-777436984.png)   第7行,第8行說明輸入的引數個數要大於1。第11行將第一個引數0x8(%rsp) 和7比較,大於7則爆炸,說明輸入的引數要小於等於7,同時ja為無符號跳轉,則引數還有大於0,因此得出第一個引數的範圍[0,7]。第14行為間接跳轉,以 *0x402470 處的值為基地址,再加上8 * %rax 進行跳轉,不同的 %rax 跳轉到不同的位置。 ![image-20201113223846991](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171127989-1380396166.png)   我們可以看下0x402470的值為0x400f7c。當%rax 為0時,跳轉到0x400f7c。此時%eax = 207,最後跳轉到33行,將207於輸入的值比較。說明0 和 207為一組正確資料。測試下 ![image-20201117154411847](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171128503-2044017996.png)   其他結果如彙編中的註釋所示。 ## phase_4 ```bash 000000000040100c : 40100c: 48 83 ec 18 sub $0x18,%rsp 401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # 輸入的第二個數 401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx #傳參 輸入的第一個數 40101a: be cf 25 40 00 mov $0x4025cf,%esi # %d %d 40101f: b8 00 00 00 00 mov $0x0,%eax 401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt> 401029: 83 f8 02 cmp $0x2,%eax # 輸入兩個引數 40102c: 75 07 jne 401035 40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) # 第一個引數和14比較,要比14小 401033: 76 05 jbe 40103a 401035: e8 00 04 00 00 callq 40143a 40103a: ba 0e 00 00 00 mov $0xe,%edx 40103f: be 00 00 00 00 mov $0x0,%esi # 三個引數 401044: 8b 7c 24 08 mov 0x8(%rsp),%edi 401048: e8 81 ff ff ff callq 400fce # 三個引數:0x8(%rsp),0,14 40104d: 85 c0 test %eax,%eax # 測試返回值是否為0 40104f: 75 07 jne 401058 # 返回值不為0 爆炸 401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) # 測試第二個引數是否為0 401056: 74 05 je 40105d # 為0 不會爆炸,所以第二個數一定要輸入0 401058: e8 dd 03 00 00 callq 40143a 40105d: 48 83 c4 18 add $0x18,%rsp 401061: c3 retq ```   從第5行看起,0x4025cf指向的地方儲存的仍然是 兩個int型整數。第8行和2比較,說明輸入引數的個數為2。第10行和14比較,說明輸入的第一個引數一定要小於14。第13,14,15行向func4()傳遞三個引數0x8(%rsp),0,14 。第17行測試函式返回值是否為0,要想不爆炸,函式返回值一定要為0。第19行說明輸入的第二個引數一定要為0。   所以,我們要確定的是當輸入的第一個引數為多少的時候,fun4()的返回值為0。下面看下fun4()的反彙編。 ```bash 0000000000400fce : 400fce: 48 83 ec 08 sub $0x8,%rsp 400fd2: 89 d0 mov %edx,%eax # %edx第三個引數 400fd4: 29 f0 sub %esi,%eax # %esi = %esi-%eax %esi 第二個引數 400fd6: 89 c1 mov %eax,%ecx 400fd8: c1 e9 1f shr $0x1f,%ecx # 0x1f >> %ecx 400fdb: 01 c8 add %ecx,%eax # %ecx =%ecx,%eax 400fdd: d1 f8 sar %eax 400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx # %ecx = %rax + %rsi 400fe2: 39 f9 cmp %edi,%ecx # %edi第一個引數 400fe4: 7e 0c jle 400ff2 # %edi<= %ecx 跳轉400ff2 400fe6: 8d 51 ff lea -0x1(%rcx),%edx # %edx = %rcx-0x1 400fe9: e8 e0 ff ff ff callq 400fce # 遞迴 400fee: 01 c0 add %eax,%eax # %eax = 2 * %eax 400ff0: eb 15 jmp 401007 # return 400ff2: b8 00 00 00 00 mov $0x0,%eax 400ff7: 39 f9 cmp %edi,%ecx 400ff9: 7d 0c jge 401007 # %edi >= %ecx return 400ffb: 8d 71 01 lea 0x1(%rcx),%esi # %esi = %rcx + 0x1 400ffe: e8 cb ff ff ff callq 400fce # 遞迴呼叫 401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax # %eax = %rax + %rax 401007: 48 83 c4 08 add $0x8,%rsp 40100b: c3 retq ```   將彙編翻譯為C如下所示 ```C //x: %edi y:%esi z:%edx k: %ecx t:%eax void func4(int x,int y,int z) {//x in %rdi,y in %rsi,z in %rdx,t in %rax,k in %ecx //y的初始值為0,z的初始值為14 int t=z-y; int k=t>>31; t=(t+k)>>1; k=t+y; if(k>x) { z=k-1; func4(x,y,z); t=2t; return; } else { t=0; if(k: 401062: 53 push %rbx 401063: 48 83 ec 20 sub $0x20,%rsp # 開闢空間 401067: 48 89 fb mov %rdi,%rbx 40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 401071: 00 00 401073: 48 89 44 24 18 mov %rax,0x18(%rsp) 401078: 31 c0 xor %eax,%eax 40107a: e8 9c 02 00 00 callq 40131b # 字串長度 40107f: 83 f8 06 cmp $0x6,%eax # 字串長度要為6 401082: 74 4e je 4010d2 401084: e8 b1 03 00 00 callq 40143a 401089: eb 47 jmp 4010d2 ##############################################start################################################################################ 40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx # %ecx = %rbx + %rax ASCII碼值 給%ecx 40108f: 88 0c 24 mov %cl,(%rsp) # ASCII碼值取低8位 401092: 48 8b 14 24 mov (%rsp),%rdx 401096: 83 e2 0f and $0xf,%edx # ASCII碼值取低4位 401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx # %edx = %rdx + 0x4024b0(maduiersnfotvbyl)%edx:%rdx低4位有效 4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) # %edx低八位存在 0x10 + %rsp + %rax 中 4010a4: 48 83 c0 01 add $0x1,%rax # %rax = %rax + 1 4010a8: 48 83 f8 06 cmp $0x6,%rax 4010ac: 75 dd jne 40108b # %rax 不等於6 則迴圈 #################################################end################################################################################### 4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp) 4010b3: be 5e 24 40 00 mov $0x40245e,%esi # %esi指向從0x40245e記憶體單元讀入的字串flyers 4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi # %rdi指向前面迴圈中構造好的長度為6的字串 4010bd: e8 76 02 00 00 callq 401338 # 判斷%esi和%rdi指向的字串是否相等 4010c2: 85 c0 test %eax,%eax 4010c4: 74 13 je 4010d9 #只有兩個字串相等,才會解除 4010c6: e8 6f 03 00 00 callq 40143a 4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 4010d0: eb 07 jmp 4010d9 4010d2: b8 00 00 00 00 mov $0x0,%eax # 迴圈開始賦初值 4010d7: eb b2 jmp 40108b 4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax 4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 4010e5: 00 00 4010e7: 74 05 je 4010ee 4010e9: e8 42 fa ff ff callq 400b30 <__stack_chk_fail@plt> 4010ee: 48 83 c4 20 add $0x20,%rsp 4010f2: 5b pop %rbx 4010f3: c3 retq #%rbp %rbx %r12~%15 被呼叫者儲存暫存器 # %r10 %r11 呼叫者儲存暫存器 %rdi %rsi %rdx %rcx %r8 %r9 依次儲存引數1~6 %dl表示%rdx暫存器最低8bit ```   這個題目有點擾啊,兜了很大一個圈子!   第7行 ~ 13行說明輸入的字串長度要為6。   第15行 ~ 23行為一個迴圈。輸入的字串儲存在%rbx中,第15行表示把輸入字串的第%eax個字元的ASCII碼值給%ecx,%cl為%ecx的低8位,所以第16行為取%ecx的低八位。   第18行表示再取低4位。   第19行的0x4024b0檢視內容為maduiersnfotvbyl,這句話的意思是以0x4024b0為基地址,以%rdx為偏移,從maduiersnfotvbyl字串中取字元的**低32位**,結果放在%edx中。   第20行,%dl中的值應為0x4024b0+%rdx表示的字元,將其賦值給0x10(%rsp,%rax,1),最後計數器%rax+1。   第22行,表示是否迴圈夠了6次。   第25行,0x40245e字串為flyers,比較兩個字串,如果%eax為0(兩個字串相同),則解除炸彈,否則爆炸。   所以,0x4024b0 + %rdx = {flyers的ASCII碼}。 ![](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171129426-1622021520.png)   flyers對應的ascii值 0x66 0x6c 0x79 0x65 0x72 0x73。   與0x4024b0記憶體地址開始的查詢表比較獲得偏移量 0x9 0xF 0xE 0x5 0x6 0x72。   因此輸入長度為6的字串中每個字元的低4bit的值分別為0x9 0xF 0xE 0x5 0x6 0x72。   若輸入為大寫字母,將低4bit的值加上0x40,獲得輸入字串IONEFG。   若輸入為小寫字母,將低4bit的值加上0x60,獲得輸入字串ionefg。 ![image-20201116224707768](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171129698-282377742.png) ## phase_6 ```bash (gdb) disas phase_6 Dump of assembler code for function phase_6: 0x00000000004010f4 <+0>: push %r14 將被呼叫者儲存暫存器壓入棧 0x00000000004010f6 <+2>: push %r13 0x00000000004010f8 <+4>: push %r12 0x00000000004010fa <+6>: push %rbp 0x00000000004010fb <+7>: push %rbx %rsp = 0x7fffffffe2c0 0x00000000004010fc <+8>: sub $0x50,%rsp 分配棧空間 %rsp = 0x7fffffffe270 0x0000000000401100 <+12>: mov %rsp,%r13 0x0000000000401103 <+15>: mov %rsp,%rsi 0x0000000000401106 <+18>: callq 0x40145c 讀入6個值,儲存至從 %rsi 開始的地址 0x000000000040110b <+23>: mov %rsp,%r14 0x000000000040110e <+26>: mov $0x0,%r12d %r12 置0,並且%r13 %r14 %rbp 均和 %rsp 指向相同地址 0x7fffffffe270 0x0000000000401114 <+32>: mov %r13,%rbp 0x0000000000401117 <+35>: mov 0x0(%r13),%eax 將第 %r13 指向的輸入數複製到 %eax 0x000000000040111b <+39>: sub $0x1,%eax 將輸入數減1 0x000000000040111e <+42>: cmp $0x5,%eax 判斷輸入數是否小於等於6,因為上一步中減1操作 0x0000000000401121 <+45>: jbe 0x401128 若大於6,則呼叫 explode_bomb 0x0000000000401123 <+47>: callq 0x40143a ========================================================================================================================================================= 0x0000000000401128 <+52>: add $0x1,%r12d 將 %r12 加1 0x000000000040112c <+56>: cmp $0x6,%r12d 判斷 %r12 是否等於6 0x0000000000401130 <+60>: je 0x401153 若等於6,跳轉,否則繼續執行 0x0000000000401132 <+62>: mov %r12d,%ebx 將 %r12 複製到 %ebx 0x0000000000401135 <+65>: movslq %ebx,%rax 將 %ebx 符號位擴充套件複製到 %rax 0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax 將第 %ebx 輸入數複製到 %eax 0x000000000040113b <+71>: cmp %eax,0x0(%rbp) 比較 %r13 指向的輸入數和 第 %ebx 輸入數 是否相等 0x000000000040113e <+74>: jne 0x401145 如果相等,則呼叫 explode_bomb 0x0000000000401140 <+76>: callq 0x40143a 0x0000000000401145 <+81>: add $0x1,%ebx 將 %ebx 加1 0x0000000000401148 <+84>: cmp $0x5,%ebx 判斷 %ebx 是否小於等於5 0x000000000040114b <+87>: jle 0x401135 若小於等於,跳轉,否則繼續執行;該迴圈判斷 %r13 指向的資料和其後輸入數不相等 0x000000000040114d <+89>: add $0x4,%r13 將 %r13 指向下一個輸入數,該迴圈判斷所有的輸入數全部不相等 0x0000000000401151 <+93>: jmp 0x401114 ========================================================================================================================================================= 0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi 將 %rsi 指向棧中跳過讀入資料位置作為結束標記,並且 %r14 仍和 %rsp 指向同一個位置 0x0000000000401158 <+100>: mov %r14,%rax 將 %r14 複製到 %rax 0x000000000040115b <+103>: mov $0x7,%ecx 0x0000000000401160 <+108>: mov %ecx,%edx 將立即數0x7複製到 %edx 0x0000000000401162 <+110>: sub (%rax),%edx 立即數7減去 %r14 指向的資料 0x0000000000401164 <+112>: mov %edx,(%rax) 將7減的結果存回 %r14 執行的記憶體單元 0x0000000000401166 <+114>: add $0x4,%rax %rax 指向下一個輸入數 0x000000000040116a <+118>: cmp %rsi,%rax 比較是否達到輸入陣列的末尾, 0x000000000040116d <+121>: jne 0x401160 該迴圈使用立即數7減去每個輸入資料 ========================================================================================================================================================== 0x000000000040116f <+123>: mov $0x0,%esi 將 %rsi 置0 0x0000000000401174 <+128>: jmp 0x401197 0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx 將 0x8(%rdx) 指向記憶體單元的內容複製到 %rdx, 指向連結串列下一個元素 0x000000000040117a <+134>: add $0x1,%eax 將 %eax 加1 0x000000000040117d <+137>: cmp %ecx,%eax 比較 %ecx 和 %eax 是否相等 0x000000000040117f <+139>: jne 0x401176 不相等,繼續遍歷連結串列,最終 %rdx 指向連結串列的第 %ecx 個節點 0x0000000000401181 <+141>: jmp 0x401188 0x0000000000401183 <+143>: mov $0x6032d0,%edx 重置連結串列首地址 0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2) 0x000000000040118d <+153>: add $0x4,%rsi 0x0000000000401191 <+157>: cmp $0x18,%rsi 0x0000000000401195 <+161>: je 0x4011ab 0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx 將 (%rsp + %rsi) 指向的資料複製到 %ecx 0x000000000040119a <+166>: cmp $0x1,%ecx 比較 %ecx 是否小於等於1 0x000000000040119d <+169>: jle 0x401183 若小於等於,跳轉,否則繼續執行, 等於1, %edx 直接指向連結串列首地址 0x000000000040119f <+171>: mov $0x1,%eax 將 %eax 置1 0x00000000004011a4 <+176>: mov $0x6032d0,%edx 將 %rdx 指向記憶體單元 0x6032d0 0x00000000004011a9 <+181>: jmp 0x401176 跳轉; 該迴圈根據輸入數將連結串列中對應的第輸入數個節點的地址複製到 0x20(%rsp) 開始的棧中 ========================================================================================================================================================== 0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx 將0x20(%rsp)的連結串列節點地址複製到 %rbx 0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax 將 %rax 指向棧中下一個連結串列節點的地址 0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi 將 %rsi 指向儲存的連結串列節點地址的末尾 0x00000000004011ba <+198>: mov %rbx,%rcx 0x00000000004011bd <+201>: mov (%rax),%rdx 0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx) 將棧中指向的後一個節點的地址複製到前一個節點的地址位置 0x00000000004011c4 <+208>: add $0x8,%rax 移動到下一個節點 0x00000000004011c8 <+212>: cmp %rsi,%rax 判斷6個節點是否遍歷完畢 0x00000000004011cb <+215>: je 0x4011d2 0x00000000004011cd <+217>: mov %rdx,%rcx 0x00000000004011d0 <+220>: jmp 0x4011bd 0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx) 該迴圈按照7減去輸入資料的索引重新調整連結串列 ========================================================================================================================================================== 0x00000000004011da <+230>: mov $0x5,%ebp 0x00000000004011df <+235>: mov 0x8(%rbx),%rax 將 %rax 指向 %rbx 下一個連結串列節點 0x00000000004011e3 <+239>: mov (%rax),%eax 0x00000000004011e5 <+241>: cmp %eax,(%rbx) 比較連結串列節點中第一個欄位值的大小,如果前一個節點值大於後一個節點值,跳轉 0x00000000004011e7 <+243>: jge 0x4011ee 0x00000000004011e9 <+245>: callq 0x40143a 0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx 將 %rbx 向後移動,指向棧中下一個連結串列節點的地址 0x00000000004011f2 <+254>: sub $0x1,%ebp 判斷迴圈是否結束,該迴圈判斷棧中重新調整後的連結串列節點是否按照降序排列 0x00000000004011f5 <+257>: jne 0x4011df 0x00000000004011f7 <+259>: add $0x50,%rsp 0x00000000004011fb <+263>: pop %rbx 0x00000000004011fc <+264>: pop %rbp 0x00000000004011fd <+265>: pop %r12 0x00000000004011ff <+267>: pop %r13 0x0000000000401201 <+269>: pop %r14 0x0000000000401203 <+271>: retq End of assembler dump. ``` ```bash %rsi儲存呼叫者phase_2棧幀的區域性變數開始地址 %rdx = %rsi + 0 %rcx = %rsi + 4 %r8 = %rsi + 8 %r9 = %rsi + 12 (%rsp) = %rsi + 16 8(%rsp) = %rsi + 20 Dump of assembler code for function read_six_numbers: 0x000000000040145c <+0>: sub $0x18,%rsp 0x0000000000401460 <+4>: mov %rsi,%rdx 0x0000000000401463 <+7>: lea 0x4(%rsi),%rcx 0x0000000000401467 <+11>: lea 0x14(%rsi),%rax 0x000000000040146b <+15>: mov %rax,0x8(%rsp) 0x0000000000401470 <+20>: lea 0x10(%rsi),%rax 0x0000000000401474 <+24>: mov %rax,(%rsp) 0x0000000000401478 <+28>: lea 0xc(%rsi),%r9 0x000000000040147c <+32>: lea 0x8(%rsi),%r8 0x0000000000401480 <+36>: mov $0x4025c3,%esi 0x0000000000401485 <+41>: mov $0x0,%eax 0x000000000040148a <+46>: callq 0x400bf0 <__isoc99_sscanf@plt> 0x000000000040148f <+51>: cmp $0x5,%eax 0x0000000000401492 <+54>: jg 0x401499 0x0000000000401494 <+56>: callq 0x40143a 0x0000000000401499 <+61>: add $0x18,%rsp 0x000000000040149d <+65>: retq End of assembler dump. %rbp %rbx %r12~%15 被呼叫者儲存暫存器 %r10 %r11 呼叫者儲存暫存器 %rdi %rsi %rdx %rcx %r8 %r9 依次儲存輸入數1~6 ```   假設輸入資料為4 3 2 1 6 5   猜測0x6032d8為連結串列首地址,連結串列中每個節點佔用12個Byte,前8位元組儲存兩個4字Byte的整型數,剩餘的4Byte存放下個節點地址   GDB檢視使用7減去對應的輸入後的資料 ``` (gdb) p /x $rsp $1 = 0x7fffffffe270 (gdb) x/6dw 0x7fffffffe270 0x7fffffffe270: 3 4 5 6 0x7fffffffe280: 1 2 ```   重新調整連結串列前的連結串列的結構 ``` (gdb) x/24xw 0x006032d0 0x6032d0 : 0x0000014c 0x00000001 0x006032e0 0x00000000 0x6032e0 : 0x000000a8 0x00000002 0x006032f0 0x00000000 0x6032f0 : 0x0000039c 0x00000003 0x00603300 0x00000000 0x603300 : 0x000002b3 0x00000004 0x00603310 0x00000000 0x603310 : 0x000001dd 0x00000005 0x00603320 0x00000000 0x603320 : 0x000001bb 0x00000006 0x00000000 0x00000000 ```   儲存在棧中連結串列節點資訊 ``` (gdb) x/6xg 0x7fffffffe290 0x7fffffffe290: 0x00000000006032f0 0x0000000000603300 0x7fffffffe2a0: 0x0000000000603310 0x0000000000603320 0x7fffffffe2b0: 0x00000000006032d0 0x00000000006032e0 ```   按照7減去對應的輸入後重新調整連結串列後的連結串列結構,索引順序為 3 4 5 6 1 2 ``` (gdb) x/24xw 0x006032d0 0x6032d0 : 0x0000014c 0x00000001 0x006032e0 0x00000000 0x6032e0 : 0x000000a8 0x00000002 0x00000000 0x00000000 0x6032f0 : 0x0000039c 0x00000003 0x00603300 0x00000000 0x603300 : 0x000002b3 0x00000004 0x00603310 0x00000000 0x603310 : 0x000001dd 0x00000005 0x00603320 0x00000000 0x603320 : 0x000001bb 0x00000006 0x006032d0 0x00000000 ```   破解思路:   將連結串列中每個節點按照前4位元組降序排序:3 4 5 6 1 2   因為在前面使用7減去對應的值,所以破解密碼:4 3 2 1 6 5 > phase6太難了,心力憔悴。phase6的解析來自以下連結: [這是連結](https://gitee.com/zhoulee/CSAPP/blob/master/bomb/phase_6.txt) ## 總結   實驗太難了。分析彙編像看天書,不過通過本次實驗也提高了自己閱讀彙編程式碼的能力,學會了基本的GDB除錯程式碼的步驟。收穫頗豐! **  養成習慣,先贊後看!如果覺得寫的不錯,歡迎關注,點贊,轉發,謝謝!** **如遇到排版錯亂的問題,可以通過以下連結訪問我的CSDN。** **CSDN:[CSDN搜尋“嵌入式與Linux那些事”](https://blog.csdn.net/qq_16933601?spm=1000.2115.3001.5113)** **歡迎歡迎關注我的公眾號:嵌入式與Linux那些事,領取秋招筆試面試大禮包(華為小米等大廠面經,嵌入式知識點總結,筆試題目,簡歷模版等)和2000G學習資料。** ![](https://img2020.cnblogs.com/other/1421380/202012/1421380-20201210171147324-601829