Preamble

I did that thing you are not supposed to do: Digging up a random .exe in a GitHub repo, and running it before I audited it or built it from its source. It was an unofficial “pro” fork of an otherwise legitimate open-source developer tool—the kind of repo that ships a compiled binary next to the source and asks you to trust that the two match. It worked. Then, about 2 minutes later, the paranoia set in. Was that gonna keylog me? Screenshot my desktop? Did it just phone home with my session tokens? Had it quietly installed persistence so it survives a reboot?

That nagging feeling to nuke the drive, reinstall Windows, rotate every password in a panic was starting to set in. The calmer reaction to answer that question with evidence: is there anything malicious here, and did running it leave anything behind? This post is the workflow I used to address this calmly/sainly. It is two-pronged: static source review of the repository, and live host forensics on the machine where the binary had already executed. Every host command below is a read-only diagnostic—nothing here changes the system, which matters when you are trying to preserve evidence rather than stomp on it.

The tools are PowerShell, rg (ripgrep), and Windows’ own security stack. The ethics frame is the same one I use for Metasploit in a Controlled Lab After Nmap and sqlmap Basics with a Defensive Mindset: only touch systems you own, and stay honest in the write-up.


Framing: “malware” and “untrustworthy” are two different verdicts

Before touching a command, separate the two questions that paranoia fuses together:

  1. Is it malware? Does it keylog, capture the screen, exfiltrate data, install persistence, or open a command-and-control channel?
  2. Is it trustworthy? Is it signed, does it match its source, and does it do only what it claims?

An executable can be not malware and still not trustworthy—an unsigned binary from an anonymous fork author is exactly that. Keeping these separate stops you from either declaring “it’s fine” too early or “it’s a virus” without proof. The output you want at the end is a small verdict table, not a vibe.


Part 1 — Static source review

The repo shipped source and a binary. The source cannot prove the binary is clean (more on that later), but it is the cheapest place to find intent, and it tells you what the app believes it does.

1.1 Hunt for keylogging and screen capture

Case-insensitive rg across src/ for the APIs that spyware actually needs:

rg -i "getDisplayMedia|desktopCapturer|screenshot|captureScreen|getUserMedia|MediaRecorder" src
rg -i "keylog|keystroke|robotjs|iohook|GetAsyncKeyState|SetWindowsHookEx" src
rg -i "keydown|keypress|keyup|onKeyDown|addEventListener\(.key" src

The trick is reading the context, not counting hits. In my case:

  • No screen-capture APIs anywhere.
  • No keyboard-logging libraries (robotjs, iohook) or Win32 hooks.
  • Every keydown/keyup was a legitimate UI handler—accessibility, search filters, editor focus, tab/list navigation.
  • The scary-looking keyLog matches were a TLS key log file feature (SSLKEYLOGFILE, for decrypting your own captured TLS in Wireshark)—not keyboard logging.

That last one is the whole lesson: a substring match is a question, not a finding. keyLog and keylogger share five letters and nothing else.

1.2 Look for obfuscation and dynamic execution

Payloads hide behind runtime evaluation and encoding:

rg -i "eval\(|new Function|atob\(|fromCharCode|\\\\x[0-9a-f]{2}" src

Here the matches were all in developer tools components—base64/hex/JWT/AES/QR helpers—which is exactly what an HTTP-debugging-style app is supposed to contain. No packed blobs, no hidden second stage.

1.3 Trace the network / exfiltration surface

rg -i "fetch\(|axios|\.post\(|XMLHttpRequest|sendBeacon|new WebSocket\(" src
rg -o -i "https?://[a-z0-9._/-]{4,80}" src | Sort-Object -Unique

Account for every outbound destination:

  • App/server calls targeted localhost only (127.0.0.1 on a fixed port, ws://127.0.0.1:...), confirmed in the .env.* files.
  • Service-worker fetch was same-origin cache logic.
  • The only external telemetry was Google Firebase Analytics—normal-shape analytics (event names, error messages, stack traces), but pointed at the fork author’s project rather than upstream.

Telemetry redirected to a stranger’s account is a trust problem, not a keylogger. Naming it precisely is the point.

1.4 The “crack” finding

The account/licensing module had been rewritten so isLoggedIn and isPaidUser are hardcoded true, with a dummy user object. That confirms the repo is a pirated build of the tool’s commercial features. Again: a licensing/trust issue, not malware—but exactly the sort of thing that tells you why an anonymous author bothered to fork and rebuild.


Part 2 — Binary analysis

Source review only covers the source. The shipped .exe is what actually ran, and there is no buildable desktop source to rebuild it from—so it has to be inspected as an opaque artifact.

2.1 Identity and hashing

Get-FileHash .\mystery.exe -Algorithm SHA256
(Get-Item .\mystery.exe).VersionInfo | Format-List

Record the SHA-256 and the embedded version metadata. The hash is your anchor for a VirusTotal lookup—paste it into https://www.virustotal.com/gui/file/<sha256>. If the hash has never been seen, submit the file for a fresh multi-engine scan. (Mind that uploading a private build shares it publicly; a hash lookup alone does not.)

2.2 Digital signature

Get-AuthenticodeSignature .\mystery.exe | Format-List

Mine came back NotSigned. That single word is the core trust verdict: the binary cannot be attributed to a publisher and cannot be verified against source. Not proof of malice—plenty of legitimate hobby builds are unsigned—but it is why “not malware” is the ceiling on how much you can trust it.

2.3 Strings and capability inspection

You can read a surprising amount straight out of a binary with rg -a (treat-as-text):

rg -a -o "https?://[a-zA-Z0-9._/-]{4,80}" mystery.exe | Sort-Object -Unique
rg -a -c -i "GetAsyncKeyState|SetWindowsHookEx|keybd_event|BitBlt|GetDC|CreateCompatibleBitmap|GetClipboardData" mystery.exe
rg -a -o ".{60}GetAsyncKeyState.{60}" mystery.exe
rg -a -o ".{60}BitBlt.{60}" mystery.exe

This is where paranoia peaks and then deflates. GetAsyncKeyState and BitBlt do appear in the binary—both classic keylogger/screen-grabber primitives. But read the neighbours:

...EnableMenuItem SetWindowLongW GetKeyboardState GetAsyncKeyState CreateIcon DrawIconEx...
...DwmGetWindowAttribute BitBlt CreateDIBSection SelectObject DeleteDC ReleaseDC...

Those are the standard GUI/windowing runtime—menu accelerators and window drawing pulled in by the desktop framework (a Tauri/WebView2 shell, per other strings like tauri_runtime_wry and WebView2 references). There was no SetWindowsHookEx, no keybd_event, no GetClipboardData, and no screenshot-to-network chain. An API being present is not the same as it being wired into a malicious flow. Context—the surrounding symbols and whether any exfiltration path consumes them—is what separates a keylogger from a menu bar.


Part 3 — Live host forensics (it already ran)

The binary had executed, so the real question shifts from “what could it do” to “what did it leave behind.” Everything here is read-only.

3.1 Processes and their children

Malware often spawns shells, script hosts, or droppers. Walk the tree:

Get-Process | Where-Object { $_.ProcessName -like "*mystery*" } |
  Select-Object Id, ProcessName, Path, StartTime
Get-CimInstance Win32_Process -Filter "ParentProcessId=<PID>"

The only child was the expected embedded browser process (msedgewebview2.exe) launched by the desktop shell. No shells, no powershell.exe/wscript.exe children, no dropper.

3.2 Network connections

Get-NetTCPConnection -OwningProcess <PID> |
  Where-Object { $_.RemoteAddress -notin @('127.0.0.1','0.0.0.0','::','::1') }

No non-local TCP connections from the process or its WebView child at inspection time. No command-and-control beacon holding a socket open.

3.3 Persistence

The high-value question for “will it survive a reboot.” Check every common autostart surface:

Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run'
Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run'
Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce'
Get-ChildItem "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
Get-ScheduledTask | Where-Object { $_.TaskName -like "*mystery*" }
Get-CimInstance Win32_Service | Where-Object { $_.PathName -like "*mystery*" }

The Run keys held only pre-existing, recognizable software; RunOnce and the Startup folder were empty; no matching scheduled task or service. Nothing tied itself to boot. This is the check that most directly dissolves the “it’s still in there” fear.

3.4 Files it created

Get-ChildItem "$env:LOCALAPPDATA\<app-data-dir>" -Recurse
Get-ChildItem "$env:USERPROFILE\Desktop\<extract-folder>" -Force

Everything mapped to a standard Edge WebView2 profile (Crashpad, GPUCache, ShaderCache, Local State) plus the tool’s own bundled Node server directory. Nothing anomalous, nothing written outside the app’s own folders.

3.5 Root CA certificates — the one that actually matters here

For any tool in the traffic-interception family, the most security-sensitive artifact it can create is a trusted root CA. If one is installed, the tool (or anyone with its key) can transparently MITM your HTTPS. So check both stores explicitly:

Get-ChildItem Cert:\CurrentUser\Root  | Where-Object { $_.Subject -like "*<tool-name>*" }
Get-ChildItem Cert:\LocalMachine\Root | Where-Object { $_.Subject -like "*<tool-name>*" }

None installed. (Such a CA is only added when interception is actively started, which had not happened.) If you do find a rogue root CA, that is the one artifact worth removing immediately, not just noting.

3.6 Antivirus status and an on-demand scan

Finally, ask the defender that was already watching:

Get-MpComputerStatus | Select-Object AntivirusEnabled, RealTimeProtectionEnabled,
  AntivirusSignatureLastUpdated, IsTamperProtected
Get-MpThreatDetection | Where-Object { $_.InitialDetectionTime -gt (Get-Date).AddHours(-3) }
Start-MpScan -ScanPath "<path-to-exe>" -ScanType CustomScan

Windows Defender was fully active—real-time protection on, tamper protection on, current signatures—with zero detections, both when the file executed and on a follow-up scan. Real-time protection having been on the whole time and silent is meaningful evidence, not nothing.


The verdict table

The whole point of the exercise is to collapse the anxiety into a defensible summary:

Area Verdict
Keylogging (source + binary) Not present
Screen capture Not present
Covert exfiltration Not present
Persistence on host None found
External C2 connections None observed
Antivirus detection None (real-time protection active)
Signed / verifiable No (NotSigned, anonymous author)
Overall Not malware, but not trustworthy

The residual concerns were exactly the ones I could not wave away with forensics: an unsigned binary I can’t verify against source, a paywall bypass, and analytics redirected to the fork author. Trust problems—not a virus.


Cleanup and hardening (after the read-only pass)

Once the diagnostics are done, then you make changes, deliberately:

  1. Prefer the official, signed build from the real vendor over a cracked fork—always.
  2. Submit the hash (or file) to VirusTotal for a second opinion.
  3. Run a Start-MpScan -ScanType QuickScan to confirm a clean full pass.
  4. If interception was ever started, re-check and remove any rogue root CA.
  5. To fully remove: Stop-Process -Id <PID> -Force, delete the extracted folder, and clear its AppData\Local\<app-data-dir>.

The lesson: sometimes you gotta fight the paranoia

The instinct to reformat is really an admission that you don’t know what happened. Running forensics can at least increase confidence that you didn’t get totally pwned, and it saves a day or two of reinstalling your OS. The reusable checklist that came out of this:

  • Signature and hash first—cheap, and they set the trust ceiling immediately.
  • Grep for capability, then read context—a matched API name is a lead, not a conviction.
  • On a host that already ran it, chase five things: child processes, live network connections, persistence, files written, and (for interception tools) root CAs.
  • Keep the host commands read-only until you understand the picture; you cannot un-stomp evidence.
  • Separate “malware” from “untrustworthy.” Most sketchy GitHub binaries are the latter—which is still a good enough reason to delete them and use the signed original.

I admit the paranoia might still stick around because it’s hard to squash that feeling of ‘what if that process spawned other processes somehow and maybe I’m just not looking in the right place…’. Thankfully there are built in defences around that, and that kind of behavior is flaggable and catchable with a little digging and can be eliminated as a possibility. Blackhats do be finding creative ways to stay silent and hidden but sometimes life just has to go on. Save that system wipe for a rainy day lol.


Conclusion

Deciding whether a binary is dangerous is the operational cousin of reading one for fun. The static half of this post—strings, capabilities, mapping symbols to intent—is the same literacy I built in GHIDRA on a Tiny C++ Binary: Strings and Control Flow; the forensic half is just applying that same “verify, don’t assume” discipline to a whole host. The best defense against a random .exe is not fear—it is a checklist you trust more than the download.