In the third and final part of our NahamCon CTF series, we’re going to relax and play a little text-based adventure game. I enjoy little coding puzzles like this, so without further delay, let’s get into it! Also, to check out parts 1 and 2, click here or here.
This challenge is titled “Really Powerful Gnomes” and is worth 150 points. The description reads:
You must beat the boss to get the flag.
nc jh2i.com 50031
If we connect using the provided command, we’re greeted with:
It looks like we can select menu options to perform different actions in game. The apparent goal is to defeat the gnomes (option 1). Let’s give it a shot.
0% chance of success? I’ll take those odds. I’m feeling lucky today.
Perhaps fighting the gnomes this early was a bad idea. Let’s take a look at the first menu screen again. We seem to have some starting gold, a weapon level, a few level-dependent quests, and a shop we can visit. Our weapon level is currently 0, so we can’t safely do any quests yet. Let’s visit the shop instead:
There are few weapons available, and some are quite expensive. Each weapon has a level, and each of these levels also corresponds to a specific quest on the main menu. To defeat the level 10 gnomes, we’re going to need the level 10 tank, and this requires a lot of gold. Currently, we have just enough gold to buy a sword. Let’s do that:
Our gold decreased, our weapon level increased, and the game returned us to the main menu. Now we can safely go on a level 1 journey (option 5):
Now we have a source of gold! If we repeat this action, we get a random amount of gold each time (somewhere between 0 and 10). Presumably, higher level quests will give us better rewards. Now that we have a source of gold and know how to upgrade our weapons, we just need to repeatedly gather enough gold to buy upgrades until we can defeat the gnomes. This is all the information we need to script up the rest of the challenge.
Here’s a simple algorithm we can follow to reach our goal:
- If we have enough gold to buy a weapon upgrade, buy it
- Gather more gold by doing the best quest available (based on our current weapon level)
Below is my implementation of this in Python:
r.recvuntil function is part of the pwntools library and allows us to read data from the server until a certain known pattern is found. This helps us know when to expect server output like current gold, and it also helps us determine when the server will be waiting for us to send input. Sending or receiving data at the wrong time can result in both sides waiting on each other, causing the program to freeze, so it’s a good idea to be aware of this and keep the client in sync with the functions at our disposal.
The only things we need to keep to keep track of here are our current gold, our current weapon, and the price of the next weapon upgrade. The variables “gold”, “idx”, and the “prices” array are used to track this information above. An important line here is the price check
if gold >= prices[idx]. This checks if we have enough gold for the next upgrade. The array index “idx” corresponds to the “prices” array and allows us to look up this price. If our current gold equals or exceeds it, we can send the necessary commands to enter the shop, confidently buy the next weapon, and then do the next quest. Incidentally, this also ensures that we will defeat the gnomes once we have unlocked the last upgrade. If we cannot upgrade, we continue questing for gold until we can. Once we have unlocked the last upgrade (once
idx == len(prices)), we exit the loop and print the flag.
Just an extra note about the shop and quest menu options — We’re able to take advantage of how the menu options are numbered as well as their level requirements in order to always select the next shop upgrade option and best available quest. In the menus below, notice how the shop upgrade options are numbered from 1 to 5 in increasing order of weapon level. Also notice now the quest options are numbered from 1 to 5, but in decreasing order of weapon level. We can relate our price array index (which increases from 0 to 5) to these menu options with some arithmetic. For the shop upgrades, the next appropriate option will always be expressed as
idx+1. For the quest options, we use
6-idx, since the level requirements are in decreasing level order.
If we run the script to completion, it will eventually defeat the gnomes and print the flag:
Success! A word of advice for CTF challenges like this: I’d suggest running Wireshark in the background while testing. Wireshark can be a powerful debugging tool for remote scripting challenges, especially when the script execution time may be long. In my original script, I defeated the gnomes and the server replied with the flag, but I forgot to print it to the screen! However, since I had Wireshark running, I only had to go back and check the TCP stream to retrieve the flag instead of running the program again and waiting for it to complete. (Needless to say, I still fixed the script.)
A creative alternative
Crafting algorithms for challenges like this is what draws me to the CTF scripting category, and I enjoy solving them this way. However, let me share with you an interesting and completely different solution posted on ctftime.org.
While my solution took some time and algorithmic thinking, someone else discovered a trick to avoid this thinking altogether. Their solution can be viewed here, and it is quite creative. Instead of sending server commands one at time and parsing output, this person sent one big stream of commands to buy a sword, do the lowest level quest 30,000 times, then defeat the gnomes without any extra logic. Because the server instantly processes any sent commands, there is practically no delay in sending 30,000 commands at once (a measly total of 60 kilobytes). Their solution is not only shorter and faster than mine, but it also saves valuable competition time that could be spent looking at other CTF challenges.
This highlights another potential hazard of CTFs: overfocus. Although I didn’t notice the same clever trick for this challenge, seeing their solution serves as a good reminder to avoid becoming too focused on a specific technique when easier, less conventional solutions might also exist. Thankfully, though, it only takes finding one solution to get the points, so it doesn’t have to be perfect.
In this series, we looked at a few coding challenges involving ASCII and Caesar-cipher decoding, unordered data manipulation, and the automation of a text-based RPG game. After manually inspecting each of these challenges and identifying patterns in the data, we were able to construct general algorithms to solve them expressed in basic language. Finally, we translated the available information into a usable form and implemented these algorithms in code.
I hope you enjoyed reading about the challenges we presented here. I’d encourage readers and other CTF enthusiasts to explore more CTF write-ups on ctftime.org and other parts of the web. There are a wide variety of write-ups out there, and reading write-ups is by far one of the best ways to learn new tips, tricks, and techniques for future events. Good luck, and happy flag hunting!