HKCERT CTF 2022: Writeup on ★☆☆☆☆ (II) === ## 渣華道街市及熟食中心 / Farfetched (Reverse) 1. Open the webpage. It will show a prompt. 2. Randomly type something and press enter. What did you see? _____ 3. Open Developer Tool in your browser (Ctrl+Shift+I) and click on "Network" tab 4. Reload the page. Besides the prompt shows again, what did you see in the Network tab in Developer Tool? _____ 5. Click on the bottom-most request (data:;base64,Zj1wcm...), and click "Preview". 6. How many conditions did you see in the preview page of the script? _____ 7. Click on "Console" tab in the Developer Tool, type `v0` and press enter. 8. What is the condition shown in the console? _____ 9. Recall that the flag format starts with `hkcert22{` 10. What will be the 52nd character of the flag based on the first condition you found? _____ 11. Decode the rest of the conditions by yourself... <details> <summary>Check the answers</summary> <ul> <li>2. No</li> <li>4. Some network traffic started with <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) ### Challenge Summary We are given a web game. The player can navigate in the map and the goal is to access the flag. ![](https://i.imgur.com/fm00bi3.png) Notably, the game state (example given below) is encrypted by AES-ECB and is stored in the cookie. ```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}]} ``` ### Solution :::warning **Note.** Copying the cookies from the write-up would not work. The encryption keys are different when I compile the write-up and for the actual challenge. ::: #### Interacting with the browser :cookie:s If you are using Google Chrome (probably the same for the common browsers), you can press F12 to open the developer tools and switch to the "Console" tab. You can read the cookies by typing the below snippet, and execute it by pressing Enter: ```javascript document.cookie ``` To set a cookie (for example, setting the value of the `game-token` to `bar`), type the below snippet and press Enter: ```javascript document.cookie="game-token=bar" ``` That's it! You can manipulate cookies easily from your browser. #### Encrypting messages of arbitrary length for block ciphers Block ciphers are only capable to encrypt messages of a fixed length. For instance, _Advanced Encryption Standard (AES)_ can only encrypt messages of 16 bytes. To encrypt messages of variable length, the messages are first padded such that its length is a multiple of the block size. After that, a mode of operation is picked. ##### The electronic codebook (ECB) mode of operation ![](https://i.imgur.com/1b8hc9Z.png) We are using ECB in this challenge. As illustrated above, the message blocks are encrypted independently. Sounds intuitive? Let's use a cookie as an example. It is encrypted in AES, which the block size is 16 bytes (equivalent to 32 hex characters): ```plaintext fff21bf4a7d27a027502b1e1a253b35f071efbc3642eea3269d634e6c984feebf89e4736ab5ba5b4ef81b78a57a889ba4cf06024a9c302947c9a620592c23a76476e8424c54f1f47216f45d903c1baa4d2bd6f06d268a81b9d30326c80f521249fdba79cf386395248f82a0236c0771ae4210d738aa474035eda8131cc3f384ac551a93538d21903eb1b717741df7e1b7ac7350304f0d7f6db588f809cf319706f0a09ededf7547fab175c8e132a832c878303dd6064ba98361cf4f9784a0699 ``` corresponds to the state ```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}]} ``` Let `_` be the padding character (ASCII value `0x0b`) according to PKCS5. These are the relations extracted between the plaintext and ciphertext blocks: | Plaintext Block | Ciphertext Block | | ------------------ | ---------------------------------- | | `{"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` | #### Casting the cut-and-paste attack There is an interesting property for electronic codebook (ECB). Since the blocks are independently encrypted, no one would be able to identify if we swap two blocks. For instance, this is a message-ciphertext pair that we can forge (given the above relations): ```plaintext Message: {"username":"mys{"username":"mys Ciphertext: fff21bf4a7d27a027502b1e1a253b35f fff21bf4a7d27a027502b1e1a253b35f ``` This seemed to be useless, but we will use this idea to solve the challenge. There are many ways to get the flag. My approach is to fill my inventory with keys. The `inventory` in the state is an array of item IDs. For instance, when there is two keys (ID = 0) in the inventory, the state would be `{...,"inventory":[0,0],...}`. if we register an account called `mys0,0,0,0,0,0,0,0 tiz_`, then we have the corresponding token: ```plaintext fff21bf4a7d27a027502b1e1a253b35ffb2333bf9573b1d240840aefb4f78b1e071efbc3642eea3269d634e6c984feebf89e4736ab5ba5b4ef81b78a57a889ba4cf06024a9c302947c9a620592c23a76476e8424c54f1f47216f45d903c1baa4d2bd6f06d268a81b9d30326c80f521249fdba79cf386395248f82a0236c0771ae4210d738aa474035eda8131cc3f384ac551a93538d21903eb1b717741df7e1b7ac7350304f0d7f6db588f809cf319706f0a09ededf7547fab175c8e132a832c878303dd6064ba98361cf4f9784a0699 ``` | Plaintext Block | Ciphertext Block | | ------------------ | ---------------------------------- | | `{"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` | If we move the second block between the fourth and the fifth blocks, then this is the new plaintext: ```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}]} ``` We can concatenate the ciphertext blocks and update the cookie accordingly. Now we are able to fill our inventory with eight keys, which is more than enough! :tada: ![](https://i.imgur.com/hcQQSI9.png) ### Final Words There are a lot of possible ways to solve the challenge. These are some more weird game states that we crafted: ![](https://i.imgur.com/jVO5I5s.png) ![](https://i.imgur.com/pKHE82d.png) ![](https://i.imgur.com/lfkkrXm.png) :::success **💭 More ideas?** Can you solve the challenge in a different way? Let me know! ::: ## 九龍灣綜合回收中心 / uaf (Pwn) 1. Understand the source code - User has 3 actions: - Add animal: allocate a new chunk to store an animal object, which contains the pointer of another new chunk with size < 64 bytes to store the user input animal name. Then put the animal pointer to the first meet empty zone of the animal list. - Remove animal: input the zone number, free all the chunk allocated for the animal of that zone - Report name: input the zone number, call the `speak` function pointer inside the animal of that zone, which will print the name of the animal. 2. Find out the bug - To find out the bug, you may need to know some fundamental knowledge about heap in the c program. - In short, a program has two places to keep the "buffers": stack and heap. - Stack keeps the "buffers" that the size is known before the program start. For example, local variables are used by a function. - Heap keeps the "buffers" that the size can only be decided in runtime. For example, an online shop VIP member username in which the system has no way to know how long is the username before the program start, as well as the length of the dynamic list storing these usernames because a modern system should allow admin to add/remove usernames. - The programmer has to call the `malloc(SIZE)` function which returns a pointer of a dynamic buffer (we called it `chunk`) with size `SIZE`. - Programmer has to call `free(<pointer_of_the_chunk>)` function to free the malloc chunk. - An freed chunk will temporarily move to a LIFO list called `bin`. There are several `bin`s to keep the different sizes of the freed chunk, i.e. a 0x20 size chunk will go to the "0x20" `bin`. - The first `bin` is "0x20", then "0x30", "0x40", ... - Assume the program called `malloc(0x20)`, heap will visit the `bin` of the nearest chunk size by rounding up. In this case, the chunk size of `malloc(0x20)` is 0x20+8=0x28 (chunk_size = malloc_size+8), thus heap will find if any freed chunk inside "0x30" `bin`, if yes return it directly. - The chunk size of an animal chunk is `malloc(sizeof(Animal))` = `malloc(0x18)` = 0x18 + 8 = 0x20, meanwhile the chunk size of the animal name chunk depends on user provided size => can be any size lower than 64. - The bug in this program: removing an animal will free the chunks allocated for that animal but forget to clear the pointers of the freed chunk in the animal list, i.e. user can still call remove the animal and report name function to a removed animal. It is called use after free (UAF) vulnerability. 3. Check the protection - PIE is disabled, therefore address of the `get_shell` function is fixed. 4. Exploit - If the heap returns a freed animal chunk when the program `malloc` a chunk for an animal name, the user can control the value of freed animal chunk (since no restriction on inputting the animal name). - Craft an order of adding/removing animals to achieve the case above, and overwrite the `speak` function pointer (first 8 bytes of the chunk) with the `get_shell` function. - Call the report name action on the freed animal from the previous step, which will invoke the `speak` function pointer => invoke `get_shell` => get the shell!! ## 歷史檔案館 / Back to the Past (Web) ### Solution Read the challenge README, it give you some hints that this challenge related to ==the past==. You also found that the webpage can be directory listed. ![](https://i.imgur.com/hwoqlW8.png) ![](https://i.imgur.com/q03aa6i.png) Try to go to `/.git/` and found that it is accessable ![](https://i.imgur.com/m3SbNT3.png) As `/.git/` exist, we can download the repository to local using the command below: ```bash! wget --recursive --no-parent http://chal.hkcert22.pwnable.hk:28222/.git/ ``` ![](https://i.imgur.com/zqhHeBV.png) There are serval command you can use to view the git commit history: 1. [`git log`](https://git-scm.com/docs/git-log) 2. [`git reflog`](https://git-scm.com/docs/git-reflog) We can compare the output difference of these two commands: ![](https://i.imgur.com/YPbg1Jd.png) ![](https://i.imgur.com/WxwbNe4.png) Please read [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` shows the current HEAD and its ancestry. `git reflog` doesn't traverse HEAD's ancestry at all. The reflog is an ordered list of the commits that HEAD has pointed to: it's undo history for your repo. ::: In short, we found that there is commit that is an orphan. Checkout this commit as it looks weird: ```bash git checkout 4ba5380 ``` ![](https://i.imgur.com/5qsos8g.png) Found the `flag.txt` file, view it ![](https://i.imgur.com/oLpNwZT.png) ## 照鏡環山訊號站 / Zoonn Recording (Misc) - Intended Learning Outcome - Don't watch pwn video during Zoonn meeting, or else the pwn content will be reflected on your glass (but luckily it is mirrored?) - How to approach the challenge 1. Download the video 2. Download [ffmpeg](https://ffmpeg.org/) 3. Search `ffmpeg flip video horizontally` on Google. Too difficult? [Let me Google that for you](https://www.google.com/search?q=ffmpeg+flip+video+horizontally) 4. Flip the video according to the tutorial you found on Google 5. View the flipped video the view the flag - Too difficult again? Maybe you can just capture screen and flip the image with image editor...