Case Study 10 HTB Foreigner Write-up Challenge
Foreigner Write-up
Description
This is my write-up for Sherlock Challange Foreigner, a malware reverse engineering using SAST method and TTPs against the dropper Wifi driver bundled with second stage malware in it in a DFIR scenario.
Scenario
An organization has requested assistance after a user followed instructions from a website claiming their system required an urgent fix. The site prompted the user to manually execute several commands to resolve the issue. Shortly afterward, the workstation began exhibiting suspicious behavior, including the execution of unknown processes and unexpected outbound network communications. The organization requires your help to analyze the provided artifacts
Questions
Q1 - What compiler timestamp is embedded in the malware binary? (UTC)
A1 - 2089-10-01 03:41:58 UTC. Found via objdump -p WiFi-22.160.0-Driver32-Win10.exe, PE header Time/Date field = raw hex e13e9b06 (decimal 3778976518). objdump flags this field as “reproducible build file hash, not a timestamp” expected for .NET/Mono deterministic builds, which write a hash into the TimeDateStamp field instead of real compile time. This date is bogus to the future, a indication of messing with .NET.

Verified in CyberChef too:

Q2 - What is the name of the suspicious packed section found in the executable?
A2 - .CSS. Found via objdump -h command. A non-standard PE section name, size 0x22600 (~139KB, larger than .text), strange section that need futher investigation.

Q3 - Which encryption algorithm is used by the malware?
A3 - RC4. Found via decompiling Program.cs (main assembly) with ILSpy:

Verified by reimplementing standard RC4 (KSA+PRGA) in Python against dataKey (36-byte key) and inputData (1355-byte ciphertext) hardcoded in Program.cs. Verified against cyberchief too:

Q4 - Which WinAPI function is leveraged to execute the shellcode?
A4 - ResumeThread is the one responsible, we also have other WinAPI calls like CreateProcessW, VirtualAlloc, GetThreadContext, ReadProcessMemory, VirtualAllocEx, WriteProcessMemory, SetThreadContext. Which makes the extracted bin to point the execution to its EIP with SetThreadContext and execute with ResumeThread besides the allocation with memory, write of memory and other process that clearly try to implant the injection

Q5 - Which process is targeted by the shellcode for injection?
A5 - MSBuild.exe, via xxd we could find the path it will try to inject to, giving the complete path to C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe. So its going to use a signed Microsoft binary for defense evasion using the technique known as living-off-the-land though the injection of that binary.
Bellow is the evidence collected:

Q6 - What technique is used to execute the second-stage payload?
A6 - Its the Reflective DLL Injection. Using the strings command there is some evidence that it uses VirtualAllocEx, WriteProcessMemory and finally the LoadLibrary a bit bellow it’s as >Loadu...aryAu which alignes perfectly with MITRE ATT&CK T1055.001 making this second-stage payload run entirely from memory with no file ever written to the disk.

Note — extracting the real second-stage payload: To get the real second-stage payload for the remaining questions: insde the .CSS section’s raw bytes directly from the original sample via objdump -h (file offset 0x2800, size 0x22600) + dd, then RC4-decrypted them with _key using the same rc4_decrypt.py from Q3. Result starts with 4D 5A (MZ) and file confirms PE32 executable for MS Windows 6.00 (GUI), Intel i386, 7 sections, showing that the bigger picture of the second-stage payload of the malware.




Q7 - Which Computer name does the second-stage malware check for to evade analysis environments? (lowercase)
A7 - hal9th. The malware is checking after the call of GetComputerNameW and GetUserNameW WinAPI call. Soon after we have the Ghidra decompiler version bellow showing it will exit the malware in the second-stage to prevent automated analysis enviroments:
if ((((local_720[0] == L'J') && (local_716 == L'o')) && (local_76c == 0x7)) &&
(((local_714 == L'e' && (local_520[0] == L'H')) && (local_51a == L'9')))) {
return 0;
}
OSInt in VirusTotal/tria.ge (tria.ge/260403-yr8feadx2v) classifies this second-stage payload as Vidar Stealer, and eSentire’s Vidar Stealer analysis documents this exact anti-sandbox trick: Vidar checks for the HAL9TH computer name / JohnDoe username pair. The character fragments line up exactly (H__9__ → HAL9TH, J____oe len 7 → JohnDoe), confirming the family and the check.

Q8 - What string does the second-stage malware register to prevent multiple executions on the same host? (Format lowercase: string)
A8 - approve_april. Analysing the disassembly of the entry point of the function after the anti-analysis demonstrated in A7, we have a string approve_apri + l after the buffer. Completing the string found for the A8, it is used as a single-instance guard: OpenEventA(0x1F0003, 0, "approve_april") tries to open an existing event by that name, and if it already exists (another instance is running) the code loops closing/retrying via CloseHandle; if not, CreateEventA(NULL, 0, 0, "approve_april") registers it for the first time, accordingly with Microsoft’s official WinAPI documentation on event objects. After some digging in other research about this malware family, I could have found that it indeed uses a multithreaded architecture during its operations

Q9 - Which function address initializes the malware’s internal string structure by setting the fields to zero?
A9 - 0x0040f3b0. Found by tracing the anti-sandbox check in A7 forward: right after the sandbox-check function returns, execution falls into a call at 0x0041761b which jumps to an internal routine at 0x00416050. That routine calls 0x0040f3b0 four times.
Disassembly of 0x0040f3b0:
40f3b0: mov eax,ecx
40f3b2: mov DWORD PTR [ecx+0x8],0x0
40f3b9: mov DWORD PTR [ecx],0x0
40f3bf: mov DWORD PTR [ecx+0x4],0x0
40f3c6: ret

Q10 - Besides FileZilla, which other file transfer client is targeted for credential extraction?
A10 - WinSCP. Function 0x0040c8c0 reads the WinSCP registry configuration. Its disassembly opens Software\Martin Prikryl\WinSCP 2\Configuration, reads the Security\UseMasterPassword value, then opens Software\Martin Prikryl\WinSCP 2\Sessions and enumerates saved sessions. The routine formats session data with the Soft: WinSCP and Host: labels, and queries values including HostName and PortNumber. These direct WinSCP-specific registry operations confirm that WinSCP is the other targeted file-transfer client.

Q11 - What is the function address that starts crawling the Steam process by acquiring its handle?
A11 - 0x0040f170. Found by tracing forward from the sandbox/mutex checks (A7/A8): this function hardcodes the string steam.exe (resolved at .rdata VA 0x41f453 via strings -t x) and pushes it into 0x00411250, a generic process-finder that walks CreateToolhelp32Snapshot + Process32First/Process32Next comparing process names, returning the matching PID. 0x0040f170 then passes that PID into 0x0040ee70, which calls OpenProcess on it (confirmed via IAT resolution: ds:0x4203b0 = OpenProcess, args 0x1fffff/0/PID) and goes on to ReadProcessMemory (ds:0x4203c4) against it. 0x00411250 and 0x0040ee70 are both generic, reusable utilities (no hardcoded process name); 0x0040f170 is the one that is Steam-specific and kicks off the whole crawl-and-handle sequence. Ghidra corroborates this independently: it auto-names the function steam-injection-process, shows the s_steam.exe_0041f453 string push, the call into inject-2-process (our 0x00411250), and the call into func-openprocess-memory-copy-exfiltration (our 0x0040ee70), cross-referenced from steam_exfiltration:0040f5db8.

Q12 - What type of authentication artifact does the second-stage malware search for in Steam’s process memory?
A12 - steam_tokens.txt. Traced from 0x0040f170 (Q11): once the handle to steam.exe is acquired, 0x00415da0 builds a target filename via 0x0040f5b0 (GetProcessHeap/HeapAlloc/lstrcpyA/lstrcatA string concat) using the literal at .rdata VA 0x0041efa6, pushed at instruction 0x00415e00 inside 0x00415da0. Raw bytes at file offset 0x1d5a6 read Soft\Steam\steam_tokens.txt (truncated registry-style path, i.e. Software\Valve\Steam\steam_tokens.txt), sitting in a cluster of other targeted filenames (_key.txt, passwords.txt, information.txt). steam_tokens.txt is Steam’s local session/authentication token file, confirming the malware crawls Steam’s process memory looking for this token artifact to exfiltrate saved login sessions.

Q13 - What unique hardcoded identifier does this second-stage malware instance use to identify itself during data exfiltration?
A13 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/131.0.0.0 Safari/537.36 OPR/116.0.0.0. This hardcoded User-Agent string sits in .data alongside the Telegram channel string (repeated several times, interleaved with the channel/boundary constants every ~0x600 bytes). It is pushed as the User-Agent header on the multipart HttpOpenRequestA/HttpSendRequestA calls used for exfiltration (the same call sites that push the Content-Type: multipart/form-data; boundary=---- string at 0x41f9de, referenced from 4 separate POST-building routines at 0x402224, 0x402837, 0x4030a5, 0x403c0f). Since this exact UA never belongs to the victim’s real browser, it acts as a fixed signature on this malware’s own outbound traffic — the hardcoded identifier used while posting stolen data.

Q14 - What special marker string did the second-stage malware use in the Discord crawling process?
A14 - dQw4w9WgXcQ (the YouTube video ID for Rick Astley’s “Never Gonna Give You Up” — a Rickroll). Found at file offset 0x1dd8f (VA 0x0041f78f) in .rdata, sitting in the same string cluster as \discord\ and Local State. Pinned down the containing function (starting ~0x00414600): it builds the path to Discord’s Electron Local State file via lstrcatA chains using the literals \discord\ (0x414652) and Local State (0x414664), reads/parses that file’s content (call 0x4106a0 at 0x414679), then runs StrStrA(parsed_content, "dQw4w9WgXcQ") at 0x41477b. If the marker is found, it reads the field located +0xc bytes past the match (add esi, 0xc at 0x41478e) and bails out entirely if it’s absent (je 0x4148ce). This confirms dQw4w9WgXcQ is a hardcoded sentinel/anchor the malware searches for inside the parsed Discord state data to locate the field it wants to exfiltrate.

Q15 - Which messaging platform does the second-stage malware exfiltrate stolen data to?
A15 - Telegram. Traced via 0x415a10, which loops over candidate strings and runs strstr() (confirmed as a direct thunk to msvcrt.dll_strstr at 0x41d07c) against two deliberately truncated fragments stored in .rdata: .me/ (0x41f94f) and amcommunity.com (0x41f27c, with the ste prefix dropped). Storing fragments instead of the full domains means strings/YARA scans of the binary never surface the literal, flaggable domains t.me or steamcommunity.com, while the substring check still matches genuine URLs at runtime. Matches are handed to 0x415810/0x4158c0, both of which call the WinInet GET wrapper at 0x403850 (InternetOpenA -> InternetConnectA -> HttpOpenRequestA with method GET -> HttpSendRequestA -> HttpQueryInfoA checking for HTTP 200 -> InternetReadFile). Combined with the hardcoded channel found in A13 (hxxps://t[.]me/l793oy), this confirms Telegram as the exfiltration channel.

Conclusion
This challange is a good practice to test the defender skills gainst a very real type of malware (Vidar info Stealer). Mapped to MITRE ATT&CK, this chain touches T1027 (obfuscated/RC4-encrypted payloads and fragmented IOC strings), T1055.012 (process hollowing into MSBuild.exe via CreateProcessW/WriteProcessMemory/SetThreadContext/ResumeThread), T1055.001 (reflective DLL loading of the second stage via VirtualAllocEx/WriteProcessMemory/LoadLibraryA), T1497 (sandbox/anti-analysis checks via computer name/username), T1552/T1555 (credentials from password stores/files — FileZilla, WinSCP, Steam, Discord), and T1071.001/T1567 (application-layer C2 and exfiltration over a legitimate web service, Telegram)
The exfiltration part (Q13 to Q15) is the easies part, since the binary second stage binary is already discovered after the attempt to obfuscation though the RC4-encrypted payload inside the .NET . Its only about searching and reverse engeneering the “plain” binary that is already there with all the strings and assembly code.
Finally, with the right tools and a bit of patiance to put the malware to the test without the needing to do DAST analysis just with SAST analysis. It is easily reverse engeneered to the core and extract IoCs (steam community and telegram channel with User-Agent hardcoded in the malware) as well its TTPs to single it out as Vidar info Stealer