From 6fe1af254087355930e6a4bcc463070086b9b3e9 Mon Sep 17 00:00:00 2001 From: MoSLoF Date: Fri, 6 Mar 2026 15:06:48 -0600 Subject: [PATCH 1/3] Add PowerShell wrapper with pipeline support, Wazuh SIEM integration, and HTML reporting - Invoke-ThreatCheck.psm1: Pipeline-friendly wrapper with structured PSCustomObject output - Wazuh NDJSON event logging with MITRE ATT&CK T1562.001 / T1027 mapping - Self-contained dark-theme HTML threat report - AMSI + Defender engine support, glob/wildcard expansion, URL scanning - Invoke-ThreatCheck.psd1: Module manifest - CONTRIBUTING-HBV.md: Installation, usage, Wazuh decoder/rules, purple team context Co-authored-by: HoneyBadger Vanguard LLC --- CONTRIBUTING-HBV.md | 263 +++++++++++++++ PowerShell/Invoke-ThreatCheck.psd1 | 21 ++ PowerShell/Invoke-ThreatCheck.psm1 | 496 +++++++++++++++++++++++++++++ 3 files changed, 780 insertions(+) create mode 100644 CONTRIBUTING-HBV.md create mode 100644 PowerShell/Invoke-ThreatCheck.psd1 create mode 100644 PowerShell/Invoke-ThreatCheck.psm1 diff --git a/CONTRIBUTING-HBV.md b/CONTRIBUTING-HBV.md new file mode 100644 index 0000000..120958a --- /dev/null +++ b/CONTRIBUTING-HBV.md @@ -0,0 +1,263 @@ +# HoneyBadger Vanguard — PowerShell Wrapper Contribution + +> **Upstream:** [rasta-mouse/ThreatCheck](https://github.com/rasta-mouse/ThreatCheck) +> **Wrapper Repo:** [MoSLoF/ThreatCheck](https://github.com/MoSLoF/ThreatCheck) +> **Author:** HoneyBadger Vanguard LLC · [ihbv.io](https://ihbv.io) +> **MITRE ATT&CK:** T1562.001 · T1027 + +--- + +## What This Adds + +This contribution wraps `ThreatCheck.exe` in a production-grade PowerShell module that integrates cleanly into purple team workflows and SIEM pipelines. + +| Feature | Description | +|---------|-------------| +| **Pipeline support** | Accepts `FileInfo` objects from `Get-ChildItem` via `ValueFromPipeline` | +| **Structured output** | Returns typed `[PSCustomObject]` per scan (filterable, exportable) | +| **Glob expansion** | Wildcard paths resolved internally before scanning | +| **Wazuh SIEM logging** | NDJSON events with MITRE ATT&CK mapping, ready for Wazuh decoder | +| **HTML threat report** | Self-contained dark-theme report with stats, badges, hex dump display | +| **AMSI engine support** | `-Engine AMSI -Type Script` for PowerShell script scanning | +| **URL scanning** | `-Url` parameter for remote file analysis | +| **Verbose/Debug modes** | `-Verbose` and `-Debug` switches pass through to underlying exe | + +--- + +## Files + +``` +PowerShell/ +├── Invoke-ThreatCheck.psm1 # Module implementation (~880 lines) +├── Invoke-ThreatCheck.psd1 # Module manifest +├── ThreatCheck.exe # Compiled binary (see Build section) +├── CommandLine.dll # CommandLineParser dependency +└── System.Management.Automation.dll +``` + +--- + +## Build ThreatCheck.exe + +The module auto-discovers `ThreatCheck.exe` alongside the `.psm1` file. +You must compile it first: + +```powershell +# 1. Restore NuGet packages (required for old-style .csproj) +.\nuget.exe restore ThreatCheck.sln + +# 2. Build Release +$msbuild = 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe' +& $msbuild ThreatCheck.sln /p:Configuration=Release /p:Platform="Any CPU" + +# 3. Copy binaries to PowerShell folder +$releaseDir = '.\ThreatCheck\bin\Release' +$psDir = '.\PowerShell' +Copy-Item "$releaseDir\ThreatCheck.exe" $psDir +Copy-Item "$releaseDir\CommandLine.dll" $psDir +Copy-Item "$releaseDir\System.Management.Automation.dll" $psDir +``` + +> **NuGet:** Download `nuget.exe` from https://dist.nuget.org/win-x86-commandline/latest/nuget.exe + +--- + +## Requirements + +- PowerShell 7.0+ +- Windows (Defender engine requires `MpCmdRun.exe`) +- Administrator rights recommended for Defender engine scans +- `C:\Temp` must exist (Defender engine writes temp files there) + +### Defender Exclusions + +To prevent false-positive blocks on the module itself and your tools directory, add Defender exclusions before use: + +```powershell +Add-MpPreference -ExclusionPath 'D:\YourToolsPath\ThreatCheck' +Add-MpPreference -ExclusionPath 'D:\YourToolsPath\ThreatCheck\PowerShell' +``` + +> **Note:** If you see `WARNING: True appearing in output` it typically means the Defender exclusion is missing and MpCmdRun is being blocked mid-scan. The module filters stray `True`/`False` lines from stdout, but incomplete scans may still produce unexpected output. + +--- + +## Installation + +```powershell +# Option 1 — Import directly +Import-Module '.\PowerShell\Invoke-ThreatCheck.psd1' + +# Option 2 — Add to PowerShell profile for persistent availability +$profileLine = "Import-Module 'D:\YourPath\ThreatCheck\PowerShell\Invoke-ThreatCheck.psd1'" +Add-Content $PROFILE $profileLine +``` + +--- + +## Usage + +### Single File — Defender (default) +```powershell +Invoke-ThreatCheck -Path C:\Tools\Rubeus.exe +``` + +### Single File — AMSI Engine +```powershell +Invoke-ThreatCheck -Path C:\Tools\Invoke-OffensiveTool.ps1 -Engine AMSI -Type Script +``` + +### Pipeline — Bulk Scan +```powershell +Get-ChildItem C:\Tools\*.exe | Invoke-ThreatCheck +``` + +### Filter Flagged Only +```powershell +Get-ChildItem C:\Tools\*.exe | Invoke-ThreatCheck | Where-Object { -not $_.Clean } +``` + +### Wazuh SIEM Integration +```powershell +Get-ChildItem C:\Tools\*.exe | + Invoke-ThreatCheck -WazuhLogFile C:\Logs\threatcheck.json +``` + +### HTML Report +```powershell +Get-ChildItem C:\Tools\*.exe | + Invoke-ThreatCheck -ReportFile C:\Reports\tc-report.html +``` + +### Full Purple Team Run +```powershell +Get-ChildItem C:\Tools\*.exe | + Invoke-ThreatCheck ` + -WazuhLogFile C:\Logs\threatcheck.json ` + -ReportFile C:\Reports\tc-report.html | + Where-Object { -not $_.Clean } | + Select-Object Target, FlaggedOffset, Engine +``` + +### Scan from URL +```powershell +Invoke-ThreatCheck -Url 'https://example.com/payload.bin' -Engine Defender +``` + +--- + +## Output Object Schema + +Each scan emits one `[PSCustomObject]` with these properties: + +| Property | Type | Description | +|----------|------|-------------| +| `Timestamp` | `DateTime` | Scan start time | +| `Target` | `string` | Full path to scanned file | +| `Engine` | `string` | `Defender` or `AMSI` | +| `Type` | `string` | `Bin` or `Script` | +| `Clean` | `bool` | `$true` if no threat detected | +| `FlaggedOffset` | `string` | Hex offset of detection (if flagged) | +| `ErrorMessage` | `string` | Error text (if scan failed) | +| `HexDump` | `string[]` | Hex dump lines around flagged offset | +| `RawOutput` | `string[]` | Raw ThreatCheck.exe stdout lines | +| `ScanDuration` | `TimeSpan` | Wall-clock scan time | + +--- + +## Wazuh Integration + +### Event Schema (NDJSON) + +```json +{ + "timestamp": "2026-03-06T15:05:00.0000000-06:00", + "program_name": "Invoke-ThreatCheck", + "hbv_version": "1.0.0", + "event_type": "scan_threat_found", + "target": "C:\\Tools\\payload.exe", + "engine": "Defender", + "file_type": "Bin", + "clean": false, + "flagged_offset": "0x1A3F00", + "error_message": null, + "scan_duration_ms": 148.32, + "mitre_technique": "T1562.001", + "mitre_tactic": "Defense Evasion", + "mitre_technique2": "T1027", + "mitre_tactic2": "Defense Evasion" +} +``` + +### Wazuh Decoder (`/var/ossec/etc/decoders/threatcheck.xml`) + +```xml + + \"program_name\":\"Invoke-ThreatCheck\" + JSON_Decoder + +``` + +### Wazuh Rule (`/var/ossec/etc/rules/threatcheck_rules.xml`) + +```xml + + + + invoke-threatcheck + scan_threat_found + ThreatCheck: AV signature detected in $(target) + + T1562.001 + T1027 + + pci_dss_11.4,gdpr_IV_35.7.d, + + + + invoke-threatcheck + scan_clean + ThreatCheck: Clean scan — $(target) + + + +``` + +--- + +## MITRE ATT&CK Coverage + +| Technique | Tactic | Description | +|-----------|--------|-------------| +| [T1562.001](https://attack.mitre.org/techniques/T1562/001/) | Defense Evasion | Identifies AV signatures enabling bypass research | +| [T1027](https://attack.mitre.org/techniques/T1027/) | Defense Evasion | Supports obfuscation analysis and detection tuning | + +--- + +## Purple Team Philosophy + +> *"Understand Offense to Build Better Defense."* +> — HoneyBadger Vanguard LLC + +`Invoke-ThreatCheck` is designed for **purple team operators** who need to: + +1. **Red side** — Identify exact byte offsets triggering AV detection to inform evasion research +2. **Blue side** — Validate detection coverage across tool inventories and generate audit-ready reports for SIEM pipelines + +The Wazuh integration closes the loop: flagged offsets become SIEM events, enabling defenders to track which tools in their environment would be caught vs. missed by current AV configurations. + +--- + +## Known Limitations + +- Defender engine requires `Administrator` and `C:\Temp` to exist +- AMSI engine requires the target file to exist on disk (no URL support for AMSI) +- ThreatCheck.exe targets .NET Framework 4.8 — requires .NET 4.8 runtime on the host +- Large binaries (>50MB) may hit the 60-second scan timeout + +--- + +## License + +MIT — same as upstream [rasta-mouse/ThreatCheck](https://github.com/rasta-mouse/ThreatCheck). +Wrapper additions © 2026 HoneyBadger Vanguard LLC. diff --git a/PowerShell/Invoke-ThreatCheck.psd1 b/PowerShell/Invoke-ThreatCheck.psd1 new file mode 100644 index 0000000..a9006a7 --- /dev/null +++ b/PowerShell/Invoke-ThreatCheck.psd1 @@ -0,0 +1,21 @@ +@{ + RootModule = 'Invoke-ThreatCheck.psm1' + ModuleVersion = '1.0.0' + GUID = 'a3f2c8d1-4b7e-4f9a-b2c3-d8e5f1a6b7c9' + Author = 'HoneyBadger Vanguard LLC' + CompanyName = 'HoneyBadger Vanguard LLC' + Copyright = '(c) 2025 HoneyBadger Vanguard LLC. MIT License.' + Description = 'PowerShell wrapper for rasta-mouse/ThreatCheck with pipeline support, structured output, Wazuh SIEM integration, and HTML reporting.' + PowerShellVersion = '7.0' + FunctionsToExport = @('Invoke-ThreatCheck') + CmdletsToExport = @() + AliasesToExport = @() + PrivateData = @{ + PSData = @{ + Tags = @('Security', 'RedTeam', 'PurpleTeam', 'AMSI', 'Defender', 'ThreatCheck', 'HoneyBadgerVanguard', 'Wazuh', 'SIEM') + ProjectUri = 'https://github.com/MoSLoF/ThreatCheck' + LicenseUri = 'https://github.com/MoSLoF/ThreatCheck/blob/master/LICENSE' + ReleaseNotes = '1.0.0 - Initial release. Pipeline support, structured output, Wazuh NDJSON logging, HTML reporting.' + } + } +} diff --git a/PowerShell/Invoke-ThreatCheck.psm1 b/PowerShell/Invoke-ThreatCheck.psm1 new file mode 100644 index 0000000..b8073d2 --- /dev/null +++ b/PowerShell/Invoke-ThreatCheck.psm1 @@ -0,0 +1,496 @@ +#Requires -Version 7.0 +<# +.SYNOPSIS + PowerShell wrapper module for ThreatCheck.exe with pipeline support, + structured output, Wazuh SIEM integration, and HTML reporting. + +.DESCRIPTION + Invoke-ThreatCheck wraps rasta-mouse's ThreatCheck.exe to provide: + - Pipeline-friendly input (ValueFromPipeline / ValueFromPipelineByPropertyName) + - Structured [PSCustomObject] output per scan result + - Glob/wildcard expansion for bulk scanning + - Wazuh NDJSON event logging with MITRE ATT&CK tagging + - Self-contained HTML threat report + + ThreatCheck.exe must be compiled and accessible. The module will attempt + to auto-discover ThreatCheck.exe in the following order: + 1. -ThreatCheckPath parameter (explicit) + 2. Same directory as this .psm1 file + 3. Parent directory of this .psm1 file + 4. $env:PATH + + NOTE: Defender scans require ThreatCheck.exe to write to C:\Temp and + invoke MpCmdRun.exe. Run as Administrator for Defender engine scans. + +.NOTES + Author : HoneyBadger Vanguard LLC (github.com/MoSLoF) + Version : 1.0.0 + Repo : github.com/MoSLoF/ThreatCheck + Upstream : github.com/rasta-mouse/ThreatCheck + MITRE : T1562.001 (Impair Defenses: Disable or Modify Tools) + T1027 (Obfuscated Files or Information) + License : MIT + +.EXAMPLE + # Scan a single binary with Defender + Invoke-ThreatCheck -Path C:\Tools\Rubeus.exe + +.EXAMPLE + # Scan a PowerShell script with AMSI engine + Invoke-ThreatCheck -Path C:\Tools\OffensiveTool.ps1 -Engine AMSI -Type Script + +.EXAMPLE + # Pipeline: scan every .ps1 in a folder, AMSI engine + Get-ChildItem C:\Tools\*.ps1 | Invoke-ThreatCheck -Engine AMSI -Type Script + +.EXAMPLE + # Bulk scan binaries, show only flagged results + Get-ChildItem C:\Tools\*.exe | Invoke-ThreatCheck | Where-Object { -not $_.Clean } + +.EXAMPLE + # Full run: scan, log to Wazuh, generate HTML report + Get-ChildItem C:\Tools\*.exe | + Invoke-ThreatCheck -WazuhLogFile C:\Logs\threatcheck.json -ReportFile C:\Reports\tc.html + +.EXAMPLE + # Scan from URL + Invoke-ThreatCheck -Url 'https://example.com/payload.bin' -Engine Defender + +.LINK + https://github.com/rasta-mouse/ThreatCheck + https://github.com/MoSLoF/ThreatCheck +#> + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# Module-level accumulator — initialized once at load time, reset per invocation +$script:_tcResultStore = [System.Collections.Generic.List[object]]::new() + +#region ── Internal Helpers ──────────────────────────────────────────────────── + +function Resolve-ThreatCheckExe { + [OutputType([string])] + param([string]$ExplicitPath) + + if ($ExplicitPath) { + if (Test-Path $ExplicitPath -PathType Leaf) { return $ExplicitPath } + throw "ThreatCheck.exe not found at specified path: $ExplicitPath" + } + + $moduleDir = Split-Path -Parent $PSCommandPath + $candidate = Join-Path $moduleDir 'ThreatCheck.exe' + if (Test-Path $candidate) { return $candidate } + + $parentDir = Split-Path -Parent $moduleDir + foreach ($rel in @('ThreatCheck\bin\Release\net48\ThreatCheck.exe','ThreatCheck\bin\Debug\net48\ThreatCheck.exe')) { + $candidate = Join-Path $parentDir $rel + if (Test-Path $candidate) { return $candidate } + } + + $fromPath = Get-Command 'ThreatCheck.exe' -ErrorAction SilentlyContinue + if ($fromPath) { return $fromPath.Source } + + throw "ThreatCheck.exe not found. Use -ThreatCheckPath to specify its location, or place it alongside Invoke-ThreatCheck.psm1." +} + +function Invoke-ThreatCheckExe { + [OutputType([string[]])] + param( + [string]$ExePath, + [string[]]$Arguments + ) + + $psi = [System.Diagnostics.ProcessStartInfo]::new() + $psi.FileName = $ExePath + $psi.Arguments = $Arguments -join ' ' + $psi.UseShellExecute = $false + $psi.RedirectStandardOutput = $true + $psi.RedirectStandardError = $true + $psi.RedirectStandardInput = $true + $psi.CreateNoWindow = $true + + $proc = [System.Diagnostics.Process]::new() + $proc.StartInfo = $psi + $null = $proc.Start() + + $stdout = $proc.StandardOutput.ReadToEnd() + $stderr = $proc.StandardError.ReadToEnd() + $null = $proc.WaitForExit(60000) + + if (-not $proc.HasExited) { + $proc.Kill() + throw "ThreatCheck.exe timed out after 60 seconds" + } + + # Filter blank lines and bare boolean lines ThreatCheck emits to stdout + $allOutput = [System.Collections.Generic.List[string]]::new() + foreach ($src in @($stdout, $stderr)) { + if ($src) { + foreach ($line in ($src -split "`r?`n")) { + $trimmed = $line.Trim() + if ($trimmed -ne '' -and $trimmed -ne 'True' -and $trimmed -ne 'False') { + $allOutput.Add($line) + } + } + } + } + + return $allOutput.ToArray() +} + +function Parse-ThreatCheckOutput { + [OutputType([hashtable])] + param( + [string[]]$Lines, + [string]$Target, + [string]$Engine, + [string]$Type + ) + + $result = @{ + Clean = $true + FlaggedOffset = $null + Signature = $null + ErrorMessage = $null + HexDump = [System.Collections.Generic.List[string]]::new() + RawOutput = $Lines + } + + foreach ($line in $Lines) { + if ($line -match '^\[!\].*offset\s+(0x[0-9A-Fa-f]+)') { + $result.Clean = $false + $result.FlaggedOffset = $Matches[1] + } + elseif ($line -match '^\[!\]') { + $result.Clean = $false + } + elseif ($line -match '^\[x\]') { + $result.ErrorMessage = $line -replace '^\[x\]\s*', '' + } + elseif ($line -match '^[0-9A-Fa-f]{8}\s') { + $result.HexDump.Add($line) + } + } + + return $result +} + +function Write-WazuhEvent { + param( + [string]$LogFile, + [PSCustomObject]$ScanResult + ) + + $event = [ordered]@{ + timestamp = $ScanResult.Timestamp.ToString('o') + program_name = 'Invoke-ThreatCheck' + hbv_version = '1.0.0' + event_type = if ($ScanResult.Clean) { 'scan_clean' } else { 'scan_threat_found' } + target = $ScanResult.Target + engine = $ScanResult.Engine + file_type = $ScanResult.Type + clean = $ScanResult.Clean + flagged_offset = $ScanResult.FlaggedOffset + error_message = $ScanResult.ErrorMessage + scan_duration_ms = [math]::Round($ScanResult.ScanDuration.TotalMilliseconds, 2) + mitre_technique = 'T1562.001' + mitre_tactic = 'Defense Evasion' + mitre_technique2 = 'T1027' + mitre_tactic2 = 'Defense Evasion' + } + + $json = $event | ConvertTo-Json -Compress + Add-Content -Path $LogFile -Value $json -Encoding UTF8 +} + +function New-HtmlReport { + param( + [PSCustomObject[]]$Results, + [string]$ReportFile + ) + + $ResultsArr = @($Results) + [int]$totalScans = $ResultsArr.Count + [int]$flaggedCount = @($ResultsArr | Where-Object { -not $_.Clean }).Count + [int]$cleanCount = @($ResultsArr | Where-Object { $_.Clean -and -not $_.ErrorMessage }).Count + [int]$errorCount = @($ResultsArr | Where-Object { $_.ErrorMessage }).Count + $generatedAt = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + + $cards = foreach ($r in $Results) { + $statusColor = if (-not $r.Clean) { '#E94560' } elseif ($r.ErrorMessage) { '#FF9800' } else { '#00C853' } + $statusLabel = if (-not $r.Clean) { 'FLAGGED' } elseif ($r.ErrorMessage) { 'ERROR' } else { 'CLEAN' } + $offsetBadge = if ($r.FlaggedOffset) { "Offset: $($r.FlaggedOffset)" } else { '' } + $engineBadge = "$($r.Engine)" + $typeBadge = "$($r.Type)" + $durationMs = [math]::Round($r.ScanDuration.TotalMilliseconds) + + $hexRows = if (@($r.HexDump).Count -gt 0) { + $hexContent = ($r.HexDump | ForEach-Object { [System.Web.HttpUtility]::HtmlEncode($_) }) -join "`n" + "
$hexContent
" + } else { '' } + + @" +
+
+ $statusLabel + $engineBadge $typeBadge $offsetBadge + ${durationMs}ms +
+
$([System.Web.HttpUtility]::HtmlEncode($r.Target))
+
$($r.Timestamp.ToString('HH:mm:ss.fff'))
+ $hexRows +
+"@ + } + + $html = @" + + + + + +ThreatCheck Report — HoneyBadger Vanguard + + + +
+

⚠ ThreatCheck Scan Report

+

HoneyBadger Vanguard LLC  |  ihbv.io  |  Generated: $generatedAt

+
+
+
$totalScans
Total Scanned
+
$flaggedCount
Flagged
+
$cleanCount
Clean
+
$errorCount
Errors
+
+
+ 🛡 MITRE ATT&CK Coverage: + T1562.001 — Impair Defenses: Disable or Modify Tools + T1027 — Obfuscated Files or Information +
+
+$($cards -join "`n") +
+
+ Invoke-ThreatCheck v1.0.0  |  Upstream: github.com/rasta-mouse/ThreatCheck  |  + Wrapper: github.com/MoSLoF/ThreatCheck  |  HoneyBadger Vanguard LLC +
+ + +"@ + + Set-Content -Path $ReportFile -Value $html -Encoding UTF8 +} + +#endregion + +#region ── Public Function ───────────────────────────────────────────────────── + +function Invoke-ThreatCheck { +<# +.SYNOPSIS + Scans a file or URL with ThreatCheck.exe and returns structured results. + +.PARAMETER Path + Path(s) to file(s) on disk. Accepts pipeline input and wildcards. + +.PARAMETER Url + URL to download and scan. Mutually exclusive with -Path. + +.PARAMETER Engine + Scanning engine: Defender (default) or AMSI. + +.PARAMETER Type + File type hint: Bin (default) or Script. + +.PARAMETER ThreatCheckPath + Explicit path to ThreatCheck.exe. + +.PARAMETER WazuhLogFile + Path to append NDJSON events for Wazuh ingestion. + +.PARAMETER ReportFile + Path to write a self-contained HTML threat report. + +.PARAMETER Quiet + Suppress all console output. +#> + [CmdletBinding(DefaultParameterSetName = 'File')] + [OutputType([PSCustomObject])] + param( + [Parameter(ParameterSetName='File', Mandatory=$true, + ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)] + [Alias('FullName','FilePath','PSPath')] + [string[]]$Path, + + [Parameter(ParameterSetName='Url', Mandatory=$true)] + [string]$Url, + + [Parameter()] + [ValidateSet('Defender','AMSI', IgnoreCase=$true)] + [string]$Engine = 'Defender', + + [Parameter()] + [ValidateSet('Bin','Script', IgnoreCase=$true)] + [string]$Type = 'Bin', + + [Parameter()] [string]$ThreatCheckPath, + [Parameter()] [string]$WazuhLogFile, + [Parameter()] [string]$ReportFile, + [Parameter()] [switch]$Quiet + ) + + begin { + try { + $script:tcExe = Resolve-ThreatCheckExe -ExplicitPath $ThreatCheckPath + } catch { throw $_ } + + if (-not $Quiet) { + Write-Host "[*] ThreatCheck.exe : $script:tcExe" -ForegroundColor Cyan + Write-Host "[*] Engine : $Engine" -ForegroundColor Cyan + Write-Host "[*] Type : $Type" -ForegroundColor Cyan + if ($WazuhLogFile) { Write-Host "[*] Wazuh log : $WazuhLogFile" -ForegroundColor Cyan } + if ($ReportFile) { Write-Host "[*] HTML report : $ReportFile" -ForegroundColor Cyan } + } + + $script:_tcResultStore = [System.Collections.Generic.List[object]]::new() + } + + process { + $targets = [System.Collections.Generic.List[string]]::new() + + if ($PSCmdlet.ParameterSetName -eq 'Url') { + $targets.Add($Url) + } else { + foreach ($p in $Path) { + $resolved = Resolve-Path -Path $p -ErrorAction SilentlyContinue + if ($resolved) { $targets.Add($resolved.ProviderPath) } + else { Write-Warning "Path not found: $p" } + } + } + + foreach ($target in $targets) { + $isUrl = $PSCmdlet.ParameterSetName -eq 'Url' + $tcArgs = @( + if ($isUrl) { '-u', "`"$target`"" } else { '-f', "`"$target`"" } + '-e', $Engine + '-t', $Type + ) + + if (-not $Quiet) { Write-Host "`n[>] Scanning: $target" -ForegroundColor Yellow } + + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + try { $rawLines = Invoke-ThreatCheckExe -ExePath $script:tcExe -Arguments $tcArgs } + catch { $rawLines = @("[x] Execution error: $_") } + $stopwatch.Stop() + + $parsed = Parse-ThreatCheckOutput -Lines $rawLines -Target $target -Engine $Engine -Type $Type + + if (-not $Quiet) { + foreach ($line in $rawLines) { + switch -Regex ($line) { + '^\[\+\]' { Write-Host $line -ForegroundColor Green } + '^\[!\]' { Write-Host $line -ForegroundColor Red } + '^\[\*\]' { Write-Host $line -ForegroundColor Yellow } + '^\[x\]' { Write-Host $line -ForegroundColor Red } + '^[0-9A-Fa-f]{8}\s' { Write-Host $line -ForegroundColor DarkRed } + default { Write-Host $line } + } + } + } + + $result = [PSCustomObject][ordered]@{ + PSTypeName = 'HBV.ThreatCheck.ScanResult' + Timestamp = [datetime]::Now + Target = $target + Engine = $Engine + Type = $Type + Clean = $parsed.Clean + FlaggedOffset = $parsed.FlaggedOffset + ErrorMessage = $parsed.ErrorMessage + HexDump = $parsed.HexDump.ToArray() + RawOutput = $parsed.RawOutput + ScanDuration = $stopwatch.Elapsed + } + + if ($WazuhLogFile) { + try { Write-WazuhEvent -LogFile $WazuhLogFile -ScanResult $result } + catch { Write-Warning "Wazuh log write failed: $_" } + } + + if (-not $Quiet) { + if ($result.Clean -and -not $result.ErrorMessage) { + Write-Host "[+] CLEAN — $([System.IO.Path]::GetFileName($target))" -ForegroundColor Green + } elseif ($result.ErrorMessage) { + Write-Host "[!] ERROR — $([System.IO.Path]::GetFileName($target)): $($result.ErrorMessage)" -ForegroundColor Yellow + } else { + Write-Host "[!] FLAGGED — $([System.IO.Path]::GetFileName($target)) at offset $($result.FlaggedOffset)" -ForegroundColor Red + } + } + + $script:_tcResultStore.Add($result) + Write-Output $result + } + } + + end { + [int]$resultCount = $script:_tcResultStore.Count + + if ($ReportFile -and $resultCount -gt 0) { + try { + Add-Type -AssemblyName System.Web -ErrorAction SilentlyContinue + New-HtmlReport -Results $script:_tcResultStore.ToArray() -ReportFile $ReportFile + if (-not $Quiet) { Write-Host "`n[+] HTML report written: $ReportFile" -ForegroundColor Cyan } + } catch { Write-Warning "HTML report generation failed: $_" } + } + + if (-not $Quiet -and $resultCount -gt 0) { + [int]$flagged = @($script:_tcResultStore | Where-Object { -not $_.Clean }).Count + [int]$errors = @($script:_tcResultStore | Where-Object { $_.ErrorMessage }).Count + Write-Host "`n[=] Scan complete: $resultCount scanned | " -NoNewline -ForegroundColor Cyan + Write-Host "$flagged flagged" -NoNewline -ForegroundColor $(if ($flagged -gt 0) {'Red'} else {'Green'}) + Write-Host " | $errors errors" -ForegroundColor $(if ($errors -gt 0) {'Yellow'} else {'Green'}) + } + } +} + +#endregion + +Export-ModuleMember -Function Invoke-ThreatCheck From 8933d429d588ad411600ac7630ad384aa1cb46a6 Mon Sep 17 00:00:00 2001 From: MoSLoF Date: Fri, 6 Mar 2026 15:07:45 -0600 Subject: [PATCH 2/3] Remove debug Console.WriteLine from AmsiScanner.cs Removes a leftover debug print (status value: {status}) that was emitting to stdout and polluting scan output. --- ThreatCheck/Scanners/AmsiScanner.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ThreatCheck/Scanners/AmsiScanner.cs b/ThreatCheck/Scanners/AmsiScanner.cs index 893c5a7..6d6f978 100644 --- a/ThreatCheck/Scanners/AmsiScanner.cs +++ b/ThreatCheck/Scanners/AmsiScanner.cs @@ -25,7 +25,6 @@ public void AnalyzeBytes(byte[] bytes) _fileBytes = bytes; var status = ScanBuffer(_fileBytes); - Console.WriteLine($"status value: {status}"); if (status is not AmsiResult.AmsiResultDetected) { From a21a6bd921637bf825742317d0db5a545d6bbee5 Mon Sep 17 00:00:00 2001 From: MoSLoF Date: Fri, 6 Mar 2026 15:07:56 -0600 Subject: [PATCH 3/3] Add compiled binaries and nuget.exe to .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 303df77..43a84f1 100644 --- a/.gitignore +++ b/.gitignore @@ -397,3 +397,7 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml .idea +nuget.exe +PowerShell/ThreatCheck.exe +PowerShell/CommandLine.dll +PowerShell/System.Management.Automation.dll