Malware Forensics · False-Positive Anatomy
Wacatac.B!mlVerdict: Textbook false positive, and an instructive one. Microsoft Defender's machine-learning model fired on a legitimate, commercially-shipped game executable not because of any malicious code — there is none — but because the binary sits at the intersection of four independently-innocent features the model has learned to distrust: unsigned, MinGW/GCC-compiled, carrying an API set that resembles a shellcode loader, and exposing a TLS callback. The APIs the model keyed on are the MinGW C runtime's exception handler doing its ordinary job. The APIs a real trojan would need are entirely absent. The deeper lesson: an ML verdict can feed the OS reputation graph and harden from "scan warning" into an unbypassable launch block.
!ml actually meansThe .B!ml suffix on Trojan:Win32/Wacatac.B!ml is the tell. !ml means the verdict came from a machine-learning classifier, not a signature. There is no specific byte-pattern being matched — so there is nothing to "find" by disassembling the file looking for the malware. The model scored a cluster of features over a decision threshold. To understand the false positive you have to reconstruct the cluster.
| Feature | Why the model weights it "bad" | What it actually is here |
|---|---|---|
| Unsigned (cert table size = 0) | Legitimate commercial software is almost always Authenticode-signed; unsigned raises the baseline suspicion score. | The developer simply didn't sign this build. Common for indie/hobby releases. |
| MinGW-w64 / GCC toolchain | Most goodware in the training set is MSVC-compiled; a disproportionate share of MinGW console binaries in the wild are malware. The toolchain itself is a weak "bad" correlate. | Confirmed from symbols: _gnu_exception_handler, __mingw_oldexcpt_handler, libgcc, emutls.c. Just a GCC-built Windows binary. |
| Loader-shaped API set | VirtualAlloc + VirtualProtect + GetThreadContext + SetThreadContext + ResumeThread + IsDebuggerPresent + LoadLibraryA/GetProcAddress is the fingerprint of process hollowing / injection. | It's the MinGW C runtime. GCC's structured-exception unwinding manipulates thread contexts and flips page protections; IsDebuggerPresent is called during CRT startup; LoadLibrary/GetProcAddress resolve game_engine.dll and GL functions. |
| TLS callback | Malware uses TLS callbacks to run code before main(), ahead of a debugger's entry breakpoint. | MinGW's emulated-TLS (emutls). Entirely routine. |
Each feature is benign in isolation. The model does not judge them in isolation — it weights their co-occurrence, and this binary happens to present all four at once. That is the whole mechanism of the false positive.
A real injection/loader trojan is defined not by the APIs above but by the ones it needs to act — and every one of those is absent:
WriteProcessMemory, VirtualAllocEx, CreateRemoteThread, NtUnmapViewOfSection. Every flagged call operates only on the current process.CreateProcess, WinExec, ShellExecute.InternetOpen, WinHttp*, URLDownloadToFile. Every embedded URL is legitimate (the game's API domain, Discord/Reddit/YouTube links).CryptEncrypt.Read: the model saw unsigned + MinGW + context-manipulation APIs + TLS callback and scored the combination over threshold. There is no malicious code to chase in a disassembler, because the flagged APIs are the runtime, not the author's code — and the capabilities that would make it a trojan were never there.
The instructive twist is what happened after the verdict. The user added a Defender exclusion — and the program still would not start. The reason is that two separate enforcement layers were involved:
| Layer | Model | Consequence |
|---|---|---|
| Defender scanner | Block-by-detection. An exclusion suppresses future scans of a path. | The exclusion silenced the scanner but touched nothing else. (Also: an exclusion does not un-quarantine an already-removed file — that needs an explicit Restore.) |
| Smart App Control (SAC) | Allow-by-trust. Blocks any executable that is both unsigned and unknown/badly-reputed to Microsoft's cloud. | SAC never consults Defender exclusions. It has no per-app allowlist — it is all-or-nothing, and turning it off is a one-way door (re-enabling requires a full Windows reset). |
The launch error — "An application control policy has blocked this file. Malicious binary reputation." (0x11CC) — is the key artifact. It shows the !ml verdict had fed the Intelligent Security Graph: the binary was no longer merely "unknown" to SAC, it was actively marked bad by reputation. SAC looks up the exact hash, the cloud says malicious, and it hard-blocks at process creation — which is why launching through Steam changed nothing. An ML scan warning had propagated into an OS-level launch prohibition.