Post

Huntress CTF 2025

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

CategoryAuthor
šŸ‘¶ WarmupsJohn 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

CategoryAuthor
šŸž MalwareJohn 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

CategoryAuthor
šŸ” ForensicsJohn 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

CategoryAuthor
šŸ” ForensicsJohn 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

CategoryAuthor
šŸ” 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

CategoryAuthor
šŸž MalwareJohn 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

CategoryAuthor
šŸ” 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

CategoryAuthor
šŸž MalwareBen 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}

This post is licensed under CC BY 4.0 by the author.