Skip to main content

Fetch the Flag CTF 2022 writeup: Treasure Trove

Written by:
Luke Watts
wordpress-sync/feature-ctf-treasure-trove

November 10, 2022

0 mins read

Thanks for playing Fetch with us! Congrats to the thousands of players who joined us for Fetch the Flag CTF. And a huge thanks to the Snykers that built, tested, and wrote up the challenges!

This year’s Fetch the Flag was a blast, as well as a lot of work! With 16 challenges and limited time, not every team was able to complete them all. For those of you that couldn’t beat the clock, we’ll take a look at how our team tackled the Treasure Trove challenge.

Walkthrough

The challenge prompt was, “the latest Treasure Trove is out, but where did I put my license key!” To get us started, there is a tag that states Reverse Engineering and a obfuscator.js file has been provided.

There is also a website linked, which contains 5x5 character inputs, a button, and some wonderful artwork that takes me back to playing Curse of Monkey Island.

wordpress-sync/blog-treasure-trove-start

Taking a look at the provided code, you’ll find a tool that will take human readable code and turn it into junk.

Next, take a look at the website. There is a swashbuckle.js endpoint which contains a method entitled validate. The code there seems to match the output that obfuscator will generate. Hmm…

Looking a little closer, we can see that the validate method is available on the window and invoked when submitting the form. Oh wow, reverse engineering validate looks like the only way… I wonder if the browser can help here?  Let’s invoke validate.toString() via a browser console. Bingpot!

1function anonymous(
2) {
3
4const segementOne = document.getElementById('seg-1');
5const segementTwo = document.getElementById('seg-2');
6const segementThree = document.getElementById('seg-3');
7const segementFour = document.getElementById('seg-4');
8const segementFive = document.getElementById('seg-5');
9
10const setError = (message) => {
11  document.getElementById('error-msg').innerText = message;
12}
13
14setError('');
15const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
16const key = segementOne.value + segementTwo.value + segementThree.value + segementFour.value + segementFive.value;
17if (key.length != 25) {
18  setError('Invalid redemtion code!')
19  return;
20}
21
22let val = 0;
23for (let i = 0; i < 25; i++) {
24  val += key.charCodeAt(i);
25  if (!characters.includes(key[i])) {
26    setError('invalid characters');
27    return;
28  }
29}
30
31if (val != 1800) {
32  setError('invalid code');
33  return;
34}
35
36fetch('/api/validatekey/' + key)
37.then(async res => {
38  const result = await res.json();
39  setError('Argg ye got it! ' + result.flag);
40});
41
42}

It appears that the code is performing the following steps:

  1. Fetches the values out of the HTML input fields

  2. Verifies that the characters match A-Z0-9 range

  3. Loops over the characters converting them to a charCode and adding those values together. The charCodeAt() method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index. Source

  4. Throws an error if the value does not equal 1800

  5. If the value equal 1800, the original input values will be posted to an API endpoint

The most interesting takeaway from this is that unless the value is 1800 we have an invalid code.

  1. 1800 / 25 (the total number of available characters)

  2. 72

  3.  String.fromCharCode(72)

  4. Returns H

Entering HHHHH for each input

wordpress-sync/blog-treasure-trove-hhhhh

Double bingpot!

wordpress-sync/blog-treasure-trove-solved

We managed to solve this challenge without having to engage with the obfuscator codebase. Giving us plenty of time to start on the next challenge!

Want to learn how we found all the other flags? Check out our Fetch the Flag solutions page to see how we did it.