Fetch the Flag CTF 2022 writeup: Treasure Trove
Luke Watts
10 de novembro de 2022
0 minutos de leituraThanks 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.
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:
Fetches the values out of the HTML input fields
Verifies that the characters match
A-Z0-9
rangeLoops over the characters converting them to a
charCode
and adding those values together. ThecharCodeAt()
method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index. SourceThrows an error if the value does not equal 1800
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.
1800 / 25 (the total number of available characters)
72
String.fromCharCode(72)
Returns H
Entering HHHHH for each input
Double bingpot!
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.