零碎記事
久違的,昨天打了一整天的游戲,玩的LOL,就只玩刀妹這個英雄,本人絕活。
不得不說,刀妹這個英雄設計得太好玩了,可以說是將游戲中的博弈部分放大到了極致。這個容錯率極低的英雄,每一步都要操作謹慎,但高操作帶來的高收益,在我還沒出現失誤的時候,我的對手也在時刻警惕着我的動作。我Q上去,瞬間接近對方,他以為我要打了,趕緊回頭扔技能,我卻往回Q走跑了,又以為我不想打的時候,我又突然以兩個小兵作為跳板Q回來,接近拉E暈人幾乎一瞬間完成,還疊滿了被動,這時對方才知道大事不妙,然而已經跑不了了,兩段突進之下只能被我活活A死。這個英雄所帶來的的線上支配感,讓我深受喜歡。
一個游戲為什么會好玩?很大一部分,是因為游戲的博弈在暗中會使人產生一種情感變化,知乎上有個游戲設計師就對游戲設計的情感變化這部分做過很精彩的講解:
假設你正好在與一個陌生人下國際象棋,現在輪到你動子,而且看起來你要輸。由於想不出一步好棋,你會感覺有壓力,並且精神緊張。在分析過棋盤上的局勢之后,你更加緊張了。接下來卻峰回路轉,你發現了一招好棋——如果你向后方跳馬,不但可以保護自己的王,同時還可以威脅到對方!你緊綳的神經得到了極大的緩解,隨之帶來一絲成就感。你移動棋子,對方因為發現了你的意圖而面色沉重。看到對方的樣子,你會感覺自己有一種支配力,你的臉上露出了一絲得意。對方陷入了沉思,在你仍然感到志得意滿的時候,你發現了自己的一個薄弱環節。如果對方飛象過來,就能吃掉你自己的馬。這步棋比較隱蔽,對方會注意到嗎?你的滿足感逐漸被焦慮所取代。你努力讓自己保持神情自若,同時時間一點一點的流逝,這種焦慮在你的腦子里亂撞,你整個大腦都被這種焦慮的情感所淹沒,你的腎上腺素開始飆升,心跳加快。終於,對手只是移動了一個卒。你緊綳的神經再次得到了緩解,甚至比上次有過之而無不及。你感覺這一局已經穩操勝券了。
從旁觀者的角度來看,這盤棋平淡無奇。兩個人面色凝重地坐在桌旁對弈,各自在棋盤上移動棋子,甚至連對於雙方都沒有意識到自己所經歷的這些情感。即便如此,他們從下棋的競技過程中感受到了過山車般的情感變化。並且之后,他們還將一次又一次經歷這種情感的變化。
可以說,玩家與游戲機制之間的互動是產生感情波動的來源,對於一個有吸引力的游戲來說,這些互動需要能夠激發人類熱血沸騰的情感。當這些時間真正激發出了自豪、歡樂、可怕或者恐怖等情感的時候,這個游戲才好玩。
在優秀的游戲設計中,玩家不會注意到這種情感是由游戲機制本身產生的。比如你在《LOL》里使用龍龜打野,背后一群人在追着你打,但你一直開着反傷,最后追你的人成為了被你追的人。
而劣質的游戲設計里,玩家在遭受挫折時,通常第一個想到的就是游戲機制的不公平,比如在《一刀999》游戲里,你開着和平模式在野外掛機,這時候一個氪金玩家突然出現把你擊殺,你最好的裝備掉在了地上被氪金玩家拾取,並分解。
《LOL》里每個英雄的技能設計,都可以讓玩家在艾歐尼亞的戰斗中產生豐富的情感。當你用雪人在地上逐漸推起一個大雪球時;當你用瞎子站在敵方玩家看不到的地方,扔出一個Q將敵方玩家擊殺時;當你用瑞文的Q跨過一道牆時;當你殘血閃現,結果撞到牆時;當你用炸彈人的W成功逃生時;當你用寒冰千里之外命中控場時。這些設計產生的情感才是游戲存在的真正意義,這也是玩家願意花時間、精力和金錢在棋盤上移動棋子,或者把球投進籃筐里的真正原因。
Bomb Lab
實驗說明
這個實驗是依靠匯編代碼解開六道謎題也就是phase_1到phase_6,都是以函數形式給出,我們只需要看匯編代碼的函數部分,讓輸入數據進入函數后能繞過所有炸彈就行。
個人感覺,其實只要耐心看下去,這個實驗誰都能解開所有謎題,唯一的難點就是剛開始對匯編和gdb一無所知的時候,幾乎都不知道這個實驗到底要自己干嘛。我建議,就算是打算自己完全獨立去解開這些炸彈謎題的,最好也是先看看phase_1和phase_2別人是怎么分析怎么解開的,熟悉下這個實驗到底是在干嘛后,自己再往后做,少做那兩道簡單的謎題,是完全不影響你的能力成長的。遵循這個思路,我在講解phase_1和phase_2的時候,會連同去講解gdb的一些基本操作和一些反匯編后看匯編代碼的經驗邏輯,方便大家先入個門,但往后面,我還是主要介紹我在解題過程中的思路,不再過於詳細地展開。
實驗前置准備
解壓bomb.tar
bomb的tar包解壓完后,我們會得到下面幾個文件
其中bomb是我們的可執行程序,README我個人建議做實驗前先看看,bomb.c的話看不看都無所謂,看了也沒什么用的。
我們的任務就是運行這個可執行程序,做六次輸入,每次都不能觸發炸彈。
就拿第一次輸入作為例子,運行程序,會出現這么個東西。。。
其實這個就是phase_1,你要輸入數據他才會繼續往下走,如果胡亂輸入東西按下回車,就會理所當然的觸發炸彈。。。
如果我輸入了正確的數據。。。
就會有提示phase_1已通過,並且要求你做下一個輸入,也就是phase_2的輸入,phase_2輸入不對,那么phase_2就會炸,后邊以此類推了,直到phase_6都是這樣。
那么問題來了,我們怎么知道正確的輸入啊?
這個就要反匯編,用匯編代碼去反推了,也就是這篇文章的重點。
輸入objdump -d bomb > bomb.asm,可以得到bomb程序的反匯編代碼
推薦傳到windows上拿Notepad++看看,直接vim看也是可以,但因為想方便后邊打注釋分析,遲早要拿到Notepad++上面的。
打開匯編代碼看看
嗯,沒問題。
Linux上裝個gdb,不會裝的自己百度吧,一搜一大把。
輸入gdb bomb,把程序裝載到調試器上方便調試
簡單說明下要用到的gdb指令
r 運行程序
b <*0x某某某> 在某個地址設置斷點,具體哪里,可以看反匯編的代碼,可以根據那個直接復制粘貼設斷點的
d 刪除所有斷點
d <斷點號> 刪除指定斷點
info b 查看所有斷點信息
continue 從斷點處繼續執行
display <$寄存器> 跟蹤寄存器,碰到斷點停下時會顯示出所有跟蹤的寄存器的當前值,非常好用的一個命令,注意的是gdb中表示寄存器的話前面用的不是百分符號%,而是美元符號$
x/參數 <地址> 訪問地址的內存,其實就是間接訪問,也是很好用的指令,關於參數,s是輸出為字符串,d為輸出為十進制,x為輸出為十六進制,b、w、l、q控制輸出字節,默認是w,四字節,s字符串不受這個控制除外。
info r 查看所有寄存器的值
前置工作就這些了,接下來正式解題
phase_1
看匯編代碼,直接拉到phase_1函數部分
這就是個HelloWorld讓你入門的東西,看看我是怎么從這些代碼中分析得到正確的輸出的。
首先要讓大家知道一個很關鍵的東西
看到圈起來的那部分嗎?你的代碼要是運行了那行指令,那你就觸發炸彈涼涼了。所以你得繞過它,這個繞過是什么意思呢?別急,看看前面一行代碼。
看到沒,上面一行有個跳轉指令,直接跳過這個炸彈的,當然,你得滿足這個跳轉條件才行,怎么滿足呢?結合第350行那個,我們可以知道只要%eax為0就滿足這個跳轉條件,怎么讓%eax為0呢?首先要知道%eax其實就是%rax的后32位,那%rax又是什么?前面有函數調用的話,一般%rax的值就是程序的返回值,實際上,我們確實看到它在前面調用了一個函數
從函數的名字我們很容易猜出來這個函數是用來判斷字符串是否相等的,不等返回1,相等返回0,也就說,想讓%rax等於0,就等讓字符串相等,什么字符串相等?當然是我們輸入的串跟答案的串相等啊。可是我們又怎么才能知道答案的串是什么呢?先別急,至少現在我們明白了繞過炸彈的條件了。
先看看上邊前面兩行
上面一行是開棧操作,與之對應的是第353行的返回前棧指針回收,這個是每個函數首尾都會有的,表明了函數中開了多大的局部變量。看這個有什么用呢?我們輸入的可能就是輸入到函數里面的局部變量的,那這樣我們順着%rsp指針就能找到我們輸入的內容,嘛,這么說而已,其實這個phase里邊並不是這樣的,可以試給你們看看
首先我在0x400ee9那設了個斷點,也就是callq 401338 <strings_not_equal>這一行,那么程序會在碰到這條匯編指令但還沒執行的時候停下來,這樣我就可以查看程序運行到那里時寄存器或者內存的一些信息。
然后我開始跑我的程序,並隨便輸入了點東西,再往下,碰到斷點
我們的目的是找出正確的字符串,%rsp開了8個字節的空間,這里很可能有放東西,比如我們的輸入之類的,先看看吧,輸入x/s $rsp,也就是以字符串形式查看棧頂的東西
然而並不是,里面的東西我們啥也看不懂,什么用都沒有
我們看看下一行
要知道call的函數需要傳參的話,參數一般得實現准備好,然后這個%rdi往往就是放第一個參數的地方,其實這里很明顯%rsi就是作為參數傳進<strings_not_equal>的,我們可以大膽猜測這傳的是我們所輸入的字符串或者是正確的字符串,查它!。
一查,果然有個字符串在里面,然而並不是我輸入的串,但是應該就是正確的字符串了,至於我輸入的串去哪了,我也不知道,但是到這為止至少我們可以確定輸入行為是在這個phase_1函數的外部發生的,現在先不管這些,先試試剛剛拿到的串對不對,對的話也不用進一步深究了。
因為之前隨便輸入的串肯定不對,往下走肯定是callq 40143a <explode_bomb>被執行然后炸掉,就不continue了,直接r重新開始吧,這次我們輸入剛剛的的得到的疑似正確的字符串。
嗯,看來還真是這個,phase_1就過了,反推就是這么一回事。
phase_2
這是phase_2的函數的反匯編代碼,我們可以先看看,我是邊分析邊注釋的,所以有些自己加的東西,注意,當時到phase_2時我還不太懂匯編,注釋的東西都只是順着想法寫腦海中某一步的情況,你理解我的注釋不一定跟我實際注釋的意思一樣,而且突然知道怎么解后我就直接不注釋到下一關了,顯得很有跳躍性,不建議看這個注釋的。
1 0000000000400efc <phase_2>: 2 400efc: 55 push %rbp 3 400efd: 53 push %rbx 4 400efe: 48 83 ec 28 sub $0x28,%rsp //開棧 5 400f02: 48 89 e6 mov %rsp,%rsi //%rsi = %rsp 6 400f05: e8 52 05 00 00 callq 40145c <read_six_numbers> 7 400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) //第一個數是1 8 400f0e: 74 20 je 400f30 <phase_2+0x34> 9 400f10: e8 25 05 00 00 callq 40143a <explode_bomb> 10 400f15: eb 19 jmp 400f30 <phase_2+0x34> 11 400f17: 8b 43 fc mov -0x4(%rbx),%eax //%rax = %rbx - 4 12 400f1a: 01 c0 add %eax,%eax //%rax += %rax 13 400f1c: 39 03 cmp %eax,(%rbx) //第二個數在%rax里 14 400f1e: 74 05 je 400f25 <phase_2+0x29> 15 400f20: e8 15 05 00 00 callq 40143a <explode_bomb> 16 400f25: 48 83 c3 04 add $0x4,%rbx %rbx += 4 17 400f29: 48 39 eb cmp %rbp,%rbx 18 400f2c: 75 e9 jne 400f17 <phase_2+0x1b> 19 400f2e: eb 0c jmp 400f3c <phase_2+0x40> 20 400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx 21 400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp 22 400f3a: eb db jmp 400f17 <phase_2+0x1b> 23 400f3c: 48 83 c4 28 add $0x28,%rsp 24 400f40: 5b pop %rbx 25 400f41: 5d pop %rbp 26 400f42: c3 retq
看看第6行,有個callq 40145c <read_six_numbers>,可以推測phase_2是要我們輸入6個數,輸入的時候我們用空格隔開就行了
看看第7行,發生了一個比較cmpl $0x1,(%rsp),結合下兩行的跳轉+bomb觸發,我們可以知道要不觸發第一個bomb,那么就得讓棧頂的東西,也就是(%rsp)的值等於1才行。首先我們看看棧頂專門存什么的,其實可以猜測我們輸入的數就是從棧頂開始放的,那么這樣一來(%rsp)就會是我們輸入的第一個數,驗證下這個想法先,在第八行設個斷點,我隨便輸入了六個數字,然后查看了下(%rsp)的值
正如我所猜,輸入的數從棧頂開始放的,那這就好說了啊,滿足第8行的跳轉條件,我輸入的第一個數等於1就行。繼續往下分析。
滿足條件后是一個jmp
jmp后執行了兩個計算,分別是lea 0x4(%rsp),%rbx 和 0x18(%rsp),%rbp,再往后是到上面的jmp語句
這個是無條件跳轉的,Nodepad++很方便,我們還發現了再往上有同樣跳轉目的的一個條件跳轉
從有條件的跳轉入手,我們容易發現那里是個循環,這里我為了方便后續分析,就做了標記
這種習慣是非常有利於我們去分析反匯編代碼的,分析循環,發現里面有個炸彈,我們得繞開它,我上邊因為當時還不是很懂匯編,所以有些注釋是有問題的,尤其是這循環里的注釋,你大可不管。反正分析知道,%rbx里的東西是我們輸入的數,而拿來比較的(%rbx),是他的正確答案,我們可以慢慢查看這個(%rbx),就是在炸彈前設斷點,獲得一個正確的數字就重新來重新輸入再看這樣,具體就不弄了,大家可以試試的,最后知道要輸入的內容就是1 2 4 8 16 32。
OK,phase_2解掉
phase_3
匯編代碼+注釋,從這個phase開始往后,這個注釋和思路是沒問題的,大家可以看看,有些地方可能有些跳躍性,可自行分析接上,不影響大體
1 0000000000400f43 <phase_3>: 2 400f43: 48 83 ec 18 sub $0x18,%rsp //開棧 3 400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx //%rcx = (%rsp + 0xc) 4 400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx //%rdx = (%rsp + 0x8) 5 400f51: be cf 25 40 00 mov $0x4025cf,%esi //%rsi = 0x4025cf 6 400f56: b8 00 00 00 00 mov $0x0,%eax //%rax = 0 7 400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt> //該函數返回值是輸入數字的個數 8 400f60: 83 f8 01 cmp $0x1,%eax //進入函數后返回值要大於1,說明輸入要超過1個數字 9 400f63: 7f 05 jg 400f6a <phase_3+0x27> 10 400f65: e8 d0 04 00 00 callq 40143a <explode_bomb> 11 400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) //((%rsp + 0x8) > 7),此條件成立就引爆炸彈,所以輸入的第一個數要小於或等於7 12 400f6f: 77 3c ja 400fad <phase_3+0x6a> //跳到炸彈 13 400f71: 8b 44 24 08 mov 0x8(%rsp),%eax //%rax = 第一個數 14 400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8) //jump到8*第一個數 + 0x402470 15 400f7c: b8 cf 00 00 00 mov $0xcf,%eax //%rax = 0xcf 16 400f81: eb 3b jmp 400fbe <phase_3+0x7b> 17 400f83: b8 c3 02 00 00 mov $0x2c3,%eax //%rax = 0x2c3 18 400f88: eb 34 jmp 400fbe <phase_3+0x7b> 19 400f8a: b8 00 01 00 00 mov $0x100,%eax //%rax = 0x100 20 400f8f: eb 2d jmp 400fbe <phase_3+0x7b> 21 400f91: b8 85 01 00 00 mov $0x185,%eax //%rax = 0x185 22 400f96: eb 26 jmp 400fbe <phase_3+0x7b> 23 400f98: b8 ce 00 00 00 mov $0xce,%eax //%rax = 0xce 24 400f9d: eb 1f jmp 400fbe <phase_3+0x7b> 25 400f9f: b8 aa 02 00 00 mov $0x2aa,%eax //%rax = 0x2aa 26 400fa4: eb 18 jmp 400fbe <phase_3+0x7b> 27 400fa6: b8 47 01 00 00 mov $0x147,%eax //%rax = 0x147 28 400fab: eb 11 jmp 400fbe <phase_3+0x7b> 29 400fad: e8 88 04 00 00 callq 40143a <explode_bomb> 30 400fb2: b8 00 00 00 00 mov $0x0,%eax //%rax = 0 31 400fb7: eb 05 jmp 400fbe <phase_3+0x7b> 32 400fb9: b8 37 01 00 00 mov $0x137,%eax //%rax = 0x137 33 400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax //%rax不等於(%rsp + 12)就引爆炸彈,(%rsp + 12)是第二個數,試了下,第一個數等於3時,到這里rax = 256 34 400fc2: 74 05 je 400fc9 <phase_3+0x86> //到達函數出口 35 400fc4: e8 71 04 00 00 callq 40143a <explode_bomb> 36 400fc9: 48 83 c4 18 add $0x18,%rsp 37 400fcd: c3 retq
answer:輸入超過1個數,第一個數要小於或等於7,最終測試得知,過關條件之一是 3 256
phase_4
1 0000000000400fce <func4>: 2 400fce: 48 83 ec 08 sub $0x8,%rsp 3 400fd2: 89 d0 mov %edx,%eax //%rax = 14 4 400fd4: 29 f0 sub %esi,%eax //%rax = 14 - 0 = 14 5 400fd6: 89 c1 mov %eax,%ecx //%rcx = 14 6 400fd8: c1 e9 1f shr $0x1f,%ecx //%rcx = 0 7 400fdb: 01 c8 add %ecx,%eax //%rax = 14 8 400fdd: d1 f8 sar %eax //%rax = 14 / 2 = 7 9 400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx //%rcx = 1 * 0 + 7 = 7 10 400fe2: 39 f9 cmp %edi,%ecx //第一個數小於等於7,就跳轉到%rax = 0 11 400fe4: 7e 0c jle 400ff2 <func4+0x24> 12 400fe6: 8d 51 ff lea -0x1(%rcx),%edx 13 400fe9: e8 e0 ff ff ff callq 400fce <func4> 14 400fee: 01 c0 add %eax,%eax 15 400ff0: eb 15 jmp 401007 <func4+0x39> 16 400ff2: b8 00 00 00 00 mov $0x0,%eax //%rax = 0 17 400ff7: 39 f9 cmp %edi,%ecx //第一個數大於等於7,跳轉到函數尾巴,由此可知,第一個數等於7 18 400ff9: 7d 0c jge 401007 <func4+0x39> 19 400ffb: 8d 71 01 lea 0x1(%rcx),%esi 20 400ffe: e8 cb ff ff ff callq 400fce <func4> 21 401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax 22 401007: 48 83 c4 08 add $0x8,%rsp 23 40100b: c3 retq 24 25 000000000040100c <phase_4>: 26 40100c: 48 83 ec 18 sub $0x18,%rsp 27 401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx 28 401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx 29 40101a: be cf 25 40 00 mov $0x4025cf,%esi 30 40101f: b8 00 00 00 00 mov $0x0,%eax 31 401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt> 32 401029: 83 f8 02 cmp $0x2,%eax //從這個下一行可推斷要輸入兩個數 33 40102c: 75 07 jne 401035 <phase_4+0x29> 34 40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) //第一個數要小於等於14 35 401033: 76 05 jbe 40103a <phase_4+0x2e> 36 401035: e8 00 04 00 00 callq 40143a <explode_bomb> 37 40103a: ba 0e 00 00 00 mov $0xe,%edx //%rdx = 14 38 40103f: be 00 00 00 00 mov $0x0,%esi //%rsi = 0 39 401044: 8b 7c 24 08 mov 0x8(%rsp),%edi //%rdi = 第一個數 40 401048: e8 81 ff ff ff callq 400fce <func4> //進入一個函數 41 40104d: 85 c0 test %eax,%eax 42 40104f: 75 07 jne 401058 <phase_4+0x4c> //函數返回值不等於0就引爆炸彈 43 401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) //第二個數等於0才行 44 401056: 74 05 je 40105d <phase_4+0x51> 45 401058: e8 dd 03 00 00 callq 40143a <explode_bomb> 46 40105d: 48 83 c4 18 add $0x18,%rsp 47 401061: c3 retq
answer:直接看匯編,甚至不用gdb都能推出來,答案是7 0
phase_5
1 0000000000401062 <phase_5>: 2 401062: 53 push %rbx //%rbx是保存的字符串 3 401063: 48 83 ec 20 sub $0x20,%rsp 4 401067: 48 89 fb mov %rdi,%rbx 5 40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 6 401071: 00 00 7 401073: 48 89 44 24 18 mov %rax,0x18(%rsp) 8 401078: 31 c0 xor %eax,%eax //%rax清0 9 40107a: e8 9c 02 00 00 callq 40131b <string_length> //計算輸入的字符串長度 10 40107f: 83 f8 06 cmp $0x6,%eax //字符串長度等於6才合法 11 401082: 74 4e je 4010d2 <phase_5+0x70> 12 401084: e8 b1 03 00 00 callq 40143a <explode_bomb> 13 401089: eb 47 jmp 4010d2 <phase_5+0x70> 14 15 Loop:for(%rax=0; %rax!=6 ;%rax++) 16 40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx //%rcx = %rax + %rbx。其實這里是%rcx從第一個字符遍歷到第六個字符 17 40108f: 88 0c 24 mov %cl,(%rsp) //(%rsp) = %rcx的后8位 18 401092: 48 8b 14 24 mov (%rsp),%rdx //%rdx = %rcx的后8位 19 401096: 83 e2 0f and $0xf,%edx //%rdx = %rcx的后4位 20 401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx //%rdx = (%rdx + 0x4024b0) //0x4024b0的前16位是"maduiersnfotvbyl" 21 4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) //(%rsp + %rax + 16) = %rdx的最后四位。這里推出我們輸入字符的ASCII碼會的后四位會對應上上邊字符串的字符 22 4010a4: 48 83 c0 01 add $0x1,%rax //%rax += 1 23 4010a8: 48 83 f8 06 cmp $0x6,%rax //%rax != 6就繼續回到上邊的循環 24 4010ac: 75 dd jne 40108b <phase_5+0x29> 25 Loop End 26 4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp) //(%rsp + 0x16) = 0。實際上就是給字符串加結束符 27 4010b3: be 5e 24 40 00 mov $0x40245e,%esi //%rsi = 0x40245e。是字符串"flyers",可推出我們輸入的字符串的每個字符的ASCII碼的后四位應分別為9、F、E、5、6、7 ==> 符合條件的字符串之一是"9ON567" 28 4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi //%rdi = (%rsp + 0x10) 29 4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal> //這里有個字符串檢測,設斷點看看前面的值 30 4010c2: 85 c0 test %eax,%eax 31 4010c4: 74 13 je 4010d9 <phase_5+0x77> 32 4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb> 33 4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 34 4010d0: eb 07 jmp 4010d9 <phase_5+0x77> 35 4010d2: b8 00 00 00 00 mov $0x0,%eax //%rax = 0 36 4010d7: eb b2 jmp 40108b <phase_5+0x29> 37 4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax 38 4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 39 4010e5: 00 00 40 4010e7: 74 05 je 4010ee <phase_5+0x8c> 41 4010e9: e8 42 fa ff ff callq 400b30 <__stack_chk_fail@plt> 42 4010ee: 48 83 c4 20 add $0x20,%rsp 43 4010f2: 5b pop %rbx 44 4010f3: c3 retq
answer:在貼出代碼的第20行和第26行結合起來看,注釋已經充分說明了答案,輸入的字符串符合這個規律“輸入的字符串的每個字符的ASCII碼的后四位應分別為9、F、E、5、6、7”就行,比如符合條件之一的字符串是"9ON567"
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> 40110b: 49 89 e6 mov %rsp,%r14 //機子上測試了下,輸入的是6個4字節整數,開始地址是%rsp 40110e: 41 bc 00 00 00 00 mov $0x0,%r12d //%r12d = 0 Loop 1: 401114: 4c 89 ed mov %r13,%rbp //這里跟%r13同值 401117: 41 8b 45 00 mov 0x0(%r13),%eax //把輸入的整數拿進%rax,不出意外應該是遍歷操作 40111b: 83 e8 01 sub $0x1,%eax //%rax -= 1 40111e: 83 f8 05 cmp $0x5,%eax //%rax <= 5 ==>輸入的數要小於等於6,因為無符號數,上面又-1,所以還要大於等於1 401121: 76 05 jbe 401128 <phase_6+0x34> 401123: e8 12 03 00 00 callq 40143a <explode_bomb> 401128: 41 83 c4 01 add $0x1,%r12d 40112c: 41 83 fc 06 cmp $0x6,%r12d //結合上下文,其實已經可以推出%r12d是程序計數變量了 401130: 74 21 je 401153 <phase_6+0x5f> 401132: 44 89 e3 mov %r12d,%ebx Loop 2: 401135: 48 63 c3 movslq %ebx,%rax 401138: 8b 04 84 mov (%rsp,%rax,4),%eax //程序計數變量作為數組索引,是遍歷操作 40113b: 39 45 00 cmp %eax,0x0(%rbp) //實際上是在把當前遍歷到的數與后面的每個數作比較 40113e: 75 05 jne 401145 <phase_6+0x51> //可推出不能輸入兩個相同的數 401140: e8 f5 02 00 00 callq 40143a <explode_bomb> 401145: 83 c3 01 add $0x1,%ebx 401148: 83 fb 05 cmp $0x5,%ebx 40114b: 7e e8 jle 401135 <phase_6+0x41> Loop 2 End 40114d: 49 83 c5 04 add $0x4,%r13 401151: eb c1 jmp 401114 <phase_6+0x20> Loop 1 End 分析循環收獲:一、輸入的數要小於等於6並且大於等於1 二、不能輸入兩個相同的數 401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi 401158: 4c 89 f0 mov %r14,%rax //是輸入的第一個數字 40115b: b9 07 00 00 00 mov $0x7,%ecx //%rcx = 7 Loop: 401160: 89 ca mov %ecx,%edx //看循環后面%rcx的值就沒動過,可認為這里恆有%rdx=7 401162: 2b 10 sub (%rax),%edx //所以是7減去每個數 401164: 89 10 mov %edx,(%rax) //因為是內存引用,這里改變了數組實際的值,這個循環過后,數組里的值變成7-輸入的數 401166: 48 83 c0 04 add $0x4,%rax //實際上還是在遍歷那六個數 40116a: 48 39 f0 cmp %rsi,%rax 40116d: 75 f1 jne 401160 <phase_6+0x6c> Loop End 分析循環收獲:數組的值被變成7-每個輸入的數 40116f: be 00 00 00 00 mov $0x0,%esi 401174: eb 21 jmp 401197 <phase_6+0xa3> Loop 1:Loop 2: 401176: 48 8b 52 08 mov 0x8(%rdx),%rdx //這里應該是個鏈表 40117a: 83 c0 01 add $0x1,%eax 40117d: 39 c8 cmp %ecx,%eax //%rax初始值是1,這里可看成是循環條件為%rax<7-input[i] ==>%rdx的值為node[7-input[i]]的地址(鏈式順序) 40117f: 75 f5 jne 401176 <phase_6+0x82> Loop 2 End 401181: eb 05 jmp 401188 <phase_6+0x94> Loop 3: 401183: ba d0 32 60 00 mov $0x6032d0,%edx 401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) //棧中存入%rdx的值,也就是node的地址,整個循環的目的就在於這里 40118d: 48 83 c6 04 add $0x4,%rsi 401191: 48 83 fe 18 cmp $0x18,%rsi //%rsi是計數變量,看看下行跳出大循環的匯編代碼,估計又是遍歷了。。。 401195: 74 14 je 4011ab <phase_6+0xb7> 401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx //7減過后的輸入數字 40119a: 83 f9 01 cmp $0x1,%ecx 40119d: 7e e4 jle 401183 <phase_6+0x8f> //小於等於1就回Loop3,意思是某個輸入數字等於6就回Loop3 Loop 3 End 40119f: b8 01 00 00 00 mov $0x1,%eax //%rax要做計數變量了 4011a4: ba d0 32 60 00 mov $0x6032d0,%edx //得看看這個地址裝的什么,看到是node1~6,是鏈表結點,通過查看數據,其鏈式data為{332, 168, 924, 691, 477, 443},對應node{1, 2, 3, 4, 5, 6} 4011a9: eb cb jmp 401176 <phase_6+0x82> Loop 1 End 分析循環收獲:%rsp+0x20存了6個結點,分別為node[7-input[i]] 4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx //%rbx = node[7-input[1]]的地址 4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax //%rax = node棧[2] 4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi //%rsi = node棧[6] 4011ba: 48 89 d9 mov %rbx,%rcx //%rcx = node[7-input[1]]的地址 Loop: 4011bd: 48 8b 10 mov (%rax),%rdx //%rdx = node[7-input[i]] 4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) 4011c4: 48 83 c0 08 add $0x8,%rax 4011c8: 48 39 f0 cmp %rsi,%rax //%rax == node[7-input[6]]是循環退出條件,結合上邊有地址偏移8取next,下邊又有next賦值之類的,容易猜到這個是在重構鏈表 4011cb: 74 05 je 4011d2 <phase_6+0xde> 4011cd: 48 89 d1 mov %rdx,%rcx 4011d0: eb eb jmp 4011bd <phase_6+0xc9> Loop End 循環分析收獲:棧中的node重構成了鏈 4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) //node[7-input[6]].next = NULL 4011d9: 00 4011da: bd 05 00 00 00 mov $0x5,%ebp Loop: 4011df: 48 8b 43 08 mov 0x8(%rbx),%rax 4011e3: 8b 00 mov (%rax),%eax 4011e5: 39 03 cmp %eax,(%rbx) 4011e7: 7d 05 jge 4011ee <phase_6+0xfa> //結合上邊三條語句和這個循環,其實意思是:node[7-input[i]]->data >= node[7-input[i+1]]->data 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 //這里看出%ebp是計數變量,循環實際上是for(%rbp=5; %rbp!=0 ;%rbp--) 4011f5: 75 e8 jne 4011df <phase_6+0xeb> Loop End 循環分析收獲:node[7-input[i]]->data >= node[7-input[i+1]]->data 至此謎題已經解開 有6個node,通過查看數據,其鏈式data為{332, 168, 924, 691, 477, 443},對應node{1, 2, 3, 4, 5, 6} 7減去我們的輸入所獲得的node的data要符合其降序 不考慮7減去時,輸入為3, 4, 5, 6, 1, 2 考慮7減去時,輸入為4, 3, 2, 1, 6, 5。其實這個就是正確的輸入 4011f7: 48 83 c4 50 add $0x50,%rsp 4011fb: 5b pop %rbx 4011fc: 5d pop %rbp 4011fd: 41 5c pop %r12 4011ff: 41 5d pop %r13 401201: 41 5e pop %r14 401203: c3 retq
answer:其實就是我最后注釋的內容
有6個node,通過查看數據,其鏈式data為{332, 168, 924, 691, 477, 443},對應node{1, 2, 3, 4, 5, 6}
7減去我們的輸入所獲得的node的data要符合其降序
不考慮7減去時,輸入為3, 4, 5, 6, 1, 2
考慮7減去時,輸入為4, 3, 2, 1, 6, 5。其實這個就是正確的輸入