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.
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.
WinServer-Collection.zip → unzip with the password from DANGER.txt. Holds the user profiles, $MFT, $J and the sampleConsoleHost_history.txt under the contractor profile → read as textMFTECmd, read the CSV with Pythonsample.elf, a 64-bit Linux ELF, and the Defender-named copy in the Administrator Downloads folder → objdump, readelf, strings, Python. Static analysis only, never executed*.note files left in the encrypted folders → read as textFormat 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.
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
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
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.
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
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
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\.'
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
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
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
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
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.
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'
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
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
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.
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/'
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:
readdir64is the obvious guess and has the wrong length. Count the asterisks in the format hint before submitting.
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
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\.'
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
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' .
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'
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.
| Phase | Action |
|---|---|
| Initial Access | Contractor account used for the foothold on the server |
| Discovery | whoami and net user run from PowerShell |
| Privilege Escalation | PsExec launched with -s -i to get a SYSTEM cmd.exe |
| Defense Evasion | ELF packed with AES-256-CBC and zlib, run from an in-memory file as a plain-named process |
| Defense Evasion | Strings and extension table XOR-encoded; debugger check on /proc/self/status with a self-inflicted SIGSEGV |
| Discovery | getdents64 over the user profiles under /mnt/c/Users |
| Impact | Targeted files encrypted, renamed to .31337 (33 files), originals removed with unlink |
| Impact | Ransom note with a Bitcoin address dropped |
| Type | Value |
|---|---|
| Account | contractor01 |
| Command | .\PsExec64.exe -s -i cmd.exe |
| SHA256 (sample) | 6b091a4a56a66f858d4c75eb72e16e87169526873147fc78cc4dc261d4cac000 |
| SHA256 (stage two) | aff1da66671714afa591007c55702c478b461a2dd2699a451f832860d7c17878 |
| AES key | a5f41376d435dc6c61ef9ddf2c4a9543c7d68ec746e690fe391bf1604362742f |
| AES IV | 95e61ead02c32dab646478048203fd0b |
| XOR key | daV324982S3bh2 |
| memfd name | test |
| Process name | PROGRAM |
| File extension | .31337 |
| Bitcoin address | 16ftSEQ4ctQFDtVZiUBusQUjRrGhM3JYwe |
| URL (decoded from stage two, not fetched) | hxxps[://]pastebin[.]com/raw/Wgnjffdi |
| Build path (author) | /home/blitztide/Projects/Payloads/LockPick3.0/lib/ssl |
| Technique | ID | Description |
|---|---|---|
| Valid Accounts | T1078 | Contractor account used for initial access |
| System Services: Service Execution | T1569.002 | PsExec to get a SYSTEM shell |
| Obfuscated Files or Information: Software Packing | T1027.002 | AES-256-CBC and zlib packer |
| Deobfuscate/Decode Files or Information | T1140 | Stage one decrypts and inflates stage two; XOR-decoded config |
| Debugger Evasion | T1622 | TracerPid check and self-inflicted SIGSEGV |
| File and Directory Discovery | T1083 | getdents64 over /mnt/c/Users |
| Data Encrypted for Impact | T1486 | Files encrypted and renamed to .31337 |
| Indicator Removal: File Deletion | T1070.004 | Originals removed with unlink |
| Obtain Capabilities: Malware | T1588.001 | Strain bought from a third-party author whose build path leaked in the binary |
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.
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.