In the second installment of our NahamCon CTF series, we’re going to look at a slightly trickier 100-point CTF challenge involving some dynamic decoding. If you’d like to read part 1 first, please click here.
This challenge is titled “Rotten” and has the following description:
Ick, this salad doesn’t taste too good!
Connect with: nc jh2i.com 50034
Given a hostname and port, we can connect to it with Netcat:
The server sends us a message and instructs us to send it back as-is. After a simple copy/paste of the output, the server accepts it and responds with a new and strange message:
The characters don’t make sense, but word gaps are identical between the first and second messages. At first glance, you might suspect that this is a Caesar shift cipher. We can confirm this with a tool like CyberChef to advance each letter of the message forward or backward through the alphabet by a fixed amount.
In CyberChef, the ROT13 recipe can perform this operation. Doing so reveals that the new message is a Caesar-shifted version of the first (in this case, forward by 6 letters). If we send the decoded message back to the server, we get another encoded message in a slightly different format:
The last part of the message has changed, and apparently so has the cipher shift distance. This suggests that the shift distance might be different with each new message. How can we predict the distance without guessing, though? Since some of the word gaps are still consistent with previous messages, it might be safe to assume that all messages begin with the same phrase: “send back this line exactly”. Let’s confirm this.
Comparing the first letters of the old and new messages (“q” and “s”), the alphabetic distance between them is -2. The negative sign indicates a backwards direction. We’ll discover that the new message has been shifted by exactly this amount! It reads:
Nice! The server revealed a character of the flag and its numeric position within it. If we repeat this decoding process, we’ll discover more characters at different positions in the flag (and some filler messages containing no information).
It looks like we’ll have to repeat these steps until all of the flag characters are found. This might take a while, so let’s script it. Here’s an outline of the algorithm we used above:
- Read the encoded message
- Identify its Caesar shift distance
- Decode the message using this distance
- If the message contains part of the flag, store the revealed character and position
- Send the decoded message back to the server
- Go to step 1 if any character positions are still unknown
- Once all characters have been found, print them in order to get the flag
Here’s my implementation of this algorithm in Python:
Let’s step through this script. There are some initial statements to connect to the server and define some variables for later.
We then enter a loop which reads the encoded message using the pwntools library. Since we know that every decoded message starts with “s” (from “send back this line exactly”), we can find the distance between it and the first encoded letter by comparing their ASCII values:
ord('s') - ord(encoded). We can then construct a translation table for these messages by shifting the regular alphabet by this distance (see this article and maketrans Python docs). The entire message is then decoded using this table:
Next, we use regex to check if the decoded message reveals a part of the flag (a single character and position) or whether it contains a filler message.
If it contains the data we’re looking for, we extract the relevant fields from the regex capture groups (
(.?)) and store them in a dictionary. This dictionary associates the character positions to their character values. It’s a handy data structure here because it allows us to easily check how many unique characters of the flag have already been found and for later displaying these characters in the correct order.
We repeat this process until the dictionary contains every position and character of the flag–but how do we know when we’re done?
We don’t initially know how many total letters we’re looking for. However, from the CTF instructions, we know that flags are always in the form
} character always signifies the end of the flag, so we can just wait for this character to appear and then use its revealed position to determine the total flag length. Until this character is found (while
None), we continue looping, gathering, and adding new characters to the dictionary.
Once the dictionary has enough elements in it to match the flag, it’s time to reconstruct the characters in order. Sorting the characters (values) based on their revealed positions (keys) will display the characters in the proper order:
Running the script, it will process a few hundred messages and eventually reveal the flag:
Stay tuned for part 3!
Nice! 100 points in the bag. This was a fun little coding exercise, and this solution is just one way of approaching it. I’m sure there are many others. Find out what works best for you!
To read the third and final post in this series, please click here. In the next installment, we will use our coding skills to save some villagers from evil gnomes. Stay tuned!