mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dc2714566 | ||
|
|
efb7c67ab0 | ||
|
|
2d5e0fb623 |
@@ -0,0 +1,30 @@
|
||||
name: Investigate Windows release detections
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [codex/scan-740-defender]
|
||||
paths:
|
||||
- .github/workflows/defender-release-scan.yml
|
||||
- scripts/scan-windows-releases.ps1
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
defender:
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
- name: Scan exact published samples without executing them
|
||||
shell: pwsh
|
||||
run: ./scripts/scan-windows-releases.ps1
|
||||
- name: Preserve text evidence only
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||||
with:
|
||||
name: defender-scan-evidence
|
||||
path: ${{ runner.temp }}/impeccable-defender-evidence/
|
||||
if-no-files-found: warn
|
||||
retention-days: 14
|
||||
@@ -0,0 +1,121 @@
|
||||
# Maintainer-authorized diagnostic for #740. Never executes release binaries,
|
||||
# adds exclusions, restores quarantine, or disables antivirus protection.
|
||||
# A completed scan is evidence for this engine/definition/host only, not a
|
||||
# false-positive determination or clearance for other machines.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$PSNativeCommandUseErrorActionPreference = $false
|
||||
$evidence = Join-Path $env:RUNNER_TEMP 'impeccable-defender-evidence'
|
||||
New-Item -ItemType Directory -Path $evidence -Force | Out-Null
|
||||
$samples = Join-Path $env:RUNNER_TEMP ('impeccable-defender-samples-' + [guid]::NewGuid())
|
||||
New-Item -ItemType Directory -Path $samples | Out-Null
|
||||
$report = [ordered]@{
|
||||
startedAt = (Get-Date).ToUniversalTime().ToString('o')
|
||||
os = (Get-CimInstance Win32_OperatingSystem).Caption
|
||||
status = 'initializing'
|
||||
samples = @()
|
||||
}
|
||||
$failed = $false
|
||||
try {
|
||||
$before = Get-MpComputerStatus
|
||||
$report.before = $before | Select-Object AMServiceEnabled, AntivirusEnabled, RealTimeProtectionEnabled, AMEngineVersion, AMProductVersion, AntivirusSignatureVersion, AntivirusSignatureLastUpdated
|
||||
$preferences = Get-MpPreference
|
||||
$report.preferencesBefore = $preferences | Select-Object DisableRealtimeMonitoring, DisableIOAVProtection, DisableBehaviorMonitoring, MAPSReporting, SubmitSamplesConsent, ExclusionPath, ExclusionProcess, ExclusionExtension
|
||||
if (-not $before.AMServiceEnabled) {
|
||||
# Enabling an installed service is safe on this disposable runner. Never
|
||||
# weaken protection or change remediation policies.
|
||||
Start-Service WinDefend
|
||||
}
|
||||
$mp = Get-ChildItem "$env:ProgramData\Microsoft\Windows Defender\Platform\*\MpCmdRun.exe" -ErrorAction SilentlyContinue |
|
||||
Sort-Object FullName -Descending | Select-Object -First 1 -ExpandProperty FullName
|
||||
if (-not $mp) { $mp = "$env:ProgramFiles\Windows Defender\MpCmdRun.exe" }
|
||||
if (-not (Test-Path -LiteralPath $mp)) { throw 'Microsoft Defender scanner is unavailable on this runner.' }
|
||||
$report.scanner = $mp
|
||||
$updateOutput = & $mp -SignatureUpdate 2>&1 | Out-String
|
||||
$report.signatureUpdateExitCode = $LASTEXITCODE
|
||||
$updateOutput | Set-Content (Join-Path $evidence 'signature-update.txt')
|
||||
Write-Output $updateOutput
|
||||
if ($report.signatureUpdateExitCode -ne 0) { throw 'Defender signature update failed; cannot claim a current-definition scan.' }
|
||||
# Hosted images can default to real-time monitoring off. Enable it for the
|
||||
# download-time reproduction and explicitly refuse to mislabel a scan if
|
||||
# policy prevents activation. Never turn protection off.
|
||||
# The hosted image excludes both entire drives. Remove those existing
|
||||
# exclusions on this disposable machine; never add any. Enable the checks
|
||||
# a normal consumer install has, but keep automatic sample uploads off.
|
||||
foreach ($excludedPath in @($preferences.ExclusionPath)) {
|
||||
if ($excludedPath) { Remove-MpPreference -ExclusionPath $excludedPath }
|
||||
}
|
||||
foreach ($excludedProcess in @($preferences.ExclusionProcess)) {
|
||||
if ($excludedProcess) { Remove-MpPreference -ExclusionProcess $excludedProcess }
|
||||
}
|
||||
foreach ($excludedExtension in @($preferences.ExclusionExtension)) {
|
||||
if ($excludedExtension) { Remove-MpPreference -ExclusionExtension $excludedExtension }
|
||||
}
|
||||
Set-MpPreference -DisableRealtimeMonitoring $false -DisableIOAVProtection $false -DisableBehaviorMonitoring $false -MAPSReporting Advanced -SubmitSamplesConsent NeverSend
|
||||
Start-Sleep -Seconds 3
|
||||
$current = Get-MpComputerStatus
|
||||
$report.scannerStatus = $current | Select-Object AMServiceEnabled, AntivirusEnabled, RealTimeProtectionEnabled, AMEngineVersion, AMProductVersion, AntivirusSignatureVersion, AntivirusSignatureLastUpdated
|
||||
if (-not $current.AMServiceEnabled -or -not $current.AntivirusEnabled) { throw 'Defender is not active; no scan verdict can be inferred.' }
|
||||
if (-not $current.RealTimeProtectionEnabled) { throw 'Real-time monitoring remains disabled by runner policy; cannot reproduce download-time detection on this host.' }
|
||||
$report.protectionPreferences = Get-MpPreference | Select-Object DisableRealtimeMonitoring, DisableIOAVProtection, DisableBehaviorMonitoring, MAPSReporting, SubmitSamplesConsent, ExclusionPath, ExclusionProcess, ExclusionExtension
|
||||
$cloudOutput = & $mp -ValidateMapsConnection 2>&1 | Out-String
|
||||
$report.cloudConnectionExitCode = $LASTEXITCODE
|
||||
$cloudOutput | Set-Content (Join-Path $evidence 'cloud-connection.txt')
|
||||
Write-Output $cloudOutput
|
||||
$http = [System.Net.Http.HttpClient]::new()
|
||||
$http.Timeout = [TimeSpan]::FromSeconds(90)
|
||||
foreach ($sample in @(
|
||||
@{ version = '0.1.0'; sha256 = 'a522fcf352b47f325facc3964b337a6d6d7d55e136440f1442e8013aad27f1d7' },
|
||||
@{ version = '0.1.1'; sha256 = '5d2f844a7f1dac3acdbac6035785043ab0cba6b81c1af97ba5c9cd1ecdd3dff8' }
|
||||
)) {
|
||||
$item = [ordered]@{ version = $sample.version; expectedSha256 = $sample.sha256; status = 'pending' }
|
||||
$file = Join-Path $samples ("impeccable-" + $sample.version + '.exe')
|
||||
try {
|
||||
$url = "https://github.com/pbakaus/impeccable/releases/download/engine-v$($sample.version)/impeccable-windows-x64.exe"
|
||||
$bytes = $http.GetByteArrayAsync($url).GetAwaiter().GetResult()
|
||||
$item.actualSha256 = [Convert]::ToHexString([System.Security.Cryptography.SHA256]::HashData($bytes)).ToLowerInvariant()
|
||||
$item.bytes = $bytes.Length
|
||||
if ($item.actualSha256 -ne $sample.sha256) { throw 'Release bytes do not match the pinned investigation hash.' }
|
||||
[System.IO.File]::WriteAllBytes($file, $bytes)
|
||||
$bytes = $null
|
||||
if (-not (Test-Path -LiteralPath $file)) { throw 'Sample disappeared after writing; inspect real-time detection evidence.' }
|
||||
$item.authenticodeStatus = [string](Get-AuthenticodeSignature -LiteralPath $file).Status
|
||||
# This suppresses remediation for this custom scan, not real-time
|
||||
# protection. Detections appear in stdout; preserve it without guessing
|
||||
# from exit 2 (which can mean either a detection or a scanning error).
|
||||
$scanOutput = & $mp -Scan -ScanType 3 -File $file -DisableRemediation 2>&1 | Out-String
|
||||
$item.scanExitCode = $LASTEXITCODE
|
||||
$scanOutput | Set-Content (Join-Path $evidence ("scan-" + $sample.version + '.txt'))
|
||||
Write-Output "Engine $($sample.version):"
|
||||
Write-Output $scanOutput
|
||||
$item.presentAfterScan = Test-Path -LiteralPath $file
|
||||
$item.status = 'scan-finished-review-output'
|
||||
if ($item.scanExitCode -ne 0) { $failed = $true }
|
||||
} catch {
|
||||
$item.status = 'unavailable-or-error'
|
||||
$item.error = $_.Exception.Message
|
||||
$failed = $true
|
||||
}
|
||||
$report.samples += $item
|
||||
}
|
||||
$http.Dispose()
|
||||
$report.status = 'completed-review-evidence'
|
||||
} catch {
|
||||
$report.status = 'unavailable-or-error'
|
||||
$report.error = $_.Exception.Message
|
||||
$failed = $true
|
||||
} finally {
|
||||
try {
|
||||
Get-MpThreatDetection | Select-Object InitialDetectionTime, ThreatID, Resources, ActionSuccess |
|
||||
ConvertTo-Json -Depth 6 | Set-Content (Join-Path $evidence 'realtime-detections.json')
|
||||
Get-MpThreat | Select-Object ThreatID, ThreatName, IsActive, DidThreatExecute |
|
||||
ConvertTo-Json -Depth 6 | Set-Content (Join-Path $evidence 'threat-names.json')
|
||||
} catch { $_.Exception.Message | Set-Content (Join-Path $evidence 'detection-query-error.txt') }
|
||||
$report.finishedAt = (Get-Date).ToUniversalTime().ToString('o')
|
||||
$json = $report | ConvertTo-Json -Depth 8
|
||||
$json | Set-Content (Join-Path $evidence 'report.json')
|
||||
Write-Output $json
|
||||
if ($env:GITHUB_STEP_SUMMARY) {
|
||||
"## Defender investigation evidence`n`nThis is not a vendor false-positive determination.`n`n``````json`n$json`n``````" | Add-Content $env:GITHUB_STEP_SUMMARY
|
||||
}
|
||||
}
|
||||
if ($failed) { exit 1 }
|
||||
Reference in New Issue
Block a user