// HackTheBox  ·  writeup

Foreigner

HackTheBox Hard pefilednfilePyCryptodomeCapstonestrings7-Zip
Malware Analysis

Scenario

This Sherlock challenges you to analyze a fake WiFi driver update delivered via a social engineering FixIt site. Through static analysis of a packed .NET binary, you’ll decrypt RC4-encrypted shellcode, trace process hollowing into MSBuild.exe, and reverse engineer a second-stage payload performing sandbox evasion, credential theft from browsers and FTP clients, Steam JWT scraping, Discord token extraction, and Telegram-based exfiltration.

Executive Summary

A fake WiFi driver update delivered through a social-engineering site shipped a packed .NET loader. The loader decrypts an embedded shellcode blob with RC4, and the shellcode hollows a legitimate .NET build tool to host the real payload. A second RC4 pass over an oversized high-entropy section yields a native second stage, a commodity-style information stealer. It refuses to run on a known analysis machine name, guards against double execution with a named event, and then harvests browser and FTP client credentials, Steam session tokens read from process memory, and Discord tokens. The loot is tagged with a hardcoded build identifier and exfiltrated through a messaging platform.

Artifacts

Address note: Both address questions only accept the 0x prefix with uppercase hex (0x40F3B0 style), even though the answer mask is all stars. Bare 0040F3B0, lowercase and RVA forms (0000F3B0) are all rejected.

Defense Evasion

Q1 — What compiler timestamp is embedded in the malware binary? (UTC)

Extract the archive and read FILE_HEADER.TimeDateStamp with pefile. The value decodes to a date far in the future, so the compiler timestamp is forged rather than a real build time. The date is still what the question wants.

7z x -p<password> evidence/malware.zip -o work/s1
python3 -I -c "import pefile,sys,datetime;pe=pefile.PE(sys.argv[1]);print(datetime.datetime.fromtimestamp(pe.FILE_HEADER.TimeDateStamp,datetime.UTC))" work/s1/<sample>.exe

Tip: A 2089 timestamp is a deliberate fake. Don’t treat it as an error in your tooling.

Click flag to reveal 2089-10-01 03:41:58
Q2 — What is the name of the suspicious packed section found in the executable?

Walk the section table in pefile and compare raw size and entropy. One non-standard section is about 140 KB at entropy 8.0, which is encrypted or compressed data and not code or resources.

python3 -I -c "import pefile,sys;pe=pefile.PE(sys.argv[1]);[print(s.Name,s.SizeOfRawData,s.get_entropy()) for s in pe.sections]" work/s1/<sample>.exe
Click to reveal answer .CSS
Q3 — Which encryption algorithm is used by the malware?

The sample is .NET, so dump the metadata with dnfile and read the loader’s type and method names. A method named DeleteSentence runs a key-scheduling loop followed by a keystream loop, the shape of RC4’s KSA and PRGA. Proof came from decrypting: RC4 with a 36-byte key at RVA 14608 over the 1355-byte blob at RVA 14648 produces readable shellcode.

python3 -I -c "import dnfile,sys;pe=dnfile.dnPE(sys.argv[1]);[print(m.Name) for m in pe.net.mdtables.MethodDef]" work/s1/<sample>.exe
python3 -I work/rc4_dec.py work/s1/<sample>.exe > work/sc.bin

Gotcha: The shellcode’s own strings are not the key. The 36-byte key sits in the loader’s data, next to the blob.

Click flag to reveal RC4

Execution

Q4 — Which WinAPI function is leveraged to execute the shellcode?

No decompiler was available, so read the ImplMap table through dnfile to list the loader’s P/Invoke imports. Two stand out: VirtualProtect to make the buffer executable, and CallWindowProcA. Passing the shellcode address as the window procedure runs it as a callback, a known way to avoid CreateThread.

python3 -I -c "import dnfile,sys;pe=dnfile.dnPE(sys.argv[1]);[print(r.ImportName) for r in pe.net.mdtables.ImplMap]" work/s1/<sample>.exe
Click to reveal answer CallWindowProcA
Q5 — Which process is targeted by the shellcode for injection?

Run strings on the decrypted shellcode. It carries a full path to the .NET Framework v4.0.30319 directory ending in the host binary, which is the process it spawns and hollows.

strings -a work/sc.bin
Click flag to reveal msbuild.exe
Q6 — What technique is used to execute the second-stage payload?

Disassemble the shellcode with capstone and list its API calls: CreateProcessW (suspended host), GetThreadContext, WriteProcessMemory, SetThreadContext, ResumeThread. That sequence replaces the host’s image and redirects its entry point, which is process hollowing.

python3 -I work/dis_sc.py work/sc.bin
Click to reveal answer process hollowing

Second stage extraction: A second RC4 pass, with an 11-byte key at RVA 14592, over the .CSS section from Q2 produces a PE starting with MZ. Every remaining question is answered from this file.

python3 -I work/rc4_stage2.py work/s1/<sample>.exe > work/stage2.bin

Discovery

Q7 — Which Computer name does the second-stage malware check for to evade analysis environments? (lowercase)

Disassemble stage2.bin and find the GetComputerNameW call. At 0x4173dc through 0x417606 it compares the returned wide string one character at a time, a chain that spells out H, A, L, 9, T, H. A match exits the malware, and HAL9TH is a known sandbox machine name. This one was reasoned from the compare chain, not read as a single string, because the characters are split across instructions.

python3 -I work/dis.py work/stage2.bin | grep -n -E '4173d|4174|4175|4176'
Click flag to reveal hal9th
Q8 — What string does the second-stage malware register to prevent multiple executions on the same host? (Format lowercase: string)

Near 0x416080 the code builds a string on the stack: a 16-byte movaps from 0x41e3b0 followed by a single byte l. Combined, that gives the name passed to OpenEventA/CreateEventA, the run-once marker.

python3 -I work/dis.py work/stage2.bin | grep -n -E '416080|41e3b0'

Tip: The string is never contiguous in .rdata. Rebuild it from the stack stores instead of searching for it with strings.

Click to reveal answer approve_april

Credential Access

Q9 — Which function address initializes the malware's internal string structure by setting the fields to zero?

While tracing the event-name code from Q8, the string object is first passed to a tiny function at 0x40f3b0 that zeroes [ecx], [ecx+4] and [ecx+8], the three fields of the stealer’s custom string type.

python3 -I work/dis.py work/stage2.bin | grep -n -A6 '40f3b0:'

Gotcha: The answer needs the 0x prefix and uppercase hex. 0040F3B0, 0040f3b0 and 0000F3B0 (RVA) were all rejected.

Click flag to reveal 0x40F3B0
Q10 — Besides FileZilla, which other file transfer client is targeted for credential extraction?

strings over the second stage shows the registry path Software\Martin Prikryl\WinSCP 2, WinSCP’s stored-session key.

strings -a work/stage2.bin | grep -i -E 'winscp|filezilla|prikryl'
Click to reveal answer winscp
Q11 — What is the function address that starts crawling the Steam process by acuiring its handle?

Find the steam.exe string at 0x41f453. It is referenced from a wrapper at 0x40f170, which calls 0x40ee70. That function holds OpenProcess (0x40eeb7) and ReadProcessMemory (0x40ef95), so it is the one that acquires the handle and starts reading. The wrapper and a later helper at 0x415DA0 are neighbours, not the answer.

python3 -I work/dis.py work/stage2.bin | grep -n -E '41f453|40f170|40ee70|40eeb7|40ef95'

Gotcha: The wrapper that references steam.exe is not the handle-acquiring function. Follow its call to the one that contains OpenProcess.

Click flag to reveal 0x40EE70
Q12 — What type of authentication artifact does the second-stage malware search for in Steam's process memory?

The Steam routine scans the process memory it read in Q11 for a token prefix beginning eyJ, the base64 start of a JSON header. That prefix is how JWTs begin. This was concluded from the eyJ pattern and the Steam context, not from a string that names the artifact.

strings -a work/stage2.bin | grep -n 'eyJ'
Click to reveal answer JWT
Q14 — What special marker string did the second-stage malware use in the Discord crawling process?

In .rdata at 0x41f78f sits an 11-character string, dQw4w9WgXcQ, among the Discord strings. It looks like a YouTube video ID, an odd value to find in a token scraper, which marks it as a planted marker. This is inferred from its position beside the Discord strings rather than from a traced code path.

strings -a -t x work/stage2.bin | grep dQw4w9WgXcQ
Click flag to reveal dQw4w9WgXcQ

Exfiltration

Q13 — What unique hardcoded identifier does this second-stage malware instance use to identify itself during data exfiltration?

A 32-character hex string at 0x41e020 in .rdata is the build identifier sent with the stolen data.

strings -a -t x work/stage2.bin | grep -E '[0-9a-f]{32}'
Click to reveal answer ee4724586a69cda3db87344f07509f40
Q15 — Which messaging platform does the second-stage malware exfiltrate stolen data to?

The .data section holds a t.me/l793oy link, a Telegram channel that serves as the dead drop and C2 pointer.

strings -a work/stage2.bin | grep -E 't\.me/'
Click flag to reveal telegram

Attack Summary

PhaseAction
Initial AccessVictim lured by a social-engineering site into running a fake WiFi driver update
Defense EvasionPacked .NET loader with a forged 2089 compile timestamp and a high-entropy .CSS section
Defense EvasionLoader RC4-decrypts shellcode, then RC4-decrypts .CSS into the second-stage PE
ExecutionShellcode run through CallWindowProcA after VirtualProtect
ExecutionProcess hollowing of MSBuild.exe
Defense EvasionSecond stage exits on a known sandbox computer name and sets a named event to block double runs
Credential AccessBrowser and FTP client (FileZilla, WinSCP) credentials harvested
Credential AccessSteam process memory read for JWTs; Discord tokens crawled
ExfiltrationData sent with a hardcoded build ID to a Telegram channel

IOCs

TypeValue
Compile timestamp (forged)2089-10-01 03:41:58
PE section.CSS (entropy 8.0, about 140 KB)
Injection targetC:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe
Sandbox computer namehal9th
Run-once event nameapprove_april
Build IDee4724586a69cda3db87344f07509f40
Discord markerdQw4w9WgXcQ
Exfil channelt[.]me/l793oy
Registry key readSoftware\Martin Prikryl\WinSCP 2

MITRE ATT&CK

TechniqueIDDescription
User Execution: Malicious FileT1204.002Fake WiFi driver update run by the victim
Obfuscated Files or Information: Software PackingT1027.002Encrypted .CSS section holding the second stage
Deobfuscate/Decode Files or InformationT1140Two RC4 passes in the loader
Process Injection: Process HollowingT1055.012Shellcode hollows MSBuild.exe
Virtualization/Sandbox Evasion: System ChecksT1497.001Computer name compared against a sandbox name
Execution GuardrailsT1480Named event prevents repeat execution
Credentials from Password Stores: Credentials from Web BrowsersT1555.003Browser credential theft
Unsecured Credentials: Credentials In FilesT1552.001FileZilla and WinSCP stored sessions
Steal Application Access TokenT1528Steam JWTs and Discord tokens
Exfiltration Over Web ServiceT1567Stolen data posted to Telegram

Defender Takeaways

A future compile timestamp and a lone 8.0-entropy section are enough to triage a sample as packed. Both are visible in the PE header without running anything. A static rule on an oversized high-entropy non-standard section name catches the loader before detonation.

MSBuild.exe spawned by an unexpected parent, then making network connections, is a hollowing signal. MSBuild has no reason to talk to the internet outside a build server. Alert on it running from a user context and on suspended-process creation followed by WriteProcessMemory into it.

Callback-based shellcode execution skips the usual thread-creation telemetry. CallWindowProcA with a freshly VirtualProtected buffer produces no CreateThread event. Hunt for executable memory changes in .NET processes instead of relying on thread-start detections.

Stored FTP sessions, Steam and Discord tokens are all readable by the user’s own processes. Stop saving WinSCP and FileZilla passwords on workstations, and rotate Steam and Discord sessions after any stealer detection, since JWTs remain valid after a password change until they expire.

Traffic to Telegram from a workstation that never used it is a cheap egress detection. The exfil path rides a mainstream platform over HTTPS, so blocking or alerting on the domain for non-approved hosts removes the channel.

Conclusion

The attacker needed no exploit: a believable driver-update lure, two RC4 layers and a hollowed MSBuild.exe were enough to run a credential stealer that harvested FTP, Steam and Discord secrets and sent them out through Telegram. Static analysis alone, headers, metadata and a disassembler, unwound the full chain without detonating anything.