NahamCon CTF Write-Up Part 3: Really Powerful Gnomes

The Hurricane Labs team enjoys participating in CTFs as a fun and different way to keep our hacking skills sharp. The final part of this blog series takes you on a little text-based adventure game so you can learn new tips, tricks, and techniques for future events. Enjoy!

Overview

To read parts 1 and 2, click here or here.

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!

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:

Welcome to Newlandia!!!
Unfortunately, the villagers have become attacked by gnomes.

They need YOU to help take back their land!



What would you like to do?
Gold: 100
Weapon level: 0

1. Defeat the gnomes (level 10)
2. Fight a dragon (level 7)
3. Raid the cyclops (level 5)
4. Plunder the pirates (level 3)
5. Go on a journey (level 1)
6. Browse the shop
7. End journey

>

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.

> 1

You might want to get a higher level weapon before you attempt this journey.
You have a 0% chance of it succeeding!
Would you like to continue? (Type 'y' to continue): 

0% chance of success? I'll take those odds. I'm feeling lucky today.

YOU WERE NO MATCH FOR THE GNOMES!!!!!!
Game Over.

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:

> 6

Gold: 100
Weapon level: 0

1. sword (100 gold) (level 1)
2. bow (1000 gold) (level 3)
3. axe (2000 gold) (level 5)
4. missle launcher (10000 gold) (level 7)
5. tank (100000 gold) (level 10)

What would you like to buy? (press 0 to exit the shop):

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:

What would you like to buy? (press 0 to exit the shop): 1

sword bought

What would you like to do?
Gold: 0
Weapon level: 1

1. Defeat the gnomes (level 10)
2. Fight a dragon (level 7)
3. Raid the cyclops (level 5)
4. Plunder the pirates (level 3)
5. Go on a journey (level 1)
6. Browse the shop
7. End journey

> 

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):

> 5

Congrats you have returned from your journey with 8 gold

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:

  1. If we have enough gold to buy a weapon upgrade, buy it
  2. Gather more gold by doing the best quest available (based on our current weapon level)
  3. Repeat

Below is my implementation of this in Python:

#!/usr/bin/env python3
import pwn

r = pwn.remote('jh2i.com', 50031) # connect to the server

prices = [100, 1000, 2000, 10000, 100000]  # store a list of weapon prices
idx = 0  # price index. we use this to check weapon prices (above), and for choosing menu options

while idx < len(prices):
	r.recvuntil('What would you like to do?\n')
	gold = int(r.recvline().split(b':')[1])  # parse our current gold
	r.recvuntil('\n> ')

	# check if we have enough gold to buy the next weapon upgrade
	if gold >= prices[idx]:
		print(f'upgrading to weapon {idx+1}/{len(prices)}')
		r.send('6\n') # browse the shop
		r.recvuntil('What would you like to buy? (press 0 to exit the shop): ')
		r.send(f'{idx+1}\n') # select weapon upgrade option, increasing from 1
		idx += 1

		# consume the next menu response so we can immediately do the next quest
		r.recvuntil('\n> ')

	# do the best quest available
	r.send(f'{6-idx}\n') # select best quest option, decreasing from 5

# we have defeated the gnomes. print the flag
print(r.recv().decode())

The 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.

Shop Upgrades:
1. sword (100 gold) (level 1)
2. bow (1000 gold) (level 3)
3. axe (2000 gold) (level 5)
4. missile launcher (10000 gold) (level 7)
5. tank (100000 gold) (level 10)

Quests:
1. Defeat the gnomes (level 10)
2. Fight a dragon (level 7)
3. Raid the cyclops (level 5)
4. Plunder the pirates (level 3)
5. Go on a journey (level 1)

If we run the script to completion, it will eventually defeat the gnomes and print the flag:

YAYYY!!! You have defeated the gnomes and saved the villagers
Here's your flag: flag{it_was_in_fact_you_that_was_really_powerful}

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. 

Conclusion

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!



Close off Canvas Menu