Huntress CTF 2025
Some writeups from Huntress CTF 2025. It was my first CTF event, really good fun, would definitely recommend. Although, to compete for prizes next year, I will have to take some time off in October
Maximum Sound
| Category | Author |
|---|---|
| š¶ Warmups | John Hammond |
Challenge Prompt
Dang, this track really hits the target! It sure does get loud though, headphone users be warned!!
Solution
Firstly I inpected the spectogram of the file
Nothing interesting there. However the audio of the file is very characteristic for the SSTV audio. With that in mind i decided to convert the audio to a file. To do that I used qsstv and pavucontrol tool. I run these 2 commands:
1
2
$ pactl load-module module-null-sink sink_name=virtual-cable
$ paplay -d virtual-cable maximum_sound.wav
And with correct configuration I got an image
I searched Google by this image and found out that itās a MaxiCode barcode. I upscaled the image and uploaded it to an online tool
Sandy
| Category | Author |
|---|---|
| š Malware | John Hammond |
Challenge Prompt
My friend Sandy is really into cryptocurrencies! Sheās been trying to get me into it too, so she showed me a lot of Chrome extensions I could add to manage my wallets. Once I got everything sent up, she gave me this cool program!
She says it adds better protection so my wallets canāt get messed with by hackers.
Sandy wouldnāt lie to me, would sheā¦? Sandy is the best!
This is theĀ
MalwareĀ category, and as such, includes malware.Ā Please be sure to analyze these files within an isolated virtual machine.
The password to the archive isĀ infected.
Solution
Firstly I checked the EXE file in Detect it Easy (DIE)
The binary is packed, but more interestingly there is a AutoIt script in the overlay. To retrieve it I used autoit-ripper.
1
$ C:\Python310\Scripts\autoit-ripper.exe .\SANDY.exe autoitripped
While looking trough the script, huge blob of base64 encoded data stood out
After decoding it I got a lot of encoded scripts which led me nowhere. Within all those scripts there was an encoded Json
There was the flag mixed in with different crypto wallets
Trashcan
| Category | Author |
|---|---|
| š Forensics | John Hammond |
Challenge Prompt
Have you ever done forensics on the Recycle Bin? Itās⦠a bit of a mess. Looks like the threat actor pulled some tricks to hide data here though.
The metadata might not be what it should be. Can you find a flag?
Solution
Firstly I looked up the structure of the $I recycle bin files to identify where could the flag be stored
I parsed the recycle bin data with RBCmd
I sorted the files based on the deletion date and created a script that goes through each file, reads the filesize and deletion date bytes and creates a binary file with them combined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import os
output_filename = "combined_output_filesize_date_sorted.bin"
files = ["$ITE5TYG.txt", "$ITMVJR4.txt", "$IW1WPZX.txt", "$I1D6OCR.txt", "$I3Y2C18.txt", "$IEFXDY8.txt", "$I8Q6O5D.txt", "$IISFJJQ.txt", "$IMU3AKY.txt", "$I1MMUNX.txt", "$IIENSHP.txt", "$IJ9U0RR.txt", "$IC04FJS.txt", "$IJWBEUE.txt", "$IU5E39M.txt", "$IM7QAYI.txt", "$INU5TNU.txt", "$IPD4HRC.txt", "$IQQAA2F.txt", "$IUSDLBT.txt", "$IXEGH08.txt", "$I4VJ9VP.txt", "$I59AMQZ.txt", "$IR2JCOS.txt", "$IEYL8JP.txt", "$IIYLT3Z.txt", "$ITUQ7CQ.txt", "$I2ZP91D.txt", "$IOYHHQ5.txt", "$IPWI3VK.txt", "$I2J48PI.txt", "$IA88SVT.txt", "$IUIEHU7.txt", "$I5F20WX.txt", "$I9PGBL6.txt", "$IDEBQ1M.txt", "$IOSGXAZ.txt", "$IQPFIEQ.txt", "$IYQJIUH.txt", "$I3O4FNO.txt", "$ISXWEK6.txt", "$IZ9EJJK.txt", "$I4S3J0O.txt", "$I7QCZXB.txt", "$IG64RJD.txt", "$ID557GC.txt", "$IPMSABA.txt", "$IS67SUB.txt", "$ICVE4M2.txt", "$IHE2HRX.txt", "$IJUUXYN.txt", "$I1WD5RF.txt", "$IG8TC80.txt", "$IW14HF1.txt", "$I90UOKS.txt", "$IAZPA8T.txt", "$IQ6XS48.txt", "$IK9LJEF.txt", "$IMZ20SR.txt", "$IPDV01V.txt", "$I198LLE.txt", "$IGUW5S7.txt", "$IV05A2U.txt", "$I17RAD1.txt", "$I6676MD.txt", "$IE0RMKA.txt", "$I2CVFM2.txt", "$IOFYR05.txt", "$IWKOHFD.txt", "$I01XCGF.txt", "$I7UPDZU.txt", "$IV6X20I.txt", "$I08ZI07.txt", "$IL4WLXW.txt", "$IWS5CL5.txt", "$IFUV73N.txt", "$ILD51DG.txt", "$IUYP5MU.txt", "$I61YGX7.txt", "$IL2QDPK.txt", "$IZ3RIQE.txt", "$IFRUSE0.txt", "$IIIY075.txt", "$ILRXLH5.txt", "$I3B7EP8.txt", "$IAKHV8Y.txt", "$IOXKN7X.txt", "$IKK9TWO.txt", "$IUDDG43.txt", "$IUQNBS2.txt", "$I2FNXOW.txt", "$I5HCDNO.txt", "$IDWR2DY.txt", "$I0AP14L.txt", "$I467CFX.txt", "$IND6VW0.txt", "$I7MXUTD.txt", "$IKQ5M0F.txt", "$ISBYUOH.txt", "$I60PUB8.txt", "$IEXEX7I.txt", "$ISTAZD1.txt", "$I5RPN3M.txt", "$I99VSUL.txt", "$ILN2HDS.txt", "$ING16RB.txt", "$IQ9QLU0.txt", "$IXWYRKV.txt", "$I65140O.txt", "$IABA5GX.txt", "$IHRXQ2Y.txt", "$I4VAGUQ.txt", "$IN4C62C.txt", "$ITB15DJ.txt", "$IO7IO01.txt", "$IS4YB00.txt", "$IUG82GT.txt"]
with open(output_filename, "wb") as outfile:
for filename in files:
if not os.path.isfile(filename) or filename == output_filename:
continue
if filename.endswith(".txt"):
try:
with open(filename, "rb") as infile:
infile.seek(8)
data = infile.read(16)
if len(data) == 16:
outfile.write(data)
else:
print(f"Skipping {filename}: not enough bytes.")
except Exception as e:
print(f"Error reading {filename}: {e}")
print(f"Combined binary written to {output_filename}")
After inspecting the bytes of the created binary file, it was clear the flag is consists of the first byte of each line. Additionally every byte is repeated 3 times
I used another python script to reconstruct the flag
1
2
3
4
5
6
7
8
9
10
11
12
13
import os
output_filename = "flag.bin"
file_size = os.path.getsize("combined_output_filesize_date_sorted.bin")
with open(output_filename, "wb") as outfile:
with open("combined_output_filesize_date_sorted.bin", "rb") as infile:
for i in range(0,file_size,48):
infile.seek(i)
data = infile.read(1)
outfile.write(data)
print(f"Combined binary written to {output_filename}")
I Forgot
| Category | Author |
|---|---|
| š Forensics | John Hammond |
Challenge Prompt
Soā¦. bad news.
We got hit with ransomware.
And⦠worse news⦠we paid the ransom.
AfterĀ the breach we FINALLY set upĀ someĀ sort of backup solution⦠itās not that good, but, it might save our bacon⦠because my VM crashed while I was trying to decrypt everything.
And perhaps the worst newsā¦Ā I forgot the decryption key.
Gosh, I have such bad memory!!
The archive password isĀ
i_forgot.
Solution
We are provided with a ZIP file containing a memory dump and an encrypted flag
After analyzing the memory dump with Volatility3 I came across an interesting looking process BackupHelper
1
$ python3 /home/remnux/volatility3/vol.py -f memdump.dmp windows.pslist
I decided to dump the memory of this process
1
$ python3 /home/remnux/volatility3/vol.py -f memdump.dmp windows.memmap --dump --pid 2132
Then I ran strings on the dump
1
$ strings -n 6 pid.2132.dmp > strings_2132.txt
Within those i found this
I tried to find this DECRYPT_PRIVATE_KEY.zip file with Volatility3 windows.filescan and windows.dumpfiles plugins but no luck. I decided to look for it within the BackupHelperās process memory and carve it out. To do that I searched for the offsets of 50 4B 03 04 (PK) magic bytes (start of the zip file) and 50 4B 05 06 (end of the zip file)
1
2
3
4
5
6
7
8
9
10
11
$ grep -aobU $'\x50\x4b\x03\x04' pid.2132.dmp
16384:PK
17793:PK
1369037541:PK
1369100349:PK
1369101184:PK
1369101378:PK
1369101418:PK
$ grep -aobU $'\x50\x4b\x05\x06' pid.2132.dmp
18300:PK
1369106851:PK
I took the first offset of 50 4B 03 04 bytes (16384) and last of 50 4B 05 06 (1369106851) and wrote python script that reads the bytes between these offsets from the BackupHelperās process memory and creates a zip file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
start = 16384
end = 1369106851
size = end - start
chunk = 1024 * 1024
with open("pid.2132.dmp", "rb") as f_in, open("carved.zip", "wb") as f_out:
f_in.seek(start)
remaining = size
while remaining > 0:
data = f_in.read(min(chunk, remaining))
if not data:
break
f_out.write(data)
remaining -= len(data)
print(f"Carved out carved.zip ({size} bytes from offset {start})")
Inside the carved.zip I found a pem key and an encrypted key file
I unzipped the file with the password found in the strings output and I ran the command that appears later. It wasnāt completed so I tried different padding modes, in the end it worked with oaep
1
$ openssl pkeyutl -decrypt -inkey private.pem -in key.enc -out key_raw.bin -pkeyopt rsa_padding_mode:oaep
Then based on the strings Decrypt the AES key+IV I assumed that the first part of the key_raw.bin file is AES key and second IV. The fact that the key_raw.bin had 48 bytes meant that the AES key had 32 bytes and IV 16
1
2
$ dd if=key_raw.bin of=aeskey.bin bs=1 count=32 status=none
$ dd if=key_raw.bin of=iv.bin bs=1 skip=32 count=16 status=none
I printed their hex values with xxd and used them to decrypt the flag
1
$ openssl enc -d -aes-256-cbc -in flag.enc -out flag.txt -K 289ea58a38549d5faf7a97a6dd19cdf2ddc0496a8a64f99a77c643529c94b804 -iv 2c6a55b0a89141056517687a977305d6
Bussing Around
| Category | Author |
|---|---|
| š Forensics | @Soups71 |
Challenge Prompt
One of the engineers noticed that an HMI was going haywire.
He took a packet capture of some of the traffic but he canāt make any sense of it⦠it just looks like gibberish!
For some reason, some of the traffic seems to be coming from someoneās computer. Can you help us figure out whatās going on?
Solution
The pcap file consists mainly of Modbus communication, either function 6: Write Single Register or 5: Write Single Coil. After inspecting it a bit, I noticed a a field Register Value (UINT16) in function 6 requests, which to me looked like it could contain some data when reconstructed
To retrieve all these values I used tshark. I filtered on function code and source ip, to not get the same value twice since itās repeated in Query and Response packets
1
$ tshark -r bussing_around.pcapng -Y "ip.src==172.20.10.2 and modbus.func_code==6" -T fields -e modbus.regval_uint16 > out.txt
Values were following 2 patterns. Either 0s and 1s or some bigger numbers
Since I didnt get anything when focusing on the bigger numbers, I decided to delete them and treat the remaining values as binary
I downloaded the reconstructed ZIP file, unpacked it with the password in the comment and got the flag
My Hawaii Vacation
| Category | Author |
|---|---|
| š Malware | John Hammond |
Challenge Prompt
Oh jeeeez⦠I was on Booking.com trying to reserve my Hawaii vacation.
Once I tried verifying my ID, suddenly I got all these emails saying that my password was changed for a ton of different websites!! What is happening!?!
I had aĀ flag.txtĀ on my desktop, but thatās probably not importantā¦
Anyway, I still canāt even finish booking my flight to Hawaii!! Here is the site I was on⦠can you get this thing to work!??!
This is theĀ
MalwareĀ category, and as such, includes malware.Ā Please be sure to analyze these files within an isolated virtual machine.
Solution
After downloading the malware from the VM provided by the challenge, I decided to analyze it dynamically. To do that I used anyrun.
First, it downloads 7zip as 293221af461c2421.exe
Then it saves the bytes of userās SID to a .log file
It sends this file to a server using prometheus:PA4tqS5NHFpkQwumsd3D92cb credentials
Then it creates a password protected (userās SID) zip file with .mp3 extension, compressing everything inside C:\Users\admin\ and again sends the file to a server. So it acts like a stealer
I logged in to the server with credentials used in the commands, and found 2 files: .log and .zip
To reconstruct SID from the bytes in the log file I ran these powershell commands
1
2
3
4
5
$log = 'WINDOWS11-Administrator.log'
$nums = Get-Content $log | ForEach-Object { [byte]$_ }
$bytes = [byte[)$nums
$sidObj = New-Object System.Security.Principal.SecurityIdentifier($bytes, 0)
$sidObj.Value
Finally I unzipped the zip file using SID as a password and got the flag
Puzzle Pieces Redux
| Category | Author |
|---|---|
| š Forensics | @Nordgaren |
Challenge Prompt
Well, I accidentally put my important data into a bunch of executables⦠just donāt ask, okay?
It was fine⦠until my cat Sasha stepped on my keyboard and messed everything up! _OH NOoOoO00!!!!!111_
Can you help me recover my important data?
Solution
I inspected the files in hex editor and noticed they have weird 6 bytes at the end, for example
Additionally I ran strings on all these files and searched for āflagā
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
strings *.bin | findstr flag
C:\Users\user\Desktop\puzzles\24b429c2b4f4a3c.bin: flag_part_5.pdb
C:\Users\user\Desktop\puzzles\4fb72a1a24.bin: flag_part_2.pdb
C:\Users\user\Desktop\puzzles\53bc247952f.bin: flag_part_6.pdb
C:\Users\user\Desktop\puzzles\5e47.bin: flag_part_1.pdb
C:\Users\user\Desktop\puzzles\5fa.bin: flag_part_3.pdb
C:\Users\user\Desktop\puzzles\7b217.bin: flag_part_2.pdb
C:\Users\user\Desktop\puzzles\8208.bin: flag_part_1.pdb
C:\Users\user\Desktop\puzzles\8c14.bin: flag_part_4.pdb
C:\Users\user\Desktop\puzzles\945363af.bin: flag_part_0.pdb
C:\Users\user\Desktop\puzzles\a4c71d6229e19b0.bin: flag_part_4.pdb
C:\Users\user\Desktop\puzzles\aa60783e.bin: flag_part_5.pdb
C:\Users\user\Desktop\puzzles\c54940df1ba.bin: flag_part_7.pdb
C:\Users\user\Desktop\puzzles\c8c5833b33584.bin: flag{
C:\Users\user\Desktop\puzzles\c8c5833b33584.bin: flag_part_0.pdb
C:\Users\user\Desktop\puzzles\d2f7.bin: flag_part_7.pdb
C:\Users\user\Desktop\puzzles\e1204.bin: flag_part_3.pdb
C:\Users\user\Desktop\puzzles\f12f.bin: flag_part_6.pdb
Interestingly file c8c5833b33584 had flag{ string. I went back to hex editor and searched for it
It was located at 000161C0 offset. I decided to extract 5 bytes at 000161C0 from the rest of the files plus the last 6 bytes I found earlier
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import os
offset = 0x161C0
num_bytes = 5
output_file = "results.txt"
with open(output_file, "w") as out:
for filename in os.listdir('.'):
if filename.lower().endswith('.bin'):
try:
with open(filename, 'rb') as f:
f.seek(offset)
data = f.read(num_bytes)
f.seek(-6,2)
data_end = f.read(6)
ascii_repr = ''.join([chr(b) if 32 <= b <= 126 else '.' for b in data])
hex_end = data_end.hex()
out.write(f"{filename}: | {ascii_repr} | {hex_end}\n")
except Exception as e:
out.write(f"{filename}: ERROR - {e}\n")
print(f"Results saved to {output_file}")
After I added the flag_part_X.pdb found in each file, I was left with this
Both the flag_part_X.pdb and the last 6 bytes were repeated twice. Two files that I knew were correct were c8c5833b33584 and c54940df1ba as they contained the strings in the flag format ({}). Then I simply cleaned the result so that no 6 bytes or flag parts are repeated
I was left with 2 flag_part_2.pdb so I tried both of them
flag{be7a1e6817d85d549f8b5abfaf18ba02}
Telestealer
| Category | Author |
|---|---|
| š Malware | Ben Folland |
Challenge Prompt
Our threat intelligence team reported that Benās data is actively being sold on the dark web. During the incident response, the SOC identified a suspicious JavaScript file within Benās Downloads folder.
Can you recover the stolen data?
The password to the ZIP archive isĀ
telestealer
Solution
JavaScript file contained base64 encoded command, divided into parts, which was later executed using powershell
I run the JS code in online compiler until the parts.join part to get the full encoded command. After decoding I got powershell code
I decrypted the AES encrypted blob in CyberChef and got an executable file
The recovered file was a .NET binary
I opened the file in dnSpy. After looking through the code, I found the main functionality of the program which was stealing data and sending it to Telegram bot
The configuration of the Telegram Bot could be found within the code
I used these to forward the Telegram bot messages to me, which contained the flag
1
1..100 | ForEach-Object { curl "https://api.telegram.org/bot8485770488:AAH8YOjqaRckDPIy7xNwZN2KcaLx6EME-L0/forwardMessage?chat_id=<my_chat_id>&from_chat_id=-4862820035&message_id=$_"} | Out-File messages.txt
flag{5f5b173825732f5404acf2f680057153}






































