HKCERT CTF 2022: Writeup on ★☆☆☆☆ (II) === ## 渣華道街市及熟食中心 / Farfetched (Reverse) 1. 開啟網頁。你會看到一個對話框。 2. 隨便輸入一些文字然後按 Enter。你看到甚麼? _____ 3. 開啟瀏覽器的開發人員工具 (Ctrl+Shift+I),然後點選 "Network" 分頁 4. 重新整理/刷新網頁。 除了剛才的對話框,你還在開發人員工具的 "Network" 分頁下看到甚麼? _____ 5. 點選最底的請求 (data:;base64,Zj1wcm...), 然後點 "Preview"。 6. 在預覽頁中,你能看到這段腳本裡有多少個檢查條件? _____ 7. 在開發人員工具點選 "Console" 分頁,然後輸入 `v0`,再按 Enter。 8. 在控制面版裡顯示的檢查條件是甚麼? _____ 9. 還記得旗幟格式嗎?頭 9 個字是 `hkcert22{` 10. 根據你剛看到的檢查條件,旗幟的第 52 個字應該是甚麼? _____ 11. 嘗試分析剩餘的檢查條件吧! <details> <summary>檢查答案</summary> <ul> <li>2. No</li> <li>4. 有一些以 <code>data:;base64,</code> 開頭的網頁請求</li> <li>6. 53 <li>8. <code>(v)=>{return v.charCodeAt(0)==v.charCodeAt(51)}</code></li> <li>10. h</li> </ul> </details> ## 九龍公園 / Catch-22 (Crypto) ### 題目大意 我們可以在以下網頁遊戲裡面行走及跟物件(鑰匙、門,還有旗幟)互動。目標是到達旗幟格。 ![](https://i.imgur.com/fm00bi3.png) 值得留意的是,遊戲的狀態(下例)是透過 AES-ECB 加密,並儲存在 cookies 裡面。 ```json {"username":"mystiz","x":13,"y":5,"inventory":[],"onMapItems":[{"item":0,"x":3,"y":4},{"item":0,"x":3,"y":4},{"item":1,"x":4,"y":5},{"item":1,"x":5,"y":5},{"item":1,"x":6,"y":5},{"item":0,"x":15,"y":1}]} ``` ### 題解 :::warning **注意。** 直接把題解裡面的 cookie 複製貼上是沒有用的,因為寫題解時的密鑰跟比賽時的密鑰是不一樣的。 ::: #### 查看及修改瀏覽器的 cookies 如果你是 Google Chrome(或大多數常見的瀏覽器)的用戶,你可以按 F12 來打開 DevTools。裡面有一個「主控台」(Console)頁面。要查看 Cookies 的話,你可以輸入以下的字段,然後按 Enter: ```javascript document.cookie ``` 要修改你的 Cookie(例如把 `game-token` 設成 `bar`),輸入以下的字段然後按 Enter: ```javascript document.cookie="game-token=bar" ``` 就是這樣!這樣你就可以輕鬆地在瀏覽器修改你的 Cookies。 #### 在分組密碼系統加密任意長度的內容 所有的分組密碼只提供加密特定長度的內容,例如進階加密標準(AES,一個常用的密碼學算法)只能夠加密 16 bytes 的內容。要加密任意長度的文字,我們需要先進行填充至區塊的長度(以 AES 為例,那就是 16 的倍數),然後把訊息拆做若干個區塊,最後會採用一個加密模式把每個區塊進行加密。 ##### 電子密碼本(ECB)加密模式 ![](https://i.imgur.com/1b8hc9Z.png) 這個遊戲的狀態是以 ECB 模式加密。如上圖所示,明文塊是獨立加密的。這是一個分組加密最直觀的用法。 我們用 `game-token` 來舉個例子。它是用 AES 加密的,所以它每一組都是 16 bytes(即 32 個十六進制字符)。這個 `game-token` ```plaintext fff21bf4a7d27a027502b1e1a253b35f071efbc3642eea3269d634e6c984feebf89e4736ab5ba5b4ef81b78a57a889ba4cf06024a9c302947c9a620592c23a76476e8424c54f1f47216f45d903c1baa4d2bd6f06d268a81b9d30326c80f521249fdba79cf386395248f82a0236c0771ae4210d738aa474035eda8131cc3f384ac551a93538d21903eb1b717741df7e1b7ac7350304f0d7f6db588f809cf319706f0a09ededf7547fab175c8e132a832c878303dd6064ba98361cf4f9784a0699 ``` 對應的是以下的狀態: ```json {"username":"mystiz_","x":13,"y":5,"inventory":[],"onMapItems":[{"item":0,"x":3,"y":4},{"item":1,"x":4,"y":5},{"item":1,"x":5,"y":5},{"item":1,"x":6,"y":5},{"item":0,"x":15,"y":1}]} ``` 不妨設 `_` 為填充字元(根據 PKCS5,其 ASCII 值為 `0x0b`)。以下是它的明文塊及密文塊: | 明文塊 | 密文塊 | | ------------------ | ---------------------------------- | | `{"username":"mys` | `fff21bf4a7d27a027502b1e1a253b35f` | | `tiz_","x":13,"y"` | `071efbc3642eea3269d634e6c984feeb` | | `:5,"inventory":[` | `f89e4736ab5ba5b4ef81b78a57a889ba` | | `],"onMapItems":[` | `4cf06024a9c302947c9a620592c23a76` | | `{"item":0,"x":3,` | `476e8424c54f1f47216f45d903c1baa4` | | `"y":4},{"item":1` | `d2bd6f06d268a81b9d30326c80f52124` | | `,"x":4,"y":5},{"` | `9fdba79cf386395248f82a0236c0771a` | | `item":1,"x":5,"y` | `e4210d738aa474035eda8131cc3f384a` | | `":5},{"item":1,"` | `c551a93538d21903eb1b717741df7e1b` | | `x":6,"y":5},{"it` | `7ac7350304f0d7f6db588f809cf31970` | | `em":0,"x":15,"y"` | `6f0a09ededf7547fab175c8e132a832c` | | `:1}]}___________` | `878303dd6064ba98361cf4f9784a0699` | #### 比卡超,使出拼貼攻擊! 電子密碼本(ECB)模式有個有趣的特點:因為所有區塊都是獨立加密的,我們無法得知兩個區塊是否被交換過。由上列的明文跟密文關係,我們可以偽造以下密文: ```plaintext 明文:{"username":"mys{"username":"mys 密文:fff21bf4a7d27a027502b1e1a253b35f fff21bf4a7d27a027502b1e1a253b35f ``` 雖然看起來沒甚麼用,可是我們會延伸這個想法到我們的題解裡。 這道題目有很多做法,而我們會把物品欄塞滿鑰匙。狀態裡面的 `inventory` 是一個由數字組成的陣列。如果我們的物品欄有兩把鑰匙 (ID = 0),那麼它的狀態會是 `{...,"inventory":[0,0],...}`。 以下是一個名為 `mys0,0,0,0,0,0,0,0 tiz_` 所對應的 token: ```plaintext fff21bf4a7d27a027502b1e1a253b35ffb2333bf9573b1d240840aefb4f78b1e071efbc3642eea3269d634e6c984feebf89e4736ab5ba5b4ef81b78a57a889ba4cf06024a9c302947c9a620592c23a76476e8424c54f1f47216f45d903c1baa4d2bd6f06d268a81b9d30326c80f521249fdba79cf386395248f82a0236c0771ae4210d738aa474035eda8131cc3f384ac551a93538d21903eb1b717741df7e1b7ac7350304f0d7f6db588f809cf319706f0a09ededf7547fab175c8e132a832c878303dd6064ba98361cf4f9784a0699 ``` | 明文塊 | 密文塊 | | ------------------ | ---------------------------------- | | `{"username":"mys` | `fff21bf4a7d27a027502b1e1a253b35f` | | `0,0,0,0,0,0,0,0 ` | `fb2333bf9573b1d240840aefb4f78b1e` | | `tiz_","x":13,"y"` | `071efbc3642eea3269d634e6c984feeb` | | `:5,"inventory":[` | `f89e4736ab5ba5b4ef81b78a57a889ba` | | `],"onMapItems":[` | `4cf06024a9c302947c9a620592c23a76` | | `{"item":0,"x":3,` | `476e8424c54f1f47216f45d903c1baa4` | | `"y":4},{"item":1` | `d2bd6f06d268a81b9d30326c80f52124` | | `,"x":4,"y":5},{"` | `9fdba79cf386395248f82a0236c0771a` | | `item":1,"x":5,"y` | `e4210d738aa474035eda8131cc3f384a` | | `":5},{"item":1,"` | `c551a93538d21903eb1b717741df7e1b` | | `x":6,"y":5},{"it` | `7ac7350304f0d7f6db588f809cf31970` | | `em":0,"x":15,"y"` | `6f0a09ededf7547fab175c8e132a832c` | | `:1}]}___________` | `878303dd6064ba98361cf4f9784a0699` | 如果我們把第二個區塊搬到第四跟第五個區塊之間,那麼以下的就是對應的明文: ```json {"username":"mystiz_","x":13,"y":5,"inventory":[0,0,0,0,0,0,0,0 ],"onMapItems":[{"item":0,"x":3,"y":4},{"item":1,"x":4,"y":5},{"item":1,"x":5,"y":5},{"item":1,"x":6,"y":5},{"item":0,"x":15,"y":1}]} ``` 我們可以把密文的各個區塊連接來偽造對應的密文。把 Cookie 更新之後,我們即可獲得八個鑰匙來開僅有的三道門。:tada: ![](https://i.imgur.com/hcQQSI9.png) ### 結語 這道題目有很多方法解決,我們弄了以下幾個奇怪的情況: ![](https://i.imgur.com/jVO5I5s.png) ![](https://i.imgur.com/pKHE82d.png) ![](https://i.imgur.com/lfkkrXm.png) :::success **💭 有更多想法嗎?** 你有其他方法來解決這道問題嗎?歡迎分享給我們知道! ::: ## 九龍灣綜合回收中心 / uaf (Pwn) 1. 理解源碼 - 使用者可以作出3種行動: - Add animal: 分配chunk來儲存一個新的動物物件,這物件會記住了一個新分配且小於64bytes大小的chunk(塊)指針用作儲存使用者輸入的動物名字。然後把新的動物物件的地址指針放到動物列表裡面第一個遇到的空的區域。 - Remove animal: 輸入區域指數,釋放該區域動物的所有分配過的chunks - Report name: 輸入區域指數,調用該區域動物的`speak`函數指針所指著的函數,會打印出動物的名字。 2. 尋找漏洞 - 如果想要找到這個程序的漏洞,你想要擁有一些c程序裡面heap相關的基本知識。 - 簡單而言,一個程序會有著兩個 "buffers"(緩衝記憶體): 分別是stack(棧)和heap(堆). - Stack保存著一些程序運行前便已知大小的"buffers"。例如函數中使用的局部變量。 - Heap保存著一些程序運行中才能知道大小的"buffers"。比如說,一個網店VIP會員名字的長度在系統運作前是不會知道的,儲存著這些會員名字的動態列表亦是相同,因為正常的網店系統可以讓管理員隨意加減會員。 - 程序員需要調用`malloc(SIZE)` 函數,返回一個指向大小為SIZE的"buffers"(稱之為`chunk`)的指針。 - 程序員需要調用`free(<pointer_of_the_chunk>)` 函數來釋放malloc分配出來的chunk。 - 已釋放的chunk會暫時性地放到一個名為`bin`的LIFO鍊錶。Heap共有多個`bin`來放置不同大小的已釋放的chunk。 i.e. 一個0x20大小的chunk釋放後會放置到"0x20"的`bin`. - 第一個`bin`是"0x20",接著的是"0x30"、"0x40"、... - 假設程式調通了`malloc(0x20)`,heap會到訪大小最近(往上取)的`bin`。在這個例子下`malloc(0x20)`的chunk大小將會是0x20+8=0x28 (chunk大小 = malloc大小 + 8), 因此heap會往上到訪"0x30" `bin`去尋找有沒有已釋放的chunk,假如有的話直接把它返回。 - 注意,動物的chunk大小是`malloc(sizeof(Animal))` = `malloc(0x18)` = 0x18 + 8 = 0x20,另一方面動物名字的chunk大小是由使用者提供的 => 可以是低於64bytes的任何大小。 - 這個程序的漏洞是: Remove animal行動只是釋放了所有分配個該動物的chunk,但沒有清除動物列表上指向它的指針,i.e. 使用者依然可以對這個以釋放的動物進行Remove animal或是Report name行動。這個漏洞亦被稱作use after free (UAF)。 3. 檢查程式的保護 - 由於PIE關閉了,`get_shell`函數地址是固定的。 4. 漏洞利用 - 假如程序在`malloc`動物名字的chunk時返回的是某個已釋放的動物chunk,使用者便可以控制該已釋放的動物chunk裡面的內容 (因為動物名字沒有輸入限制)。 - 想出一個Add animal/Remove animal行動的順序以達成上面說的情況,接著便能把`speak`函數指針(chunk的首8 bytes)覆蓋為`get_shell`函數指針。 - 對上述那該已釋放的動物的作出Report name行動,即調通`speak`函數指針 => 調用`get_shell`函數 => 獲得shell!! ## 歷史檔案館 / Back to the Past (Web) ### 題解 閱讀一次題目,它已經給你有關 ==過去== 的提示。同時你亦能知道網頁能被目錄列舉。 ![](https://i.imgur.com/hwoqlW8.png) ![](https://i.imgur.com/q03aa6i.png) 嘗試進入 `/.git/`,毫無阻攔 ![](https://i.imgur.com/m3SbNT3.png) 正因 `/.git/` 目錄存在,我們可以使用以下指令去下載整個存儲庫: ```bash! wget --recursive --no-parent http://chal.hkcert22.pwnable.hk:28222/.git/ ``` ![](https://i.imgur.com/zqhHeBV.png) 你可以使用以下的指令去查找 git 提交歷史: 1. [`git log`](https://git-scm.com/docs/git-log) 2. [`git reflog`](https://git-scm.com/docs/git-reflog) 我們可以對比這兩個指令的輸出: ![](https://i.imgur.com/YPbg1Jd.png) ![](https://i.imgur.com/WxwbNe4.png) 參閱 [What's the difference between git reflog and log?](https://stackoverflow.com/questions/17857723/whats-the-difference-between-git-reflog-and-log) :::info `git log` 會顯示目前的 HEAD 與它的祖先們。 `git reflog` 則不會歷遍 HEAD 的祖先們。 reflog 是一個有序列表,盛載 HEAD 曾指向的提交歷史:亦是儲存庫的復原歷史。 ::: 簡而言之,我們找到一個提交並非爲 HEAD 的祖先。查看此提交: ```bash git checkout 4ba5380 ``` ![](https://i.imgur.com/5qsos8g.png) 找到 `flag.txt`,把它打開 ![](https://i.imgur.com/oLpNwZT.png) ## 照鏡環山訊號站 / Zoonn Recording (Misc) - 學習要點 - 切勿在 Zoonn 會議時看 pwn 影片,否則你正在看的 pwn 內容會反映在你的眼鏡上 (不過通常係水平反轉咗) - 如何切入? 試下中路好唔好? 1. 下載附件影片 2. 下載 [ffmpeg](https://ffmpeg.org/) 3. Google 搜尋 `ffmpeg flip video horizontally` (ffmpeg 水平翻轉影片)。 太難? [幫緊你幫緊你](https://www.google.com/search?q=ffmpeg+flip+video+horizontally) 4. 根據你從 Google 找到的教學,用 ffmpeg 水平翻轉影片。 5. 觀看影片,然後你就能看到旗幟。 - 又太難?咁你就咁 cap screen 貼去小畫家水平翻轉幅圖就算啦唉⋯