超精講-逐例分析 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
``` c++
0000000000401062 :
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
40107f: 83 f8 06 cmp $0x6,%eax
401082: 74 4e je 4010d2
401084: e8 b1 03 00 00 callq 40143a
```
剛開始就開了個金絲雀這個題應該不太對勁。。`rsp+0x18`這個位置儲存了我們金絲雀的值這是為了防止緩衝區溢位隨後呼叫`string_length` 函式來判斷輸入的字串長度。可以發現這裡規定來我們輸入的字串長度必須是6否則直接爆炸。滿足要求後跳轉到` `
#### 2. 閱讀
```c++
4010d2: b8 00 00 00 00 mov $0x0,%eax
4010d7: eb b2 jmp 40108b
把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
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
4010c2: 85 c0 test %eax,%eax
4010c4: 74 13 je 4010d9
4010c6: e8 6f 03 00 00 callq 40143a
4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
4010d0: eb 07 jmp 4010d9
4010d2: b8 00 00 00 00 mov $0x0,%eax
4010d7: eb b2 jmp 40108b
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
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裡面到底發生了什麼這裡寫一個虛擬碼會更好理解
```c++
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 "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();
```
這個的彙編有億點點長。下面先放一個總體邏輯的虛擬碼來幫助大家理解
```c++
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 :
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
//這裡的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 //a1-6<=0
/*
這裡我們發現如果輸入的引數> 6則會直接爆炸
*/
--------------------------------------------------------------------
401123: e8 12 03 00 00 callq 40143a
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 // 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 // 如果a1 和a2 相同則直接爆炸
401140: e8 f5 02 00 00 callq 40143a
401145: 83 c3 01 add $0x1,%ebx //ebx+=1 =2
401148: 83 fb 05 cmp $0x5,%ebx // ebx-5
40114b: 7e e8 jle 401135 //ebx-5<=0 ebx<=5
40114d: 49 83 c5 04 add $0x4,%r13 //%r13+=4 %r13=%rsp+4
401151: eb c1 jmp 401114
// 這裡有遞迴關係注意
```
對上面的程式碼寫一個簡單的c語言虛擬碼如下
```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才能繼續
然後繼續往下執行
```c++
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
```
上面又形成一個遞迴這裡在寫一個c語言的虛擬碼
```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。
```c++
40116f: be 00 00 00 00 mov $0x0,%esi
401174: eb 21 jmp 401197
//將%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
}
// 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
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
401181: eb 05 jmp 401188
```
這裡我們需要一個簡單的c語言程式碼來看一下到底發生了什麼
首先我們這裡讀取了m[6032d8]的值我們需要看一下這裡面有什麼
```
(gdb) x 0x6032d8
0x6032d8 : 0x006032e0
```
這裡的node1就很有靈性。我們在這多看幾個值
```
0x6032d0 : 0x0000014c 0x00000001 0x006032e0 0x00000000
0x6032e0 : 0x000000a8 0x00000002 0x006032f0 0x00000000
0x6032f0 : 0x0000039c 0x00000003 0x00603300 0x00000000
0x603300 : 0x000002b3 0x00000004 0x00603310 0x00000000
0x603310 : 0x000001dd 0x00000005 0x00603320 0x00000000
0x603320 : 0x000001bb 0x00000006 0x00000000 0x00000000
```
這裡我們可以發現這其實是一個單鏈表。上面的操作就是從開始一直往後移動。移動的步數等於7-a[i]
```c++
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
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
40119f: b8 01 00 00 00 mov $0x1,%eax
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx
4011a9: eb cb jmp 401176
```
這裡其實又是一個大的迴圈。看到這裡慢慢好像看懂了。這裡設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
```c++
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
4011cd: 48 89 d1 mov %rdx,%rcx
4011d0: eb eb jmp 4011bd
```
上面的功能就是把我們重新排列之後的連結串列串聯起來。
```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
4011e9: e8 4c 02 00 00 callq 40143a
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx
4011f2: 83 ed 01 sub $0x1,%ebp
4011f5: 75 e8 jne 4011df
```
上面的式子告訴我們我們重新排列完之後的節點必須按照遞減的順序否則就會直接爆炸。那我們先按照之前的結點把結點大小排序一下。
```
0x6032d0 : 0x0000014c 0x00000001 0x006032e0 0x00000000
0x6032e0 : 0x000000a8 0x00000002 0x006032f0 0x00000000
0x6032f0 : 0x0000039c 0x00000003 0x00603300 0x00000000
0x603300 : 0x000002b3 0x00000004 0x00603310 0x00000000
0x603310 : 0x000001dd 0x00000005 0x00603320 0x00000000
0x603320 : 0x000001bb 0x00000006 0x00000000 0x00000000
```
> node3>node4>node5>node6>node1>node2
通過上面的分析我們可以很容易的總結出答案。用一個簡單的虛擬碼來模擬一下上面的所有過程
```c++
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
![](https://img2020.cnblogs.com/blog/2282357/202101/2282357-20210128105220143-1189286614.png)
這裡顯示我們通過了所有的實驗。是不是超爽的。但是先別急這個實驗還有彩蛋下面讓我們去找一下彩蛋。
### 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
```c++
00000000004015c4 :
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
4015df: jne 40163f
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行
```c++
401602: jne 401635
401604: mov $0x402622,%esi //這裡又有奇怪的地址 check一下 "DrEvil"
401609: lea 0x10(%rsp),%rdi //%rdi=0x10(%rsp)
40160e: callq 401338
401613: test %eax,%eax
401615: jne 401635
401617: mov $0x4024f8,%edi //check 0x4024f8 Curses, "you've found the secret phase!"
40161c: callq 400b10
401621: mov $0x402520,%edi
// check 0x402520 "But finding it and solving it are quite different..."
401626: callq 400b10
40162b: mov $0x0,%eax
401630: callq 401242 # 呼叫彩蛋關
401635: mov $0x402558,%edi // "Congratulations! You've defused the bomb!"
40163a: callq 400b10
40163f: mov 0x68(%rsp),%rax
401644: xor %fs:0x28,%rax
```
通過上面的程式碼可以發現我們在輸入三個引數`%d %d %s`的時候最後輸入DrEvil即可開啟隱藏關。對於第三關和第四關的結果同樣適用。這裡我們需要找出在哪一關的時候輸入才能開啟隱藏關。
之前我們發現0x603870作為sscanf函式的第一個引數它不應該為空的下面給phase_defused加一個斷點來分析。可以發現最後的時候裡面的值竟然第四關的密碼。可以肯定我們是在第四關的時候輸入DrEvil進入隱藏關。
```c++
(gdb) b *0x4015fa
Breakpoint 4 at 0x4015fa
(gdb) info break
Num Type Disp Enb Address What
4 breakpoint keep y 0x00000000004015fa
(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 "7 0"
```
接下來的關鍵就是secret_phase和fun7
#### 2.閱讀secret_phase
```c++
0000000000401242 :
401242: 53 push %rbx
401243: e8 56 02 00 00 callq 40149e
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 //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
401267: e8 ce 01 00 00 callq 40143a
40126c: 89 de mov %ebx,%esi
40126e: bf f0 30 60 00 mov $0x6030f0,%edi
401273: e8 8c ff ff ff callq 401204
```
這裡把我們輸入的值和0x6030f0傳遞給fun7
```c++
401278: 83 f8 02 cmp $0x2,%eax
40127b: 74 05 je 401282
40127d: e8 b8 01 00 00 callq 40143a
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
40128c: e8 33 03 00 00 callq 4015c4
401291: 5b pop %rbx
401292: c3 retq
```
通過上面我們發現如果fun7能夠返回2的話我們就完成了彩蛋關那麼關鍵就在於fuc7
#### 3. fun7分析
```assembly
0000000000401204 :
401204: 48 83 ec 08 sub $0x8,%rsp
401208: 48 85 ff test %rdi,%rdi
40120b: 74 2b je 401238
40120d: 8b 17 mov (%rdi),%edx
40120f: 39 f2 cmp %esi,%edx
401211: 7e 0d jle 401220
```
上面我們取了`m[rdi]=m[0x6030f0]`的值然後和esi也就是我們輸入的值進行比較。不如先看看0x6030f0裡放了些什麼 這裡我們把本題要用到的全部取出來。感覺上應該是一個樹結構。因為每一個結點都有兩個指標域和一個值域。
```
(gdb) x/120 0x6030f0
0x6030f0 : 0x00000024 0x00000000 0x00603110 0x00000000
0x603100 : 0x00603130 0x00000000 0x00000000 0x00000000
0x603110 : 0x00000008 0x00000000 0x00603190 0x00000000
0x603120 : 0x00603150 0x00000000 0x00000000 0x00000000
0x603130 : 0x00000032 0x00000000 0x00603170 0x00000000
0x603140 : 0x006031b0 0x00000000 0x00000000 0x00000000
0x603150 : 0x00000016 0x00000000 0x00603270 0x00000000
0x603160 : 0x00603230 0x00000000 0x00000000 0x00000000
0x603170 : 0x0000002d 0x00000000 0x006031d0 0x00000000
0x603180 : 0x00603290 0x00000000 0x00000000 0x00000000
0x603190 : 0x00000006 0x00000000 0x006031f0 0x00000000
0x6031a0 : 0x00603250 0x00000000 0x00000000 0x00000000
0x6031b0 : 0x0000006b 0x00000000 0x00603210 0x00000000
0x6031c0 : 0x006032b0 0x00000000 0x00000000 0x00000000
0x6031d0 : 0x00000028 0x00000000 0x00000000 0x00000000
0x6031e0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x6031f0 : 0x00000001 0x00000000 0x00000000 0x00000000
0x603200 : 0x00000000 0x00000000 0x00000000 0x00000000
0x603210 : 0x00000063 0x00000000 0x00000000 0x00000000
0x603220 : 0x00000000 0x00000000 0x00000000 0x00000000
0x603230 : 0x00000023 0x00000000 0x00000000 0x00000000
0x603240 : 0x00000000 0x00000000 0x00000000 0x00000000
0x603250 : 0x00000007 0x00000000 0x00000000 0x00000000
0x603260 : 0x00000000 0x00000000 0x00000000 0x00000000
0x603270 : 0x00000014 0x00000000 0x00000000 0x00000000
0x603280 : 0x00000000 0x00000000 0x00000000 0x00000000
0x603290 : 0x0000002f 0x00000000 0x00000000 0x00000000
0x6032a0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x6032b0 : 0x000003e9 0x00000000 0x00000000 0x00000000
0x6032c0 : 0x00000000 0x00000000 0x00000000 0x00000000
```
根據上圖可以畫出這棵樹
![](https://img2020.cnblogs.com/blog/2282357/202101/2282357-20210128105500698-1389833845.png)
可以發現這是一顆二分查詢樹。這下就簡單多了。
```c++
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 //每次都向左走找到符合的值
40121c: 01 c0 add %eax,%eax
40121e: eb 1d jmp 40123d
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 //=0 return
401229: 48 8b 7f 10 mov 0x10(%rdi),%rdi // 如果r->val小 去右邊
40122d: e8 d2 ff ff ff callq 401204
401232: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401236: eb 05 jmp 40123d
401238: b8 ff ff ff ff mov $0xffffffff,%eax //為空來這裡
40123d: 48 83 c4 08 add $0x8,%rsp
401241: c3 retq
```
用虛擬碼來解釋上面的過程
```c++
int res=0;
func(Bitree *r ,long input){
if(!r)return 0xffffffff
if(r->val<=input){
res=0;
if(r->val 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賽高。
彩蛋關的時候其實如何找到彩蛋有點參考了別人的教程。當時確實沒想到是這樣找的。以及第六關這個連結串列結構也是得到了一點提示。這樣可能降低難度了把。
寫的有點匆忙可能會有一些問題。歡迎大家積極指出。我先準備考試啦後面的實驗考完試