作業系統:linux
調試工具:gdb
文章分為實驗前準備、複習、拆炸彈三個子產品。讀者可以自取所需。
目錄
Bomb Lab
實驗前準備
複習
拆炸彈
phase_1
phase_2
phase_3
phase_4
phase_5
phase_6
secret_phase
結語
實驗由<code>phase_1 ~ phase_6</code>組成,共6個階段,
我們需要分别輸入6個字元串密碼,以便解除Dr. Evil的炸彈,
但隻要有1個字元串輸錯,炸彈就會爆炸。
那麼如何知道6個正确的字元串呢?
我們需要分别檢視<code>phase_1 ~ phase_6</code>的彙編代碼,
而炸彈就存放在其中,我們要根據彙編代碼小心地避開炸彈,分析出正确的字元串即可。
而分析需要用到學習的彙編知識、常用的gdb指令以及linux終端指令。
需要用到的linux終端指令:
别忘了先進入檔案位址中
<code>objdump -d bomb > assembly.s</code>
反編譯出<code>bomb.c</code>的彙編代碼,并存放在<code>assembly.s</code>中(assembly.s是自己命名的)
<code>gdb 檔案名</code>
用gdb調試工具打開某個檔案
eg. <code>gdb bomb</code> 用gdb調試工具打開bomb.c檔案
而我們就是要用gdb調試工具打開bomb.c檔案後做實驗
常用gdb指令:
gdb指令必須用gdb調試工具打開檔案後才能調用
<code>run</code> (可簡寫為:<code>r</code>)
運作檔案。
<code>quit</code> (可簡寫為:<code>q</code>)
停止運作,跳出gdb調試工具
<code>break 函數名</code> (可簡寫為:<code>b 函數名</code>)
在某個函數處打斷點,以便之後用disas展示這個函數的彙編代碼。
eg. <code>b phase_1</code> 在phase_1函數打斷點
<code>disas 函數名</code>
展示這個函數的彙編代碼(但必須先打斷點)
eg. <code>disas phase_1</code> 展示phase_1的彙編代碼
<code>x/NFU address or register</code>
用指定格式(FU)、指定數量(N)輸出位址或者寄存器對應内容
eg1. <code>x/s 0x40139b</code> 輸出0x40139b對應内容
eg2. <code>x/s $rsi</code> 輸出%rsi對應内容
eg3.<code>x/4xw 0x6032d0</code> 按24個十六進制4位元組格式輸出0x6032d0對應内容
<code>p/F *address or *register</code>
用指定格式(F)列印位址或者寄存器對應内容(效果上,<code>x/F = p/F</code>)
eg1. <code>p/x *(0x6032f0)</code> 按十六進制列印0x6032f0對應内容
格式F、機關U都與c語言類似,參考連結gdb:Format、Variables and Memory部分
測試和比較指令
- <code>ZF、OF、SF、CF</code>(條件碼)
zero flag 零标志位;overflow flag 溢出标志位;sign flag 符号标志位;carry flag 進位标志位。
- <code>test S1,S2</code>即 <code>S1 & S2</code>
測試指令,進行位級與操作,若結果為0,傳回<code>ZF = 1</code>;若結果非0,傳回<code>ZF = 0</code>,但不會改變兩個數的值。同時,需要特殊注意的是,可以對同一個寄存器使用test指令,例如<code>test %eax,%eax</code>。
- <code>cmp S1,S2</code>即<code>S2 - S1</code>
比較指令,進行減法操作,但不會改變兩個數的值。
部分跳轉指令
不區分unsiged(無符号)、siged(有符号)跳轉
<code>jne</code>
根據ZF的值判斷是否跳轉,當<code>ZF = 0</code>才跳轉。
<code>je</code>
根據ZF的值判斷是否跳轉,當<code>ZF = 1</code>才跳轉。
siged jump 指令
<code>jle</code> (<code>jump<=</code>)
j:jump;l:less(小于);e:equal
<code>jge</code> (<code>jump<=</code>)
j:jump;g:great(大于);e:equal
<code>jg</code> (<code>jump></code>)
j:jump;g:great(大于)
unsiged jump 指令
<code>jb</code>(<code>jump<</code>)
j:jump;b:below(小于)
<code>jbe</code> (<code>jump<=</code>)
j:jump;b:below(小于);e:equal
<code>ja</code> (<code>jump></code>)
j:jump;a:above(大于)
<code>cmp</code>與signed jump、unsigned jump的聯系(原理與條件碼有關,在此不解釋了)
當<code>cmp S1,S2</code>伴随signed jump指令使用時,S1置前,S2置後,例如:
當<code>cmp S1,S2</code>伴随unsigned jump指令使用時,S2置前,S1置後,例如:
部分二進制操作
<code>lea S,D</code> (即<code>D <-- S</code>,等價于指派算式<code>D = S</code>)
不是讀取資料,是加載有效位址,但和<code>mov</code>指令效果相同,常用于執行簡潔的算術操作。
<code>sub S,D</code> (即<code>D <-- D - S</code>,等價于指派算式<code>D -= S</code>)
減法,D - S後指派給D。
<code>sar S,D</code> (即<code>D <-- D >> S</code>,等價于指派算式<code>D >>= S</code>)
算術右移,補符号位(sign),D >> S後指派給D。注意:若<code>sar D</code>,則預設是算術右移<code>1</code>位。
<code>shr S,D</code> (即<code>D <-- D >> S</code>,等價于指派算式<code>D >>= S</code>)
算術右移,補<code>0</code>,D >> S後指派給D。
當寄存器為空時,其值為<code>0</code>
<code>%rdi</code>
系統往往要把一個值賦給%rdi後,才能當作入參(即輸入的參數)
<code>register</code>
注意:彙編代碼為了優化常常使用同一個系列的<code>register</code>表示同一個變量
例如:
register(64bits)
作用
%rax
存放傳回值
%rbx
被調用者儲存(存放被調用者的位址,可以是函數位址,也可以是其它位址,callee save)
%rcx
第4個參數(存儲參數,可以是數字字元參數,也可以是位址參數)
%rdx
第3個參數
%rsi
第2個參數
%rdi
第1個參數(經常作入參使用)
%rbp
被調用者儲存
%rsp
棧指針(用以開辟棧空間,注意入參的先進後出)
%r8
第5個參數
%r9
第6個參數
%10
調用者儲存(存放調用者函數的位址,可以是函數位址,也可以是其它位址,caller save)
%r11
調用者儲存
%r12
%r13
%r14
%r15
優化
有些看似毫無作用的彙編代碼,其實是在做優化。
在正式拆炸彈之前需要注意到一點,我們輸入的字元串都是放入到<code>%rdi</code>當中的。
(其實一般情況下,輸入也是放到<code>%rdi</code>的)
也就是說,在每個<code>phase</code>調用時,預設<code>%rdi</code>這個寄存器存放着輸入的字元串。
如何得到這點的呢?是通過分析<code>main</code>函數的彙編代碼得出的。
分析:
我們可以看到,在每個<code>phase</code>調用前,都<code>callq 0x40149e <read_line></code>,然後<code>mov %rax,%rdi</code>,這樣子會将我們輸入的字元串最終存到<code>%rdi</code>中。(我們是把字元串輸入到<code>read_line</code>的,而<code>read_line</code>會傳回到<code>%rax</code>中,<code>mov</code>操作将<code>%rax</code>存到<code>%rdi</code>。注意,<code>%rax</code>往往用來放傳回值)
至于<code>read_line</code>的彙編代碼我就不分析了,通過名字也可以看出:它是用來傳回我們的輸入值的
下面開始分析<code>phase_1 ~ phase_6</code>的彙編代碼,
得出認為正确的字元串,并逐個解除炸彈
檢視<code>phase_1</code>的彙編代碼:
重點就是<code><+14></code>的test指令,可以看出當<code>%eax</code>為0時才不會爆炸。也就是說函數<code>strings_not_equal</code>的傳回值必須為0才行,根據名字我們可以猜測,字元串不相等時傳回1,字元串相等時傳回0。下面檢視<code>string_not_equal</code>的彙編代碼驗證我們的猜測。
通過檢視<code>string_not_equal</code>函數的彙編代碼,可以找到,<code>string_not_equal</code>函數先是比較了輸入字元串和位址<code>0x402400</code>對應的字元長度,再逐個比較字元,最終,字元相同傳回<code>0</code>,字元不同傳回<code>1</code>。我們可以看到,彙編進行不少優化。例如,如果第一個字元已經是不同的,就不用循環進行更多的操作。
key:由此可以知道,隻要我們輸入的字元和位址<code>0x402400</code>對應的字元相同即可。那麼,我們通過gdb指令<code>x/s 0x402400</code>即可通路<code>0x402400</code>對應的字元。代碼如下:
那麼,隻要在運作界面輸入<code>Border relations with Canada have never been better.</code>即可。(譯為:與加拿大的邊境關系從來沒有這麼好過。)
我們先檢視<code>phase_2</code>的彙編代碼:
<code>callq 0x40145c <read_six_numbers></code>,我們檢視這個函數的彙編代碼:
(根據名字,我們猜測其作用是讀取6個數字)
注意到<code>phase_2</code>中<code><+6></code>已經将%rsp賦給%rsi,那麼,對%rsi進行操作也就是之後對<code>phase_2</code>中的%rsp操作。
以下表格是對%rsi操作的展示:
0x4(%rsi)
0x8(%rsi)
0xc(%rsi)
0x10(%rsi)
0x14(%rsi)
(%rsp)
0x8(%rsp)
注意,<code>0x10 = 16D</code>,<code>0x14 = 20D</code>。十進制字尾<code>D</code>,Decimal.
可以看到,這樣操作傳遞了6個位址參數,其位址間隔為<code>4</code>,最後兩個因為參數寄存器不夠用了,隻能用棧幀%rsp。(注意,此處的%rsp是<code>read_six_numbers</code>自己開辟的,原先<code>phase_2</code>的%rsp已經中斷)
<code><+36></code>處,<code>mov $0x4025c3,%esi</code>,通路0x4025c3内容:
可以知道%eax中存放着格式字元串
<code><+46></code>處,<code>callq 0x400bf0 <__isoc99_sscanf@plt></code>,函數<code>sscanf</code>是c語言自帶的函數,同<code>scanf</code>類似,作用是讀取輸入的字元串,并根據指定格式存儲到指定位址。這告訴了我們的輸入的字元串,應該是6個整型數字,而且之間用空格隔開。同時,它們存放在上述的6個參數位址中,也就是之後<code>phase_2</code>的%rsp按<code>4</code>遞增位址的棧幀中。符合我們的猜測。
我們回到<code>phase_2</code>繼續分析: 可以知道第一個參數數值<code>(%rsp)</code>必須是<code>1</code>,之後會進入一個循環。我們把這個循環單獨拿出來看:
我們已經知道<code>(%rsp) = 1</code>,那麼第一次循環中,<code><+32></code>處的<code>%eax = 2</code>,故隻有<code>(%rbx) = 0x4(%rsp) = 2</code>,才不爆炸;第二次循環中,<code><+32></code>處的<code>%eax = 4</code>,故<code>(%rbx) = 0x8(%rsp) = 4</code>;第三次循環中,<code><+32></code>處的<code>%eax = 8</code>,故<code>(%rbx) = 0xc(%rsp) = 8</code>,可以看出是以1為首項,後一項是前一項兩倍的遞增數列,共6項,以此類推,有下表。
0x4(%rsp)
0xc(%rsp)
0x10(%rsp)
0x14(%rsp)
1
2
4
8
16
32
key: 由此得知,phase_2該輸入的字元串為<code>1 2 4 8 16 32</code>
<code>phase_3</code>的彙編代碼如下:
通路<code>0x4025cf</code>,可知字元串格式為<code>"%d %d"</code>,進而得知輸入字元串是兩個整數。根據<code><+39>: cmpl $0x7,0x8(%rsp)</code>和<code><+44>: ja 0x400fad <phase_3+106></code>可以知道<code>0x8(%rsp) <= 7</code>。那麼,第一個參數%rdx取值要小于等于7。
根據<code><+50>: jmpq *0x402470(,%rax,8)</code>可以知道,跳轉的位址是指針<code>*0x402470</code>的變換,增量為<code>8</code>,此時為<code>0x8(%rsp)</code>即為%eax。
下面我們用<code>x/s</code>指令逐個通路,指針位址是遞增的,但對應存放的<code>phase_3</code>的位址并不是遞增的。到這時,答案已經呼之欲出了。
(由此可以知道指針<code>*0x402470</code>指向一個存放<code>phase_3</code>部分位址的字元串。一開始我想的是對<code>phase_3</code>的部分位址直接進行增<code>8</code>操作,沒有想到是對指針<code>*0x402470</code>進行增<code>8</code>操作,進而通路不同的<code>phase_3</code>中的位址,耽誤了許久)
對<code>phase_3</code>繼續分析:
根據<code><+123>: cmp 0xc(%rsp),%eax</code>和<code><+127>: je 0x400fc9 <phase_3+134></code>知道,第二個參數<code>0xc(%rsp)</code>必須和%eax相同,此時的%eax根據<code>0x8(%rsp)</code>不同而不同。
key:
整理上述分析可以知道,共有<code>8</code>組答案如下:
3
x/s
*0x402470
*(~ +8)
*(~ +16)
*(~ +24)
<phase_3+n>
+57
+118
+64
+71
0xc(%rsp) H
0xcf
0x137
0x2c3
0x100
0xc(%rsp) D
207
311
707
256
5
6
7
*(~ +32)
*(~ +40)
*(~ +48)
*(~ +56)
+78
+85
+92
+99
0x185
0xce
0x2aa
0x147
389
206
682
327
(十六進制字尾,H,hexadecimal;十進制字尾,D,Decimal)
我們選擇第8組答案<code>7 327</code>作為輸入:
<code>phase_4</code>的彙編代碼如下:
分析:仍舊是輸入兩個<code>int</code>參數,根據<code><+34></code>處可知,第一個參數<code>0x8(%rsp)</code>滿足<code>0xe > 0x8(%rsp)</code>,第二個參數<code>0xc(%rsp)</code>必須為0。但是第一個參數需要根據<code><func4></code>函數來确定。
<code><func4></code>的彙編代碼如下:
第一次進入func4函數:
分析:其實這個時候答案已經出來了,隻要第一個參數為<code>7</code>和<code>%ecx=7</code>相同即可。但顯然<code>func</code>是一個遞歸函數,我們進入遞歸看看,還有什麼答案。
第一次遞歸:(記為<code>R1</code>)
第一個參數為<code>3</code>和<code>%ecx=3</code>相同即可。
第二次遞歸:(記為<code>R2</code>)
第一個參數為<code>1</code>和<code>%ecx=1</code>相同即可。
第三次遞歸:(記為<code>R3</code>)
第一個參數為<code>0</code>和<code>%ecx=0</code>相同即可。
第四次遞歸:(記為<code>R4</code>)
第五次遞歸:(記為<code>R5</code>,此時<code>R5 = R3</code>)
由此可以知道,<code>R4</code>之後會一直在<code>R3、R4</code>之間無限循環,直到程式崩潰。那麼,第一個參數的取值隻能為:<code>0 1 3 7</code>
得出<code>4</code>組答案為:
0x8(%rsp) 第一個輸入參數
0xc(%rsp) 第二個輸入參數
我們取<code>0 0</code>這組輸入。
<code>phase_5</code>的彙編代碼如下:
很顯然,輸入的字元串長度得為<code>6</code>。利用x/s指令,在<code><+55></code>處我們檢視<code>x/s 0x4024b0</code>,給出一句嘲諷我們的話,<code>"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"</code>,很明顯不是答案,也許是答案,也許不是答案,也許在暗示答案不能單單copy,我們無視他的嘲諷,對<code><+41> ~ <+74></code>處的循環進行專門分析。
第二次循環:
以此類推至第六次循環後,應當有<code><+41>: %ecx = %rbx+5</code>和<code><+62>: %rsp+0x10+5 = %dl</code>。此時,<code>%rbx</code>是輸入,會有: <code>%rsp+0x10+5 = %dl = %edx = 0x4024b0+(%rdx & 0xf)</code>
上式等價于:<code>= 0x4024b0+(%cl & 0xf) = 0x4024b0+((%rbx+5) & 0xf)</code>
所有情況見下表。
%rsp+0x10+0
0x4024b0+%rbx+0(0x4024b0 + (第一個輸入參數 & 0xf))
%rsp+0x10+1
0x4024b0+%rbx+1(0x4024b0 + (第二個輸入參數 & 0xf))
%rsp+0x10+2
0x4024b0+%rbx+2(0x4024b0 + (第三個輸入參數 & 0xf))
%rsp+0x10+3
0x4024b0+%rbx+3(0x4024b0 + (第四個輸入參數 & 0xf))
%rsp+0x10+4
0x4024b0+%rbx+4(0x4024b0 + (第五個輸入參數 & 0xf))
%rsp+0x10+5
0x4024b0+%rbx+5(0x4024b0 + (第六個輸入參數 & 0xf))
可以看出<code>%rsp+0x10</code>的指派是<code>0x4024b0</code>的偏移,偏移量範圍為<code>0 ~ 15</code>,也就是嘲諷句的前16個字元,即<code>maduiersnfotvbyl</code>。
我們回到<code>phase_5</code>函數繼續分析,利用x/s指令,在<code><+81></code>處我們檢視<code>x/s 0x40245e</code>,給出一個單詞字元串,<code>"flyers"</code>(譯為飛翔者們或者考熟的鴨子飛走了)。也就是說,<code><+86></code>處的入參必須與<code>flyers</code>相同。
根據<code>maduiersnfotvbyl</code>,取得<code>flyers</code>對應<code>0x4024b0</code>的偏移量是<code>9、15、14、5、6、7</code>,但是隻能輸入<code>6</code>個字元,如果直接輸入<code>91514567</code>,超過<code>6</code>個字元會爆炸。
要解決這個問題,可以先把偏移量轉換為二進制<code>1001、1111、1110、0101、0110、0111</code>。因為有<code>%rbx & 0xf</code>的位級操作,那麼我們隻要保證尾4位相同即可。這時可以聯想到用來定義字元的ASCII表,那麼答案有許多種,我比較懶,就隻找尾4位為<code>1111</code>和<code>1110</code>的字元。分别為<code>/</code>和<code>.</code>,進而得到輸入<code>9/.567</code>。
檢視<code>phase_6</code>的彙編代碼:
好家夥,一個電腦螢幕根本裝不下全部代碼。我們分成幾個部分來看。
注意:在循環開始前會有一些變量準備;<code>0x10 = 16D,0x14 = 20D</code>;<code>flag</code>這個變量命名常用來當作循環是否結束的标志。
Part1,讀入了6個整數。
根據<code>phase_2</code>的<code><read_six_numbers></code>分析可以知道,這段讀入了6個整數,對棧幀<code>%rsp</code>偏移(偏移量為<code>4</code>)即是輸入<code>input[i],i取0 ~ 5</code>。如下表所示:
input[0]
input[1]
input[2]
input[3]
input[4]
input[5]
Part2,保證6個輸入整數(<code>input[i],i取0 ~ 5</code>)都不相同且範圍為1 ~ 6。
loop1确定了<code>input[i]</code>的取值範圍為:<code>1 ~ 6</code>。loop2保證6個輸入都不相等。
取值範圍如何确定的?由<code>+39 ~ +45</code>處得出,<code>input[i] <= 6</code>。同時,因為<code>+45</code>的 <code>jbe</code> 是對無符号數操作,若<code>input[i]</code>取 <code>0</code> ,執行<code>0 - 1 = -1</code>在比較時會出錯(溢出),是以不能取<code>0</code>。故隻能取<code>1 ~ 6</code>。
Part3,執行算式:<code>input[i] = 7 - input[i]</code>
是以為了友善,我們重新命名:<code>sub_input[i] = 7 - input[i]</code>
Part4,引傳入連結表并改變連結清單順序。
首先我們檢視<code>0x6032d0</code>,一開始用<code>x/s</code>指令都是亂碼,如圖所示。
但是注意到<code>node1</code>,說明其很有可能是連結清單,而不是字元串。
我們用<code>x/24xw</code>指令,按xw格式(十六進制4位元組格式)輸出24個資料。
如圖所示,符合我們的猜測,就是連結清單,<code>0x6032d0</code>是head指針。
其中,node是節點位址,val是節點值,order是它的序列(一共6個),next是下個節點的位址(能夠發現它剛好就是按照序列排列的,最後一個節點的next是<code>0x0即NULL</code>)。
那麼,該段循環是怎麼改變連結清單順序的呢?首先引入棧幀<code>%rsp + 8*i + 0x20,i取0 ~ 5</code>,然後在<code>+130 ~ +137</code>,<code>0x6032d0</code>将随<code>sub_input[i]</code>的改變而改變,最後将改變後的連結清單逐個賦給棧幀。
即執行執行指派算式:<code>%rsp + 8*i + 0x20 = $0x6032d0 + (8*(sub_input[i]-1))</code>
這樣下來就能夠重排連結清單了。但此處還沒有給出改變連結清單順序的标準是什麼。
同時注意到一個重要的細節問題:改變時0x6032d0的間隔為8D,但我們需要的間隔為0x10 = 16D,如何解決?
事實上,間隔為8也沒關系。隻要部分+8的位址所存數值恰好為+16的位址即可。(不是所有位址都需要這樣,另一部分位址執行兩次+8就行了)
上述表述等價于三個等式
<code>*(0x6032d0+8) = 0x6032e0</code>、<code>*(0x6032f0+8) = 0x603200</code>、<code>*(0x603310+8)= 0x603320</code>成立。
我們用p/x 指令列印數值看三個等式是否成立。(這個成立是<code>phase_6</code>已經設定好的,但不檢視之前完全不知道)
執行part4後,可以得出如下表格:
rec_node0
rec_node1
rec_node2
rec_node3
rec_node4
rec_node5
%rsp + 0x20
%rsp + 0x28
%rsp + 0x30
%rsp + 0x38
%rsp + 0x40
%rsp + 0x48
說明<code>rec_node1 ~ rec_node6</code>将逐個放入棧幀<code>%rsp + 0x20 ~ %rsp + 0x48</code>中。
注意:因為node0 ~ node5已經随sub_input而重新排列了,但我們還不知道是如何排列的。
是以,不妨命名為:rec_node0 ~ rec_node5,表明連結清單重組,也就是所謂的改變連結清單順序。
(rec:recombination)
同時也可以得出<code>sub_input</code>對應<code>rec_node</code>的表格:
sub_input[0]
sub_input[1]
sub_input[2]
sub_input[3]
sub_input[4]
sub_input[5]
0x6032d0
0x6032e0
0x6032f0
0x603300
0x603310
0x603320
part5,重新連接配接next。
注意:
<code>0x50(%rsp) = 0 = NULL</code>是next連接配接完才指派的。
這裡利用棧幀<code>%0x28(%rsp) ~ 0x50(%rsp)</code>逐個賦給<code>%0x20(%rsp) ~ 0x48(%rsp)</code>
改變的是rec_node ->next指向的位址,而不是rec_ndoe本身。也就是所謂的重新連接配接next。
之是以要重連next,也是因為node已經重排為rec_ndoe了。
part6,說明改變連結清單順序的标準:按<code>val</code>降序排列。(從大到小)
之是以按<code>val</code>降序排列,是因為必須滿足<code>(0x8+0x20+%rsp+k*8) >= (0x20+%rsp+k*8),k取0~5</code>
即<code>node->val >= node->next->val</code>成立,也就是連結清單中目前節點值大于下一個節點值。
綜上part1 ~ part6可以得出答案:(<code>sub_input</code>模7得到<code>input</code>)
val
0x39c
0x2b3
0x1dd
0x1bb
0x14c
0x0a8
rec_node
sub_input
input
inuput為:<code>4 3 2 1 6 5</code>
ps:如果你仔細觀察,你會發現,按val降序排列之後,order其實恰好對應着sub_input。
可惡啊!竟然還有個彩蛋關。什麼?你說我怎麼發現的,當然是“口口相傳”啦。
(事實上可以在編譯檔案上查找<code>ctrl+f</code>到)
用到了二叉樹。
檢視<code>phase_defused</code>的彙編代碼:(可以知道在phase_4中多輸入個DrEvil即<code>0 0 DrEvil</code>才能進入彩蛋關)
我們對一些字元串進行通路,可以知道調用<code>sscanf</code>函數前得輸入,其格式為<code>"%d %d %s"</code>但輸入内容是空的,這說明我們還沒輸入。我們需要輸入,但,是什麼時候輸入呢?
要确定輸入是何時輸入的,我們可以先在<code>*0x4015f5</code>處,即輸入處打斷點,然後run bomb檔案,最後通路<code>0x603870</code>的内容。
此時有<code>0x603870 <input_strings+240>: "0 0 "</code>,這說明輸入是在<code>phase_4</code>處進行的。其輸入格式為<code>"%d %d %s"</code>,同時有<code>*(0x402622) == DrEvil</code>,之後要進行字元比對且相同才進入secret_phase。那麼我們大膽猜測,應該在<code>phase_4</code>處輸入<code>0 0 DrEvil</code>才能進入彩蛋關。
開始正式分析,首先檢視<code>secret_phase</code>的彙編代碼:(說明調用fun7之後,傳回值必須為2)
檢視<code>fun7</code>的彙編代碼:
這是一棵二叉樹,思路如圖所示:

方法一:實作eax = 2。(ps:遞歸調用順序與傳回順序相反)
下面描述調用過程:(ps:esi是input,每次調用完都會更新rid指向的位址)
B:第一次調用fun7,<code>(rdi) = (0x6030f0) = 0x24</code>。同時輸入要不滿足<code>(rdi) <= esi</code>,也就說輸入小于0x24,才能走路徑B。
此次調用結束時更新rdi指向的位址,即:<code>rdi+0x8 = 0x603110</code>。
C:第二次調用fun7,<code>(rdi) = (0x603110) = 0x8</code>。同時輸入要滿足<code>(rdi) <= esi</code>且不滿足<code>edx == esi</code>,也就說輸入大于0x8且不等于0x8,才能走路徑C。
此次調用結束時更新rdi指向的位址,即:<code>rdi+0x10 = 0x603150</code>。
D:第三次調用fun7,<code>(rdi) = (0x603150) = 0x16</code>。同時輸入要滿足<code>(rdi) <= esi</code>且滿足<code>edx == esi</code>,也就說輸入等于0x16即20D,才能走路徑D。
三次調用結束,得出input為<code>20</code>。
方法二:實作eax = 2。
key:我們取<code>20</code>這個答案。
無聊地檢視一下二叉樹内容:(此處對解題不是很有必要)
整理後的圖狀展示:
But you know, Alan, sometimes it's the very people who no one imagines anything of who do the things no one can imagine. ---The Imitation Game
從9.27到10.11,總算完結了。期間有幾天是沒有寫的。總共是8天,最後兩關都用了2天。當然不是一整天,5、6個小時應該是有的。完全靠自己寫的是phase_4,奈何沒啥基礎。前幾個都在适應gdb是怎麼調試的,後幾個難度比較大,phase_5是沒聯系到ASCII,後兩個是連結清單、二叉樹都不熟悉。也參考了不少其它大佬的解答。
自己的彙編閱讀能力是提升了不少,後面看都是比較自然,但是聯系到資料結構就比較不了解,還是得繼續提高姿勢水準。
如有謬誤,敬請指正。