0. 環境要求
關於環境已經在lab1裡配置過了這裡要記得安裝gdb
安裝命令 sudo yum install gdb
實驗的下載地址 http://csapp.cs.cmu.edu/3e/labs.html
gbd的命令地址 http://csapp.cs.cmu.edu/2e/docs/gdbnotes-x86-64.pdf
知乎同款連線 https://zhuanlan.zhihu.com/p/339461318
這裡我們需要使用objdump -d ./bomb >> bomb.s
反彙編工具來得到彙編程式碼。
下面就開始舉世盛名的bomb
實驗吧
1. 第一關
-
粗讀
main
函式initialize_bomb(); printf("Welcome to my fiendish little bomb. You have 6 phases with\n"); printf("which to blow yourself up. Have a nice day!\n"); /* Hmm... Six phases must be more secure than one phase! */ input = read_line(); /* Get input */ phase_1(input); /* Run the phase */ phase_defused(); /* Drat! They figured it out!
通過簡單的閱讀理解應該知道這裡面的
phase_1
就是我們的第一關了,然後根據函式名稱input = read_line()
應該是要驗證我們的輸入是否合理,我們先亂輸入一個看看先執行起來(gdb) r Starting program: /csapp/bomb/bomb warning: Error disabling address space randomization: Operation not permitted Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day!
輸入
hello wordl
hello world BOOM!!! The bomb has blown up. [Inferior 1 (process 67) exited with code 010]
果然?了下面開始分析我們的彙編程式碼來嘗試得到結果
-
閱讀
bomb.s
理清思路0000000000400ee0 <phase_1>: 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> 400eee: 85 c0 test %eax,%eax 400ef0: 74 05 je 400ef7 <phase_1+0x17> 400ef2: e8 43 05 00 00 callq 40143a <explode_bomb> 400ef7: 48 83 c4 08 add $0x8,%rsp 400efb: c3 retq
這個邏輯應該是很簡單的不熟悉彙編程式碼的同學可以參考一下
csapp
原書的第三章-
1 行分配棧幀
-
2 行把立即數
0x402400
放到暫存器esi
中那我們知道這是用來第二個引數的暫存器 -
3-4 行 呼叫
strings_not_equal
函式然後判斷返回值是否為0,如果是0就跳到400ef7
然後恢復棧幀結束否則就呼叫explode_bomb
引爆炸彈所以核心就在於strings_not_equal
函式
-
-
閱讀
strings_not_equal
函式emm 這個函式太長就只放重點了 我們重點關注
esi
暫存器因為上面我們傳遞了一個引數40133f: 48 89 f5 mov %rsi,%rbp 40134a: 48 89 ef mov %rbp,%rdi 40134d: e8 c9 ff ff ff callq 40131b <string_length> 40135c: 0f b6 03 movzbl (%rbx),%eax 401363: 3a 45 00 cmp 0x0(%rbp),%al
這裡我們可以發現他把
m[rbx]->eax
然後比較m[rbp]
和al
暫存器裡面的值(也就是%eax
裡面的值)這就是說我們需要比較的就是在
0x402400
位置處的字串和題目預定輸入的字串是否相同,中間有很多判斷字串長度是否相同的程式碼請大家自行閱讀吧。所以我們可以檢視0x402400
位置的值然後直接輸入這個結果就好(gdb) x/s 0x402400 0x402400: "Border relations with Canada have never been better."
我們輸入
Border relations with Canada have never been better.
就可以拆掉第一個炸彈
2. 第二關
首先還是差不多的程式碼邏輯對於輸入進行判斷,合格就可以通過
input = read_line();
phase_2(input);
phase_defused();
printf("That's number 2. Keep going!\n");
- 慢讀
phase_2
的彙編程式碼
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>
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)
- 分配大小為
40
位元組的棧幀然後把棧幀指標傳遞給%rsi暫存器接下來呼叫read_six_numbers
- 轉入
read_six_numbers
函式
000000000040145c <read_six_numbers>:
40145c: 48 83 ec 18 sub $0x18,%rsp
401460: 48 89 f2 mov %rsi,%rdx
401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx
401467: 48 8d 46 14 lea 0x14(%rsi),%rax
40146b: 48 89 44 24 08 mov %rax,0x8(%rsp)
401470: 48 8d 46 10 lea 0x10(%rsi),%rax
401474: 48 89 04 24 mov %rax,(%rsp)
401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9
40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8
401480: be c3 25 40 00 mov $0x4025c3,%esi
401485: b8 00 00 00 00 mov $0x0,%eax
40148a: e8 61 f7 ff ff callq 400bf0 <__isoc99_sscanf@plt>
40148f: 83 f8 05 cmp $0x5,%eax
401492: 7f 05 jg 401499 <read_six_numbers+0x3d>
401494: e8 a1 ff ff ff callq 40143a <explode_bomb>
401499: 48 83 c4 18 add $0x18,%rsp
40149d: c3 retq
簡單分析一下這個彙編程式碼這個需要我們輸入6個數字然後進行比較這裡還有如果數字不滿足6個的健壯性判斷
下面按順序構建sscanf所需要的引數
首先我們分析一下sscanf函式
(gdb) x/s 0x4025c3
0x4025c3: "%d %d %d %d %d %d"
我們發現sscanf
函式所需要的第二個引數居然是這樣的格式這就表示我們會依次把六個數讀入到他所傳遞的3-8個引數裡面我們知道3-6個引數可以用暫存器傳遞剩下的兩個引數則要利用棧幀來傳遞
-
%rsi
儲存在phase_2裡面傳入的%rsp
的值也就是棧幀的起始地址我們檢視
%esi
的值發現居然是這樣那麼就表示我們的sscanf
函式需要的是 -
%rdx
,由%rsi
給出,%rsi
又由phrase_2
的%rsp
給出,所以phrase2
中的%rsp
地址處存放sscanf
中第一個輸入的值 -
%rcx
也就是在phase_2%rsp+0x4
中存放著第二個值 -
%r8
也就是在phase_2%rsp+0x8
中存放著第三個值 -
%r9
也就是在phase_2%rsp+0xc
中存放著第四個值 -
在該函式的
%rsp
中存放著第五個值也就是我們把0x10(%rsi),%rax
放進了rsp
-
在該函式的
%rsp+0x8
中存放著第六個值也就是我們把0x14(%rsi),%rax
放進了rsp
上面就是對所有值的分析下面我們來依次分析這些值
上面的描述會比較亂,這裡用一個非常醜的圖來表示這個引數的構造過程1st
表示第一個引數依次類推。
3. 回到phase_2
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)
400f0e: 74 20 je 400f30 <phase_2+0x34>
400f10: e8 25 05 00 00 callq 40143a <explode_bomb>
400f15: eb 19 jmp 400f30 <phase_2+0x34>
400f17: 8b 43 fc mov -0x4(%rbx),%eax
400f1a: 01 c0 add %eax,%eax
400f1c: 39 03 cmp %eax,(%rbx)
400f1e: 74 05 je 400f25 <phase_2+0x29>
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 <phase_2+0x1b>
400f2e: eb 0c jmp 400f3c <phase_2+0x40>
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp
400f3a: eb db jmp 400f17 <phase_2+0x1b>
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 retq
-
找
(rsp)
這裡有一個和1的比較可以發現這裡必須等於1不然直接引爆炸彈了所以第一個值就是1 -
找
(%rsp+0x4)
400f17: 8b 43 fc mov -0x4(%rbx),%eax 400f1a: 01 c0 add %eax,%eax 400f1c: 39 03 cmp %eax,(%rbx)
這三行就是關鍵程式碼因為我們在跳轉之後把
rsp+0x4
賦給了rbp
那上述程式碼的前兩行就是把rsp
裡的值翻倍然後放到rsp+0x4
也就是第二個引數是2
剩下的過程就可以迴圈了六個數分別應該是1,2,4,8,16,32
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2. Keep going!
3. 第三關
好傢伙我倒要看看complex code
有多複雜邏輯 首先還是一樣都是判斷輸入是否合法
/* I guess this is too easy so far. Some more complex code will
* confuse people. */
input = read_line();
phase_3(input);
phase_defused();
printf("Halfway there!\n");
1. 閱讀phase_3
400f43: 48 83 ec 18 sub $0x18,%rsp
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
400f51: be cf 25 40 00 mov $0x4025cf,%esi
400f56: b8 00 00 00 00 mov $0x0,%eax
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmp $0x1,%eax
400f63: 7f 05 jg 400f6a <phase_3+0x27>
- 分配棧幀
-
利用
%rdx
也就是0x8+%rsp
和利用%rcx
也就是0xc+%rsp
傳遞sscanf
函式用的第三和第四個引數 -
第二個引數為一個常數
0x4025cf
隨後呼叫<__isoc99_sscanf@plt>
這裡注意一下sscanf
如果呼叫成功的話會返回2這裡如果成功的話會跳轉到400f6a
這裡讀入的rdx
和rcx
的值就是我們輸入的第一個和第二個數
2. 繼續閱讀phase_3
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)
400f6f: 77 3c ja 400fad <phase_3+0x6a>
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
400f7c: b8 cf 00 00 00 mov $0xcf,%eax
400f81: eb 3b jmp 400fbe <phase_3+0x7b>
400f83: b8 c3 02 00 00 mov $0x2c3,%eax
400f88: eb 34 jmp 400fbe <phase_3+0x7b>
400f8a: b8 00 01 00 00 mov $0x100,%eax
400f8f: eb 2d jmp 400fbe <phase_3+0x7b>
400f91: b8 85 01 00 00 mov $0x185,%eax
400f96: eb 26 jmp 400fbe <phase_3+0x7b>
400f98: b8 ce 00 00 00 mov $0xce,%eax
400f9d: eb 1f jmp 400fbe <phase_3+0x7b>
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax
400fa4: eb 18 jmp 400fbe <phase_3+0x7b>
400fa6: b8 47 01 00 00 mov $0x147,%eax
400fab: eb 11 jmp 400fbe <phase_3+0x7b>
400fad: e8 88 04 00 00 callq 40143a <explode_bomb>
400fb2: b8 00 00 00 00 mov $0x0,%eax
400fb7: eb 05 jmp 400fbe <phase_3+0x7b>
400fb9: b8 37 01 00 00 mov $0x137,%eax
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax
400fc2: 74 05 je 400fc9 <phase_3+0x86>
400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
-
%rdx
裡的值必須小於等於7否則直接爆炸然後關鍵來了我們把第一個輸入的值傳給%eax
然後在就是一步關鍵操作 -
我們要跳轉到
m[0x402470+r[%rax]*8]
這裡我們以傳入的第一個引數也就是%rdx
的值為0為例子那我要跳轉的地址就是m[0x402470]
check 一下這裡面是什麼值(gdb) x/x 0x402470 0x402470: 0x00400f7c
可以發現我們接下來要跳到
0x400f7c
這裡去 -
節選有關程式碼分析
400f7c: b8 cf 00 00 00 mov $0xcf,%eax 400f81: eb 3b jmp 400fbe <phase_3+0x7b> 400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax 400fc2: 74 05 je 400fc9 <phase_3+0x86> 400fc4: e8 71 04 00 00 callq 40143a <explode_bomb> 400fc9: 48 83 c4 18 add $0x18,%rsp
這裡我們先把
0xcf
傳遞給%eax
然後比較0xc(%rsp)
也就是我們輸入的第二個引數如果和eax
相等則完成這表示0 和 0xcf=207
就是一個合法答案
0 207
Halfway there!
這裡注意我們的第一個引數可以任取<=7
的任何數然後去檢視不同的地址得到不同的跳轉地址也就會有不同 的答案這裡再給出一個例子如果第一個引數為1那麼m[0x402470+r[%rax]*8]=m[0x402470+8]
也就是我們 要去看一下m[0x402478]
(gdb) x/x 0x402478
0x402478: 0x00400fb9
這次變成了另一地址我們接著分析一下後面會發生什麼事
400fb9: b8 37 01 00 00 mov $0x137,%eax
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax
400fc2: 74 05 je 400fc9 <phase_3+0x86>
400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
那這就是判斷第二個引數是否等於0x137
下面我們來試試1 和 311
是否可以
1 311
Halfway there!
可以發現也是可以的其他例子就不在演示了
4. 第四關
好傢伙考察數學能力開衝
/* Oh yeah? Well, how good is your math? Try on this saucy problem! */
input = read_line();
phase_4(input);
phase_defused();
printf("So you got that one. Try this one.\n");
1.讀phase_4
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
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 <phase_4+0x29>
前面的程式碼幾乎和上面的沒差,rdx
儲存了我們讀入的第一個引數rcx
儲存了我們讀入的第二個引數這裡同樣有對sscanf
的判斷如果不成功直接爆炸
2.繼續分析phase_4
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp)
401033: 76 05 jbe 40103a <phase_4+0x2e>
401035: e8 00 04 00 00 callq 40143a <explode_bomb>
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 <func4>
40104d: 85 c0 test %eax,%eax
40104f: 75 07 jne 401058 <phase_4+0x4c>
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp)
401056: 74 05 je 40105d <phase_4+0x51>
401058: e8 dd 03 00 00 callq 40143a <explode_bomb>
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 retq
這裡我們需要把m[rsp+8]=r[%rdx]
的值和0xe
比較。我們設第一個輸入的引數是a
則a > 0xe
則直接爆炸。否則我們構建對呼叫func4
的引數func4(0x8(%rsp),0,0xe)
這裡的第一個引數就是我們的a
注意後面的我們把0和m[0xc(%rsp)]=r[%rcx]
進行比較如果想等則退出也就表示了我們第二個引數必須為0
3.去看func4
對於程式碼的分析直接寫到註釋上了
0000000000400fce <func4>:(a,0,0xe)
400fce: 48 83 ec 08 sub $0x8,%rsp //分配棧幀
400fd2: 89 d0 mov %edx,%eax // %eax=0xe
400fd4: 29 f0 sub %esi,%eax // %eax=%eax-0=0xe
400fd6: 89 c1 mov %eax,%ecx // %ecx=%eax=0xe
400fd8: c1 e9 1f shr $0x1f,%ecx //對%ecx的值邏輯右移31位
400fdb: 01 c8 add %ecx,%eax //%eax=%eax+%ecx=0xe
400fdd: d1 f8 sar %eax //%eax=%eax>>1=0x7
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx //%ecx=0x7+0x0=0x7
400fe2: 39 f9 cmp %edi,%ecx //%ecx-%edi=0x7-a
400fe4: 7e 0c jle 400ff2 <func4+0x24> //if <=0 則跳轉
我們可以看到對於我們輸入的第一個引數a
存在if a>=7 則會跳轉到400ff2
我們不妨輸入a=7
下面看一下0x400ff2
400ff2: b8 00 00 00 00 mov $0x0,%eax //返回值為0
400ff7: 39 f9 cmp %edi,%ecx //再一次比較%ecx-%edi=0x7-a
400ff9: 7d 0c jge 401007 <func4+0x39> // if a<=7 則跳出
400ffb: 8d 71 01 lea 0x1(%rcx),%esi // 否則我們把%esi++重寫執行
400ffe: e8 cb ff ff ff callq 400fce <func4>
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401007: 48 83 c4 08 add $0x8,%rsp
40100b: c3 retq
可以發現這是一個簡單的for
迴圈我們直接用a=7
直接卡了這個迴圈的邊界條件直接可以過掉本題
非常迅速的做完了第四個嘿嘿
7 0
So you got that one. Try this one.
不過本著學習的目的我們還是要知道這整個遞迴函式都發生了什麼。在網上隨便找了一個版本的c語言程式碼來分析一下
void func4(int x,int y,int z) //y的初始值為0,z的初始值為14,t->%rax,k->%ecx
{
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<x)
{
y=k+1;
func4(x,y,z);
t=2*t+1;
return;
}
else
{
return;
}
}
}
分析得x = k的時候,t=0,即 %eax 為 0 。然後就可以回到 phase_4 。