超精講-逐例分析 CSAPP:實驗2-Bomb!(下)

周小倫發表於2021-01-28

好了話不多說我們書接上文繼續來做第二個實驗下面是前半部分實驗的連線

https://www.cnblogs.com/JayL-zxl/p/14303519.html

5. 第五關

首先感覺應該是個遞迴問題

   /* Round and 'round in memory we go, where we stop, the bomb blows! */
    input = read_line();
    phase_5(input);
    phase_defused();
    printf("Good work!  On to the next...\n");

1. 初讀phase_5

0000000000401062 <phase_5>:
  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 <string_length>
  40107f: 83 f8 06              cmp    $0x6,%eax
  401082: 74 4e                 je     4010d2 <phase_5+0x70>
  401084: e8 b1 03 00 00        callq  40143a <explode_bomb>

剛開始就開了個金絲雀這個題應該不太對勁。。rsp+0x18這個位置儲存了我們金絲雀的值這是為了防止緩衝區溢位隨後呼叫string_length 函式來判斷輸入的字串長度。可以發現這裡規定來我們輸入的字串長度必須是6否則直接爆炸。滿足要求後跳轉到<phase_5+0x70>

2. 閱讀<phase_5+0x70>

4010d2: b8 00 00 00 00        mov    $0x0,%eax
4010d7: eb b2                 jmp    40108b <phase_5+0x29>
把rax=0然後跳轉到40108b %rbx=%rdi
 part1 ---------------------------------------------------------
  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>
  4010ae: c6 44 24 16 00        movb   $0x0,0x16(%rsp)
 part2 ---------------------------------------------------------
  4010b3: be 5e 24 40 00        mov    $0x40245e,%esi
  4010b8: 48 8d 7c 24 10        lea    0x10(%rsp),%rdi
  4010bd: e8 76 02 00 00        callq  401338 <strings_not_equal>
  4010c2: 85 c0                 test   %eax,%eax
  4010c4: 74 13                 je     4010d9 <phase_5+0x77>
  4010c6: e8 6f 03 00 00        callq  40143a <explode_bomb>
  4010cb: 0f 1f 44 00 00        nopl   0x0(%rax,%rax,1)
  4010d0: eb 07                 jmp    4010d9 <phase_5+0x77>
  4010d2: b8 00 00 00 00        mov    $0x0,%eax
  4010d7: eb b2                 jmp    40108b <phase_5+0x29>
  part3 ---------------------------------------------------------
  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 <phase_5+0x8c>
  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   

首先我們可以發現part2部分是我們把rsp+0x10位置處的值和0x40245e位置處的值進行比較如果不想等則直接爆炸。因此rsp+0x10位置儲存的值必須和0x40245e位置處的值一樣。check一下

(gdb) x/s 0x40245e
0x40245e: "flyers"

可以發現rsp+0x10位置處也必須為"flyers"然後我們比較一下金絲雀如果沒有緩衝區溢位的話則返回。
接下來我們看part1裡面到底發生了什麼這裡寫一個虛擬碼會更好理解

func (char *c ,int rax, int 1){ //初始rax=0
    long a=c[rax*1] //這裡會把a的高32位置0 
    char tmp=byte(a)[0:8]//這裡把a的2進製表示的低八位給tmp
    //注意(rsp)=tmp tmp就是我們輸入的第一個字元
    long rdx=tmp;
    edx=edx&0xf //也就是我們只儲存後4位
    edx=m[0x4024b0+rdx] //這裡的rdx裡儲存的就是我們輸入的第一個字元
    (rsp+10+rax)=edx   //低8位
    func(c,rax+1,1);  //然後迴圈呼叫    
}

我們設我們輸入的六個字元分別為a1,a2,a3,a4,a5,a6 這裡可以發現我們的棧幀處其實儲存的是m[0x4024b0+rdx]的值首先我們看一下0x4024b0中到底儲存了哪些東西

(gdb) print (char*)0x4024b0
$5 = 0x4024b0 <array> "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"

emm這是一個字串陣列。嗷這裡其實我們傳入的就是索引值然後利用索引值來拿到我們需要的“flyers”
f-9 l-15 y-14 e-5 r-6 s-7 注意這裡我們輸入的是字串因此要把他們的ASCLL 值當作索引
因為我們只取了輸入的每一個字元的後4位ASCLl碼當作索引值。也就是說所有後四位滿足上面要求的字元都可。這裡我們隨便取一組。我們可以對上面的值。都加上64,這樣不會改變後4位的位模式。並且還能得到簡單的結果
73=I 79=O 78=N 69=E 70=F 77=G
結果是IONEFG 可以成功過掉

IONEFG
Good work!  On to the next..

6. 第六關

好了終於來到了最後一關是不是很有成就感。看上去非常難的樣子

  /* This phase will never be used, since no one will get past the
   * earlier ones.  But just in case, make this one extra hard. */
   input = read_line();
   phase_6(input);
   phase_defused();

這個的彙編有億點點長。下面先放一個總體邏輯的虛擬碼來幫助大家理解

cin>>a[6];//輸入六個數
if(a[i]=a[i+1]||a[i]>6)bomb! i=1~6所有元素都不能相同都不能大於6
//這裡我們有一個單連結串列
 node1->node2->node3->node4->node5->node6
 /由於最後我們的連結串列要滿足單調遞減
 / 按照值進行排序如下。所以我們重新排序的連結串列順序必須如下
 node3>node4>node5>node6>node1>node2
 List *L=new(List(-1));/新建一個連結串列 
for(int i=1;i<=6;i++){
  if(a[i]==6)L[i]=node1; /L[i]表示我們新連結串列的第i個結點。
  else{
    int b=7-a[i],p=node1;
    while(b--){
      p=p->next;
    }
    L[i]=p; /這裡就是7-a[i]為多少就把node[7-a[i]]賦給L[i]
  }
}  

下面在開始慢慢讀組合語言

1. 閱讀phase_6

00000000004010f4 <phase_6>:
  4010f4: 41 56                 push   %r14
  4010f6: 41 55                 push   %r13
  4010f8: 41 54                 push   %r12
  4010fa: 55                    push   %rbp
  4010fb: 53                    push   %rbx
  //以上均為對呼叫者儲存暫存器的儲存過程
  4010fc: 48 83 ec 50           sub    $0x50,%rsp
  401100: 49 89 e5              mov    %rsp,%r13
  401103: 48 89 e6              mov    %rsp,%rsi
  401106: e8 51 03 00 00        callq  40145c <read_six_numbers>
  //這裡的rsi就是我們的棧幀指標然後呼叫 read_six_numbers

這裡的分析和第二關有點像簡略分析過程。得到我們六個引數的分佈

第一個引數 m[%rsp]    設為a1
第二個引數 m[%4+rsp]  設為a2
第三個引數 m[%8+rsp]  設為a3
第四個引數 m[%c+rsp]  設為a4
第五個引數 m[%10+rsp] 設為a5
第六個引數 m[%14+rsp] 設為a6 

這個彙編特別長所以我們的大部分分析。都放在了程式碼的註釋上

  40110b: 49 89 e6              mov    %rsp,%r14   //%r14=%rsp
  40110e: 41 bc 00 00 00 00     mov    $0x0,%r12d  //%r12d=0x0
  401114: 4c 89 ed              mov    %r13,%rbp  //%rbp=%rsp
  401117: 41 8b 45 00           mov    0x0(%r13),%eax //eax= a1
  40111b: 83 e8 01              sub    $0x1,%eax  //eax=a1-1
  40111e: 83 f8 05              cmp    $0x5,%eax // a1-1-5=a1-6
  401121: 76 05                 jbe    401128 <phase_6+0x34> //a1-6<=0 
  /*
    這裡我們發現如果輸入的引數>6則會直接爆炸
  */
  --------------------------------------------------------------------
  401123: e8 12 03 00 00        callq  40143a <explode_bomb>
  401128: 41 83 c4 01           add    $0x1,%r12d  //%r12d+=1 =1
  40112c: 41 83 fc 06           cmp    $0x6,%r12d  // r12d -6 
  401130: 74 21                 je     401153 <phase_6+0x5f> // r12d=6 則跳轉
  401132: 44 89 e3              mov    %r12d,%ebx // ebx=%r12d =1
  401135: 48 63 c3              movslq %ebx,%rax // rax=ebx=1
  401138: 8b 04 84              mov    (%rsp,%rax,4),%eax  //eax=m[rsp+4*rax] =m[rsp+4]=a2 
  40113b: 39 45 00              cmp    %eax,0x0(%rbp) // a1-a2 
  40113e: 75 05                 jne    401145 <phase_6+0x51>  // 如果a1 和a2 相同則直接爆炸
  401140: e8 f5 02 00 00        callq  40143a <explode_bomb>
  401145: 83 c3 01              add    $0x1,%ebx //ebx+=1 =2
  401148: 83 fb 05              cmp    $0x5,%ebx // ebx-5
  40114b: 7e e8                 jle    401135 <phase_6+0x41> //ebx-5<=0 ebx<=5
  40114d: 49 83 c5 04           add    $0x4,%r13  //%r13+=4 %r13=%rsp+4
  401151: eb c1                 jmp    401114 <phase_6+0x20>
  // 這裡有遞迴關係注意

對上面的程式碼寫一個簡單的c語言虛擬碼如下

int r12d=0
func (int a[6],int i=0 ){ //裡面儲存了我們的6個引數
  if(a[i]>6) bomb!
  r12d+=1;
  if (r12d=6){call 0x401153}
  int tmp=r12d;
  while(tmp<=5) {
    int c=tmp;
    int res=a[c];
    if(res==a[i]) bomb!;
    else{
      tmp+=1;
    }
  }
  func(a[6],i++);
}

上面就是說我們的引數都不能一樣。並且每一個都不能大於6然後一直要到r12d=6才能繼續
然後繼續往下執行

  401153: 48 8d 74 24 18        lea    0x18(%rsp),%rsi  //%rsi=%rsp+0x18
  401158: 4c 89 f0              mov    %r14,%rax  //%rax=%r14=%rsp 
  40115b: b9 07 00 00 00        mov    $0x7,%ecx  // %ecx=7
  401160: 89 ca                 mov    %ecx,%edx  //%edx=7
  401162: 2b 10                 sub    (%rax),%edx // %edx=7-a1
  401164: 89 10                 mov    %edx,(%rax)  /a1=7-a1
  401166: 48 83 c0 04           add    $0x4,%rax  // %rax=%rsp+4
  40116a: 48 39 f0              cmp    %rsi,%rax  // 這裡其實是一個判斷 因為我們的棧幀就到%rsp+14
  40116d: 75 f1                 jne    401160 <phase_6+0x6c>

上面又形成一個遞迴這裡在寫一個c語言的虛擬碼

int rsi=6;
func(int i=0){
  a[i]=7-a[i];
  if(i!=6)func(i++);
}

上面相當於讓ai=7-ai(i=1,2,3,4,5,6)
下面的邏輯非常複雜。。。這裡要很認真的看下面說的ai都是我們一開始輸入的ai。

  40116f: be 00 00 00 00        mov    $0x0,%esi
  401174: eb 21                 jmp    401197 <phase_6+0xa3> 
  //將%esi置0之後跳轉到401197
 {
  401197: 8b 0c 34              mov    (%rsp,%rsi,1),%ecx //ecx=m[rsp+rsi]= a1
  40119a: 83 f9 01              cmp    $0x1,%ecx    // 這裡如果7-a1<=1 a1>=6 a1=6  則直接401183 
  40119d: 7e e4                 jle    401183 <phase_6+0x8f>
 } 
   // ai <6 則會走下面
  40119f: b8 01 00 00 00        mov    $0x1,%eax
  4011a4: ba d0 32 60 00        mov    $0x6032d0,%edx
  4011a9: eb cb                 jmp    401176 <phase_6+0x82>
  401176: 48 8b 52 08           mov    0x8(%rdx),%rdx  //rdx=  m[6032d8]
  40117a: 83 c0 01              add    $0x1,%eax //eax=2 
  40117d: 39 c8                 cmp    %ecx,%eax
  40117f: 75 f5                 jne    401176 <phase_6+0x82>
  401181: eb 05                 jmp    401188 <phase_6+0x94>
​

這裡我們需要一個簡單的c語言程式碼來看一下到底發生了什麼
首先我們這裡讀取了m[6032d8]的值我們需要看一下這裡面有什麼

(gdb) x 0x6032d8
0x6032d8 <node1+8>: 0x006032e0

這裡的node1就很有靈性。我們在這多看幾個值

0x6032d0 <node1>: 0x0000014c  0x00000001  0x006032e0  0x00000000
0x6032e0 <node2>: 0x000000a8  0x00000002  0x006032f0  0x00000000
0x6032f0 <node3>: 0x0000039c  0x00000003  0x00603300  0x00000000
0x603300 <node4>: 0x000002b3  0x00000004  0x00603310  0x00000000
0x603310 <node5>: 0x000001dd  0x00000005  0x00603320  0x00000000
0x603320 <node6>: 0x000001bb  0x00000006  0x00000000  0x00000000

這裡我們可以發現這其實是一個單連結串列。上面的操作就是從開始一直往後移動。移動的步數等於7-a[i]

while(7-a[i]--){
  P=P-next ;//p就表示我們連結串列的起點0x6032d0;
}
//得到這個p就是我們的第i個節點
//如果ai=6 則直接到這裡。否則經過上面的處理之後還會到這裡
  401183: ba d0 32 60 00        mov    $0x6032d0,%edx  //%edx=0x6032d0
  401188: 48 89 54 74 20        mov    %rdx,0x20(%rsp,%rsi,2) //m[%rsp+20]=rdx
  40118d: 48 83 c6 04           add    $0x4,%rsi //%rsi=4
  401191: 48 83 fe 18           cmp    $0x18,%rsi  //%rsi -0x18 
  401195: 74 14                 je     4011ab <phase_6+0xb7>
  401197: 8b 0c 34              mov    (%rsp,%rsi,1),%ecx //ecx=m[rsp+4]= 7-a2
  40119a: 83 f9 01              cmp    $0x1,%ecx    // 這裡如果7-a2<=1 a2>=6 a2=6  則直接401183 
  40119d: 7e e4                 jle    401183 <phase_6+0x8f>
  40119f: b8 01 00 00 00        mov    $0x1,%eax
  4011a4: ba d0 32 60 00        mov    $0x6032d0,%edx
  4011a9: eb cb                 jmp    401176 <phase_6+0x82>
  

這裡其實又是一個大的迴圈。看到這裡慢慢好像看懂了。這裡設a[1]-a[j]!=6 pi就是我們經過上面操作得到的第p個結點。則經過上面的彙編程式碼就會出現下面的結果。如果a[i]=6的話則r[rsp+x]=0x6032d0也就是物理意義上的node1

r[rsp+20]=p1;
r[rsp+28]=p2;
............
r[rsp+20+2*rsi]=pj
r[rsp+20+2*(rsi+1)]=0x6032d0 //a[j+1]=6

....剩下的節點不可能會是6因此會把我們其他的結點放到這裡。由於每一個數字都不相同所以從r[rsp+20]~r[rsp+50]就是我們重新排列之後的6個節點。
下面的pi均為重新排列之後的pi

  4011ab: 48 8b 5c 24 20        mov    0x20(%rsp),%rbx // rbx=p1;
  4011b0: 48 8d 44 24 28        lea    0x28(%rsp),%rax //rax= rsp+0x28
  4011b5: 48 8d 74 24 50        lea    0x50(%rsp),%rsi //rsi=rsp+0x50
  4011ba: 48 89 d9              mov    %rbx,%rcx //rcx=p1
  4011bd: 48 8b 10              mov    (%rax),%rdx //rdx=p2
  4011c0: 48 89 51 08           mov    %rdx,0x8(%rcx)//
  4011c4: 48 83 c0 08           add    $0x8,%rax //rax=rsp+0x30
  4011c8: 48 39 f0              cmp    %rsi,%rax // 這裡表示我們的6個結點是否遍歷完
  4011cb: 74 05                 je     4011d2 <phase_6+0xde>
  4011cd: 48 89 d1              mov    %rdx,%rcx
  4011d0: eb eb                 jmp    4011bd <phase_6+0xc9>
​```

上面的功能就是把我們重新排列之後的連結串列串聯起來。

```c++
  4011d2: 48 c7 42 08 00 00 00  movq   $0x0,0x8(%rdx) 
  4011da: bd 05 00 00 00        mov    $0x5,%ebp  //控制迴圈
  4011df: 48 8b 43 08           mov    0x8(%rbx),%rax //rax=p2
  4011e3: 8b 00                 mov    (%rax),%eax  
  4011e5: 39 03                 cmp    %eax,(%rbx)  //p2->val<=p1->val
  4011e7: 7d 05                 jge    4011ee <phase_6+0xfa>
  4011e9: e8 4c 02 00 00        callq  40143a <explode_bomb>
  4011ee: 48 8b 5b 08           mov    0x8(%rbx),%rbx
  4011f2: 83 ed 01              sub    $0x1,%ebp
  4011f5: 75 e8                 jne    4011df <phase_6+0xeb>

上面的式子告訴我們我們重新排列完之後的節點必須按照遞減的順序否則就會直接爆炸。那我們先按照之前的結點把結點大小排序一下。

0x6032d0 <node1>: 0x0000014c  0x00000001  0x006032e0  0x00000000
0x6032e0 <node2>: 0x000000a8  0x00000002  0x006032f0  0x00000000
0x6032f0 <node3>: 0x0000039c  0x00000003  0x00603300  0x00000000
0x603300 <node4>: 0x000002b3  0x00000004  0x00603310  0x00000000
0x603310 <node5>: 0x000001dd  0x00000005  0x00603320  0x00000000
0x603320 <node6>: 0x000001bb  0x00000006  0x00000000  0x00000000

node3>node4>node5>node6>node1>node2

通過上面的分析我們可以很容易的總結出答案。用一個簡單的虛擬碼來模擬一下上面的所有過程

cin>>a[6];//輸入六個數
if(a[i]=a[i+1]||a[i]>6)bomb!//i=1~6所有元素都不能相同都不能大於6
//這裡我們有一個單連結串列
 node1->node2->node3->node4->node5->node6
 // 由於最後我們的連結串列要滿足單調遞減
 // 按照值進行排序如下。所以我們重新排序的連結串列順序必須如下
 node3>node4>node5>node6>node1>node2
 List *L=new(List(-1));//新建一個連結串列 
for(int i=1;i<=6;i++){
  if(a[i]==6)L[i]=node1; //L[i]表示我們新連結串列的第i個結點。
  else{
    int b=7-a[i],p=node1;
    while(b--){
      p=p->next;
    }
    L[i]=p; //這裡就是7-a[i]為多少就把node[7-a[i]]賦給L[i]
  }
}   

結論可以很容易得到
由於L[5]=node1 所以我們輸入的第五個數為6
其他輸入通過公式L[i]=node[7-a[i]]

 L[1]=node3=node[7-a[1]] a[1]=4;
 L[2]=node4=node[7-a[2]] a[2]=3; 
 L[3]=node5=node[7-a[3]] a[3]=2; 
 L[4]=node6=node[7-a[4]] a[4]=1; 
 L[6]=node2=node[7-a[6]] a[1]=5;

所有最後的輸入為4 3 2 1 6 5

這裡顯示我們通過了所有的實驗。是不是超爽的。但是先別急這個實驗還有彩蛋下面讓我們去找一下彩蛋。

Bonus

首先是非常皮的一段話

  /* Wow, they got it!  But isn't something... missing?  Perhaps
  * something they overlooked?  Mua ha ha ha ha! */

其實之前讀彙編的程式碼的時候就有發現secret_phase的存在感覺彩蛋應該就在這裡了吧
我們發現在phase_defused裡面呼叫了我們的隱藏關卡。首先我們解決如何進入彩蛋關的問題。

1. 分析phase_defused

00000000004015c4 <phase_defused>:
4015c4:  sub    $0x78,%rsp
4015c8:  mov    %fs:0x28,%rax
4015cf:  
4015d1:  mov    %rax,0x68(%rsp)
4015d6:  xor    %eax,%eax
4015d8:  cmpl   $0x6,0x202181(%rip)       // 603760 <num_input_strings>
4015df:  jne    40163f <phase_defused+0x7b>
4015e1:  lea    0x10(%rsp),%r8
4015e6:  lea    0xc(%rsp),%rcx
4015eb:  lea    0x8(%rsp),%rdx
4015f0:  mov    $0x402619,%esi // 有奇怪的地址,check一下,發現是 "%d %d %s"
4015f5:  mov    $0x603870,%edi // 這裡是 ""
4015fa:  callq  400bf0 <__isoc99_sscanf@plt> //呼叫sscanf
4015ff:  cmp    $0x3,%eax 

上面check sscanf的返回值表示輸入的引數個數,如果是3個,就到401604行

401602:  jne    401635 <phase_defused+0x71>
401604:  mov    $0x402622,%esi  //這裡又有奇怪的地址 check一下 "DrEvil"
401609:  lea    0x10(%rsp),%rdi //%rdi=0x10(%rsp) 
40160e:  callq  401338 <strings_not_equal>
401613:  test   %eax,%eax
401615:  jne    401635 <phase_defused+0x71>
401617:  mov    $0x4024f8,%edi  //check 0x4024f8 Curses, "you've found the secret phase!"
40161c:  callq  400b10 <puts@plt> 
401621:  mov    $0x402520,%edi 
// check 0x402520  "But finding it and solving it are quite different..."
401626:  callq  400b10 <puts@plt>
40162b:  mov    $0x0,%eax
401630:  callq  401242 <secret_phase> # 呼叫彩蛋關
401635:  mov    $0x402558,%edi // "Congratulations! You've defused the bomb!"
40163a:  callq  400b10 <puts@plt>
40163f:  mov    0x68(%rsp),%rax
401644:  xor    %fs:0x28,%rax

通過上面的程式碼可以發現我們在輸入三個引數%d %d %s的時候最後輸入DrEvil即可開啟隱藏關。對於第三關和第四關的結果同樣適用。這裡我們需要找出在哪一關的時候輸入才能開啟隱藏關。
之前我們發現0x603870作為sscanf函式的第一個引數它不應該為空的下面給phase_defused加一個斷點來分析。可以發現最後的時候裡面的值竟然第四關的密碼。可以肯定我們是在第四關的時候輸入DrEvil進入隱藏關。

(gdb) b *0x4015fa
Breakpoint 4 at 0x4015fa
(gdb) info break
Num     Type           Disp Enb Address            What
4       breakpoint     keep y   0x00000000004015fa <phase_defused+54>
(gdb) r
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2.  Keep going!
0 207
Halfway there!
7 0
So you got that one.  Try this one.
IONEFG
Good work!  On to the next...
4 3 2 1 6 5
Breakpoint 4, 0x00000000004015fa in phase_defused ()
(gdb) p (char*) 0x603870
$13 = 0x603870 <input_strings+240> "7 0"

接下來的關鍵就是secret_phase和fun7

2.閱讀secret_phase

0000000000401242 <secret_phase>:
  401242: 53                    push   %rbx  
  401243: e8 56 02 00 00        callq  40149e <read_line>
  401248: ba 0a 00 00 00        mov    $0xa,%edx
  40124d: be 00 00 00 00        mov    $0x0,%esi
  401252: 48 89 c7              mov    %rax,%rdi
  401255: e8 76 f9 ff ff        callq  400bd0 <strtol@plt> //string to long 
  40125a: 48 89 c3              mov    %rax,%rbx
  40125d: 8d 40 ff              lea    -0x1(%rax),%eax
  401260: 3d e8 03 00 00        cmp    $0x3e8,%eax
  401265: 76 05                 jbe    40126c <secret_phase+0x2a>
  401267: e8 ce 01 00 00        callq  40143a <explode_bomb>
  40126c: 89 de                 mov    %ebx,%esi
  40126e: bf f0 30 60 00        mov    $0x6030f0,%edi
  401273: e8 8c ff ff ff        callq  401204 <fun7>

這裡把我們輸入的值和0x6030f0傳遞給fun7

  401278: 83 f8 02              cmp    $0x2,%eax
  40127b: 74 05                 je     401282 <secret_phase+0x40>
  40127d: e8 b8 01 00 00        callq  40143a <explode_bomb>
  401282: bf 38 24 40 00        mov    $0x402438,%edi //check "Wow! You've defused the secret stage!"
  401287: e8 84 f8 ff ff        callq  400b10 <puts@plt>
  40128c: e8 33 03 00 00        callq  4015c4 <phase_defused>
  401291: 5b                    pop    %rbx
  401292: c3                    retq 

通過上面我們發現如果fun7能夠返回2的話我們就完成了彩蛋關那麼關鍵就在於fuc7

3. fun7分析

0000000000401204 <fun7>:
  401204: 48 83 ec 08           sub    $0x8,%rsp
  401208: 48 85 ff              test   %rdi,%rdi
  40120b: 74 2b                 je     401238 <fun7+0x34>
  40120d: 8b 17                 mov    (%rdi),%edx 
  40120f: 39 f2                 cmp    %esi,%edx
  401211: 7e 0d                 jle    401220 <fun7+0x1c>

上面我們取了m[rdi]=m[0x6030f0]的值然後和esi也就是我們輸入的值進行比較。不如先看看0x6030f0裡放了些什麼 這裡我們把本題要用到的全部取出來。感覺上應該是一個樹結構。因為每一個結點都有兩個指標域和一個值域。

(gdb) x/120 0x6030f0
0x6030f0 <n1>:	0x00000024	0x00000000	0x00603110	0x00000000
0x603100 <n1+16>:	0x00603130	0x00000000	0x00000000	0x00000000
0x603110 <n21>:	0x00000008	0x00000000	0x00603190	0x00000000
0x603120 <n21+16>:	0x00603150	0x00000000	0x00000000	0x00000000
0x603130 <n22>:	0x00000032	0x00000000	0x00603170	0x00000000
0x603140 <n22+16>:	0x006031b0	0x00000000	0x00000000	0x00000000
0x603150 <n32>:	0x00000016	0x00000000	0x00603270	0x00000000
0x603160 <n32+16>:	0x00603230	0x00000000	0x00000000	0x00000000
0x603170 <n33>:	0x0000002d	0x00000000	0x006031d0	0x00000000
0x603180 <n33+16>:	0x00603290	0x00000000	0x00000000	0x00000000
0x603190 <n31>:	0x00000006	0x00000000	0x006031f0	0x00000000
0x6031a0 <n31+16>:	0x00603250	0x00000000	0x00000000	0x00000000
0x6031b0 <n34>:	0x0000006b	0x00000000	0x00603210	0x00000000
0x6031c0 <n34+16>:	0x006032b0	0x00000000	0x00000000	0x00000000
0x6031d0 <n45>:	0x00000028	0x00000000	0x00000000	0x00000000
0x6031e0 <n45+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031f0 <n41>:	0x00000001	0x00000000	0x00000000	0x00000000
0x603200 <n41+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x603210 <n47>:	0x00000063	0x00000000	0x00000000	0x00000000
0x603220 <n47+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x603230 <n44>:	0x00000023	0x00000000	0x00000000	0x00000000
0x603240 <n44+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x603250 <n42>:	0x00000007	0x00000000	0x00000000	0x00000000
0x603260 <n42+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x603270 <n43>:	0x00000014	0x00000000	0x00000000	0x00000000
0x603280 <n43+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x603290 <n46>:	0x0000002f	0x00000000	0x00000000	0x00000000
0x6032a0 <n46+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032b0 <n48>:	0x000003e9	0x00000000	0x00000000	0x00000000
0x6032c0 <n48+16>:	0x00000000	0x00000000	0x00000000	0x00000000

根據上圖可以畫出這棵樹

可以發現這是一顆二分查詢樹。這下就簡單多了。

if root->val <=input jmp 0x401220 else 0x401213 

  401213:	48 8b 7f 08          	mov    0x8(%rdi),%rdi
  401217:	e8 e8 ff ff ff       	callq  401204 <fun7> //每次都向左走找到符合的值
  40121c:	01 c0                	add    %eax,%eax
  40121e:	eb 1d                	jmp    40123d <fun7+0x39>
  401220:	b8 00 00 00 00       	mov    $0x0,%eax //%rax=0
  401225:	39 f2                	cmp    %esi,%edx //r->val - input
  401227:	74 14                	je     40123d <fun7+0x39> //=0 return 
  401229:	48 8b 7f 10          	mov    0x10(%rdi),%rdi // 如果r->val小 去右邊
  40122d:	e8 d2 ff ff ff       	callq  401204 <fun7>
  401232:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax
  401236:	eb 05                	jmp    40123d <fun7+0x39> 
  401238:	b8 ff ff ff ff       	mov    $0xffffffff,%eax //為空來這裡
  40123d:	48 83 c4 08          	add    $0x8,%rsp 
  401241:	c3                   	retq   

用虛擬碼來解釋上面的過程

int res=0;
func(Bitree *r ,long input){
 if(!r)return 0xffffffff
 if(r->val<=input){ 
     res=0;
     if(r->val <input){
       func(r->right,input);
       res=res*2+1;
     } 
     else return res;
     
}else{
    func(r->left,input);
    res*=2;
}
  return res;
}

可以發現當輸入為22的時候可以正好得到res=2

Curses, you've found the secret phase!
But finding it and solving it are quite different...
22
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!
[Inferior 1 (process 120) exited normally]

Summary

都寫完的時候還是很有成就感的,而且的確很有趣。不知道什麼時候國內能設計出這麼有意思的實驗cmu賽高。
彩蛋關的時候其實如何找到彩蛋有點參考了別人的教程。當時確實沒想到是這樣找的。以及第六關這個連結串列結構也是得到了一點提示。這樣可能降低難度了把。
寫的有點匆忙可能會有一些問題。歡迎大家積極指出。我先準備考試啦後面的實驗考完試在更新

相關文章