当前位置 博文首页 > 周小伦:超精讲-逐例分析 CSAPP:实验2-Bomb!(下)

    周小伦:超精讲-逐例分析 CSAPP:实验2-Bomb!(下)

    作者:周小伦 时间:2021-01-30 15:38

    好了话不多说我们书接上文继续来做第二个实验下面是前半部分实验的连接

    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赛高。
    彩蛋关的时候其实如何找到彩蛋有点参考了别人的教程。当时确实没想到是这样找的。以及第六关这个链表结构也是得到了一点提示。这样可能降低难度了把。
    写的有点匆忙可能会有一些问题。欢迎大家积极指出。我先准备考试啦后面的实验考完试在更新