Huntress CTF 23 Malware

Difficulty: Easy / Medium / Hard

HumanTwo - Easy

During the MOVEit Transfer exploitation, there were tons of “indicators of compromise” hashes available for the human2.aspx webshell! We collected a lot of them, but they all look very similar… except for very minor differences. Can you find an oddity?

Review first file. Considering the code, I decide that the password portion is the most likely to change. grep for “String.Equals(pass”. Sure enough, find a file that the format of the string is different: cc53495bb42e4f6563b68cdbdd5e4c2a9119b498b488f53c0f281d751a368f19. The value is:


Convert this from HEX to get the flag:


Zeroin - Easy

We observed some odd network traffic, and found this file on our web server… can you find the strange domains that our systems are reaching out to?

Review with file to estbalish this is a PHP file. Open it in text editor and there is some base64 encoding which appears reversed. Decode in CyberChef using Reverse on the string first. The flag is visible in the decoded output without needing to search for it:


PHP Stager - Easy

Ugh, we found PHP set up as an autorun to stage some other weird shady stuff. Can you unravel the payload?

The PHP code is all obfuscated. The quickest way I found to determine some of the variables was utilising an online compiler like this one here (and removing the large blob and function for now).


Reviewing the code knowing these variable names changes the outlook on decoding:

  • $fsPwhnfn8423 - base64_decode
  • deGRi is an bitwise XOR function
    • Accepts base64 decoded blob and “tVEwfwrN302” as the XOR key
  • Base64 decodes the returned value

Knowing this, converted the blob in CyberChef with this recipe:

  { "op": "From Base64",
    "args": ["A-Za-z0-9+/=", true, false] },
  { "op": "XOR",
    "args": [{ "option": "UTF8", "string": "tVEwfwrN302" }, "Standard", false] },
  { "op": "From Base64",
    "args": ["A-Za-z0-9+/=", true, false] }

Now there is another large script that also contains some base64 values. The first one, assigned to $back_connect_p decodes as a perl script. I tried the hash under $auth_pass but it was incorrect. Further down I spotted a uuencode string which is common in some exploit code. To decode this, I made a file with the uuencoded content (uuenc):

begin 644 uuencode.uu

And ran uudecode -o flag uuenc. Cat the contents of flag:


Hot Off The Press - Medium

Oh wow, a malware analyst shared a sample that I read about in the news! But it looks like they put it in some weird kind of archive…? Anyway, the password should be infected as usual!

Check file type: UHarc archive data. I tried an installer on Linux but had no luck with the file running (kept hanging). Download and install this tool on a Windows VM. Running to extract archive resulted in a PS1 file which is obfuscated. However, it is clear there is some base64 string there to try and grab. Easiest way to do this was to run PowerShell like so:

write-host (('H4sI'+'AIeJ'+'G2UC/+1X'+'bU/jOBD+3l9hrS'+'IlkU{0}'+'VFvb{1}IiFdWqD'+'bPRJKS8vR'+'brUKy'+'TR168TFcQplb//7'+'jfNSygJ73{1}lI94F'+'IVvwyMx4/M'+'7YfT9PYl5TH'+'hH7sku8VUnxd'+'T3gRMTT/ku'+'/fWUSjS3Mzp'+'oX7zCWHxBjby+UR'+'jzwaTw4OWq'+'kQ{1}M'+'u8XW2'+'DtJM{1}'+'omtGI'+'TFM8he5nIGAnbP'+'rOfiSf'+'Cfat2qb8W'+'uPFW{0}rlufP'+'gOzYcaD'+'GTrnvKbeq/'+'SWj0tC/ftXN8U5'+'9Uj2+ST2'+'WGHp/nUiIqgFjuk'+'l+mGrCi/USDN2'+'hvuAJn8rqJY'+'13G9VBn'+'HhTcNHa'+'ChyQMx4'+'kul'+'nZ{0}{1}a'+'AT{1}Wcr0kZyUUMHa'+'tdwX0'+'7CAQkiW6RsTI'+'/nkx+N8bF'+'3{0}00'+'ljS'+'CaieWIPiyD'+'2JFfUiq'+'n704YNC'+'D6QS1+l{0}Q'+'OJyYJoq'+'t+AIM{0}U4Zs8'+'i/MWO4c'+'Fsi91olY1sJpbpS'+'mBYG'+'9Jl1OjxIG'+'eSa+jOO'+'5kl'+'g4pcngl'+'n5UalMy7'+'yJvPq'+'3o6eZs2mX'+'3zgbAHTX6PK'+'{1}Zr'+'qHp'+'GYRBy'+'f2JBdrbGoXIgVz'+'sgGbaNGe/Yf'+'1SmP1UhP1V'+'u0U'+'e8ZDToP'+'JRn0r'+'7tr0pj38q{1}'+'ReTuIjmNI'+'YjtaxF1G/'+'zFPjuWjAl{1}{1}GR'+'7UUc9{1}9Qy8'+'GIDgCB'+'q{1}nFb4qKZ6oHU'+'dUbnSbKWUB'+'CNvHiCb'+'oFQbbfO'+'xMHjJD78QORAhd3'+'sYs'+'1aa4O6'+'CU{0}nb'+'{1}upxdtVFIbz{1}v'+'SSzSTXF7+hbpg8c'+'gsIgdJ7QYs'+'lPJs6r+4K6T'+'Mkl9{0}5Glu'+'Yn5{1}5zFtC'+'0eJ1KkPgYVIbj'+'o{0}8'+'GnHlOIWO'+'QzDaC57'+'tOwnF5/Fo+Wxx'+'juG7S0wnhgj8'+'Kh{0}1Wq'+'CPQ0Swuz2g'+'fZiZYMIpTJjosT5'+'oV4'+'OBS7I'+'8st{0}4RAf8HRc'+'hPkGa+Q'+'KSHZchP'+'D3WdcWmRIhcTDR6'+'GM2fVfnHhy'+'6uTOtAQ'+'UwTGyvTVur'+'qXKfi0+P'+'W8sVI4WAGVwCI'+'lQn'+'AgeNb0{1}ftv{0}Dxjj'+'Q6dlh+/lvbyX'+'9/K/{0}22X+XG'+'vHr'+'RZ0mnV635'+'0N7'+'+6d'+'Pmob8sR'+'bf{0}gc+/2j'+'O6vT'+'ufHt856786'+'dO6lz{1}e5i'+'e302D2/PjuxV'+'tzFMr'+'xqfFqP{0}3nQU3'+'c1G'+'9zXmzq+'+'YGzn4P8b'+'iM7f'+'Rwf85lk'+'4+Nh8w5'+'36Q1Z17P6vn7'+'WP8h1gW2R/n+0'+'m2g8UuZ'+'M{0}M3kN7UYyHh'+'T17M5+aw22'+'ch1+GvZO{0}oc3+bF'+'+FX2jz'+'PmifrIOWvTq'+'nNhse'+'D91Ba+iPwsPD'+'D2ZlPKCx3G1M1{1}W'+'+qwhS'+'RWP+p/'+'2tS+Al6'+'ud4'+'Ipl5DC8H5HTl'+'FX3C'+'xUnB1{0}qcKg3DU'+'{1}x/'+'ASIGhvQYCXR5sd'+'mMcV+RxJzSIUP'+'NeaOisYNO'+'5tVzNZNsBM0'+'H9lh2HRyM'+'0{1}u8{0}{0}O7rH'+'oKcShnVu1ut1ZD'+'7le7q+3htfj6'+'pbX4cm3ktix'+'FHjNwNtZZZt2s'+'0CkxjDfHC9'+'8H{1}unK{0}xB7C'+'Tyce'+'4H0AvlOfukrCJ'+'ucs20A'+'i5Vt8'+'u{1}R'+'fghcHVc/Vq+'+'D{0}FPQxA7'+'c{1}{1}0q/rzFxrX0'+'+uz6TZOnIC8z/AX'+'/mDwPfb8YfVVC1a'+'wcoCfd'+'jzseiN/bIX'+'DpUYmCf'+'aRhDPKHwQtAFB'+'tmK8gqP{0}gbpsWn'+'Hspnq'+'dxx8'+'emlmODf2GZMc5'+'4PA'+'AA=')-f'L','E')

Now in CyberChef the flag can be obtained with some “baking-fu”:

  { "op": "From Base64",
    "args": ["A-Za-z0-9+/=", true, false] },
  { "op": "Gunzip",
    "args": [] },
  { "op": "Regular expression",
    "args": ["User defined", "[A-Za-z0-9+/=]{30,}", true, true, false, false, false, false, "List matches"] },
  { "op": "From Base64",
    "args": ["A-Za-z0-9+/=", true, false] },
  { "op": "Regular expression",
    "args": ["User defined", "%[a-f0-9]{2}", true, true, false, false, false, false, "List matches"] },
  { "op": "Find / Replace",
    "args": [{ "option": "Extended (\\n, \\t, \\x...)", "string": "\\n" }, "", true, false, true, false] },
  { "op": "URL Decode",
    "args": [] }


VeeBeeEee - Easy

While investigating a host, we found this strange file attached to a scheduled task. It was invoked with wscript or something… can you find a flag?

Data is obfuscated but after searching for VBE files based off the challenge name, I found a script to decode the file from none other than John Hammond. Run the script, then deobfuscate in CyberChef to find a pastebin link with the flag:


Snake Eater - Easy

First, I wasted some time trying static analysis, then decompiling python to bytecode but had to do workarounds with versioning etc. so I dropped that idea. Instead I opted for the dynamic approach - running the binary while monitoring with Procmon. After execution, I started testing filters and got lucky by adding CreateFile and WriteFile off the bat as the program had created a file with the flag as its name in a random directory:


Opendir - Medium

Browse to the instance URL and decided to start by checking for text files as being a likely place to stash the flag. Since there looks to be a lot of files, I started downloading them to disk to search easily:

wget --user=opendir --password=opendir -r -np -nH --cut-dirs=1 -A '*.txt'

Run grep for flag{ and get a hit in sir/64_bit_new/oui.txt


Thumb Drive - Medium

Run file, it is defined as a Windows shortcut. Run strings -el and recover this URL Browse here and copy the text into CyberChef which recommomends Base32 for decoding. Try this and end up with a DLL file.

Fire up my Windows VM, copy the DLL there and run it with regsvr32 recovered.dll which pops a message box containing the flag:


Snake Oil - Medium

File is PE executeable. Open in PEStudio and spot python strings. Try the approach of decompiling into bytecode then to python script using this blog for reference. Once I reached the point of being able to run pycdc brain-melt.pyc > I was able to review how to get the flag between these 2 functions:

def decrypt(s1, s2):
    return ''.join((lambda x: [ chr(ord(c1) ^ ord(c2)) for c1, c2 in x ])(zip('\x17*\x07`BC\x14*R@\x14^*', 'uKeVuzwIexplW')))

def deobfuscate():
    part1 = '2ec7627d{galf'[::-1]
    part2 = str(base64.b64decode('NjIwM2I1Y2M2OWY0'.encode('ascii')), 'UTF8')
    part3 = decrypt('\x17*\x07`BC\x14*R@\x14^*', 'uKeVuzwIexplW')
    key = part1 + part2 + part3
    return key

After fixing the decrypt function (removed .0 and replaced with x), run with python to get the flag:


RAT - Medium

Check the file with DiE and establish it is a .NET file start my analysis with ILSpy. Can see some references to arrays/strings but nothing visible in ILSpy so open in PEStudio and find a large string that contains some split blocks that match regex patterns seen in the code. I split these in a text editor and tried replicating the decode functions in CyberChef but the libArr didn’t seem to decode correctly. I did successfully decode fileArr using CyberChef to XOR it with key 199, then gunzip it to obtain a binary file. Opening this file with PEStudio, I could see it was originally called Client.exe. Wanting some debug functionality, I installed dnSpy opened Client.exe.

Within Settings class, the config decryption functions and encrypted strings can be seen. One that isn’t called is:

public static string Flag = "mZzroGSIkpZlwvCwLG0PHQMXzjphDowlbeBayjWJhmYPJ5KiQeUAbcv9SzTnLGpr3uYQ0VvZ02rGlxz71tOXMemdK1DKKY6uX2QfUJW+WlDPcLi1u48xBrhmDcpRaK1G";

I tried editing the method but the program kept on ignoring that I had flag and would decrypt everything in it’s original order. Instead, I set a breakpoint on the call to Ports being decrypted, and replaced the value being sent to Decrypt with the ciphertext of the flag which in turn, decrypted it for me:


I did also set some breakpoints on decrypt of the original binary return values to confirm libArr also decoded to a binary that was named ManeySubLib.dll

Batchfuscation - Medium

I was reading a report on past Trickbot malware, and I found this sample that looks a lot like their code! Can you make any sense of it?

I seemed to find a quick and dirty method to complete this one. I started by editing the file to append echo to each line using CyberChef: Find_/_Replace({'option':'Regex','string':'\\n'},'\\necho ',true,false,true,false). Having saved the changes, I renamed the file to include the .bat extension and ran it in my Windows VM which started writing out all the strings and variables. Knowing this was working, I ran it again to output into a text file. Opening in Notepad++ I searched for flag and spot characters all out of order:


I made use of CyberChef once again to strip and reorder the results to get the flag:

Find_/_Replace({'option':'Regex','string':':: set flag_character'}`**,'',true,false,true,false)
Sort('Line feed',false,'Numeric')


Speakfriend - Medium

It seems like this website was compromised. We found this file that seems to be related… can you make any sense of these and uncover a flag?

Key info from the description:

  • You will need access this service with HTTPS. Please use https:// as the URL schema rather than plain http://.
  • This website uses a self-signed certificate. The “Warning: connection not secure” message is expected and intended. You can continue on to the website.

Open binary in Ghidra - can see quite obviously that some settings are being set for curl to be used:

  • Checks for port 443
  • Applies a specific user agent: Mozilla/5.0 93bed45b-7b70-4097-9279-98a4aef0353e

Try basic curl with the user agent:


Ok, so back into Ghidra, I check out the opcodes being set and can validate most of them from here:

  • URL
  • Couldn’t find opcode

Based on this information, I updated my curl command to include --insecure to avoid SSL errors, and -L to follow redirects. This time, I got the flag returned:


BlackCat - Easy

We’ve been hit by the infamous BlackCat Ransomware Group! We need you to help restore the encrypted files. Please help! My favorite rock got encrypted and I’m a wreck right now!

I enjoyed this challenge, more so on the way I solved it.

Start by running the decryptor which prompts for a key to decrypt. A little fuzzing on the input establishes it needs 8 characters to attempt a decrypt with whatever key was provided. I wasted a bit of time chucking it in Ghidra to realise it’s a Golang binary, and that likely RE of the decryptor is out of scope since the challenge is ‘easy’.

Referring to the provided encrypted files, I opened them all in HxD and note some look similar in their encryption. Closer inspection of the files tells me the similar ones are PNG files, which made me recall a Flare-On challenge of similar ilk I have completed. So change of tactics - take a real PNG header (8 bytes), and the first 8 bytes from one of the encrypted PNGs, then write a python script to perform XOR between them to establish what key was used.

orig = bytearray(b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A')
enc = bytearray(b'\xea\x3f\x3d\x2a\x62\x68\x75\x63')

key = bytearray(len(orig))  # Create an empty key of the same length as orig

for i in range(len(orig)):
    key[i] = orig[i] ^ enc[i]  # XOR each byte of orig with enc to obtain the key

# XOR the key with enc to get a new array that matches orig
dec = bytearray(len(orig))  # Create an empty bytearray of the same length as orig

for i in range(len(orig)):
    dec[i] = enc[i] ^ key[i]

print("[+] XOR Key:", key)
print("[+] Decrypted Array:", dec)

Running the script returns the key as cosmoboi which is then used to get the flag:


Crab Rave - Hard

This one provided an easier route, and a harder one. For the sake of time available to check this out, I opted for the crab_rave_easier.

Opening in PEStudio, I note that the file is a 64 bit DLL with 2 exports: DLLMain and NTCheckOSArchitecture. Also note that ASLR is enabled, so I patched the binary to disable this with CFF Explorer.

Opening in Ghidra, a quick check of strings tells us this is a Rust binary based on the cargo package strings. Checking DLLMain first, it isn’t reached and is classed as its own Entry Point which means that NTCheckOSArchitecture will not be reached.

I decided to try and force a call to DLLMain using x64dbg. To do this I took the following steps:

  • Open rundll32 in x64dbg
  • Change command line to include the ordinal #1 for DLLMain
  • Change Options > Preferences > Select DLL Entry

For some reason, I the ordinal option didn’t work and I’d always end up at offset:10001350. No matter, I set a breakpoint on the first call and change it so that DLLMain is called instead and work from there.

Under NTCheckOSArchitecture a couple of functions stand out due to their names referencing decrypt, and inject_flag. Set a breakpoint on inject_flag and run, but of course it doesn’t get there. Anyway, once inside the NTCheckOSArchitecture function, I obtained what some interesting values m.yeomans30801 and WIN-DEV-13 from the first 2 calls to the decrypt function. Was -rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-r used as a key? (would later discover this was an XOR key).

Continuing, there is a call at offset:1000af3f which checks the host username and continues to offset:1000b112 which is just after a comparison between m.yeomans30801 and the host username. Patch the Zero Flag to bypass this check, which then hits a function call at offset:1000b124 which retrieves the hostname, presumably to be compared against WIN-DEV-13. Patch the next check at offset:1000b332 to continue.

The decrypt call at offset:1000b356 returns the value notepad.exe - perhaps this will be used to open a file or maybe process injection? Next up is the call to the injectflag function at offset:1000b36d, which goes on to decrypt a URL string at offset:1000a245:

The gist contains a base64 encoded value but must be encrypted as it decodes to gibberish. Trying to carry on debugging at this point failed as it appears the program tried to reach the internet and my VM had no connection with it being a sandbox. Not to worry, continuing to analyse the function within Ghidra, I spot the following string:

"rAcbUUWWNFlqMbruiYOIsAyVQHS78orvMoJ8C6O4D3asAApBBase64 decoding error: "

Using advice from this blog post, I was able to clear the code bytes and separate the key/IV values to decrypt the base64 that was found and obtain the flag. I done so on CyberChef with the following recipe:

  { "op": "From Base64",
    "args": ["A-Za-z0-9+/=", true, false] },
  { "op": "AES Decrypt",
    "args": [{ "option": "UTF8", "string": "rAcbUUWWNFlqMbruiYOIsAyVQHS78orv" }, { "option": "UTF8", "string": "MoJ8C6O4D3asAApB" }, "CBC", "Raw", "Raw", { "option": "Hex", "string": "" }, { "option": "Hex", "string": "" }] }


Snake Eater II - Medium

Taking a leaf out of my approach to the original Snake Eater, I start by running Procmon with filters set for to only include the binary name and path contains flag. I can see on each run that flag.txt is being written within appdata\roaming<random> but then deleted before it can be browsed to.

I attempted to try stopping on CreateFile APIs in x64dbg but no luck as it the debugger would drop out once pyarmour has done its job. I debated about using Autopsy or another simiar tool to try and recover flag.txt but decided this would be a bit excessive. I installed PhotoRec since it is used by Autopsy to try and recover the flag but it wasn’t targetted enough (like folders and folders of recovered txt files!).

Instead, I installed Recuva, running it initially with no success. I ran a fresh Procmon and ran the binary again to obtain the WriteFile path, which I then used to target the Recuva checks. Sure enough, it found the flag.txt which I recovered to Desktop to get the flag:


BlackCat II - Hard

Be advised analyst: BlackCat is back! And they’re mad. Very mad. Help our poor user recover the images that they downloaded while browsing their favorite art site. Quickly!

Oh where to start with this one! After checking the file in DiE I established it was a 32-bit .NET binary so opened it with dnSpy.

Straight away we have a Class name of interest: DecryptorUtil. Review of the DecryptFiles method provides the following info:

  • Reads all .encry files in given path
  • The first file is decrypted with the key provided as input
  • Other files are decrypted by the SHA256 hash of the previous files decrypted content

Now, the problem is figuring out the key. Spend some time reviewing the AES function and we have:

  • Uses CFB more with 0 padding
  • Hardcoded IV
  • Salt used

Ok, so upon research, the AES CFB mode could potentially be vulnerable to known plaintext or crytanalysis - BUT - the key is 32 bytes and iterated 10000 times to make it very difficult to even attempt.

Within the first 30 mins (and no solves) the decryption functionality has been understood. But where to find the hash of the first file in the victim_files:


Quite a unique name! I spent a lot of time doing OSINT/research based on all the painting names and artists, trying to find a site that had them all. Wikiart had most, but I couldn’t get a match on the filenames or correct sizes. I used all the search engines I could muster to try and get different results… Turns out I just needed a vague search of famous paintings, which eventually after opening a few tabs, I found this site which had all the painting names by the artists. However…clicking on the image takes you to a checkout to purchase a print - and the images there aren’t the correct ones. Going back a page, right-click > Save Image as, but it is a webp file - though the entire filename matches otherwise so I download it and obtain the SHA256 80d60bddb3b57a28d7c7259103a514cc05507c7b9cf0c42d709bdc93ffc69191.

After hours searching (and watching the completion numbers slowly rise) - Run the decryptor with this hash as the key…