日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Bomblab(ICS课程回课pku)

發布時間:2024/3/13 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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.


隱藏劇情🎭

邪惡的內心獨白🧛?♂?
Wow, they got it! But isn’t something… missing? Perhaps something they overlooked? Mua ha ha ha ha!

面對強敵的驚愕💦
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命令:

  • 更改字符串:set {char[20]} $rdi = “new string.”
  • 改變條件碼,控制跳轉方向:set $eflags = ($eflags & ~0b1000000),即設置ZF為0
  • 設置不同輸入,計算函數返回值:print (int) fun7(0x55fa6e688150, 7)
  • 顯示內存中一段區塊的內容:x/32gx 0x55fa6e688150
  • eflags中一些比較重要的標志位:

    11109876543210
    OF---SFZF---PF-CF

    閱讀匯編代碼的技巧:

  • 根據匯編,寫出偽代碼,進一步寫出對應的C語言代碼。
  • 根據循環結構和jump的終點,將代碼分出模塊,分而治之。
  • 可以邊在gdb中運行,邊確定函數的功能(避免頭暈眼花)。
  • 也可以把匯編代碼內容復制到有著色功能的編輯器中。

  • 從一堆限制條件中確定答案的技巧:

  • 當然可以試圖去理解函數的功能,從而確定答案。不過在已知很多限定條件時,也可以利用枚舉。
  • 可以利用gdb自帶的call命令,比如print (int) fun7(0x55fa6e688150, 7),就可以輸出fun7以給定值作為參數的返回值(要先確保目標函數是否會有side effects)。
  • 可以利用C語言的asm關鍵字或者直接翻譯出C代碼,直接在本地上循環運行相應代碼段。

  • ?Cautions

  • 不可以修改程序計數器(包括在set $pc, set $rip, jump *),此類操作會直接觸發炸彈。
  • 不要以為在explode_bomb上設置斷點后炸彈就不會爆炸,只要explode_bomb被觸發,處罰會立刻上報。正確做法是在所有call explode_bomb的指令上設置斷點。

  • 🛵開始闖關!

    Ready

    gdb的一些常用操作:

  • 召出圖形化界面,gdb -tui
  • 打開文件,file <executable file name>
  • start, run, continue, break, si, ni, display, info registers, kill, quit…
  • 刷新界面,鍵盤按ctrl+L

  • phase_1

    最開始在無法確定密碼是什么時,可以先提前輸入很長一段字符串作為密碼,預留好空間,之后再改。

    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>

    根據strings_not_equal函數名可以推斷,此段代碼的功能就是比較%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.”

    我所見過的事物,你們人類絕對無法置信,我目睹了戰艦在獵戶星座的端沿起火燃燒,我看著C射線在唐懷瑟之門附近的黑暗中閃耀,所有這些時刻,終將隨時間消逝,一如眼淚消失在雨中。 --《銀翼殺手》


    phase_2

    觀察頭部和尾部的功能:開棧,設置金絲雀值以及檢測棧是否被破壞。

    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>

    主干部分,首先讀取六個數字,并將結果存儲在%rsp所指向的數組中。

    0x000000000000280a <+25>: mov %rsp,%rsi 0x000000000000280d <+28>: callq 0x31f3 <read_six_numbers>

    之后的內容無非是一連串的比對,只要細心就可以分析好的,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

    很長很長,采用逐段分析&翻譯的方式:

    # 設置金絲雀值 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所指向的數組中 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> # 確保讀入的是兩個數 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

    之后的代碼,其實就是一段跳轉表實現的switch結構。

    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]值所對應的跳轉標號一目了然,不再贅述。

    每一種跳轉都對應一種答案,可自行分析。


    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>

    關鍵在于使得func4(rsp[0], 0, 14) == 0x1b的數字rsp[0],直接通過遍歷[0,14]并執行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]可以保證循環15次,且rsp[1]剛好等于循環結束生成的ecx的值。由于rsp[0] ∈ \in [0,15],我們可以通過遍歷來尋找答案。


    先把循環的一段翻譯成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數組的內容:

    (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

    這樣,我們就可以在本地上進行通過測試來確定輸入的兩個數字的值。


    #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);}} }

    輸出的結果為:“rsp[0] & 0xf = 5, rsp[1] = 115”

    所以5 115即為一個答案。


    phase_6

    查看匯編代碼,整整91行😟,跳轉了21次(對不起,我再也不會用goto語句了)。

    把所有跳轉語句指向的地方進行斷塊處理,一步步分析每一個模塊的功能。

    第一個檢測模塊

    通過追蹤,發現這一部分結構為大循環套小循環,功能為保證每一個數字都屬于[1,6]且兩兩不等。

    相應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]; }

    功能:將讀取的六個數字變換大小。


    part2

    再往后將遇到node(鏈表節點),故先判斷node的結構:

    (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

    根據名字和內容,推斷node的結構為: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;

    功能:結合part2,將node(rsp[i])指向node(rsp[i+1]),node(rsp[5])指向空地址。舉個例子,如果讀取的六個數字第一個是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); }

    功能:保證鏈表值從前往后遞增。

    由于節點值:3>5>6>1>4>2,所以鏈表的結構應為:2->4->1->6->5->3->null,所以int rsp[6]={2, 4, 1, 6, 5, 3},則在調整之前int rsp[6]={5, 3, 6, 1, 2, 4}。

    故答案為:5 3 6 1 2 4


    👨?💻secret_phase

    進入

    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!

    根據提示以及匯編代碼中遇到的奇怪的函數名,可以判斷匯編代碼中abracadabra(a magical charm or incantation)和alohomora(阿拉霍洞開,一個用于開鎖的咒語)為進入的關鍵。


    main函數的結尾部分:

    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為庫函數,phase_6也已經全部分析過了,二者都不含有特殊入口,因此首要懷疑對象確定為phase_defused函數。


    disas phase_defused之后,果然發現了這些函數名:

    0x55ac4dff2468 <phase_defused+229> callq 0x55ac4dff1678 <abracadabra> 0x55ac4dff248b <phase_defused+264> callq 0x55ac4dff1729 <alohomora> 0x55ac4dff24b1 <phase_defused+302> callq 0x55ac4dff1bd4 <secret_phase>

    而后,通過si, ni大法,可以發現abracadabra是必然會執行的。

    觀察abracadabra內容,要點也就是:從第phase_2的輸入中按照"%d %d %d %d %d %d %s"讀取內容,如果發現讀取到了最后的字符串,就將讀取到的字符串與"NothingThatHasMeaning1sEasy…"(凡是有意義的事都不會容易…)進行比對,如果比對成功,就返回1。(也就是說正常情況下這個函數都是返回0的。)

    得到返回值為1的憑證之后,我們就能進入到alohomora中。


    alohomora的功能為:從phase_4的輸入中按照"%d %d %s"的格式讀取內容,如果發現讀取到了最后的字符串,就把字符串中每一個char都加2,最后與"000Gcu{FqgupvGpvgt3pvqItqypWrNkhg0"進行比對,如果相同,就返回1。

    對于"000Gcu{FqgupvGpvgt3pvqItqypWrNkhg0",將其每一個字符都減2,便得到第二處密文:“…EasyDoesntEnter1ntoGrownUpLife.”(成年人的生活里沒有容易二字。)

    因此,我們分別在第二處和第四處補加上這兩處密碼,最后就能進入到secret_phase中。


    破譯

    主體內容就是三部分:讀取字符串并轉為整數,確保1<=x<=1001且fun7(n1, x) == 4,以及最后的恭喜。

    在fun7中我們遇到了新的數據類型:二叉樹。

    (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…)。


    發現部分指針(0x8080等)指向的節點未知,果然漏掉了部分節點:

    (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是遞歸函數,內容很少,可以直接寫出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);}} }

    輸出的結果只有:7,即為最終密碼。


    完結撒花🎉🎉🎉

    總結

    以上是生活随笔為你收集整理的Bomblab(ICS课程回课pku)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。