Bomblab(ICS课程回课pku)
💬全劇情
充斥邪魅氣息の歡迎🤗
Welcome to Dr. Evil’s little bomb. You have 6 phases with which to blow yourself up. Have a nice day! Mua ha ha ha!
命中注定的逐步潰敗👼
Phase 1 defused. How about the next one?
That’s number 2. Keep going!
Halfway there!
So you got that one. Try this one.
Good work! On to the next…
來自博士的贊賞👍
Congratulations! You’ve defused the bomb!
Your instructor has been notified and will verify your solution.
隱藏劇情🎭
邪惡的內(nèi)心獨(dú)白🧛?♂?
Wow, they got it! But isn’t something… missing? Perhaps something they overlooked? Mua ha ha ha ha!
面對強(qiáng)敵的驚愕💦
Curses, you’ve found the secret phase!
But finding it and solving it are quite different…
PKUer不好惹,逃💨
Bravo! You’ve defused the secret stage!
Dr. Evil finally knew that the students at PKU are not easy to defeat.
He fled in haste, but you know it is not over…
但邪惡永存,He will be back again!🎃
? “A sleepless malice”, uttered you as you caught a whiff of the next trouble…
🚗Tips
一些好用的gdb命令:
eflags中一些比較重要的標(biāo)志位:
| OF | - | - | - | SF | ZF | - | - | - | PF | - | CF |
閱讀匯編代碼的技巧:
從一堆限制條件中確定答案的技巧:
?Cautions
🛵開始闖關(guān)!
Ready
gdb的一些常用操作:
phase_1
最開始在無法確定密碼是什么時,可以先提前輸入很長一段字符串作為密碼,預(yù)留好空間,之后再改。
0x00000000000027cd <+0>: endbr64 0x00000000000027d1 <+4>: sub $0x8,%rsp 0x00000000000027d5 <+8>: lea 0x2a24(%rip),%rsi # 0x5200 0x00000000000027dc <+15>: callq 0x2d2e <strings_not_equal> 0x00000000000027e1 <+20>: test %eax,%eax 0x00000000000027e3 <+22>: jne 0x27ea <phase_1+29> 0x00000000000027e5 <+24>: add $0x8,%rsp 0x00000000000027e9 <+28>: retq 0x00000000000027ea <+29>: callq 0x3105 <explode_bomb> 0x00000000000027ef <+34>: jmp 0x27e5 <phase_1+24>根據(jù)strings_not_equal函數(shù)名可以推斷,此段代碼的功能就是比較%rdi和%rsi(0x5200)所指向的字符串是否相同。
(gdb) x/s 0x5200
0x5200: “All those moments will be lost in time, like tears in rain. Time to die.”
(gdb) set {char[100]} $rdi = “All those moments will be lost in time, like tears in rain. Time to die.”
我所見過的事物,你們?nèi)祟惤^對無法置信,我目睹了戰(zhàn)艦在獵戶星座的端沿起火燃燒,我看著C射線在唐懷瑟之門附近的黑暗中閃耀,所有這些時刻,終將隨時間消逝,一如眼淚消失在雨中。 --《銀翼殺手》
phase_2
觀察頭部和尾部的功能:開棧,設(shè)置金絲雀值以及檢測棧是否被破壞。
0x00000000000027f1 <+0>: endbr64 0x00000000000027f5 <+4>: push %rbx 0x00000000000027f6 <+5>: sub $0x20,%rsp 0x00000000000027fa <+9>: mov %fs:0x28,%rax 0x0000000000002803 <+18>: mov %rax,0x18(%rsp) 0x0000000000002808 <+23>: xor %eax,%eax ... 0x0000000000002848 <+87>: mov 0x18(%rsp),%rax 0x000000000000284d <+92>: xor %fs:0x28,%rax 0x0000000000002856 <+101>: jne 0x285e <phase_2+109> 0x0000000000002858 <+103>: add $0x20,%rsp 0x000000000000285c <+107>: pop %rbx 0x000000000000285d <+108>: retq 0x000000000000285e <+109>: callq 0x2290 <__stack_chk_fail@plt>主干部分,首先讀取六個數(shù)字,并將結(jié)果存儲在%rsp所指向的數(shù)組中。
0x000000000000280a <+25>: mov %rsp,%rsi 0x000000000000280d <+28>: callq 0x31f3 <read_six_numbers>之后的內(nèi)容無非是一連串的比對,只要細(xì)心就可以分析好的,C版本代碼如下:
// int rsp[6]; assert(rsp[0] >= 0); for (int ebx = 1; ebx <= 5; ++ebx) {assert(rsp[ebx] == ebx + rsp[ebx-1]) }自然可以得到一種答案:0 1 3 6 10 15
phase_3
很長很長,采用逐段分析&翻譯的方式:
# 設(shè)置金絲雀值 0x0000000000002863 <+0>: endbr64 0x0000000000002867 <+4>: sub $0x18,%rsp 0x000000000000286b <+8>: mov %fs:0x28,%rax 0x0000000000002874 <+17>: mov %rax,0x8(%rsp) 0x0000000000002879 <+22>: xor %eax,%eax # 讀取兩個int,存儲在%rsp所指向的數(shù)組中 0x000000000000287b <+24>: lea 0x4(%rsp),%rcx 0x0000000000002880 <+29>: mov %rsp,%rdx 0x0000000000002883 <+32>: lea 0x2e36(%rip),%rsi # 0x56c0 0x000000000000288a <+39>: callq 0x2350 <__isoc99_sscanf@plt> # 確保讀入的是兩個數(shù) 0x000000000000288f <+44>: cmp $0x1,%eax 0x0000000000002892 <+47>: jle 0x28af <phase_3+76> # 確保0<=rsp[0]<=7 0x0000000000002894 <+49>: mov (%rsp),%eax 0x0000000000002897 <+52>: cmp $0x7,%eax 0x000000000000289a <+55>: ja 0x2900 <phase_3+157> # explode_bomb之后的代碼,其實(shí)就是一段跳轉(zhuǎn)表實(shí)現(xiàn)的switch結(jié)構(gòu)。
0x000000000000289c <+57>: mov %eax,%eax 0x000000000000289e <+59>: lea 0x2abb(%rip),%rdx # 0x5360 0x00000000000028a5 <+66>: movslq (%rdx,%rax,4),%rax 0x00000000000028a9 <+70>: add %rdx,%rax 0x00000000000028ac <+73>: notrack jmpq *%rax(gdb) x/8wx 0x5360
0x5360: 0xffffd556 0xffffd5ac 0xffffd576 0xffffd57d
0x5370: 0xffffd584 0xffffd58b 0xffffd592 0xffffd599
不同的rsp[0]值所對應(yīng)的跳轉(zhuǎn)標(biāo)號一目了然,不再贅述。
每一種跳轉(zhuǎn)都對應(yīng)一種答案,可自行分析。
phase_4
# some initialization 0x0000000000002955 <+0>: endbr64 0x0000000000002959 <+4>: sub $0x18,%rsp 0x000000000000295d <+8>: mov %fs:0x28,%rax 0x0000000000002966 <+17>: mov %rax,0x8(%rsp) 0x000000000000296b <+22>: xor %eax,%eax # read two nums from %rdi and # save them in an array designated by %rsp 0x000000000000296d <+24>: lea 0x4(%rsp),%rcx 0x0000000000002972 <+29>: mov %rsp,%rdx 0x0000000000002975 <+32>: lea 0x2d44(%rip),%rsi # 0x56c0 0x000000000000297c <+39>: callq 0x2350 <__isoc99_sscanf@plt> # assert two nums in total 0x0000000000002981 <+44>: cmp $0x2,%eax 0x0000000000002984 <+47>: jne 0x2992 <phase_4+61> # explode_bomb# assert rsp[0]>=0 0x0000000000002986 <+49>: mov (%rsp),%eax 0x0000000000002989 <+52>: test %eax,%eax 0x000000000000298b <+54>: js 0x2992 <phase_4+61> # explode_bomb # assert rsp[0] <= 14 0x000000000000298d <+56>: cmp $0xe,%eax 0x0000000000002990 <+59>: jle 0x2997 <phase_4+66> 0x0000000000002992 <+61>: callq 0x3105 <explode_bomb> # call func4(rsp[0], 0, 14) 0x0000000000002997 <+66>: mov $0xe,%edx 0x000000000000299c <+71>: mov $0x0,%esi 0x00000000000029a1 <+76>: mov (%rsp),%edi 0x00000000000029a4 <+79>: callq 0x291f <func4> # assert func4(rsp[0], 0, 14) == 0x1b 0x00000000000029a9 <+84>: cmp $0x1b,%eax 0x00000000000029ac <+87>: jne 0x29b5 <phase_4+96>
# assert rsp[1] == 0x1b 0x00000000000029ae <+89>: cmpl $0x1b,0x4(%rsp) 0x00000000000029b3 <+94>: je 0x29ba <phase_4+101> 0x00000000000029b5 <+96>: callq 0x3105 <explode_bomb> # check stack protector and exit 0x00000000000029ba <+101>: mov 0x8(%rsp),%rax 0x00000000000029bf <+106>: xor %fs:0x28,%rax 0x00000000000029c8 <+115>: jne 0x29cf <phase_4+122> 0x00000000000029ca <+117>: add $0x18,%rsp 0x00000000000029ce <+121>: retq 0x00000000000029cf <+122>: callq 0x2290 <__stack_chk_fail@plt>
關(guān)鍵在于使得func4(rsp[0], 0, 14) == 0x1b的數(shù)字rsp[0],直接通過遍歷[0,14]并執(zhí)行print (int) func4(x, 0, 14)試出來。
(gdb) print (int) func4(9, 0, 14)
27
故答案為:9 27
phase_5
# some initialization 0x00000000000029d4 <+0>: endbr64 0x00000000000029d8 <+4>: sub $0x18,%rsp 0x00000000000029dc <+8>: mov %fs:0x28,%rax 0x00000000000029e5 <+17>: mov %rax,0x8(%rsp) 0x00000000000029ea <+22>: xor %eax,%eax # read two nums from %rdi and # save them in an array designated by %rsp 0x00000000000029ec <+24>: lea 0x4(%rsp),%rcx 0x00000000000029f1 <+29>: mov %rsp,%rdx 0x00000000000029f4 <+32>: lea 0x2cc5(%rip),%rsi # 0x56c0 0x00000000000029fb <+39>: callq 0x2350 <__isoc99_sscanf@plt> # assert two nums in total 0x0000000000002a00 <+44>: cmp $0x1,%eax 0x0000000000002a03 <+47>: jle 0x2a36 <phase_5+98> # rsp[0] = rsp[0] & 0xf (rsp in [0, 15]) 0x0000000000002a05 <+49>: mov (%rsp),%eax 0x0000000000002a08 <+52>: and $0xf,%eax 0x0000000000002a0b <+55>: mov %eax,(%rsp)# ecx = 0, edx = 0 0x0000000000002a0e <+58>: mov $0x0,%ecx 0x0000000000002a13 <+63>: mov $0x0,%edx # LOOP BEGIN # eax = rsp[0] 0x0000000000002a18 <+68>: mov (%rsp),%eax # if eax == 0xf, goto +105 0x0000000000002a1b <+71>: cmp $0xf,%eax 0x0000000000002a1e <+74>: je 0x2a3d <phase_5+105> # edx += 1 0x0000000000002a20 <+76>: add $0x1,%edx 0x0000000000002a23 <+79>: cltq # eax = array[eax] 0x0000000000002a25 <+81>: lea 0x2954(%rip),%rsi # 0x5380 <array.3500> 0x0000000000002a2c <+88>: mov (%rsi,%rax,4),%eax # rsp[0] = eax, ecx += eax 0x0000000000002a2f <+91>: mov %eax,(%rsp) 0x0000000000002a32 <+94>: add %eax,%ecx 0x0000000000002a34 <+96>: jmp 0x2a18 <phase_5+68> # LOOP END
0x0000000000002a36 <+98>: callq 0x3105 <explode_bomb> 0x0000000000002a3b <+103>: jmp 0x2a05 <phase_5+49> # assert edx == 0xf 0x0000000000002a3d <+105>: cmp $0xf,%edx 0x0000000000002a40 <+108>: jne 0x2a48 <phase_5+116> # assert rsp[1] == ecx 0x0000000000002a42 <+110>: cmp %ecx,0x4(%rsp) 0x0000000000002a46 <+114>: je 0x2a4d <phase_5+121> 0x0000000000002a48 <+116>: callq 0x3105 <explode_bomb> # check stack protector 0x0000000000002a4d <+121>: mov 0x8(%rsp),%rax 0x0000000000002a52 <+126>: xor %fs:0x28,%rax 0x0000000000002a5b <+135>: jne 0x2a62 <phase_5+142> 0x0000000000002a5d <+137>: add $0x18,%rsp 0x0000000000002a61 <+141>: retq 0x0000000000002a62 <+142>: callq 0x2290 <__stack_chk_fail@plt>
簡單理解一下,就是rsp[0]可以保證循環(huán)15次,且rsp[1]剛好等于循環(huán)結(jié)束生成的ecx的值。由于rsp[0] ∈ \in ∈[0,15],我們可以通過遍歷來尋找答案。
先把循環(huán)的一段翻譯成C語言:
rsp[0] = rsp[0] & 0xf; int ecx = 0, edx = 0; for (int eax = rsp[0]; eax != 0xf; ++edx) {eax = array[eax];ecx += eax; } assert(edx == 0xf); assert(ecx == rsp[1]);然后,我們再讀取array數(shù)組的內(nèi)容:
(gdb) x/16wd 0x5380
0x5380 <array.3500>: 10 2 14 7
0x5390 <array.3500+16>: 8 12 15 11
0x53a0 <array.3500+32>: 0 4 1 13
0x53b0 <array.3500+48>: 3 9 6 5
這樣,我們就可以在本地上進(jìn)行通過測試來確定輸入的兩個數(shù)字的值。
#include <stdio.h>int main() {int array[] = {10, 2, 14, 7, 8, 12, 15, 11, 0, 4, 1, 13, 3, 9, 6, 5};for (int rsp0 = 0; rsp0 < 15; ++rsp0) {int ecx = 0, edx = 0;for (int eax = rsp0; eax != 0xf; ++edx) {eax = array[eax];ecx += eax;}if (edx == 15) {printf("rsp[0] & 0xf = %x, rsp[1] = %d\n", rsp0, ecx);}} }
輸出的結(jié)果為:“rsp[0] & 0xf = 5, rsp[1] = 115”
所以5 115即為一個答案。
phase_6
查看匯編代碼,整整91行😟,跳轉(zhuǎn)了21次(對不起,我再也不會用goto語句了)。
把所有跳轉(zhuǎn)語句指向的地方進(jìn)行斷塊處理,一步步分析每一個模塊的功能。
第一個檢測模塊
通過追蹤,發(fā)現(xiàn)這一部分結(jié)構(gòu)為大循環(huán)套小循環(huán),功能為保證每一個數(shù)字都屬于[1,6]且兩兩不等。
相應(yīng)C代碼:
int rsp[6]; for (int ebp = 0; ebp < 6; ++ebp) {assert(1 <= rsp[ebp] && rsp[ebp] <= 6);for (int ebx = ebp + 1; ebx < 6; ++ebx) {assert(rsp[ebp] != rsp[ebx]);} }處理模塊
總共有三個部分。
part1
for (int eax = 0; eax < 6; ++eax) {rsp[eax] = 7 - rsp[eax]; }功能:將讀取的六個數(shù)字變換大小。
part2
再往后將遇到node(鏈表節(jié)點(diǎn)),故先判斷node的結(jié)構(gòu):
(gdb) x/10gx 0x9230
0x9230 <node1>: 0x000000010000021e 0x0000000000009240
0x9240 <node2>: 0x000000020000010e 0x0000000000009250
0x9250 <node3>: 0x00000003000003e2 0x0000000000009260
0x9260 <node4>: 0x000000040000020a 0x0000000000009270
0x9270 <node5>: 0x00000005000003b5 0x0000000000008120
(gdb) x/2gx 0x8120
0x8120 <node6>: 0x000000060000035b 0x0000000000000000
根據(jù)名字和內(nèi)容,推斷node的結(jié)構(gòu)為:class node: int value, node *next
node *ptrs[6]; for (int esi = 0; esi < 6; ++esi) {node *rdx = &node1;for (int eax = 1; eax < rsp[esi]; ++eax) {rdx = rdx->next;}ptrs[esi] = rdx; }
功能:將ptrs[i]指向node(rsp[i]),舉個例子,如果rsp[1]為3,那么ptrs[1]=&node3。
part3
node *rcx = ptrs[0]; for (int eax = 1; eax < 6; ++eax) {rcx = (rcx->next = ptrs[eax]); } rcx->next = NULL;功能:結(jié)合part2,將node(rsp[i])指向node(rsp[i+1]),node(rsp[5])指向空地址。舉個例子,如果讀取的六個數(shù)字第一個是3,第二個是4,那么rsp[0]=4,rsp[1]=3,則node4將指向node3。
第二個檢測模塊
for (int ebp = 0; ebp < 5; ++ebp, rbx = rbx->next) {assert(rbx->value < rbx->next->value); }功能:保證鏈表值從前往后遞增。
由于節(jié)點(diǎn)值:3>5>6>1>4>2,所以鏈表的結(jié)構(gòu)應(yīng)為:2->4->1->6->5->3->null,所以int rsp[6]={2, 4, 1, 6, 5, 3},則在調(diào)整之前int rsp[6]={5, 3, 6, 1, 2, 4}。
故答案為:5 3 6 1 2 4
👨?💻secret_phase
進(jìn)入
Never could a muggle senses the magic. It’s purely beyond their ken. Do they really think alohomora could open all the doors in the world? No way! Neither would they have any idea that I further secured it with some abracadabra. Mua ha ha ha! Just bother with these most impregnable spells ever!
根據(jù)提示以及匯編代碼中遇到的奇怪的函數(shù)名,可以判斷匯編代碼中abracadabra(a magical charm or incantation)和alohomora(阿拉霍洞開,一個用于開鎖的咒語)為進(jìn)入的關(guān)鍵。
main函數(shù)的結(jié)尾部分:
0x000000000000265b <+370>: mov %rax,%rdi0x000000000000265e <+373>: callq 0x2a67 <phase_6>0x0000000000002663 <+378>: mov %rbx,%rdi0x0000000000002666 <+381>: callq 0x3383 <phase_defused>0x000000000000266b <+386>: mov %rbx,%rdi0x000000000000266e <+389>: callq 0x2230 <free@plt>0x0000000000002673 <+394>: jmpq 0x253d <main+84>由于free為庫函數(shù),phase_6也已經(jīng)全部分析過了,二者都不含有特殊入口,因此首要懷疑對象確定為phase_defused函數(shù)。
disas phase_defused之后,果然發(fā)現(xiàn)了這些函數(shù)名:
0x55ac4dff2468 <phase_defused+229> callq 0x55ac4dff1678 <abracadabra> 0x55ac4dff248b <phase_defused+264> callq 0x55ac4dff1729 <alohomora> 0x55ac4dff24b1 <phase_defused+302> callq 0x55ac4dff1bd4 <secret_phase>而后,通過si, ni大法,可以發(fā)現(xiàn)abracadabra是必然會執(zhí)行的。
觀察abracadabra內(nèi)容,要點(diǎn)也就是:從第phase_2的輸入中按照"%d %d %d %d %d %d %s"讀取內(nèi)容,如果發(fā)現(xiàn)讀取到了最后的字符串,就將讀取到的字符串與"NothingThatHasMeaning1sEasy…"(凡是有意義的事都不會容易…)進(jìn)行比對,如果比對成功,就返回1。(也就是說正常情況下這個函數(shù)都是返回0的。)
得到返回值為1的憑證之后,我們就能進(jìn)入到alohomora中。
alohomora的功能為:從phase_4的輸入中按照"%d %d %s"的格式讀取內(nèi)容,如果發(fā)現(xiàn)讀取到了最后的字符串,就把字符串中每一個char都加2,最后與"000Gcu{FqgupvGpvgt3pvqItqypWrNkhg0"進(jìn)行比對,如果相同,就返回1。
對于"000Gcu{FqgupvGpvgt3pvqItqypWrNkhg0",將其每一個字符都減2,便得到第二處密文:“…EasyDoesntEnter1ntoGrownUpLife.”(成年人的生活里沒有容易二字。)
因此,我們分別在第二處和第四處補(bǔ)加上這兩處密碼,最后就能進(jìn)入到secret_phase中。
破譯
主體內(nèi)容就是三部分:讀取字符串并轉(zhuǎn)為整數(shù),確保1<=x<=1001且fun7(n1, x) == 4,以及最后的恭喜。
在fun7中我們遇到了新的數(shù)據(jù)類型:二叉樹。
(gdb) x/28gx 0x9150
0x9150 <n1>: 0x0000000000000024 0x0000000000009170
0x9160 <n1+16>: 0x0000000000009190 0x0000000000000000
…
0x9210 <n34>: 0x000000000000006b 0x0000000000008060
0x9220 <n34+16>: 0x0000000000008100 0x0000000000000000
觀察其中每一個元素都占用了32byte空間,且最后8個byte均為0。猜測是alignment。
第二第三個8-byte很明顯是指針,第一個8-byte包含了一個int或long(short,char…)。
發(fā)現(xiàn)部分指針(0x8080等)指向的節(jié)點(diǎn)未知,果然漏掉了部分節(jié)點(diǎn):
(gdb) x/20gx 0x8080
0x8080 <n44>: 0x0000000000000023 0x0000000000000000
0x8090 <n44+16>: 0x0000000000000000 0x0000000000000000
…
0x8100 <n48>: 0x00000000000003e9 0x0000000000000000
0x8110 <n48+16>: 0x0000000000000000 0x0000000000000000
推斷:class node: int val, node *llink, *rlink
fun7是遞歸函數(shù),內(nèi)容很少,可以直接寫出C代碼:
int fun7(node *n, int x) {if (n == NULL) {return -1;}if (n->val > x) {return 2 * fun7(n->llink, x);} else if (tree[id] < x) {return 2 * fun7(n->rlink, x) + 1;} else {return 0;} }因此我們直接本地遍歷:
int tree[] = { 0x024, 0x008, 0x032, 0x006, 0x016, 0x02d, 0x06b, 0x001, 0x007, 0x014, 0x023, 0x028, 0x02f, 0x063, 0x3e9 }; int NUM = 15;int fun7(int id, int x) {if (id >= NUM) {return -1;}if (tree[id] > x) {return 2 * fun7(2 * id + 1, x);} else if (tree[id] < x) {return 2 * fun7(2 * id + 2, x) + 1;} else {return 0;} }int main() {for (int x = 1; x <= 1001; ++x) {if (fun7(0, x) == 4) {printf("%d\n", x);}} }
輸出的結(jié)果只有:7,即為最終密碼。
完結(jié)撒花🎉🎉🎉
總結(jié)
以上是生活随笔為你收集整理的Bomblab(ICS课程回课pku)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人身三流指什么_什么是“下三流”哪三流,
- 下一篇: VS2017配置PCL1.9(win10