CSAPP實驗2:Bomb Lab筆記
實驗簡介
Bomb LAB 目的是熟悉彙編。
一共有7關,六個常規關卡和一個隱藏關卡,每次我們需要輸入正確的拆彈密碼才能進入下一關,而具體的拆彈密碼藏在彙編程式碼中。實驗中的bomb
實際上是一個程式的二進位制檔案,該程式由一系列phase組成,每個phase需要我們輸入一個字串,然後該程式會進行校驗,如果輸入的字串不滿足拆彈要求,那麼就會列印BOOM!!!
完成整個實驗的思路是通過objdump
對bomb
進行反編譯(objdump -d bomb > bomb.txt
),獲取所有的彙編程式碼。提取每個階段對應的程式碼並藉助gdb
進行分析,逐一拆彈。
準備
彙編複習
型別 | 語法 | 例子 | 備註 |
---|---|---|---|
常量 | 符號$ 開頭 |
$-42 , $0x15213 |
一定要注意十進位制還是十六進位制 |
暫存器 | 符號 % 開頭 |
%esi , %rax |
可能存的是值或者地址 |
記憶體地址 | 括號括起來 | (%rbx) , 0x1c(%rax) , 0x4(%rcx, %rdi, 0x1) |
括號實際上是去定址的意思 |
一些彙編語句與實際命令的轉換:
指令 | 效果 |
---|---|
mov %rbx, %rdx |
rdx = rbx |
add (%rdx), %r8 |
r8 += value at rdx |
mul $3, %r8 |
r8 *= 3 |
sub $1, %r8 |
r8-- |
lea (%rdx, %rbx, 2), %rdx |
rdx = rdx + rbx*2 |
比較與跳轉是拆彈的關鍵,基本所有的字元判斷就是通過比較來實現的,比方說 cmp b,a
會計算 a-b
的值,test b, a
會計算 a&b
,注意運算子的順序。例如
cmpl %r9, %r10
jg 8675309
等同於 if %r10 > %r9, jump to 8675309
各種不同的跳轉:
指令 | 效果 | 指令 | 效果 |
---|---|---|---|
jmp | Always jump | ja | Jump if above(unsigned >) |
je/jz | Jump if eq / zero | jae | Jump if above / equal |
jne/jnz | Jump if !eq / !zero | jb | Jump if below(unsigned <) |
jg | Jump if greater | jbe | Jump if below / equal |
jge | Jump if greater / eq | js | Jump if sign bits is 1(neg) |
jl | Jump if less | jns | Jump if sign bit is 0 (pos) |
jle | Jump if less / eq | x | x |
舉幾個例子
cmp $0x15213, %r12
jge deadbeef
若 %r12 >= 0x15213
,則跳轉到 0xdeadeef
cmp %rax, %rdi
jae 15213b
如果 %rdi
的無符號值大於等於 %rax
,則跳轉到 0x15213b
test %r8, %r8
jnz (%rsi)
如果 %r8 & %r8
不為零,那麼跳轉到 %rsi
存著的地址中。
x86-64
暫存器規則: 預設函式的第一個引數是%rdi
第二個引數%rsi
第三個引數%rdx
反彙編
# 檢查符號表
# 然後可以尋找跟 bomb 有關的內容
objdump -t bomb | less
# 反編譯
# 搜尋 explode_bomb
objdump -d bomb > bomb.txt
# 顯示所有字元
strings bomb | less
GDB
gdb bomb
help # 獲取幫助
break explode_bomb # 設定斷點
break phase_1
run # 開始執行
disas # 反彙編
info registers # 檢視暫存器內容
print $rsp # 列印指定暫存器
stepi # (單步跟蹤進入)執行一行程式碼,如果函式呼叫,則進入該函式 可以使用s簡化
n # (單步跟蹤) 執行一行程式碼,如果函式呼叫,則一併執行
x/4wd $rsp # 檢查暫存器或某個地址,檢視記憶體地址裡面的內容(常用)
用 ctl+c
可以退出,每次進入都要設定斷點(保險起見),炸彈會用 sscanf
來讀取字串,瞭解清楚到底需要輸入什麼。
Phase 1
Dump of assembler code for function phase_1:
0x0000000000400ee0 <+0>: sub $0x8,%rsp
0x0000000000400ee4 <+4>: mov $0x402400,%esi
0x0000000000400ee9 <+9>: callq 0x401338 <strings_not_equal>
0x0000000000400eee <+14>: test %eax,%eax
0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23>
0x0000000000400ef2 <+18>: callq 0x40143a <explode_bomb>
0x0000000000400ef7 <+23>: add $0x8,%rsp
0x0000000000400efb <+27>: retq
先 gdb bomb
,然後設定斷點 break explode_bomb
和 break phase_1
這段程式碼還是挺好理解的,儲存Stack pointer,將$0x402400
傳給%esi
,呼叫位於0x401338
的strings_not_equal
函式,比較%eax
是否為0,不為零則呼叫explode_bomb
函式,為零則返回設定斷點 phase_1
和explode_bomb
,輸入命令r
執行會在斷點處停下,此時隨便輸入一個字串用於測試“abcd”
,然後disas
檢視反彙編程式碼:=>箭號為當前執行的位置
檢視暫存器內容 info register
,eax
就是rax
的低位用print $eax
打印出來,是一個地址用x/s $eax
,查看出地址裡的內容,發現是輸入字串
用stepi
逐步執行,執行完 mov
之後,把地址中的內容傳到%esi
中,用print
檢視!得到字串,這就是第一關的答案。退出後新建一個文字 touch sol.txt
,方便之後輸入
Phase 2
這次我們有了第一關的答案,進入gdb後設置好斷點,和命令引數。
試執行,在phase_1
停住,然後continue
,答案正確,觸發 phase_2
的斷點,這次輸入abc
反彙編Phase2部分的程式碼
(gdb) disas
Dump of assembler code for function phase_2:
=> 0x0000000000400efc <+0>: push %rbp
0x0000000000400efd <+1>: push %rbx
0x0000000000400efe <+2>: sub $0x28,%rsp
0x0000000000400f02 <+6>: mov %rsp,%rsi
0x0000000000400f05 <+9>: callq 0x40145c <read_six_numbers> #讀取6個數字
0x0000000000400f0a <+14>: cmpl $0x1,(%rsp) #第一個數字和1比較,不相等則爆炸
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52> #相等,跳轉到<52>
0x0000000000400f10 <+20>: callq 0x40143a <explode_bomb>
0x0000000000400f15 <+25>: jmp 0x400f30 <phase_2+52>
0x0000000000400f17 <+27>: mov -0x4(%rbx),%eax #將(%rbx)前一個數字存到%eax
0x0000000000400f1a <+30>: add %eax,%eax #%eax數字加倍
0x0000000000400f1c <+32>: cmp %eax,(%rbx) #%eax和(%rbx)比較,=(%rbx)則跳過爆炸
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: callq 0x40143a <explode_bomb>
0x0000000000400f25 <+41>: add $0x4,%rbx #(%rbx)地址+4,下一個數字
0x0000000000400f29 <+45>: cmp %rbp,%rbx #比較%rbp和%rbx,迴圈是否結束
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
0x0000000000400f30 <+52>: lea 0x4(%rsp),%rbx #指向第2個數字,%rbx儲存第2個數字地址
0x0000000000400f35 <+57>: lea 0x18(%rsp),%rbp #0x18 = 0x0 + 4 bit * 6 個數字
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
0x0000000000400f3c <+64>: add $0x28,%rsp
0x0000000000400f40 <+68>: pop %rbx
0x0000000000400f41 <+69>: pop %rbp
0x0000000000400f42 <+70>: retq
End of assembler dump.
根據Phase1,很敏感的會發現movl $0x4025c3, %esi
這行。通過之前一樣的方法,得到0x4025c3
記憶體裡的字串
(gdb) x/s $esi
0x4025c3: "%d %d %d %d %d %d
再根據bomb[0x40148a] <+46>: callq 0x400bf0 ; symbol stub for: __isoc99_sscanf
這句,猜一下,立馬就能聯想到scanf("%d %d %d %d %d %d",a,b,c,d,e,f);
,也就是說,輸入的格式已經確定了。
解讀出迴圈中,從1開始,是一個等比數列,公比為2。1 2 4 8 16 32
Phase 3
Dump of assembler code for function phase_3:
=> 0x0000000000400f43 <+0>: sub $0x18,%rsp
0x0000000000400f47 <+4>: lea 0xc(%rsp),%rcx
0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx
0x0000000000400f51 <+14>: mov $0x4025cf,%esi
0x0000000000400f56 <+19>: mov $0x0,%eax
0x0000000000400f5b <+24>: callq 0x400bf0 <[email protected]> # 呼叫函式sscanf
0x0000000000400f60 <+29>: cmp $0x1,%eax # 說明%eax>1,即輸入引數個數>1,跳過爆炸
0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
0x0000000000400f65 <+34>: callq 0x40143a <explode_bomb>
0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp) # 說明第一個數0x8(%rsp)<7,否則爆炸
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax # 第一個數存到%eax
0x0000000000400f75 <+50>: jmpq *0x402470(,%rax,8) # 跳轉,起始地址0x402470+ rax*8(第一個數)內資料所指行數
0x0000000000400f7c <+57>: mov $0xcf,%eax # case0: 0xcf = 207
0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>
0x0000000000400f83 <+64>: mov $0x2c3,%eax # case2: 0x2c3
0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>
0x0000000000400f8a <+71>: mov $0x100,%eax
0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>
0x0000000000400f91 <+78>: mov $0x185,%eax
0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>
0x0000000000400f98 <+85>: mov $0xce,%eax
0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>
0x0000000000400f9f <+92>: mov $0x2aa,%eax
0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>
0x0000000000400fa6 <+99>: mov $0x147,%eax
0x0000000000400fab <+104>: jmp 0x400fbe <phase_3+123>
0x0000000000400fad <+106>: callq 0x40143a <explode_bomb>
0x0000000000400fb2 <+111>: mov $0x0,%eax
0x0000000000400fb7 <+116>: jmp 0x400fbe <phase_3+123>
0x0000000000400fb9 <+118>: mov $0x137,%eax # 1 0x137
0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax # 比較第2個數
0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
0x0000000000400fc4 <+129>: callq 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add $0x18,%rsp # rsp+24
0x0000000000400fcd <+138>: retq
檢視地址內的內容,為輸入格式,需要輸入兩個數,後面的 cmp $0x1,%eax
表明輸入引數大於1個,
(gdb) x/s 0x4025cf
0x4025cf: "%d %d"
看到多個分片語句,反應類似siwtch語句,所以第一個數字是用來進行跳轉的
p/x
命令檢視跳轉表,可以看到
用 p/x
可以看跳轉表的地址,但是沒有x/s
直觀。用 x/s
命令可以檢視跳轉表,如case0
,對應的就是<phase_3+57>
,內容是$0xcf,%eax
,所以(0,207)就是一組輸入,同理還可以得到其他的解
(0,207) (1,311) (2,707) (3,256) (4,389) (5,206) (6,682) (7,327)
(gdb) x/s *(0x402470)
0x400f7c <phase_3+57>: "\270", <incomplete sequence \317> //0:0xcf
(gdb) x/s *(0x402470+8)
0x400fb9 <phase_3+118>: "\270\067\001" //1:0x137
(gdb) x/s *(0x402470+16)
0x400f83 <phase_3+64>: "\270\303\002" //2:0x2c3
(gdb) x/s *(0x402470+24)
0x400f8a <phase_3+71>: "\270" //3:0x100
(gdb) x/s *(0x402470+32):
0x400f91 <phase_3+78>: "\270\205\001" //4:0x185
(gdb) x/s *(0x402470+40)
0x400f98 <phase_3+85>: "\270", <incomplete sequence \316> //5:0xce
(gdb) x/s *(0x402470+48)
0x400f9f <phase_3+92>: "\270\252\002" //6:0x2aa
(gdb) x/s *(0x402470+56)
0x400fa6 <phase_3+99>: "\270G\001" //7:0x147
(gdb) x/s *(0x402470+64)
0x7564616d: <error: Cannot access memory at address 0x7564616d>
輸入一組解,成功
Phase 4
Dump of assembler code for function phase_4:
=> 0x000000000040100c <+0>: sub $0x18,%rsp
0x0000000000401010 <+4>: lea 0xc(%rsp),%rcx
0x0000000000401015 <+9>: lea 0x8(%rsp),%rdx
0x000000000040101a <+14>: mov $0x4025cf,%esi //%d %d
0x000000000040101f <+19>: mov $0x0,%eax
0x0000000000401024 <+24>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000401029 <+29>: cmp $0x2,%eax // 引數數量為2
0x000000000040102c <+32>: jne 0x401035 <phase_4+41>
0x000000000040102e <+34>: cmpl $0xe,0x8(%rsp) // 第一個引數<=14
0x0000000000401033 <+39>: jbe 0x40103a <phase_4+46>
0x0000000000401035 <+41>: callq 0x40143a <explode_bomb>
0x000000000040103a <+46>: mov $0xe,%edx //14
0x000000000040103f <+51>: mov $0x0,%esi //0
0x0000000000401044 <+56>: mov 0x8(%rsp),%edi //a1
0x0000000000401048 <+60>: callq 0x400fce <func4> //把a1,0,14分別作為引數傳到func4
0x000000000040104d <+65>: test %eax,%eax //%eax!=0,爆炸,所以fun4呼叫後要使得%eax=0
0x000000000040104f <+67>: jne 0x401058 <phase_4+76>
0x0000000000401051 <+69>: cmpl $0x0,0xc(%rsp) //第二個資料為0
0x0000000000401056 <+74>: je 0x40105d <phase_4+81>
0x0000000000401058 <+76>: callq 0x40143a <explode_bomb>
0x000000000040105d <+81>: add $0x18,%rsp
0x0000000000401061 <+85>: retq
End of assembler dump.
跟上題一樣,先看看可疑的0x4025cf
中的內容
看到輸入格式和上題一樣都是兩個整數。在執行 callq 0x400bf0 <[email protected]>
指令後,返回值(引數數量)儲存於%eax
,然後判斷%eax
是否等於2,若不等於則爆炸。否則執行cmpl $0xe,0x8(%rsp)
,該指令將輸入的第一個數和常數0xe
進行比較,如果第一個數>0xe
,拆彈失敗。否則跳轉到0x40103a
執行
0x000000000040103a <+46>: mov $0xe,%edx //14
0x000000000040103f <+51>: mov $0x0,%esi //0
0x0000000000401044 <+56>: mov 0x8(%rsp),%edi //a1
這三條指令用來設定func4
的引數,根據x86-64暫存器使用規範,第1,2,3,個引數分別儲存在暫存器%edi
,%esi
,%edx
中
在檢視func4
對應的程式碼之前,先觀察執行callq 400fce <func4>
指令之後phase_4
的操作:test %eax,%eax
指令檢查%eax
的值是否等於0,如果不等於0,則會引爆炸彈,否則執行指令cmpl $0x0,0xc(%rsp)
,該指令將輸入的第二個數與0做比較,如果相等,那麼phase_4
正常退出,拆彈成功。因此,phase_4
的第二個輸入值即為0。經過以上的分析,可以意識到phase_4
的核心目標在於要讓func4
執行後,%eax
的值等於0,這取決於輸入的第一個數。接著需要分析func4
執行的操作,其對應程式碼如下所示。
反彙編func4
0000000000400fce <func4>:
400fce: 48 83 ec 08 sub $0x8,%rsp //x = %edi y = %esi z = %edx
400fd2: 89 d0 mov %edx,%eax //
400fd4: 29 f0 sub %esi,%eax //t = z-y t = %eax
400fd6: 89 c1 mov %eax,%ecx //
400fd8: c1 e9 1f shr $0x1f,%ecx //k=t>>31 t = %ecx
400fdb: 01 c8 add %ecx,%eax //t = t+k
400fdd: d1 f8 sar %eax //t>>1
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx
400fe2: 39 f9 cmp %edi,%ecx
400fe4: 7e 0c jle 400ff2 <func4+0x24>
400fe6: 8d 51 ff lea -0x1(%rcx),%edx
400fe9: e8 e0 ff ff ff callq 400fce <func4>
400fee: 01 c0 add %eax,%eax
400ff0: eb 15 jmp 401007 <func4+0x39>
400ff2:(+0x24) b8 00 00 00 00 mov $0x0,%eax
400ff7: 39 f9 cmp %edi,%ecx
400ff9: 7d 0c jge 401007 <func4+0x39>
400ffb: 8d 71 01 lea 0x1(%rcx),%esi
400ffe: e8 cb ff ff ff callq 400fce <func4>
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401007:(+0x39) 48 83 c4 08 add $0x8,%rsp
40100b: c3 retq
在分析func4
之前,不要忘了傳遞到func4
的三個引數分別儲存於暫存器%edi
、%esi
和%edx
,其值分別為x(輸入的第一個數)、0和14。在0x400fe9
處執行了指令callq 400fce <func4>
,因此func4
很可能是個遞迴函式,我們將func4
翻譯成等價的C程式碼,如下所示。
void func4(int x, int y, int z) {
int t = z - y;
int k = t >> 31;
t = (t + k) >> 1;
k = t + y;
if(k <= x) {
t = 0;
if(k >= x) {
return;
}else {
y = k + 1;
func4(x, y, z);
}
}else {
z = k - 1;
func4(x, y, z);
}
}
func4
的目的是要讓函式退出後%eax
的值為0,而在0x400ff2
處mov $0x0,%eax
顯示的將%eax
的值設定為0,該指令對應於C程式碼中的t = 0。並且,func4
執行遞迴的退出條件為k == x,其中x對應於輸入的第一個數,而k則可以通過一系列計算得到,由於y = 0且z = 14,易知k = 7,因此輸入的第一個數即為7。將字串7 0
作為phase_4
的輸入,拆彈成功,如下圖所示。
Phase 5
Dump of assembler code for function phase_5:
=> 0x0000000000401062 <+0>: push %rbx
0x0000000000401063 <+1>: sub $0x20,%rsp
0x0000000000401067 <+5>: mov %rdi,%rbx //把字串起始地址儲存在%rbx中
0x000000000040106a <+8>: mov %fs:0x28,%rax
0x0000000000401073 <+17>: mov %rax,0x18(%rsp)
0x0000000000401078 <+22>: xor %eax,%eax //%eax清零
0x000000000040107a <+24>: callq 0x40131b <string_length>
0x000000000040107f <+29>: cmp $0x6,%eax //字串輸入長度=6
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
0x0000000000401084 <+34>: callq 0x40143a <explode_bomb>
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx //
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx
0x0000000000401096 <+52>: and $0xf,%edx
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
0x00000000004010ae <+76>: movb $0x0,0x16(%rsp)
0x00000000004010b3 <+81>: mov $0x40245e,%esi //字串 flyers
0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi
0x00000000004010bd <+91>: callq 0x401338 <strings_not_equal>
0x00000000004010c2 <+96>: test %eax,%eax
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
0x00000000004010c6 <+100>: callq 0x40143a <explode_bomb>
0x00000000004010cb <+105>: nopl 0x0(%rax,%rax,1)
0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
0x00000000004010d2 <+112>: mov $0x0,%eax
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
0x00000000004010d9 <+119>: mov 0x18(%rsp),%rax
0x00000000004010de <+124>: xor %fs:0x28,%rax
0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140>
0x00000000004010e9 <+135>: callq 0x400b30 <__stack_chk_fail@plt>
0x00000000004010ee <+140>: add $0x20,%rsp
0x00000000004010f2 <+144>: pop %rbx
0x00000000004010f3 <+145>: retq
End of assembler dump.
根據x86-64暫存器使用規範,%rdi
暫存器儲存的是第一個引數的值,由於輸入的是字串,因此%rdi儲存的應該是輸入字串的起始地址。0x401067
處的指令mov %rdi,%rbx
將字串起始地址儲存在%rbx
中,即%rbx
為基址暫存器。指令xor %eax,%eax
的作用是將%eax
清零,接著呼叫string_length
函式獲取輸入字串的長度,並將長度值(返回值)儲存於%eax
。指令cmp $0x6,%eax
將string_length
的返回值與常數6作比較,若不相等則會引爆炸彈,由此可以得知,phase_5
的輸入字串長度應該等於6。
(gdb) x/s 0x40245e
0x40245e: "flyers"
待比較的字串為flyers
,且長度也為6。所以,接下來的關鍵任務是需要對迴圈操作進行分析,理解該迴圈操作對輸入字串做了哪些操作。提取迴圈操作的程式碼,如下所示。
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx
40108f: 88 0c 24 mov %cl,(%rsp)
401092: 48 8b 14 24 mov (%rsp),%rdx
401096: 83 e2 0f and $0xf,%edx
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1)
4010a4: 48 83 c0 01 add $0x1,%rax
4010a8: 48 83 f8 06 cmp $0x6,%rax
4010ac: 75 dd jne 40108b <phase_5+0x29>
由於%rbx
儲存的是輸入字串的起始地址,%rax
初始化為0,其作用等價於下標,因此movzbl (%rbx,%rax,1),%ecx
指令的作用是將字串的第%rax個字元儲存於%ecx
,movzbl
意味做了零擴充套件。接著,mov %cl,(%rsp)
指令取%ecx
的低8位,即一個字元的大小,通過記憶體間接儲存至%rdx
中。and $0xf,%edx
指令將%edx
的值與常數0xf
進行位與,由指令movzbl 0x4024b0(%rdx),%edx
可知,位與後的值將會作為偏移量,以0x4024b0
為基址,將偏移後的值儲存至%edx。最後,指令mov %dl,0x10(%rsp,%rax,1)
以%edx
低8位的值作為新的字元,對原有字元進行替換。綜上,phase_5
遍歷輸入字串的每個字元,將字元的低4位作為偏移量,以0x4024b0
為起始地址,將新地址對應的字元替換原有字元,最終得到flyers
字串。列印0x4024b0
處的內容,如下圖所示。
例如,如果要得到字元f,那麼偏移量應為9,二進位制表示為1001
,通過查詢ASCII表,可知字元i的ASCII編碼為01101001
,滿足要求。(或者字元y(01111001
)所以解不唯一)剩餘5個字符采用同樣的策略可以依次求得,最終,phase_5
的輸入字串的一個解為ionefg
。
Phase 6
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
0x00000000004010fc <+8>: sub $0x50,%rsp
0x0000000000401100 <+12>: mov %rsp,%r13
0x0000000000401103 <+15>: mov %rsp,%rsi
0x0000000000401106 <+18>: callq 0x40145c <read_six_numbers>
0x000000000040110b <+23>: mov %rsp,%r14 # %r14儲存陣列起始地址
0x000000000040110e <+26>: mov $0x0,%r12d # 將%r12d初始化為0
#################### Section 1:確認陣列中所有的元素小於等於6且不存在重複值 ###################
0x0000000000401114 <+32>: mov %r13,%rbp # %r13和%rbp儲存陣列某個元素的地址,並不是第1個元素,意識到這點需要結合0x40114d處的指令
0x0000000000401117 <+35>: mov 0x0(%r13),%eax
0x000000000040111b <+39>: sub $0x1,%eax # 將%eax的值減1
0x000000000040111e <+42>: cmp $0x5,%eax # 將%eax的值與常數5做比較
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52>
0x0000000000401123 <+47>: callq 0x40143a <explode_bomb>
0x0000000000401128 <+52>: add $0x1,%r12d # 如果%eax的值小於等於5,%r12d加1
0x000000000040112c <+56>: cmp $0x6,%r12d # 將%r12d與常數6做比較
0x0000000000401130 <+60>: je 0x401153 <phase_6+95>
0x0000000000401132 <+62>: mov %r12d,%ebx # %ebx起了陣列下標的作用
# 用於判斷陣列6個數是否存在重複值,若存在,引爆炸彈
0x0000000000401135 <+65>: movslq %ebx,%rax # 將陣列下標儲存至%rax
0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax # 將下一個數儲存至%eax
0x000000000040113b <+71>: cmp %eax,0x0(%rbp) # 將第1個數與%eax的值(當前數)做比較
0x000000000040113e <+74>: jne 0x401145 <phase_6+81> # 若相等,引爆炸彈
0x0000000000401140 <+76>: callq 0x40143a <explode_bomb>
0x0000000000401145 <+81>: add $0x1,%ebx # 陣列下標加1
0x0000000000401148 <+84>: cmp $0x5,%ebx # 判斷陣列下標是否越界(<=5)
0x000000000040114b <+87>: jle 0x401135 <phase_6+65>
0x000000000040114d <+89>: add $0x4,%r13 # %r13儲存陣列下一個數的地址
0x0000000000401151 <+93>: jmp