Prologue⚓︎
Upon logging into the Holiday Hack Challenge 2024, we find ourselves continuing from last year next to Santa's Surf Shack! We can click on Jingle Ringford to learn about the First Terminal Challenge and move towards him by utilizing the arrow keys on the keyboard or the WASD keys.
Story of Prologue:
Welcome back to the Geese Islands! Let's help the elves pack up to return to the North Pole.
Talk to Jingle, Angel, and Poinsettia about their challenges.
With challenges solved, we're ready to head to the North Pole! Let's hope Santa is back already to direct operations.
Holiday Hack Orientation⚓︎
Talk to Jingle Ringford on Christmas Island and get your bearings at Geese Islands
Orientation Terminal Challenge⚓︎
After clicking on the terminal, we get our first terminal challenge:
This just requires us to use our mouse and type answer
and press ENTER. This will award us with our first objective complete!
Achievement
Congratulations! You have completed the Holiday Hack Orientation challenge!
Elf Connect⚓︎
Help Angel Candysalt connect the dots in a game of connections.
After navigating East to Angel Candysalt, we find our next challenge of Elf Connect.
Angel Candysalt explains the point of the game is to match groups of four words to categories to win four rounds, but randomElf
suspiciously has a high score of 50,000 points.
Elf Connect Terminal Challenge (Silver)⚓︎
After clicking on the Elf Connect terminal we are prompted with the directions:
For the Elf Connect Terminal challenge, we can utilize burp or browser developer tools to inspect the JavaScript source code of the game to understand what is going on.
We can see the wordSets
JavaScript variable (on lines 60-65) contains all the possible words for each of the 4 rounds. We can easily see the categorizes are Christmas, Tools, Encryption, and Networking Protocols.
const wordSets = {
1: ["Tinsel", "Sleigh", "Belafonte", "Bag", "Comet", "Garland", "Jingle Bells", "Mittens", "Vixen", "Gifts", "Star", "Crosby", "White Christmas", "Prancer", "Lights", "Blitzen"],
2: ["Nmap", "burp", "Frida", "OWASP Zap", "Metasploit", "netcat", "Cycript", "Nikto", "Cobalt Strike", "wfuzz", "Wireshark", "AppMon", "apktool", "HAVOC", "Nessus", "Empire"],
3: ["AES", "WEP", "Symmetric", "WPA2", "Caesar", "RSA", "Asymmetric", "TKIP", "One-time Pad", "LEAP", "Blowfish", "hash", "hybrid", "Ottendorf", "3DES", "Scytale"],
4: ["IGMP", "TLS", "Ethernet", "SSL", "HTTP", "IPX", "PPP", "IPSec", "FTP", "SSH", "IP", "IEEE 802.11", "ARP", "SMTP", "ICMP", "DNS"]
};
And the correctSets
JavaScript variable (On lines 69-74) contains all the correct set indices of the wordSets
.
let correctSets = [
[0, 5, 10, 14], // Set 1
[1, 3, 7, 9], // Set 2
[2, 6, 11, 12], // Set 3
[4, 8, 13, 15] // Set 4
];
We can utilize Excel and sort up the items with their index to easily figure out as show in the table below.
Index | Christmas Round 1 | Tools Round 2 | Encryption Round 3 | Networking Round 4 |
---|---|---|---|---|
0 | Tinsel | Nmap | AES | IGMP |
5 | Garland | netcat | RSA | IPX |
10 | Star | Wireshark | Blowfish | IP |
14 | Lights | Nessus | 3DES | ICMP |
1 | Sleigh | burp | WEP | TLS |
3 | Bag | OWASP Zap | WPA2 | SSL |
7 | Mittens | Nikto | TKIP | IPSec |
9 | Gifts | wfuzz | LEAP | SSH |
2 | Belafonte | Frida | Symmetric | Ethernet |
6 | Jingle Bells | Cycript | Asymmetric | PPP |
11 | Crosby | AppMon | hash | IEEE 802.11 |
12 | White Christmas | apktool | hybrid | ARP |
4 | Comet | Metasploit | Caesar | HTTP |
8 | Vixen | Cobalt Strike | One-time Pad | FTP |
13 | Prancer | HAVOC | Ottendorf | SMTP |
15 | Blitzen | Empire | Scytale | DNS |
Categorized answers for each of the four games:
Round 1 - Christmas : Decorations: Tinsel, Garland, Star, Lights Holiday Icons: Sleigh, Bag, Mittens, Gifts Carols: Belafonte, Jingle Bells, Crosby, White Christmas Reindeer Team: Comet, Vixen, Prancer, Blitzen
Round 2 – Tools: Vulnerability Scanners: Nmap, netcat, Wireshark, Nessus Web Application Testing: burp, OWASP Zap, Nikto, wfuzz Mobile App Testing: Frida, Cycript, AppMon, apktool Command and Control (C2) Systems: Metasploit, Cobalt Strike, HAVOC, Empire
Round 3 – Encryption: Encryption Techniques: AES, RSA, Blowfish, 3DES Wireless Encryption Methods: WEP, WPA2, TKIP, LEAP Cryptographic Paradigms: Symmetric, Asymmetric, Hash, Hybrid Ciphers: Caeser, One-Time Pad, Ottendorf, Scytale
Game 4 – Networking Network Communication Standards: IGMP, IPX, IP, ICMP Security Protocols: TLS, SSL, IPSec, SSH Network Technologies: Ethernet, PPP, IEEE 802.11, ARP Application Layer Communication Protocols: HTTP, FTP, SMTP, DNS
We can also print out each answer for the round with the following JavaScript:
correctSets.forEach(set => {
const words = set.map(index => wordSets[round][index]).join(", ");
console.log(words);
});
Tinsel, Garland, Star, Lights
Sleigh, Bag, Mittens, Gifts
Belafonte, Jingle Bells, Crosby, White Christmas
Comet, Vixen, Prancer, Blitzen
We can also auto solve each round with the following JavaScript:
correctSets.forEach(set => {
set.forEach(index => {
const word = wordSets[round][index];
const wordBox = wordBoxes.find(box => box.text == word);
selectedBoxes.push(wordBox);
wordBox.selected = true;
if (selectedBoxes.length == 4) {
checkSelectedSet(mainScene);
}
});
});
After completing it successfully with the answers above, we obtain the Silver achievement!
Achievement
Congratulations! You have completed the [Silver] Elf Connect challenge!
Elf Connect Terminal Challenge (Gold)⚓︎
To achieve a gold achievement, we need to surpass the current high score of 50,000. Normally, playing the game awards only 100 points for each correct sequence of four words, resulting in a maximum score of 1600 - far short of our goal.
Upon reviewing the code again, it becomes apparent that a score
variable plays a crucial role in tracking progress. Initially set to 0
, this value is updated dynamically each time a sequence is completed. By inspecting the code through the browser console, we can observe the score incrementing accordingly. Moreover, by manually setting the score variable to a high value - such as 100000
- we can effectively reset it and create a new high score.
Reloading the game and opening browser developer tools (F12), we can change the JavaScript console context via the drop down box and selecting "Christmas Word Connect Game":
We can then set our score variable to over the current high score (50000) and play the game normally and our high score will trigger our new achievement!
Achievement
Congratulations! You have completed the [Gold] Elf Connect challenge!
Sponsors⚓︎
Make sure to checkout the sponsors to be eligible for the raffle - One random draw answer whose user has clicked on each of the five vendor booths (Google, Microsoft, RSAC, SANS.edu, and Holiday Hack Swag Store):
Elf Minder 9000⚓︎
Assist Poinsettia McMittens with playing a game of Elf Minder 9000.
After navigating East to Poinsettia McMittens, we find our next challenge of Elf Minder 9000.
When speaking with Poinsettia McMittens, we obtain the following hints:
Elf Minder 9000: TODO
When developing a video game—even a simple one—it's surprisingly easy to overlook an edge case in the game logic, which can lead to unexpected behavior.
Elf Minder 9000: Reusable Paths
Some levels will require you to click and rotate paths in order for your elf to collect all the crates.
Elf Minder 9000: RTD (Read the Docs)
Be sure you read the "Help" section thoroughly! In doing so, you will learn how to use the tools necessary to safely guide your elf and collect all the crates.
Elf Minder 9000 Terminal Challenge (Silver)⚓︎
If you want to play the challenge yourself, you can find it here.
For the Silver, we need to successfully complete all levels of the game by navigating from the starting point to the end goal while gathering all collectible boxes before the allotted time runs out.
Analyzing the challenge files using Developer Tools or Burp site-map, we can see following source files are loaded:
Within levels.js, the definition of the level names and entity indices of all 13 games (12 normal for silver and 1 bonus for gold):
const Levels = {
"Sandy Start": {
name: "Sandy Start",
},
"Waves and Crates": {
name: "Waves and Crates",
},
"Tidal Treasures": {
name: "Tidal Treasures",
},
"Dune Dash": {
name: "Dune Dash",
},
"Coral Cove": {
name: "Coral Cove",
},
"Shell Seekers": {
name: "Shell Seekers",
},
"Palm Grove Shuffle": {
name: "Palm Grove Shuffle",
},
"Tropical Tangle": {
name: "Tropical Tangle",
},
"Crate Caper": {
name: "Crate Caper",
},
"Shoreline Shuffle": {
name: "Shoreline Shuffle",
},
"Beachy Bounty": {
name: "Beachy Bounty",
},
"Driftwood Dunes": {
name: "Driftwood Dunes",
},
"A Real Pickle": {
name: "A Real Pickle",
},
};
Within guide.js, it contains entity definitions of start
, end
, crate
, blocker
, hazard
, steam
, portal
and spring
assigned to numbers 0 to 7.
const EntityTypesRef = {
0: 'start',
1: 'end',
2: 'crate',
3: 'blocker',
4: 'hazard',
5: 'steam',
6: 'portal',
7: 'spring',
};
Within guide.js, it contains a variable called whyCantIholdAllTheseSprings
that contains ASCII art of a man holding three springs stating WHY CAN'T I HOLD ALL THESE SPRINGS??
and triggers when the number of springs is greater than 2 on lines 367-369.
const whyCantIHoldAllTheseSprings = () => `
WHY CAN'T I HOLD ALL THESE SPRINGS??
.--======-
-- --.
-= =:
.= -
-. -
:: .:
: :.
+ -
.- :
:: .-
.. =
: :. =:
: =: -
= * -
.. : .:. .. -
. :.
=. .++: .. . . : =
+ *=. = - . *@@ .+ #
-+- .:-**--:::: = :+#%*-. .:
:- -+. - = .@@# * =
.# .* :- .: #+: :
+ += -#. - = : -
# .= = . + -. .=++- :-
= -. .++=-=+- . = - ..:---- ... ::
+ .- :=. : ..-+%+ + :- :: .. -***+++++= =
- .+ ..-:: *%:--. + - .-*: .++*++++----::. ..
.: # =%:.-:. :*#:=+%= # .***+--=::--:.. .-: :++*-::----:::....: =
# .+. -.:: ..=#=: .*#. .= :+**:.+**==+:...:::::::::-. +: :.-++. .::-... +
+ .:.: .--*... .#+-:=**- -. -+*+#+-. - .-=#. :.*:+*=.. .:- - -
Within game2.js, it contains all the entity drawing logic including URL parsing, game levels and editor mode. The parsing of the URL parameters of the level number (levelNum
), resource identifier (rid
), and editor flag (isEditor
) is handled on lines 517-520. The current values are available in the console window via urlParams.id
, urlParams.level
, and urlParams.isEditor
respectively.
const urlParams = __PARSE_URL_VARS__();
const levelNum = urlParams.level ? urlDecode(urlParams.level) : '';
const rid = urlParams.id;
const isEditor = !!urlParams.edit;
Within game2.js, the hidden editor mode (lines 522-531) via the isEditor
variable being set from the edit
parameter (line 520). A congratulations message that is shown when all the levels are completed (lines 1064 to 1069) and it hints at the existence of a bonus level called A Real Pickle
. However, in sendDataToServer()
, it will not submit a solution unless isEditor
is false - so need to turn on the editor, then back off again.
...[snip]...
if (isEditor) {
adminControls.classList.remove('hidden');
console.log('⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡');
console.log('⚡ Hey, I noticed you are in edit mode! Awesome!');
console.log('⚡ Use the tools to create your own level.');
console.log('⚡ Level data is saved to a variable called `game.entities`.');
console.log('⚡ I\'d love to check out your level--');
console.log('⚡ Email `JSON.stringify(game.entities)` to evan@counterhack.com');
console.log('⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡');
}
...[snip]...
milestoneText.innerHTML = `
<p>Congratulations! You've completed all levels!</p>
<p>That said, there is one level even our best Elf Minders have struggled to complete.</p>
<p>It's <strong>A Real Pickle</strong>, to be sure.</p>
<p>We're not even sure it's solvable with our current tools.</p>
<p>I've added <strong>A Real Pickle</strong> to your level list on the main menu.</p>
<p>Can you give it a try?</p>
`;
...[snip]...
if (!isEditor) {
const result = await fetch('/game/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
return result;
}
Within guide.js on lines 297-302, there is a bug in getSpringTarget()
where it returns the first segment when it can't find a valid target. Therefore, you can make "impossible" jumps, such as where the flag is between two rocks!
if (this.isPointInAnySegment(nextPoint) || entityHere) {
if (entityHere) return this.segments[0][0]; // fix this
return nextPoint;
} else {
return;
}
Within index.html, we find a hidden admin-controls
panel!
<div class="admin-controls hidden">
<!-- <button id="startBtn">Start</button> -->
<button id="resetBtn">Reset</button>
<button id="clearPathBtn">Clear Path</button>
<button id="clearEntitiesBtn">Clear Entities</button>
<fieldset id="tools">
<legend>Select a tool:</legend>
<input type="radio" id="path" name="tool-select" value="path" checked />
<label for="path">Path</label>
<input type="radio" id="eraser" name="tool-select" value="eraser" />
<label for="eraser">Eraser</label>
<input type="radio" id="portal" name="tool-select" value="portal" />
<label for="portal">Tunnel</label>
<input type="radio" id="start" name="tool-select" value="start" />
<label for="start">Start</label>
<input type="radio" id="end" name="tool-select" value="end" />
<label for="end">End</label>
<input type="radio" id="crate" name="tool-select" value="crate" />
<label for="crate">Crate</label>
<input type="radio" id="blocker" name="tool-select" value="blocker" />
<label for="blocker">Blocker</label>
<input type="radio" id="hazard" name="tool-select" value="hazard" />
<label for="hazard">Hazard</label>
<input type="radio" id="steam" name="tool-select" value="steam" />
<label for="steam">Steam</label>
<input type="radio" id="spring" name="tool-select" value="spring" />
<label for="spring">Spring</label>
</fieldset>
</div>
We can complete these challenges normally:
Level 1: Sandy Start | Level 2: Waves and Crates |
---|---|
Level 3: Tidal Treasures | Level 4: Dune Dash |
Level 5: Coral Cove - Need to manually click path after ladder. | Level 6: Shell Seekers |
Level 7: Palm Grove Shuffle - Need to manually click on path to redirect in middle, after ladder, and at flag. | Level 8: Tropical Tangle - Need to manually click on path to redirect in middle |
Level 9: Crate Caper - Need to manually click on first ladder after passing, then move middle ladder to each crate, then move first ladder to flag. | Level 10: Shoreline Shuffle - Need to manually click on two paths, one to 2nd box and other to spring. |
Level 11: Beachy Bounty - Need to manually click on one path square. | Level 12: Driftwood Dunes |
Achievement
Congratulations! You have completed the [Silver] Elf Minder 9000 challenge!
Once we finish them all, we get a silver medal and a popup message.
Elf Minder 9000 Terminal Challenge (Gold)⚓︎
For the hard challenge, we need to pass the final level A Real Pickle
. To pass it, we must hack the game somehow, as it’s not possible to pass it with our current tools.
We can either append the edit=1
parameter to the URL, run the adminControls.classList.remove('hidden');
within in the ELD MINDER 9000
context within the Developer Tools (F12) Console, OR edit the JavaScript code to always enable editor mode and submit data to the server every time.
We can’t remove all the obstacles and crates and draw a straight path to the finish. The game has a number of checks and will throw a error if our elf passes through a location which is supposed to be occupied by an obstacle – so all the boulders and crates must remain where they stand.
The admin tools allow us to edit the existing game elements and place new ones, but most importantly it allows us to place springs in positions which would otherwise be disallowed – for example right next to the start flag or right next to boulders.
Level 13: A Real Pickle - Make sure to draw the line at the flag first so the impossible jump works correctly. Need to manually click on one path square (ladder) and the sprint path square after obtaining the last box.
I also solved this only using JavaScript and placing 2 springs and 2 tunnels (on square edges) to bypass the software restrictions.
game.entities.push([2,1,EntityTypes.SPRING]);
game.entities.push([8,3,EntityTypes.SPRING]);
game.entities.push([8,7,EntityTypes.PORTAL]);
game.entities.push([10,9,EntityTypes.PORTAL]);
And that completes the objective with a gold medal.
Achievement
Congratulations! You have completed the [Gold] Elf Minder 9000 challenge!