I recently pulled apart a small malware sample calling itself
WindowsPassKey.exe (SHA-256
a93c946c237b981189d2668d938a9d4d1d9681757e48dae8d9d65ed25b5da657,
25,600 bytes, SHA-1 1798612c9445ea7c411f269d984e2aaed4bfcaf0, MD5
e0f775585bd8580d5cdda0e7d91a370a). It's a 32-bit .NET Windows
Forms binary, and it's about as unsophisticated as malware gets — which
is exactly what makes it a useful teardown.
Caveat on method: my usual container-backed toolchain (capa, FLOSS, Ghidra) wasn't reachable for this sample — Docker/Podman wasn't running — so everything below comes from direct PE/.NET metadata parsing, managed IL inspection, and string/resource review on the host. No dynamic execution was performed. Static analysis can't prove the absence of every runtime behavior, but the application-defined method set here is small enough that I'm confident in the coverage.
The lure
On load, the form calls SetWindowPos (user32.dll)
with SWP_NOMOVE, SWP_NOSIZE, and topmost flags
— pinning itself above other windows without moving or resizing. It then
displays:
Windows Security found a threat.
Please verify it's you in order to remove threat
Please enter your login PIN
Classic pretext: borrow the visual authority of a system dialog, manufacture
urgency, and ask for a credential people are conditioned to hand over to
"Windows" itself. PE version metadata identifies the binary as
WindowsPassKey, version 1.0.0.0, original filename
WindowsPassKey.exe, with a 2025 copyright string.
The payload
The entire theft routine lives in button1_Click, and it's a
short, linear sequence:
- Read
textBox1.Textviaget_Text. - Compare against an empty string.
-
If non-empty, construct
new StreamWriter("output.txt", true)— thetrueopens in append mode. WriteLinethe submitted text.Dispose()the writer.Environment.Exit(0).
Because the file is opened in append mode, every successful submission across
multiple runs stacks up as a new line in the same output.txt,
written to the process's current working directory. The second button's click
handler is a bare return — dead weight, no action.
There's no network stack invoked anywhere in the reviewed IL: no sockets, no
HttpClient/WebRequest, no DNS resolution. No
persistence (no run-key writes, no scheduled-task or service creation
observed). No browser credential-store access, no process injection, no
follow-on payload staging. The mscoree.dll!_CorExeMain import
just confirms this is a standard managed executable — unremarkable on
its own.
Obfuscation and anti-analysis: essentially none
This is the part that made static analysis fast. Namespaces, classes, and
handlers keep their original descriptive names:
WindowsPassKey.Form1, button1_Click,
textBox1. Framework calls resolve cleanly (get_Text,
StreamWriter, WriteLine, Dispose,
Environment.Exit) with no reflection-based indirection or dynamic
assembly loading. No control-flow flattening, opaque predicates, or string
decoders in the application-defined methods.
The .text section does run ~7.23 bits/byte entropy, which on its
own might suggest packing — but the executable is managed and carries
embedded resources, both of which push entropy up without implying compression
or encryption. Given the fully transparent metadata and IL, I'd weight that
entropy reading as noise, not packing evidence.
On the anti-debugging side: no Debugger.IsAttached, no
IsDebuggerPresent/CheckRemoteDebuggerPresent/NtQueryInformationProcess,
no timing checks, no analysis-tool or VM/sandbox fingerprinting, no
exception-based detection, no self-modifying or dynamically generated code.
There is a System.Diagnostics.DebuggableAttribute reference and a
PE debug directory, but those are ordinary build artifacts, not controls. One
curiosity: the PE COFF timestamp decodes to
2078-09-15T04:25:14Z — implausibly future-dated, likely
altered or non-semantic, and not itself an anti-analysis mechanism.
Does a harvested PIN actually matter?
Windows Hello PINs are device-bound, so a PIN sitting in
output.txt isn't a portable credential the way a password would
be — it doesn't grant remote authentication on its own. But it's still
authentication material: combined with physical access to the machine, or
paired with other stolen data, it closes the gap between "has the laptop" and
"is fully authenticated on it." That's an inference about consequence, not an
additional capability observed in the binary.
Hunting notes
| Type | Value |
|---|---|
| SHA-256 |
a93c946c237b981189d2668d938a9d4d1d9681757e48dae8d9d65ed25b5da657
|
| SHA-1 | 1798612c9445ea7c411f269d984e2aaed4bfcaf0 |
| MD5 | e0f775585bd8580d5cdda0e7d91a370a |
| Internal filename | WindowsPassKey.exe |
| .NET type | WindowsPassKey.Form1 |
No domains, IPs, mutexes, registry paths, or scheduled-task/service names were
present to pivot on. output.txt is too generic a filename to
serve as a standalone IOC — treat it as corroborating context (an
unexplained append-mode text file next to a suspicious binary) rather than a
detection signature by itself.
Takeaway
Zero obfuscation, zero anti-analysis, zero network activity, zero persistence
— and yet the sample achieves its objective if even one victim believes
the dialog. This is a good specimen for the argument that the hard part of a
credential-theft attack is rarely the code; it's getting the target to trust
the box on their screen. The
if (!string.IsNullOrEmpty()) WriteLine() routine here is trivial.
The social engineering is doing 100% of the work.
Commentaires
Enregistrer un commentaire