1. 程式人生 > >CSAPP實驗2:Bomb Lab筆記

CSAPP實驗2:Bomb Lab筆記

實驗簡介

​ Bomb LAB 目的是熟悉彙編。

​ 一共有7關,六個常規關卡和一個隱藏關卡,每次我們需要輸入正確的拆彈密碼才能進入下一關,而具體的拆彈密碼藏在彙編程式碼中。實驗中的bomb實際上是一個程式的二進位制檔案,該程式由一系列phase組成,每個phase需要我們輸入一個字串,然後該程式會進行校驗,如果輸入的字串不滿足拆彈要求,那麼就會列印BOOM!!!

​ 完成整個實驗的思路是通過objdumpbomb進行反編譯(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_bombbreak phase_1

這段程式碼還是挺好理解的,儲存Stack pointer,將$0x402400傳給%esi,呼叫位於0x401338strings_not_equal函式,比較%eax是否為0,不為零則呼叫explode_bomb函式,為零則返回設定斷點 phase_1explode_bomb,輸入命令r執行會在斷點處停下,此時隨便輸入一個字串用於測試“abcd”,然後disas檢視反彙編程式碼:=>箭號為當前執行的位置

mark

檢視暫存器內容 info registereax就是rax的低位用print $eax 打印出來,是一個地址用x/s $eax,查看出地址裡的內容,發現是輸入字串

mark

stepi 逐步執行,執行完 mov 之後,把地址中的內容傳到%esi中,用print檢視!得到字串,這就是第一關的答案。退出後新建一個文字 touch sol.txt,方便之後輸入

mark

mark

Phase 2

這次我們有了第一關的答案,進入gdb後設置好斷點,和命令引數。

mark

試執行,在phase_1停住,然後continue,答案正確,觸發 phase_2的斷點,這次輸入abc

mark

反彙編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

mark

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語句,所以第一個數字是用來進行跳轉的

mark

p/x 命令檢視跳轉表,可以看到

mark

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>

輸入一組解,成功

mark

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,而在0x400ff2mov $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,%eaxstring_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個字元儲存於%ecxmovzbl意味做了零擴充套件。接著,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處的內容,如下圖所示。

mark

例如,如果要得到字元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%r12d1
   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