Holiday Hack Challenge 2015 Complete Writeup
tl; dr
For the Holidays SANS released a five part hacking challenged called the Holiday Hack Challenge that involved solving forensics problems, source code auditing, exploit development, and “attribution” (spoiler, it wasn’t China or North Korea). This is a writeup describing the solutions I came up with to solve the challenges.
Part 1
Part 1 is provided by talking with Josh in game as a download link to a PCAP file. Opening the file in Wireshark shows 802.11 broadcast traffic and a significant amount of DNS requests. The DNS requests follow the pattern of first requesting a TXT record for cmd.sg1.atnascorp.com. This request is the gnome polling the command & control (C2) for new commands to execute. The responses come in the form of TXT record containing base64 encoded data. This data decoded is one of the commands described below. If the command requires a response one or more DNS requests are made for reply.sg1.atnascorp.com and the TXT record is the base64 encoded response.
To solve this part the beginning of a semi-functional python script is provided that utilizes scapy to process the PCAP file. Running the script as is is enough to extract the image after a small amount of manual massaging is done via hex/text editor or the script can be edited to just filter out the packets required. I used the former method due to simplicity. Simply identify the start of the JPEG header and remove all bytes up to it. Trailing bytes will not effect the image and it can now be opened in any image viewer. To easily view all commands sent the conditional that looks for the “FILE” command can simply be removed and just print out all decoded data.
The commands sent across the C2 channel are:
Commands
- EXEC: executes a command and replies back with the output
- FILE: replies back with the contents of the file specified
- NONE: no operation
Responses
- START_STATE: denotes all replies, until a STOP_STATE is received, make up the reply of the command
- STOP_STATE: denotes the end of a multi-reply response
The image recovered from the packet capture of the communication between the gnome and C2 is a recon photo taken by the gnome of the house.
Part 2
Talking with Jessica in game provides the beginning to the next step of the challenge. She gives a download link that contains a binary blob of the extracted firmware that she dumped from the gnome. Running binwalk on this provides the following entries:
DECIMAL HEXADECIMAL DESCRIPTION
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -
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
The PEM certificate isn’t terribly interesting or useful and is probably just used for verification of the image. The ELF binary as well is simply just used for loading. The real interesting part is the SquashFS file system that’s embedded in here. It can be carved out using the following dd command.
$ dd if=giyh-firmware-dump.bin skip=168803 bs=1 of=out.squashfs
Then run unsquashfs on it to extract the contents of the filesystem. Digging through here provides the answers to this part.
The operating system running on the gnome is a modified version of openwrt running on an ARM Cortex A9 (specific version identified later from emails) which can be identified by running file on any of the binaries on the system. The framework used to build the web interface running on the gnome is express.js running on Node.js which can all be found in /www.
The database used to back this application is MongoDB. The files located in /opt/mongodb can be copied over to the database directory of a freshly installed mongodb where the normal command line tools can be used to explore the collections and documents. Doing this will reveal a clear text password for the “admin” user of “SittingOnAShelf”.
Part 3
Part 3 requires some basic recon to identify all of the C2 servers for the gnomes. The first can be identified by looking in the /etc/hosts file on the firmware dump but there is a simpler intended way that will lead you to all of them. The game provides a fairly large clue to use Shodan (https://shodan.io) to search for them. Simply searching for “SuperGnome” should lead you to all five of the servers.
SG-01
- IP Address: 52.2.229.189
- Location: Ashburn, United States
SG-02
- IP Address: 52.34.3.80
- Location: Boardman, United States
SG-03
- IP Address: 52.64.191.71
- Location: Sydney, Australia
SG-04
- IP Address: 52.192.152.132
- Location: Tokyo, Japan
SG-05
- IP Address: 54.233.105.81
- Location: Brazil
Part 4
Part 4 was the bulk of the work, this included auditing the code for vulnerabilities and exploiting the 5 SuperGnomes to get access to the files stored on them. All of these vulnerabilities occured in the /www/routes/index.js file with the exception of Super Gnome 5 which was a stand alone executable located at /usr/bin/sgstatd.
SG-01
This was a gimme. Once the database from the dumped firmware is loaded into a fresh mongodb instace you can simply find the username and password in the users collection and use that to login. This provides access to download the gnome.conf and other relevant files.
SG-02
The vulnerability in this Super Gnome was a local file inclusion (LFI) that utilized unchecked directory traversal but required two parts. The camera viewer attempts to verify that the file being served up is a png, but the code has a fatal flaw in that it checks the presence of “.png” in the path rather than verifying the “.png” comes at the end, relying on file magic, or verifying that that the directory storing the file is a safe location. If “.png” isn’t in the path it appends “.png” to the end to avoid potentially loading a dangerous file.
To bypass this use the settings uploader which allows the creation of a user controlled directory on the filesystem.
The path generated will end up being “/gnome/www/public/upload/<random string>/<user controlled name>”. The code strips off the intended file name to create the directory named the randomly generated string to store the user controlled file in. By using the name “.png/<anything>” it will create a directory with the name “.png” in it and this path can now be used with the directory traversal and LFI in the camera viewer to read arbitrary files. The actual solution used to pull the gnome.conf was http://52.34.3.80/cam?camera=../upload/iWBZvOpF/.png/../../../../files/gnome.conf.
SG-03
Just like SQL injection is a common attack vector for SQL databases passing unsanitized user input into to a NoSQL database can result in NoSQL injection.
Despite the application intending to accept form data the application will also accept JSON if the content-type is set to “application/json” rather than “application/x-www-form-urlencoded”. This allows an attacker to send JSON data, which has special meaning to the database, directly into the mongodb query as shown above. Opening the JavaScript console in the Chrome developer tools use the following code to execute an AJAX request to the server that bypasses the password check by checking if the password field in the user document is not equal to an empty string rather than the actual password, which presumably it is not.
When the response comes back it will set a cookie with the session information and the page can be reloaded resulting in being logged in. It’s now possible to browse to the file viewer and download the gnome.conf.
SG-04
One of the features of the file upload functionality is the ability to add post processing for images. The intended way of doing this is selecting one of the pre-defined values from the dropdown. However, the values in the dropdown are simply JavaScript code that calls the postproc function with pre-set arguments. SSJS (Server Side JavaScript) injection can be used here for remote code execution.
Exploiting the post processing functionality is pretty straight forward. The application takes user input and passes it directly into an eval call. It wraps the code in a pair of parens, presumably to cause a syntax error and avoid code injection from simply returning data to the attacker. This can be bypassed by turning your injection into a closure and using the parens. Simply change the of one of the elements in the dropdown to the following code:
Submit the upload with any file selecting the modified post processing option. This uses the two parens that are prepended and appended to the string to turn this into an anonymous function that creates a new “Buffer” object, synchronously reads in a file, and returns the base64 encoded value. Base64 encoding wasn’t necessary to pull down the gnome.conf but was used to retrieve the pcap and image to deal with binary data encoding.
SG-05
SG-05 is running an additional service to the web application listening on port 4242 called sgstatd. The binary can be found on the original firmware dump at /usr/bin/sgstatd and is an x86 executable, rather than arm, unlike the rest of them. This can either be reverse engineered or the source code can be found on any of the Super Gnomes in the files list in the sgnet.zip archive. Looking through the code the vulnerability sticks out pretty quickly as a stack based overflow.
The code allocates a 100 byte buffer then reads 200 bytes and stores them in it.
The first part of solving this challenge is figuring out exactly how to get to the vulnerable code path. Digging through the source a bit child_main is the function called after the connection is established. It presents the user with the three menu options. However, there’s a hidden fourth one available that matches when the input is X. Entering this calls the sgstatd function where the vulnerability exists.
There is some inline assembly at the beginning and end of the function here that implements a hand rolled stack canary. This is a known value that is placed right before the saved return address right under the current stack frame so that if a stack buffer is overflowed and control flow hijack is attempted the program can check itself to ensure that it’s stack’s integrity is still in tact. However, this canary implementation uses a static value rather than a random one allowing an attacker to simply replace the value on the stack with the same one that was there before defeating the canary check.
The first thing to check is whether the stack is executable (which it is) to determine whether shellcode can simply be placed on it and direct execution to it.
$ greadelf -a sgstatd | grep -i stack
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
The stack is marked RWE so as long as we can return onto the stack reliably we can execute the code on it. It’s suggested that ASLR is enabled which means that it’s not possible to just hard code the top of the stack as the return address. Luckily the canary here has a special meaning. The machine code “FF E4” translates to the instruction “jmp esp” which jumps to the top of the stack. Replacing the return address with the address 0x0804936b will cause execution to jump to the top of the stack when the function returns.
The source specifically mentions randomizing the socket descriptor to make fd reuse more difficult but there was no egress rules on the firewall so standard reverse shell payloads worked fine. This was easily generated with msfvnom.
With all this information it’s time to jump into the debugger. To make this a little easier it’s helpful to nop out the alarm that runs on the child interupting after 16 seconds and nop out the dropping of the privs to avoid having to run the process as root. Using gdb also requires a few lines of configuration to allow it to follow and be able to switch back and forth between processes that fork.
set follow-fork-mode child
set detach-on-fork off
set follow-exec-mode new
Open the executable in gdb and set a break point on sgnet_readn using “break * 0x080493aa”. This will break right before the data is read from the socket and allow looking at the stack’s state before and after to verify everything is in the correct places and get the correct offset. Inspecting memory with “x/30x $esp” should give a view of the stack down to the saved return address. Single step with “si” to jump into the read call then jump out with “n”. Some simple math and ruby later you have a full working payload that will enter the secret menu, overflow the buffer, replace the canary with the same value, overwrite the return address with the address of the “jmp esp” gadget, place the shellcode at the top of the stack frame that the vulnerable function will return to, and pad out the payload with the remaining bytes to reach 200.
This is ghetto delivered with ncat by using:
cat payload — | ncat 54.233.105.81 4242
The files can then be exfiltrated by setting up a second netcat lister on a different port that redirects output to a file and redirecting the output of cat into a second netcat session back to the host at that new listener.
Part 5
Part 5 mainly consisted of rummaging through the PCAP files stolen from the Super Gnome servers and recovering the 6th image that is taken by the gnome in the bosses office.
By selecting on a packet and following the TCP stream the packets clearly show email traffic from c@atnascorp.com to other parties.
Recovering the image is a fairly straight forward process once you have access to an image manipulation library with a friendly API and an understanding of why the image is corrupted. The “bug” in the software that causes the images to be corrupted is due to the handling of images from gnomes that have the same name. All of the images have their individual pixels xored together producing the staticy image. An important property of the xor operation is that if n values are xored together they produce a new value. This value xored against n-1 of the original values will produce the value of whichever was missing. After the factory images and the resulting image of the xor have been stolen from the Super Gnomes simply write a script which iterates over the pixels xoring the pixels from the five factory images and the resulting one which outputs the sixth.
One note to point out here is that typically pixel in PNG files are composed of 32bit values (8 bits per color channel) representing the RGBA color space. The A here stands for the alpha channel which controls opacity. It’s likely that all of the values in the six images (five factory and result) have the value 255 (100% opacity) and xoring the same value an even number of times will result in the value becoming zero. If you don’t xor the individual channels it may be required to bitwise or the result of the xor with 0x000000ff to set the alpha back to 255 like the solution below.
Digging through the emails you discover the plot is to produce two million gnomes to sell to unsuspecting victims that will bring them into their homes and move them around the house. These gnomes secretly have digital cameras and wifi access hidden in them that are used to secretly perform recon of the houses and identify high value items for theft.
On Christmas eve contracted burglars will be provided instructions and routes through homes to all of the valuables. They will pretend to be Santa and rob the houses of all the pre-identified targets ruining Christmas for everyone effected after it to complete what the Grinch had started but at a larger scale. The villain behind the plot is Cindy Lou Who.