# HKCERT CTF 2023: Guide to handwavy challenges (I) [TOC] ## 從零開始的新世界 / Re:Zero (Web) 0. Read the challenge carefully. Check what kind of achievement is required. 1. It appears the achievements are contradict. Are you suppose to get flag by completeing the game? If not, where should you check to modify the save? (keyword: local stroage) 2. You have the folloinwg structure found. What kind of modification is required? ![image.png](https://hackmd.io/_uploads/rkitnFd76.png) 3. Save and refresh the browser and the flag is located in console. ## 收到收到 / yes-I-know-I-know (Forensic) 1. You will need [wireshark](https://www.wireshark.org/download.html) to analyse pcapng file. 2. You can learn how to use the tool from many great online source. However, for this challenge, you should be able to get most of the information you need from this button ![image.png](https://hackmd.io/_uploads/HylP67PQp.png) 3. Explore around and see if anything interesting. Figure out what was being done by observing the traffics. 4. Identify the methodology/tool used to perform that action and search for relevant information on the internet. 5. Come up with a solution to extract information from relevant captured DNS packets. 6. Profit ## 求旗簽名 (I) / Sign me a Flag (I) (Crypto) ### Challenge Summary In this challenge, we are given a remote oracle and is allowed to perform the below operations: 1. `sign [key_client] [message]`: Signs an arbitrary message that does not contain the substring `flag`. 2. `verify [signature]`: Verifies the signature for the message `gib flag pls`. Let $k_\mathcal{C}$, $k_\mathcal{S}$ and $m$ be the client's key, the server's key and the message respectively. The signature of $m$ is given by $$ \text{Sign}(k_\mathcal{C}, m) := \text{HMAC-SHA256}(k_\mathcal{C} \oplus k_\mathcal{S}, m). $$ Note that $k_\mathcal{S}$ will not be given to the player. The goal is to recover the signature of the message `gib flag pls` while fixing $k_\mathcal{C}$ to be 16 null bytes. ### Partial Guide #### The exclusive-or (XOR) operator In this challenge, the XOR operation of two operands, $a$ and $b$ (i.e., $a \oplus b$) is given below: ```python def xor(a, b): return bytes(u^v for u, v in zip(a, b)) ``` Specifically, when the two strings are of different lengths, the output string has a length of the shorter input string. For example, $$ \begin{aligned} \texttt{000102}_{16} \oplus \texttt{12345678}_{16} &= \texttt{123554}_{16} \\ \texttt{00010203}_{16} \oplus \texttt{123456}_{16} &= \texttt{123554}_{16} \end{aligned} $$ Now assume that $k$ is a 16-byte, secret value, and assume that $$k \oplus \texttt{00}_{16} = \texttt{40}_{16}, $$ then we know the first byte of $k$ would be $\texttt{40}_{16}$. #### Leaking byte-by-byte :::warning 🤔 **Imaginations only.** In this part, we will assume that we have infinite oracle calls to the server. We unfortunately only have 10 calls in reality. ::: We can recover the secret byte-by-byte with the above behaviour. This is how we leak the first byte with 256 calls: 1. Compute $\hat{s_1} := \text{HMAC-SHA256}(\texttt{00}, \text{hello world})$ locally. 2. Call $\text{Sign}(\texttt{00}, \text{hello world})$, $\text{Sign}(\texttt{01}, \text{hello world})$, $\text{Sign}(\texttt{02}, \text{hello world})$.... 3. Eventually, there will be a $u_1$ such that $\text{Sign}(u_1, \text{hello world}) = \hat{s_1}$. In this case, the first byte of $k_\mathcal{S}$ would be $u_1$. Subsequently, if we want the second byte of $k_\mathcal{S}$, we can 1. Compute $\hat{s_2} := \text{HMAC-SHA256}(\texttt{0000}, \text{hello world})$ locally. 2. Call $\text{Sign}(u_1 \| \texttt{00}, \text{hello world})$, $\text{Sign}(u_1 \| \texttt{01}, \text{hello world})$, $\text{Sign}(u_1 \| \texttt{02}, \text{hello world})$, ... 3. Eventually, there will be a $u_2$ such that $\text{Sign}(u_1 \| u_2, \text{hello world}) = \hat{s_2}$. In this case, the second byte of $k_\mathcal{S}$ would be $u_2$. We can repeat the above process until $k_\mathcal{S}$ is recovered. We will be able to recover that with at most $16 \times 256 = 4096$ oracle calls. You can read `solve.py` that implements the algorithm. #### Optimizing the number of oracle calls We can actually use one remote call to retrieve one byte. This effectively reduces the number of oracle calls from 4096 to 16. This is how we leak the first byte of $k_\mathcal{S}$: 1. Call $\text{Sign}(\texttt{00}, \text{hello world})$ and denote it by $\hat{s_1}$. 2. Compute $\text{HMAC-SHA256}(\texttt{00}, \text{hello world})$, $\text{HMAC-SHA256}(\texttt{01}, \text{hello world})$, $\text{HMAC-SHA256}(\texttt{02}, \text{hello world})$, .... 3. Eventually, there will be a $u_1$ such that $\text{HMAC-SHA256}(u_1, \text{hello world}) = \hat{s_1}$. In this case, the first byte of $k_\mathcal{S}$ would be $u_1$. We can repeat the process 16 times to fully reveal $k_\mathcal{S}$. Try to reduce the number oracle calls to below 10 with this idea! ## ISA: 始料未及 / ISA Jump Scare (Pwn) ### Challenge Description In this challenge, we are asked to write a commentless program to read `flag.txt`. However, there is a catch: There is a checker that validates whether each line of the input program starts with a `JMP`. ### Partial Guide Let's first have a look at the below ISA code: ``` 0x400000: JMP 0x40000d; JMP 0x400000 ⬆️ 0x40001b: NOP ``` The program control (`PC`) would be `0x400000` (i.e., beginning of the code) when we run the program. In this case, the instruction to be executed would be `JMP 0x40000d`. Although `0x40000d` is _not_ a address of the beginning of an instruction, similar to a large number of [interpreters available in real life](https://devblogs.microsoft.com/oldnewthing/20220111-00/?p=106144), the interpreter simply parses the instruction starting from `PC`. In this case, `0x40000d` points at the beginning of the `JMP 0x400000` (which is intended to be a comment). The interpreter thinks that this would be the next instruction and decided to run `JMP 0x400000` afterwards. ``` 0x400000: JMP 0x40000d; JMP 0x400000 ⬆️ 0x40001b: NOP ``` In this case, we can actually "write" two instructions in one line, and the comment is could be executed during runtime. --- We will want to execute some other commands than `JMP` in the challenge. How do we achieve that? We can leverage the `JMP` instruction and jump to the address you want... Maybe into the middle of an instruction? Alas, the checker does not allow us to write comments. Fortunately the interpreter does not check whether the instructions are valid _at the beginning_. It would be fine as long as we are _not_ invalid instructions. This would be handy in our case. ## ISA: 世界與我有關? / ISA Intrusion (Reverse) ### Challenge Description In this challenge, we are asked to reverse a program written in Bauhinia ISA and get the flag. If you directly load the challenge and run, you will see the program terminated with exit code 65 (STEP COUNT EXCEEDED). You need to understand why this happens and see where the flag is. ### Partial Guide You can copy the code and load it into the "Debug Playground". There, you can add breakpoint (by clicking the line you want to stop), and step line by line after breakpoint. To see what happened, we add a breakpoint at the first line, run the program, and click step multiple time to see where the program runs. We notice the program never runs after line 5: it jumps back and forth at line 3-5. Remove the breakpoint and click continue, we can see the final states of the registers: it shows the final value of PC is `0x40003b`, which is the start of line 6. Notice this does not mean it finish running line 6, but instead means it finish running line 5 (as PC is set to the next line start after execution of the line). Let's first look at the code block it loops and never exit: ``` ADD R1, 1; LT R1, 100000; JNZ -35; ``` This is a simple loop structure, adding 1 to R1 each loop, and if R1 < 100000, it loops. As the runner can only run 131072 instruction, this makes this exceed the step count. Next, you have to find a method to optimize this: Such that the edited code does the same thing, but without running that many instructions. This act of editing the assembly code is called "patching", and it is a very useful technique for dynamic analysis in reverse engineering. We can use patching to run different things to make us understand the program more, bypass some restriction and so on. After you patch the code to bypass the STEP COUNT EXCEEDED error, you should be able to make the program runs without errors. (i.e. with exit code 0). However, there is still no output in the terminal. There are multiple options for you now: 1. static reverse the asm to understand its full logic (so you can then find out how it hides) 2. try to break at different point in the program, and inspect the memory (through the memory view). If the program hides the flag, it is likely that it is contained in the program memory at some point during the execution.