Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,30 @@ Example output:

<img width="979" height="800" alt="Screenshot" src="https://github.com/user-attachments/assets/c76b17b6-dad2-4e27-8157-6096e1f4d078" />

## Audit ESP or an attached drive for revoked EFI binaries (DBX)

Right-click `Scan ESP for revoked files.cmd` and select *Run as administrator*.

This script:
- Downloads the latest Microsoft DBX JSON
- Scans EFI binaries in the ESP
- Matches them against revoked hashes and certificates

You can also scan other drives (e.g., USB, CD-ROM):

`powershell -ExecutionPolicy Bypass -Command "& 'ps\Find-EfiFilesRevokedByDbx.ps1' -Paths D:\ -MatchMode Both -MsftJsonPath C:\path\dbx_info_msft_latest.json -ScanESP:$false`

Default JSON:
https://raw.githubusercontent.com/microsoft/secureboot_objects/main/PreSignedObjects/DBX/dbx_info_msft_latest.json

Example output:
<img style="width: 979px" alt="DBX audit scan output" src="docs/screenshot-audit.png" />

[!WARNING]
Detection is based on hash and certificate matching only.
Newer revocations using **SVN (version-based enforcement)** and **SBAT** are **not currently checked**. However `Check EFI file info.cmd` will display SVN/SBAT data if present, but this tool currently does not compare it against UEFI NVRAM policy. Support for SVN and SBAT comparison is welcome as a feature request.


## Re-applying the Secure Boot DBX updates

If the Secure Boot variables were accidentally reset to default in the UEFI/BIOS settings for example, it is possible to make Windows re-apply the DBX updates that Windows had previously applied. Right-click `Apply DBX update.cmd` and *Run as administrator*. Wait for awhile. The DBX updates should be applied after that.
Expand Down
11 changes: 11 additions & 0 deletions Scan ESP for revoked files.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
:: Created for cjee21/Check-UEFISecureBootVariables
@echo off
title Scan EFI files against Microsoft DBX JSON

:: NOTE: Replace URL with a raw URL or use -MsftJsonPath to a local file.
set MSFT_JSON_URL=https://raw.githubusercontent.com/microsoft/secureboot_objects/main/PreSignedObjects/DBX/dbx_info_msft_latest.json

powershell -ExecutionPolicy Bypass -Command "& '%~dp0ps\Find-EfiFilesRevokedByDbx.ps1' -ScanESP -ScanDefaultPaths -MatchMode MsftJson -MsftJsonUrl '%MSFT_JSON_URL%'"

echo.
pause
Binary file added docs/screenshot-audit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
258 changes: 258 additions & 0 deletions ps/Find-EfiFilesRevokedByDbx.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
# Created for cjee21/Check-UEFISecureBootVariables
# Purpose: Walk EFI binaries and warn if they match revocations via:
# - file hash (Authenticode hash if you wire it in later)
# - signer certificate match (X509 DER match) against current DBX (EFI_CERT_X509_GUID)
# Also supports checking against microsoft/secureboot_objects dbx_info_msft_latest.json.

[CmdletBinding()]
param(
# Root directories to scan for .efi files (optional)
[string[]] $Paths,

# If set, will mount ESP to S: (mountvol s: /s) and scan it (default: true)
[switch] $ScanESP = $true,

# Helper flag: scan common OS paths too (default: false)
[switch] $ScanDefaultPaths = $false,

# Which revocation source(s) to match against
[ValidateSet('CurrentDbx','MsftJson','Both')]
[string] $MatchMode = 'CurrentDbx',

# Optional: local path to dbx_info_msft_latest.json
[string] $MsftJsonPath,

# Optional: URL to download dbx_info_msft_latest.json
[string] $MsftJsonUrl
)

$ErrorActionPreference = 'Stop'

Import-Module "$PSScriptRoot\Get-UEFIDatabaseSignatures.ps1" -Force
Import-Module "$PSScriptRoot\Get-EfiSignatures.ps1" -Force

function Get-FileSha256Hex {
param([Parameter(Mandatory)][string] $FilePath)
(Get-FileHash -Algorithm SHA256 -LiteralPath $FilePath).Hash.ToUpperInvariant()
}

function Get-CertDerHex {
param([Parameter(Mandatory)][System.Security.Cryptography.X509Certificates.X509Certificate2] $Cert)
([System.BitConverter]::ToString($Cert.RawData) -replace '-', '').ToUpperInvariant()
}

function Get-DbxSetsFromCurrentDbx {
# Returns:
# - HashSet of SHA256 hex strings (EFI_CERT_SHA256_GUID)
# - HashSet of X509 DER hex strings (EFI_CERT_X509_GUID)
$dbx = Get-SecureBootUEFI -Name dbx
$parsed = $dbx | Get-UEFIDatabaseSignatures

$sha256Set = New-Object 'System.Collections.Generic.HashSet[string]'
$x509DerSet = New-Object 'System.Collections.Generic.HashSet[string]'

foreach ($list in $parsed) {
foreach ($entry in $list.SignatureList) {
if ($list.SignatureType -eq 'EFI_CERT_SHA256_GUID') {
[void]$sha256Set.Add(($entry.SignatureData.ToString().ToUpperInvariant()))
} elseif ($list.SignatureType -eq 'EFI_CERT_X509_GUID') {
$derHex = Get-CertDerHex -Cert $entry.SignatureData
[void]$x509DerSet.Add($derHex)
}
}
}

[PSCustomObject]@{
Name = 'CurrentDbx'
Sha256Set = $sha256Set
X509DerSet = $x509DerSet
}
}

function Get-DbxSetsFromMsftJson {
param(
[string] $Path,
[string] $Url
)

$jsonText = $null
if ($Path) {
if (-not (Test-Path -LiteralPath $Path)) {
throw "MsftJsonPath not found: $Path"
}
$jsonText = Get-Content -LiteralPath $Path -Raw
} elseif ($Url) {
# Download to memory
$jsonText = (Invoke-WebRequest -UseBasicParsing -Uri $Url).Content
} else {
throw "MatchMode requires -MsftJsonPath or -MsftJsonUrl."
}

$j = $jsonText | ConvertFrom-Json

# MSFT JSON structure: { "images": { "x64": [ { authenticodeHash, flatHash, ... }, ... ], "arm64": [ ... ] } }
$sha256Set = New-Object 'System.Collections.Generic.HashSet[string]'

foreach ($archProp in $j.images.PSObject.Properties) {
$archName = $archProp.Name
$items = $archProp.Value
foreach ($img in $items) {
if ($img.authenticodeHash -and $img.authenticodeHash.Trim()) {
#Focus on authenticodeHash for matches as most reliable
[void]$sha256Set.Add($img.authenticodeHash.Trim().ToUpperInvariant())
}
}
}

# MSFT JSON does not directly provide DER blobs for revoked cert entries (at least in the snippet provided),
# so in MsftJson mode we can only do hash-based checks unless you add a mapping of signer certs separately.
$x509DerSet = New-Object 'System.Collections.Generic.HashSet[string]'
if ($j.PSObject.Properties.Name -contains 'certificates' -and $j.certificates) {
foreach ($cert in $j.certificates) {
$tp = $cert.thumbprint
if ($tp -and $tp.Trim()) {
# normalize: remove separators/spaces just in case, and normalize case
$norm = ($tp.Trim() -replace '[^0-9a-fA-F]', '').ToUpperInvariant()

# optional sanity check: SHA1 thumbprints are 20 bytes => 40 hex chars
if ($norm.Length -eq 40) {
[void]$x509DerSet.Add($norm)
}
}
}
}

[PSCustomObject]@{
Name = 'MsftJson'
Sha256Set = $sha256Set
X509DerSet = $x509DerSet
}
}

function Get-EfiFilesUnderPaths {
param([Parameter(Mandatory)][string[]] $RootPaths)

$all = New-Object System.Collections.Generic.List[string]
foreach ($root in $RootPaths) {
if (-not (Test-Path -LiteralPath $root)) { continue }
Get-ChildItem -LiteralPath $root -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.Extension -ieq '.efi' } |
ForEach-Object { $all.Add($_.FullName) | Out-Null }
}
$all
}

# Build scan roots
$scanRoots = New-Object System.Collections.Generic.List[string]

$didMountEsp = $false
if ($ScanESP) {
try {
mountvol s: /s | Out-Null
$didMountEsp = $true
$scanRoots.Add('S:\') | Out-Null
} catch {
Write-Warning "Could not mount ESP to S:. Run as Administrator? Continuing..."
}
}

if ($ScanDefaultPaths) {
# Common locations where EFI binaries may exist on the OS volume.
$scanRoots.Add("$env:SystemRoot\Boot\EFI") | Out-Null
$scanRoots.Add("$env:SystemDrive\EFI") | Out-Null
$scanRoots.Add("$env:SystemDrive\Boot") | Out-Null
}

if ($Paths) {
foreach ($p in $Paths) { $scanRoots.Add($p) | Out-Null }
}

if ($scanRoots.Count -eq 0) {
throw "No scan roots specified and ESP mount failed. Provide -Paths or run elevated."
}

# Load revocation sets
$revocationSets = New-Object System.Collections.Generic.List[object]

if ($MatchMode -eq 'CurrentDbx' -or $MatchMode -eq 'Both') {
Write-Host "Loading current DBX..." -ForegroundColor Cyan
$revocationSets.Add((Get-DbxSetsFromCurrentDbx)) | Out-Null
}

if ($MatchMode -eq 'MsftJson' -or $MatchMode -eq 'Both') {
Write-Host "Loading Microsoft DBX JSON..." -ForegroundColor Cyan
$revocationSets.Add((Get-DbxSetsFromMsftJson -Path $MsftJsonPath -Url $MsftJsonUrl)) | Out-Null
}

foreach ($s in $revocationSets) {
Write-Host ("Loaded {0}: SHA256-like={1}, X509={2}" -f $s.Name, $s.Sha256Set.Count, $s.X509DerSet.Count)
}

Write-Host "Scanning for EFI binaries..." -ForegroundColor Cyan
$efiFiles = Get-EfiFilesUnderPaths -RootPaths $scanRoots.ToArray()
Write-Host ("Found {0} EFI file(s)." -f $efiFiles.Count)

$warnCount = 0
$idx = 0

foreach ($file in $efiFiles) {
$idx++
Write-Progress -Activity "Checking EFI files" -Status $file -PercentComplete (($idx / [Math]::Max(1, $efiFiles.Count)) * 100)

$fileSha = $null

# Signer cert DER hexes (may be empty)
$signerThumbprints = @()
try {
$sigs = Get-EfiSignatures -FilePath $file
$fileSha = $sigs.Authentihash
foreach ($sig in $sigs.Signatures) {
foreach ($c in $sig.Certificates) {
if ($c -and $c.Thumbprint) {
$signerThumbprints += $c.Thumbprint.ToUpperInvariant()
}
}
}
} catch {}

$matches = @()

foreach ($set in $revocationSets) {
# Hash match
if ($fileSha -and $set.Sha256Set.Contains($fileSha)) {
$matches += [PSCustomObject]@{ Source=$set.Name; Type='Hash'; Detail='SHA256(Authenticode) matches revocation list' }
}

# Cert match (only meaningful for CurrentDbx unless you add cert data to MsftJson mode)
if ($signerThumbprints.Count -gt 0 -$set.X509DerSet.Count -gt 0) {
foreach ($derHex in $signerThumbprints) {
if ($set.X509DerSet.Contains($derHex)) {
$matches += [PSCustomObject]@{ Source=$set.Name; Type='SignerCert'; Detail='Signer certificate DER matches DBX X509 revocation' }
break
}
}
}
}

if ($matches.Count -gt 0) {
$warnCount++
Write-Host ""
Write-Host "WARNING: EFI file matches revocation list(s)" -ForegroundColor Yellow
Write-Host (" Path: {0}" -f $file)
if ($fileSha) { Write-Host (" SHA256 (Authenticode): {0}" -f $fileSha) }
if ($signerThumbprints.Count -gt 0) {
Write-Host (" Signer thumbprint(s): {0}" -f ($signerThumbprints -join ', '))
}

foreach ($m in $matches) {
Write-Host (" Match: [{0}] {1} - {2}" -f $m.Source, $m.Type, $m.Detail) -ForegroundColor Yellow
}
}
}

Write-Host ""
Write-Host ("Scan complete. Warnings: {0}" -f $warnCount) -ForegroundColor Cyan

if ($didMountEsp) {
try { mountvol s: /d | Out-Null } catch {}
}