Skip to content

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⚓︎

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⚓︎

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!

score=100000
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⚓︎

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!