// HackTheBox  ·  writeup

Safecracker

HackTheBox Insane MFTECmdobjdumpreadelfstringsPythonVirusTotal
Malware Analysis

Scenario

You are plunged into a predicament at Forela where crucial files on a Windows server have been encrypted, accompanied by a ransom note. Contractors recently brought onboard to work on backup services are under suspicion, as the attack occurred post their inclusion into the domain. This insane difficulty task demands a thorough investigation of the incident, beyond standard triage, focusing on potentially malicious files that may have slipped past Forela’s detection tooling. You will need to apply your deep expertise in Windows servers, ransomware analysis, and advanced threat detection to unravel how this stealthy attack was executed, presenting an immersive and complex cybersecurity challenge.

Warning

This is a warning that this Sherlock includes software that is going to interact with your computer and files. This software has been intentionally included for educational purposes and is NOT intended to be executed or used otherwise. Always handle such files in isolated, controlled, and secure environments.

One the Sherlock zip has been unzipped, you will find a DANGER.txt file. Please read this to proceed.

Executive Summary

A contractor account added to the domain gave the attacker a foothold on a Windows server. From PowerShell they used a Sysinternals tool to open a SYSTEM shell and ran a Linux ELF disguised with a Defender binary name. The ELF is a two-stage packer: stage one decrypts and inflates an embedded payload, loads it into an anonymous memory file and executes it under a plain process name. The payload decodes its configuration with an XOR key, checks for a debugger through procfs, walks the Windows user profiles through a Linux-style drive mount, encrypts files with targeted extensions, renames them, deletes the originals and drops a ransom note with a Bitcoin address. The $MFT shows the result on disk, and the strings of the sample leak the author’s build path.

Artifacts

Format note: Every question here carries a character mask (* per hidden character). Use it to catch wrong guesses: a nine-letter libc wrapper does not fit a ten-letter answer. Stage two (stage2.elf) is not in the collection; produce it by decrypting and inflating the stage one blob.

Initial Access

Q1 — Which user account was utilised for initial access to our company server?

The collection holds the user profiles, and one belongs to a contractor. Its PSReadLine ConsoleHost_history.txt is short and reads like first-touch recon: whoami, net user, then a change into a folder holding a Sysinternals suite. The profile that owns the history is the account used for the foothold.

cat ConsoleHost_history.txt
Click flag to reveal contractor01

Privilege Escalation

Q2 — Which command did the TA utilise to escalate to SYSTEM after the initial compromise?

The same history continues after the recon. The actor launches PsExec from the folder they staged under the profile (PSTools in Contacts) with the switches that run a shell as NT AUTHORITY\SYSTEM on the interactive desktop. Copy the line exactly as typed, including the leading .\; the format mask lines up with it character for character.

cat ConsoleHost_history.txt
Click to reveal answer .\PsExec64.exe -s -i cmd.exe

Defense Evasion

The ELF dropped on the server is stripped and carries a large embedded blob. Triage started with file on the Defender-named binary in the Administrator Downloads folder, then objdump from the entry point to find main. Most of the questions below fall out of stage one (the packer) and stage two (the payload it unpacks), so those two were pulled apart first. Nothing was executed.

Q14 — What compiler was used to create the malware?

readelf on the .comment section shows the toolchain that built the binary. The compiler family is the first word of the string.

readelf -p .comment sample.elf
Click to reveal answer gcc
Q16 — What is the contents of the `.comment` section?

Same section, whole string. It is a Debian build of the compiler with a date, and the mask expects it verbatim.

readelf -p .comment sample.elf
Click flag to reveal GCC: (Debian 10.2.1-6) 10.2.1 20210110
Q10 — What compression library was used to compress the packed binary?

Stage one’s .rodata carries a zlib-style version string and the ZBUF error text. The decrypted blob then inflates cleanly as a zlib stream, which settles the library.

strings -a sample.elf | grep -iE 'zlib|ZBUF|1\.2\.'
Click to reveal answer zlib
Q6 — What encryption was the packer using?

The decrypt routine between 0x3a29b and 0x3a4ac takes a 32-byte key and a 16-byte IV and runs the blob through a block cipher in chained mode. That was reasoned from the routine’s parameters, then verified: decrypting the blob with a 256-bit key in CBC mode produces a valid zlib stream, which confirms the mode as well as the key size.

objdump -d sample.elf --start-address=0x3a29b --stop-address=0x3a4ac
Click to reveal answer AES-256-CBC
Q7 — What was the encryption key and IV for the packer?

Two hex strings sit next to each other in stage one’s .rodata: a 64-character one at 0x1db008 (the key) and a 32-character one at 0x1db050 (the IV). The mask is KEY:IV. Using them on the blob at file offset 0x2883a0 yields a working stage two, so they are the right pair.

python3 -I read_rodata.py sample.elf 0x1db008 0x1db050
Click flag to reveal a5f41376d435dc6c61ef9ddf2c4a9543c7d68ec746e690fe391bf1604362742f:95e61ead02c32dab646478048203fd0b
Q8 — What was the name of the memoryfd the packer used?

After decompression stage one calls memfd_create with a name stored at 0x1db094. The payload runs from that anonymous in-memory file, so the unpacked binary never touches disk.

objdump -d sample.elf | grep -B8 memfd_create
Click to reveal answer test
Q4 — What is the name of the process that the unpacked executable runs as?

Right after the memfd_create call, stage one calls execl. The argv[0] it passes (string at 0x1db0ca) is the name the payload shows in ps.

objdump -d sample.elf | grep -B10 -A4 execl
Click to reveal answer PROGRAM
Q5 — What is the XOR key used for the encrypted strings?

Run strings on the unpacked stage two. A 14-character mixed-case token at 0x28207c belongs to no message. XOR-decoding the table at 0x3674a0 with it gives readable extensions, and the same key decodes the URL list at 0x36cfc0, which confirms it.

strings -a -t x stage2.elf | less

Tip: The key is in plain text in stage two but not in stage one. Decrypt and inflate the blob first (Q6, Q7), then run strings.

Click flag to reveal daV324982S3bh2
Q11 — The binary appears to check for a debugger, what file does it check to achieve this?

Stage two’s main opens a procfs file and scans it for a tracer field, which is non-zero when a debugger is attached through ptrace. The path is among the plain strings of stage2.elf.

strings -a stage2.elf | grep '^/proc'
Click flag to reveal /proc/self/status
Q19 — What string does the binary look for when looking for a debugger?

The check reads the Q11 file line by line and looks for a single field name. The needle sits in the same strings output, right after the path.

strings -a stage2.elf | grep -n -i tracer
Click to reveal answer TracerPid
Q15 — If the malware detects a debugger, what string is printed to the screen?

If the tracer field is non-zero, the binary prints a banner: one 23-character string padded with asterisks at 0x2820a7 in .rodata. The asterisks are part of the answer.

strings -a -t x stage2.elf | grep -i debugged
Click flag to reveal *******DEBUGGED********
Q12 — What exception does the binary raise?

Disassembling main shows a call to raise with the constant 0xb, which is signal 11. The binary crashes itself on purpose, which also frustrates a debugger session.

objdump -d stage2.elf --start-address=0x4a000 --stop-address=0x4a2d0 > work/mal/main.asm

Tip: The format mask is a signal name, not a prose description of the fault.

Click to reveal answer SIGSEGV

Discovery

Q9 — What was the target directory for the ransomware?

The target path is stored in clear text in stage two (0x2d3f2a to 0x2d3f49), unlike the extension table, which is XOR-encoded. It points at the Windows user profiles through a Linux-style drive mount, which is how an ELF binary reaches the Windows files. That the ELF ran under Windows Subsystem for Linux is inferred from this path, not read from a separate artifact.

strings -a -t x stage2.elf | grep '/mnt/'
Click flag to reveal /mnt/c/Users
Q21 — What system call is utilised by the binary to list the files within the targeted directories?

Check the imports of stage2.elf for the call that reads directory entries. The mask has ten characters, so the libc wrapper readdir64 (nine) does not fit; the raw directory-entries system call does.

objdump -T stage2.elf | grep -iE 'dir|dents'

Gotcha: readdir64 is the obvious guess and has the wrong length. Count the asterisks in the format hint before submitting.

Click flag to reveal getdents64

Impact

Q13 — Out of this list, what extension is not targeted by the malware? `.pptx,.pdf,.tar.gz,.tar,.zip,.exe,.mp4,.mp3`

The extension table at 0x3674a0 is XOR-encoded with the Q5 key. Decoding it gives .pdf .docx .ppt .pptx .doc .zip .jpg .png .gif .mp3 .mp4 .mov .tar.gz .tar. Compare that with the list in the question: exactly one entry is absent from the table, so files with it are skipped.

python3 -I xor_decode.py stage2.elf 0x3674a0 daV324982S3bh2
Click flag to reveal .exe
Q17 — What file extension does the ransomware rename files to?

A format string in stage two appends a new extension to each encrypted file, and the MFT shows the same extension on disk (Q3).

strings -a stage2.elf | grep -E '%s\.'
Click flag to reveal .31337
Q3 — How many files have been encrypted by the the ransomware deployment?

Parse $MFT with MFTECmd and count the records whose file name ends in the Q17 extension. $J adds further entries for the same files, so count unique names, not raw rows.

MFTECmd -f '$MFT' --csv work/mft
Click flag to reveal 33
Q18 — What is the bitcoin address in the ransomware note?

The ransom note is dropped as a .note file in the encrypted folders. Grep the notes for a Base58 address pattern; the text contains exactly one.

grep -rhoE '[13][a-km-zA-HJ-NP-Z1-9]{25,34}' --include='*.note' .
Click to reveal answer 16ftSEQ4ctQFDtVZiUBusQUjRrGhM3JYwe
Q22 — Which system call is used to delete the original files?

After the binary writes the encrypted copy, it removes the original. The import list of stage2.elf shows the unlink call, and the six-character mask matches it.

objdump -T stage2.elf | grep -iE 'unlink|remove|rename'
Click flag to reveal unlink

Resource Development

Q20 — It appears that the attacker has bought the malware strain from another hacker, what is their handle?

Neither the packer strings, the unpacked payload, the ELF tail nor the ransom notes name a seller, and the Pastebin link decoded from stage two was not fetched. The handle turned up in the Defender-named binary in the Administrator Downloads folder: its strings keep the build path from the author’s own machine, a home directory under Projects/Payloads, compiled on the same Debian GCC from Q16. The home directory name is the author’s handle. This is a deduction from a leaked build path rather than an explicit “seller” field, and the nine-character mask fits it.

strings MsMpEng.exe | awk 'length($0) > 9' | sort -u | grep /home/

Hint: The ransomware was built on the seller’s box. Look for compile-time paths in the strings, not for a name in the ransom note.

Click flag to reveal blitztide

Attack Summary

PhaseAction
Initial AccessContractor account used for the foothold on the server
Discoverywhoami and net user run from PowerShell
Privilege EscalationPsExec launched with -s -i to get a SYSTEM cmd.exe
Defense EvasionELF packed with AES-256-CBC and zlib, run from an in-memory file as a plain-named process
Defense EvasionStrings and extension table XOR-encoded; debugger check on /proc/self/status with a self-inflicted SIGSEGV
Discoverygetdents64 over the user profiles under /mnt/c/Users
ImpactTargeted files encrypted, renamed to .31337 (33 files), originals removed with unlink
ImpactRansom note with a Bitcoin address dropped

IOCs

TypeValue
Accountcontractor01
Command.\PsExec64.exe -s -i cmd.exe
SHA256 (sample)6b091a4a56a66f858d4c75eb72e16e87169526873147fc78cc4dc261d4cac000
SHA256 (stage two)aff1da66671714afa591007c55702c478b461a2dd2699a451f832860d7c17878
AES keya5f41376d435dc6c61ef9ddf2c4a9543c7d68ec746e690fe391bf1604362742f
AES IV95e61ead02c32dab646478048203fd0b
XOR keydaV324982S3bh2
memfd nametest
Process namePROGRAM
File extension.31337
Bitcoin address16ftSEQ4ctQFDtVZiUBusQUjRrGhM3JYwe
URL (decoded from stage two, not fetched)hxxps[://]pastebin[.]com/raw/Wgnjffdi
Build path (author)/home/blitztide/Projects/Payloads/LockPick3.0/lib/ssl

MITRE ATT&CK

TechniqueIDDescription
Valid AccountsT1078Contractor account used for initial access
System Services: Service ExecutionT1569.002PsExec to get a SYSTEM shell
Obfuscated Files or Information: Software PackingT1027.002AES-256-CBC and zlib packer
Deobfuscate/Decode Files or InformationT1140Stage one decrypts and inflates stage two; XOR-decoded config
Debugger EvasionT1622TracerPid check and self-inflicted SIGSEGV
File and Directory DiscoveryT1083getdents64 over /mnt/c/Users
Data Encrypted for ImpactT1486Files encrypted and renamed to .31337
Indicator Removal: File DeletionT1070.004Originals removed with unlink
Obtain Capabilities: MalwareT1588.001Strain bought from a third-party author whose build path leaked in the binary

Defender Takeaways

Linux binaries on a Windows server are a blind spot. The payload ran from the Windows profile paths through a Linux-style mount, so Windows-focused detection saw an executable it had no way to inspect. Alert on wsl.exe, bash.exe and ELF files appearing on servers that have no business with them, and remove the optional feature where it is not needed.

A contractor account reached SYSTEM with one PsExec line. The history shows recon and escalation back to back from a user-writable folder. Block PsExec and the Sysinternals suite from user profile paths with application control, and alert on service installs for PSEXESVC.

PSReadLine history is cheap, high-value evidence. Both the account and the escalation command were read straight from ConsoleHost_history.txt. Collect it centrally, because an actor who clears it removes the first two answers.

The packer hides the payload from file-based AV, not from behaviour. The stage one decrypt and the memfd_create call leave nothing on disk, but the mass rename to a new extension is loud in the $MFT and USN journal. A rate alert on one new extension across dozens of files catches this family regardless of packing.

Conclusion

A contractor foothold, one PsExec line and a Linux ransomware were enough to encrypt user data on a Windows server. The packing hid the payload, but the on-disk rename pattern, the PowerShell history and a leaked build path told the whole story without running the sample.