Imported from Dryader/agent-skills (
enterprise-security/windows-vulnerability-scanning/SKILL.md). Install upstream withnpx skills add Dryader/agent-skills --skill windows-vulnerability-scanning. Copyright stays with the author.
Windows Vulnerability Scanning: Tools, Taxonomy, and Verification
When to use
- User asks for a tool that "scans like Windows Defender" or flags vulnerable DLLs / software / drivers.
- Comparing free vs paid security scanners, or "is there a free X like Y" questions about security tooling.
- Verifying whether a security project or tool is maintained, or whether a vendor's claims are true.
Step 1: Match the user's need to the scan model (taxonomy)
Five distinct capabilities that get confused with each other:
- Installed-software inventory (registry/WMI + CVE match): Wazuh, ManageEngine VMP, PatchCured, RoboShadow device agent, MDVM inventory. Does NOT see uninstalled files.
- File-level binary fingerprinting (version strings inside DLL/EXE matched against CVE DB): free = cve-bin-tool (~448 component checkers); paid = Microsoft Defender Vulnerability Management (E5/P2), Nessus plugin checks for famous components (log4j, OpenSSL).
- Driver hash/name lists: loldrivers.io ecosystem (LolDriverScan, loldrivers-client), Windows built-in vulnerable driver blocklist (WDAC).
- Missing OS patches: WES-NG (systeminfo vs MSRC), PatchCured.
- Reputation (malicious, not CVE): VirusTotal lookups via Sysinternals Sigcheck/Autoruns.
Key mapping: "DLL with vulnerabilities that isn't even installed, just in Downloads" = model 2 (file-level). That is the paid MDVM capability; the only free tool that does it is cve-bin-tool, limited to the components it has checkers for. Inventory agents will NOT find Downloads-folder DLLs — say this explicitly instead of recommending an agent product.
Step 2: Check the vendor's own free tier before hunting alternatives
Freemium vendors often give away most of the scan/report capability and monetize remediation/automation. Example: RoboShadow free tier claims ~90% of functionality (external scan, LAN scan, device vulnerability data, AI pen test) while Cyber Heal (auto-fix) and RoboGuard (automation) are paid. But verify the exact feature the user wants is in the free tier — the DLL "Binary Scanner" free-tier inclusion was unverifiable.
Step 3: Verify maintenance status properly (releases are a trap)
- GitHub Releases page phrasing misleads: "released this 31 Jul" means current year only, and page extraction can drop the year entirely. Use the API for timestamps.
- Data repositories rarely cut tagged releases. Freshness = commit history + the live data endpoint, not the release page. Verified example: magicsword-io/LOLDrivers releases stale since 2023-07-31, but commits active through 2026-07-10 and the scanning clients consume the live API, so stale releases do not mean stale data.
- Check the live endpoint the tool actually consumes, not the README badge: loldrivers.io drivers.json returned 660 entries vs the 2,212 driver badge (discrepancy unresolved — consumers see the API count, so detection coverage follows the API).
- Run scripts/repo_freshness.sh (curl + python3; jq is often absent on fresh systems — parse JSON with python3 -c instead).
Step 4: Separate scanning from enforcement
Some products look like scanners but are execution-control layers. MagicSword = threat-driven application control (blocks LOTL binaries, BYOVD drivers, abused RMM tools; free tier ≤100 endpoints) — it is NOT a vulnerability scanner and won't report CVEs on files. Its OSS data projects (LOLDrivers, LOLBASline, LOLRMM, bootloaders) are the reusable free parts. Note: LOLBASline executes representative commands per binary — research only, will trip security tools.
Step 5: Answer honestly — compose when no all-in-one exists
No free tool replicates MDVM's full file-level CVE catalog. Verified free all-in-one candidates and their hard limits:
- Nessus Essentials: free forever, 16 IPs (permanently counted per IP, cannot free them), same plugin engine and update cadence as Professional, but NO content audits, registry checks, compliance templates, or live results (Professional-only).
- ManageEngine Vulnerability Manager Plus Free: 25 endpoints, vuln scan + patch deployment + misconfig in one console; registry-based inventory, on-prem server or cloud.
- SecPod Saner Personal: free local Defender-like agent, scans software vulns + compliance, remediation built in; SaaS (data to SecPod cloud).
- Wazuh: OSS agent + self-hosted server, continuous, but installed-software only. When the pieces don't exist in one free tool, recommend the composition explicitly (e.g. Nessus Essentials for the scan + Defender's built-in driver blocklist + cve-bin-tool pointed at Downloads).
Step 6: Running the scans (operational, verified Aug 2026)
Nessus Essentials on Windows:
- PIN is not the password — create a dedicated local admin scan account ("Add a user without a Microsoft account") instead of fighting Microsoft-account passwords.
- SMB credential route requires Remote Registry service + admin shares (C$) on the target; the SSH credential route avoids both (OpenSSH Server optional feature; admin gets an elevated token, no UAC filtering).
- "Not credentialed" despite correct creds = UAC remote token filtering → use SSH, or LocalAccountTokenFilterPolicy=1 under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System.
- Npcap "Network Interface Not Supported / packet forgery" = Nessus bound a virtual/loopback adapter (WSL vEthernet, Npcap loopback) → Settings→Advanced→Network Interface set to the physical adapter's IP, or uncheck General→Ping host; scanning the LAN IP instead of 127.0.0.1 also sidesteps it.
- Template: Basic Network Scan + credentials is the full assessment; Host Discovery wastes the permanent 16-IP quota; compliance templates are locked in Essentials.
LOLDrivers / BYOVD scanning:
- Use scripts/Scan-LOLDrivers.ps1 (validated Aug 2026 on Win11: local release parsed to 332 entries, live API 660 drivers, positive control = real AsUpIO.sys SHA256 match on both sources, exit 1 on hash hits).
- CRITICAL: detections/hashes/*.sha256 in the GitHub release are LIEF structural authentihashes — they never match Get-FileHash output. Match flat MD5/SHA1/SHA256 from yaml/ or the API instead.
- drivers/*.bin in the release are Git LFS pointer stubs (oid = flat sha256); real samples: media.githubusercontent.com/media/magicsword-io/LOLDrivers/main/drivers/.bin.
- The famous community scanner gist (IISResetMe/MHaggis) is broken: its "Authenticode hash" is a cert thumbprint that never matches, and its JSON parse dies on PS 5.1 duplicate keys.
- Positive-control protocol before trusting any scanner: fetch a real sample via the LFS URL, scan a temp folder, expect an exact flat-hash match with correct Category/Id, then delete the sample.
- Full technical detail: loldrivers-scanning.md.
User preferences (this user)
- Use Exa MCP (mcp__exa__web_search_exa / web_fetch_exa) when asked for research; not firecrawl_agent, not raw HTTP unless explicitly requested.
- Independent sources over seller pages; verify claims before asserting — the user WILL challenge unsupported claims and check primary sources.
- Blunt, concrete answers with exact commands; state plainly when no free equivalent exists. Full sentences, no staccato fragments.
Pitfalls
- Dead tool trap: DetExploit looks like the perfect free answer (installed software + missing updates, 5 CVE DBs) but is unmaintained since 2019 — check dates before recommending anything.
- "Scans like Defender" does not mean CVE scanning: consumer Defender does no vulnerability detection at all, only malware.
- Free tiers often lock exactly the feature the user wants (DLL-level scanning) — verify, don't assume.
- Nessus Essentials IP quota is permanent per scanned IP — warn users not to waste scans on printers/IoT.
- Archive-scanning nuance: cve-bin-tool extracts exe/msi/jar/cab (needs 7z), so it digs into installers in Downloads — a selling point for that use case.
Support files
- loldrivers-scanning.md — LIEF authentihash deep-dive (why detections/hashes lists never match plain hashing), PE offset cheat sheet, Git LFS sample URLs, PowerShell 5.1 gotchas, verified counts.
- scripts/Scan-LOLDrivers.ps1 — validated drop-in local LOLDrivers scanner (flat-hash match vs live API or local release YAML; copy and tweak $LolDriversPath).
- scripts/repo_freshness.sh — probe a GitHub repo's release vs commit freshness plus optional live data endpoint count.
Reference: loldrivers-scanning.md
LOLDrivers scanning deep-dive (verified Aug 2026)
Database facts
- Repo: github.com/magicsword-io/LOLDrivers (MagicSword; founded by Jose Hernandez, ex-Splunk threat research).
- Releases are stale and are a trap: v1.0.0 = 2023-05-08, v2.0.0 = 2023-07-31, nothing since. Data is alive via commits (last commit 2026-07-10, regular PR merges). Judge freshness by commit activity + the live endpoint, NEVER the release page. GitHub's "released this 31 Jul" phrasing only means current year, and page extractors can drop the year.
- Live API: https://www.loldrivers.io/api/drivers.json — 660 entries at check (Aug 2026). Repo README badge claims 2,212 drivers. Discrepancy is real and unresolved; scanning clients (loldrivers-client, LolDriverScan) consume the API, so detection coverage follows the API count.
- Fields per driver: flat MD5/SHA1/SHA256 per KnownVulnerableSamples entry, Authentihash {MD5,SHA1,SHA256}, Filename, Signature, Category (vulnerable driver / malicious / revoked bootloader), MitreID, optional CVE. API "Name" field is sometimes empty even when the entry is valid.
Why "Authentihash" cannot be matched by hashing the file
- The DB's Authentihash values are LIEF's
pe.authentihash_*(loldrivers' bin/metadata-extractor.py uses LIEF, not pefile). It is a STRUCTURAL hash: re-serializes the parsed PE field-by-field — DOS header fields, DOS stub, "PE\0\0", COFF header, optional header WITHOUT the checksum field, all data directories EXCEPT the certificate table, section headers, header padding, then section content+padding sorted by file offset, with overlap truncation. Source: LIEF src/PE/Binary.cpp,Binary::authentihash(~line 715). - Empirically rejected on a real sample (AsUpIO.sys, LIEF as ground truth, DB value 70870e20...): skip-checksum+skip-cert, zero-checksum+zero-cert, zero-cert-only, skip-cert-only, flat — none match.
- Consequence: detections/hashes/authentihash_samples*.sha256 (and .md5/.sha1) in the GitHub release are LIEF hashes. A naive Get-FileHash comparison against them NEVER matches — this is the "works but broken" bug in the original community script. Flat hashes from yaml/ or the API are the detection primitive; a bit-identical file matches on flat hash anyway, so nothing is lost by skipping authentihash.
PE layout cheat sheet (for manual authenticode-style work; validated against LIEF + DB)
- Optional header offsets (relative to opt = pe_offset + 24 for PE32+; PE32 standard fields are 28 bytes so opt = pe_offset + 28):
- checksum field: opt + 0x40 (BOTH formats)
- NumberOfRvaAndSizes: opt + 0x6C (PE32+) / opt + 0x5C (PE32)
- data directories start: opt + 0x70 (PE32+) / opt + 0x60 (PE32)
- Security (certificate) directory: VA opt + 0x90, size opt + 0x94 (PE32+); VA opt + 0x80, size opt + 0x84 (PE32)
- The certificate table's "VirtualAddress" is a FILE offset (table is not memory-mapped); it usually sits at EOF (rva + size == file size).
- History: naive first-pass offsets (checksum 0x44/0x48, numdirs 0x70/0x64, secdir 0x98/0x88) were wrong by 2-4 bytes. Rule: never trust hand-rolled PE arithmetic without validating against a ground truth (LIEF or a DB value).
Git LFS stubs
- drivers/*.bin in the release zip are 130-byte LFS pointers: "version https://git-lfs.github.com/spec/v1 / oid sha256: / size N". The oid EQUALS the YAML flat SHA256.
- Real sample fetch: https://media.githubusercontent.com/media/magicsword-io/LOLDrivers/main/drivers/.bin (verified: AsUpIO.sys, 13952 bytes, flat hash matched the YAML exactly).
PowerShell 5.1 gotchas hit while building the scanner (generic — apply to any PS script)
- ConvertFrom-Json throws on duplicate JSON keys (PE section names 'init'/'INIT'): PS 5.1 fix = System.Web.Extensions JavaScriptSerializer with MaxJsonLength=int.MaxValue (verified working on drivers.json); PS7 = retry ConvertFrom-Json -AsHashtable.
- Get-ChildItem -Include is silently ignored without -Recurse or a wildcard path → filter with Where-Object on $_.Extension.
- Functions unroll collections on return:
AddRange((func))fails "cannot convert Object[]" → return ,$list (comma) or cast [string[]]. - Never query Win32_SystemDriver per-file inside a loop (O(n) WMI); precompute a path set once.
- Add-Type class: guard with
if (-not ('Name' -as [type])). Use TransformBlock/TransformFinalBlock chain for one-pass multi-hash (SHA256+SHA1+MD5), SHA256.Create() not SHA256Managed (obsolete).
Validated scanner behavior (scripts/Scan-LOLDrivers.ps1)
- Local mode: 332 entries parsed from v2.0.0 yaml/ via regex (no YAML module needed; sample blocks split on "(?m)^- (?:Authentihash|Filename):", flat hashes at exactly 2-space indent, nested Authentihash at 4-space — regexes must not match " OriginalFilename:" etc.).
- Live mode: 660 drivers through the PS 5.1 serializer path.
- Positive control: real AsUpIO.sys → SHA256 match, Category "vulnerable driver", correct Id, exit code 1. System scan of 92 driver files clean, no errors.
- Name-only matches surface real noise (e.g. KslD.sys on the user's box: hash negative, filename matches a known-vulnerable entry — verify the file's signature before acting).
- Testing from WSL: powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\path\Scan-LOLDrivers.ps1" -DataSource Local -Path "C:..." ; exit code via $?.
Scan targets that make sense
- Loaded drivers (Win32_SystemDriver PathName), C:\Windows\System32\drivers*.sys, DriverStore\FileRepository (opt-in, thousands of files), user folders (Downloads) for pre-staged samples. Extension filter .sys/.exe/.dll/.bin/.ocx/.drv/.com/.scr/.cpl by default.