Last active
May 15, 2026 06:38
-
-
Save jacky9813/c342653ce09c7f1c9791e352c0381a48 to your computer and use it in GitHub Desktop.
Reading EFI_VARIABLE_AUTHENTICATION_2 for updating Secure Boot variables
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| PowerShell - Read EFI_VARIABLE_AUTHENTICATION_2 binary | |
| EFI_VARIABLE_AUTHENTICATION_2 are typically used for updating | |
| Secure Boot variables like db, dbx and KEK. Examples can be | |
| found in GitHub microsoft/secureboot_objects/PostSignedObjects | |
| Download this script and import it: | |
| Invoke-WebRequest -Uri "SCRIPT_URI" -OutFile "read_auth.ps1" | |
| Import-Module .\read_auth.ps1 | |
| To enable debugging message output: | |
| $DebugPreference = "Continue" | |
| Add Microsoft 2023 KEK certificate: (Tested in Windows Server 2022 on KVM) | |
| Invoke-WebRequest -OutFile "KEKUpdate_RedHat_PK1.bin" -Uri "https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PostSignedObjects/KEK/RedHat/KEKUpdate_RedHat_PK1.bin" | |
| $auth_data = [EfiVariableAuthentication2]::new("KEKUpdate_RedHat_PK1.bin") | |
| # Use write helper function | |
| $auth_data.WriteEfiVar("KEK") | |
| # Or manually | |
| $auth_data.ExportPayload("KEKUpdate_RedHat_PK1.esl") | |
| $auth_data.AuthInfo.ExportCertData("KEKUpdate_RedHat_PK1.p7s") | |
| Set-SecureBootUEFI -Name KEK -AppendWrite -Time $auth_data.FormatTime() -ContentFilePath "KEKUpdate_RedHat_PK1.esl" -SignedFilePath "KEKUpdate_RedHat_PK1.p7s" | |
| Update to latest DBX: | |
| Invoke-WebRequest -Uri "https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PostSignedObjects/DBX/amd64/DBXUpdate.bin" -OutFile "DBXUpdate.bin" | |
| $auth_data = [EfiVariableAuthentication2]::new("DBXUpdate.bin") | |
| # Use write helper function | |
| $auth_data.WriteEfiVar("dbx") | |
| # Or manually | |
| $auth_data.ExportPayload("DBXUpdate.esl") | |
| $auth_data.AuthInfo.ExportCertData("DBXUpdate.p7s") | |
| Set-SecureBootUEFI -Name DBX -AppendWrite -Time $auth_data.FormatTime() -ContentFilePath "DBXUpdate.esl" -SignedFilePath "DBXUpdate.p7s" | |
| Add Microsoft 2023 CA certificates to DB: | |
| Invoke-WebRequest -OutFile "DBUpdate2024.bin" -Uri "https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PostSignedObjects/Optional/DB/amd64/DBUpdate2024.bin" | |
| Invoke-WebRequest -OutFile "DBUpdate3P2023.bin" -Uri "https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PostSignedObjects/Optional/DB/amd64/DBUpdate3P2023.bin" | |
| Invoke-WebRequest -OutFile "DBUpdateOROM2023.bin" -Uri "https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PostSignedObjects/Optional/DB/amd64/DBUpdateOROM2023.bin" | |
| $auth_db_win2023 = [EfiVariableAuthentication2]::new("DBUpdate2024.bin") | |
| $auth_db_win2023.WriteEfiVar("db") | |
| $auth_db_ms2023 = [EfiVariableAuthentication2]::new("DBUpdate3P2023.bin") | |
| $auth_db_ms2023.WriteEfiVar("db") | |
| $auth_db_orom2023 = [EfiVariableAuthentication2]::new("DBUpdateOROM2023.bin") | |
| $auth_db_orom2023.WriteEfiVar("db") | |
| To list signatures in auth file payload: | |
| $auth_data = [EfiVariableAuthentication2]::new("KEKUpdate_RedHat_PK1.bin") | |
| $signatures = [EfiSignatureListFile]::new($auth_data.Payload).Entries | |
| To list signatures from an ESL file: | |
| $signatures = [EfiSignatureListFile]::new("filename.esl").Entries | |
| To list db signatures from UEFI: | |
| $signatures = [EfiSignatureListFile]::new((Get-SecureBootUEFI -Name db)).Entries | |
| To export all certificates from db: | |
| $signatures = [EfiSignatureListFile]::new((Get-SecureBootUEFI -Name db)) | |
| $signatures.ExportAllSignatures("db-") | |
| #> | |
| # Mapped to WIN_CERT_TYPE_* | |
| enum WinCertType { | |
| PkcsSignedData = 0x0002 | |
| EfiPkcs1_15 = 0xEF0 | |
| EfiGuid = 0xEF1 | |
| } | |
| # Mapped to struct WIN_CERTIFICATE | |
| class WinCertificate { | |
| [UInt32]$dwLength | |
| [UInt16]$wRevision | |
| [WinCertType]$wCertificateType | |
| WinCertificate([Byte[]]$InputBytes) { | |
| $this.Decode($InputBytes, 0) | |
| } | |
| WinCertificate([Byte[]]$InputBytes, [Int]$Offset) { | |
| $this.Decode($InputBytes, $Offset) | |
| } | |
| [void] Decode([Byte[]]$InputBytes, [Int]$Offset) { | |
| $this.dwLength = [System.BitConverter]::ToUInt32($InputBytes, $Offset) | |
| $this.wRevision = [System.BitConverter]::ToUInt16($InputBytes, $Offset+4) | |
| if ($this.wRevision -ne 0x0200) { | |
| Write-Warning "Expecting wRevision = 512, got $($this.wRevision.ToString())" | |
| } | |
| $certType = [System.BitConverter]::ToUInt16($InputBytes, $Offset+6) | |
| $this.wCertificateType = [WinCertType]($certType) | |
| } | |
| } | |
| # Mapped to struct WIN_CERTIFICATE_UEFI_GUID | |
| class WinCertificateUefiGuid { | |
| [WinCertificate]$Hdr | |
| [System.Guid]$CertType | |
| [Byte[]]$CertData | |
| WinCertificateUefiGuid([Byte[]]$InputBytes) { | |
| $this.Decode($InputBytes, 0) | |
| } | |
| WinCertificateUefiGuid([Byte[]]$InputBytes, [Int]$Offset) { | |
| $this.Decode($InputBytes, $Offset) | |
| } | |
| [void] Decode([Byte[]]$InputBytes, [Int]$Offset) { | |
| $this.Hdr = [WinCertificate]::new($InputBytes, $Offset) | |
| $rawGuid = [Byte[]]$InputBytes[($Offset+8)..($Offset+23)] | |
| $this.CertType = [System.Guid]::new($rawGuid) | |
| if ($this.CertType -eq [System.Guid]::new("4aafd29d-68df-49ee-8aa9-347d375665a7")) { | |
| Write-Debug "[$($this.GetType().Name)] CertType is EFI_CERT_TYPE_PKCS7_GUID" | |
| if (($InputBytes[$Offset+24] -ne 0x30) -or ($InputBytes[$Offset+25] -ne 0x82)) { | |
| Write-Warning "The CertData does not seem like a valid PKCS#7 SignedData" | |
| } | |
| # Can't use System.BitConverter here as ASN.1 uses different byte order | |
| # Reading ASN.1 SEQUENCE LENGTH + SEQUENCE HEADER | |
| $dataLength = ($InputBytes[($Offset+26)] * 256) + $InputBytes[($Offset+27)] + 4 | |
| Write-Debug "[$($this.GetType().Name)] PKCS#7 CertData has length of $($dataLength.ToString()) bytes" | |
| $this.CertData = $InputBytes[($Offset+24)..($Offset+24+$dataLength-1)] | |
| } else { | |
| Write-Warning "Unknown CertType GUID: $($this.CertType.ToString())" | |
| $this.CertData = $InputBytes[($Offset+24)..($Offset+$this.Hdr.dwLength-1)] | |
| } | |
| } | |
| [void] ExportCertData([String]$Path) { | |
| if ([System.IO.Path]::IsPathRooted($Path)) { | |
| $truePath = $Path | |
| } else { | |
| $truePath = [System.IO.Path]::GetFullPath("$($PWD.Path)\$($Path)") | |
| } | |
| Write-Debug "[$($this.GetType().Name)] Writing CertData to file $($truePath)" | |
| [System.IO.File]::WriteAllBytes($truePath, $this.CertData) | |
| } | |
| } | |
| # Mapped to struct EFI_SIGNATURE_DATA | |
| class EfiSignatureData { | |
| [Guid]$SignatureOwner | |
| [System.Object]$SignatureData | |
| EfiSignatureData([Guid]$SignatureType, [Byte[]]$InputBytes){ | |
| $this.Decode($SignatureType, $InputBytes) | |
| } | |
| EfiSignatureData([Guid]$SignatureType, [Byte[]]$InputBytes, [Int]$Offset, [Int]$Length){ | |
| $this.Decode($SignatureType, $InputBytes[$Offset..($Offset + $Length - 1)]) | |
| } | |
| [void] Decode([Guid]$SignatureType, [Byte[]]$InputBytes){ | |
| $this.SignatureOwner = [System.Guid]::new([Byte[]]$InputBytes[0..15]) | |
| $rawSignatureData = [byte[]]$InputBytes[16..($InputBytes.length-1)] | |
| if ($SignatureType -eq [Guid]::new("a5c059a1-94e4-4aa7-87b5-ab155c2bf072")) { | |
| Write-Debug "[$($this.GetType().Name)] Signature is X.509 Certificate" | |
| $this.SignatureData = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new( | |
| $rawSignatureData | |
| ) | |
| } elseif ($SignatureType -eq [Guid]::new("c1c41626-504c-4092-aca9-41f936934328")) { | |
| Write-Debug "[$($this.GetType().Name)] Signature is SHA256 Digest" | |
| $this.SignatureData = $rawSignatureData | |
| } else { | |
| Write-Warning "[$($this.GetType().Name)] Unknown SignatureType GUID $($SignatureType.ToString())" | |
| $this.SignatureData = $rawSignatureData | |
| } | |
| } | |
| [void] ExportSignatureData([String]$Path) { | |
| if ([System.IO.Path]::IsPathRooted($Path)) { | |
| $truePath = $Path | |
| } else { | |
| $truePath = [System.IO.Path]::GetFullPath("$($PWD.Path)\$($Path)") | |
| } | |
| Write-Debug "[$($this.GetType().Name)] Writing signature data to $($truePath)" | |
| if ($this.SignatureData -is [System.Security.Cryptography.X509Certificates.X509Certificate2]){ | |
| $rawData = $this.SignatureData.RawData | |
| } elseif ($this.SignatureData -is [byte[]]) { | |
| $rawData = $this.SignatureData | |
| } else { | |
| throw "Unsupported SignatureData type: $($this.SignatureData.GetType().FullName)" | |
| } | |
| [System.IO.File]::WriteAllBytes($truePath, $rawData) | |
| } | |
| } | |
| # Mapped to struct EFI_SIGNATURE_LIST | |
| class EfiSignatureList { | |
| [Guid]$SignatureType | |
| [UInt32]$SignatureListSize | |
| [UInt32]$SignatureHeaderSize | |
| [UInt32]$SignatureSize | |
| [Byte[]]$SignatureHeader | |
| [EfiSignatureData[]]$Signatures | |
| EfiSignatureList() {} | |
| EfiSignatureList([String]$Path) { | |
| $fullPath = (Get-Item $Path).FullName | |
| Write-Debug "[$($this.GetType().Name)] Reading file $($fullPath)" | |
| $rawContent = [System.IO.File]::ReadAllBytes($fullPath) | |
| $this.Decode($rawContent) | |
| } | |
| EfiSignatureList([Byte[]]$InputBytes){ | |
| $this.Decode($InputBytes) | |
| } | |
| EfiSignatureList([Byte[]]$InputBytes, [Int]$Offset){ | |
| $this.Decode($InputBytes[$Offset..($Offset+$InputBytes.length-1)]) | |
| } | |
| EfiSignatureList([Byte[]]$InputBytes, [Int]$Offset, [Int]$Length){ | |
| $this.Decode($InputBytes[$Offset..($Offset+$Length-1)]) | |
| } | |
| [Boolean] IsX509SignatureType(){ | |
| return $this.SignatureType -eq [Guid]::new("a5c059a1-94e4-4aa7-87b5-ab155c2bf072") | |
| } | |
| [UInt32] Decode([Byte[]]$InputBytes){ | |
| $this.SignatureType = [System.Guid]::new([Byte[]]$InputBytes[0..15]) | |
| $this.SignatureListSize = [System.BitConverter]::ToUInt32($InputBytes, 16) | |
| $this.SignatureHeaderSize = [System.BitConverter]::ToUInt32($InputBytes, 20) | |
| $this.SignatureSize = [System.BitConverter]::ToUInt32($InputBytes, 24) | |
| if ($this.SignatureHeaderSize -gt 0){ | |
| $this.SignatureHeader = [Byte[]]$InputBytes[28..(28+$this.SignatureHeaderSize-1)] | |
| } else { | |
| $this.SignatureHeader = [Byte[]]::new(0) | |
| } | |
| $offset = 28 + $this.SignatureHeaderSize | |
| $this.Signatures = [EfiSignatureData[]]::new(0) | |
| while ($offset -lt $this.SignatureListSize) { | |
| Write-Debug "[$($this.GetType().Name)] Loading Signature (offset=$($offset.ToString()), length=$($this.SignatureSize.ToString()))" | |
| $sig = [EfiSignatureData]::new($this.SignatureType, $InputBytes, $offset, $this.SignatureSize) | |
| $this.Signatures += [EfiSignatureData[]]($sig) | |
| $offset += $this.SignatureSize | |
| } | |
| Write-Debug "[$($this.GetType().Name)] Loaded $($this.Signatures.Length.ToString()) signatures" | |
| return [UInt32]$offset | |
| } | |
| [UInt32] Decode([Byte[]]$InputBytes, [Int]$Offset){ | |
| return $this.Decode($InputBytes[$Offset..($InputBytes.length-1)]) | |
| } | |
| } | |
| # Each ESL can contain multiple EFI Signature Lists | |
| class EfiSignatureListFile{ | |
| [EfiSignatureList[]]$Entries | |
| EfiSignatureListFile([String]$Path){ | |
| $fullPath = (Get-Item $Path).FullName | |
| Write-Debug "[$($this.GetType().Name)] Reading file $($fullPath)" | |
| $rawContent = [System.IO.File]::ReadAllBytes($fullPath) | |
| $this.Decode($rawContent) | |
| } | |
| EfiSignatureListFile([Byte[]]$InputBytes){ | |
| $this.Decode($InputBytes) | |
| } | |
| EfiSignatureListFile([object]$ObjInput){ | |
| if ($ObjInput.Bytes -eq $null) { | |
| throw "$($ObjInput.GetType().FullName) is not a supported type" | |
| } | |
| Write-Debug "[$($this.GetType().Name)] Reading from $($ObjInput.GetType().FullName) instance" | |
| $this.Decode($ObjInput.Bytes) | |
| } | |
| [void] Decode([Byte[]]$InputBytes){ | |
| $this.Entries = [EfiSignatureList[]]::new(0) | |
| [UInt32]$offset = 0 | |
| while ($offset -lt $InputBytes.length) { | |
| Write-Debug "[$($this.GetType().Name)] Reading List #$($this.Entries.Length) (offset=$($offset))" | |
| $newEntry = [EfiSignatureList]::new() | |
| $offset += $newEntry.Decode($InputBytes, $offset) | |
| $this.Entries += [EfiSignatureList[]]($newEntry) | |
| } | |
| } | |
| [Int] ExportAllSignatures([String]$PathPrefix, [String]$FileExt) { | |
| $idx = [Int]0 | |
| foreach ($entry in $this.Entries) { | |
| foreach ($signature_data in $entry.Signatures) { | |
| $fileName = "$($PathPrefix)$($idx.ToString())$($FileExt)" | |
| $signature_data.ExportSignatureData($fileName) | |
| $idx += 1 | |
| } | |
| } | |
| return $idx | |
| } | |
| [Int] ExportAllSignatures([String]$PathPrefix) { | |
| return $this.ExportAllSignatures($PathPrefix, ".der") | |
| } | |
| } | |
| # Mapped to EFI_VARIABLE_AUTHENTICATION_2 | |
| class EfiVariableAuthentication2 { | |
| [System.DateTimeOffset]$Timestamp | |
| [WinCertificateUefiGuid]$AuthInfo | |
| [Byte[]]$Payload | |
| EfiVariableAuthentication2([Byte[]]$InputBytes) { | |
| $this.Decode($InputBytes) | |
| } | |
| EfiVariableAuthentication2([String]$Path) { | |
| $fullPath = (Get-Item $Path).FullName | |
| Write-Debug "[$($this.GetType().Name)] Reading file $($fullPath)" | |
| $rawContent = [System.IO.File]::ReadAllBytes($fullPath) | |
| $this.Decode($rawContent) | |
| } | |
| [void] Decode([Byte[]]$InputBytes) { | |
| # Write-Host "loaded content length: $($InputBytes.length.ToString())" | |
| Function Read-EfiTime { | |
| param ( | |
| [Parameter(Mandatory)][Byte[]]$InputBytes, | |
| [Int]$Offset = 0 | |
| ) | |
| # Write-Host "Input Bytes count: $($InputBytes.length.ToString())" | |
| # Write-Host "Reading EFI time at offset $($Offset.ToString())" | |
| # $InputBytes[$Offset+7] => padding | |
| $timeOffset = [System.TimeSpan]::new( | |
| 0, [System.BitConverter]::ToInt16($InputBytes, $Offset + 12), 0) | |
| # $timeDaylight = $InputBytes[14] # not used here | |
| # $InputBytes[15] => padding | |
| $dateTime = [System.DateTimeOffset]::new( | |
| [System.BitConverter]::ToUInt16($InputBytes, $Offset), # year | |
| $InputBytes[$Offset+2], # month | |
| $InputBytes[$Offset+3], # date | |
| $InputBytes[$Offset+4], # hour | |
| $InputBytes[$Offset+5], # minute | |
| $InputBytes[$Offset+6], # seconds | |
| [Math]::Round( | |
| # nanos, but System.DateTime only accept millies | |
| [System.BitConverter]::ToUInt32($InputBytes, $Offset + 8) / 1000000, | |
| [MidpointRounding]::AwayFromZero | |
| ), | |
| $timeOffset | |
| ) | |
| return $dateTime | |
| } | |
| $this.Timestamp = Read-EfiTime -InputBytes $InputBytes -Offset 0 | |
| $this.AuthInfo = [WinCertificateUefiGuid]::new($InputBytes, 16) | |
| Write-Debug "[$($this.GetType().Name)] AuthInfo has length of $($this.AuthInfo.Hdr.dwLength.ToString()) bytes" | |
| $payloadStart = 40 + $this.AuthInfo.CertData.Length | |
| $this.Payload = $InputBytes[$payloadStart..($InputBytes.length-1)] | |
| Write-Debug "[$($this.GetType().Name)] Payload has length of $($this.Payload.Length.ToString()) bytes" | |
| } | |
| [void] ExportPayload([String]$Path) { | |
| if ([System.IO.Path]::IsPathRooted($Path)) { | |
| $truePath = $Path | |
| } else { | |
| $truePath = [System.IO.Path]::GetFullPath("$($PWD.Path)\$($Path)") | |
| } | |
| Write-Debug "[$($this.GetType().Name)] Writing $($this.Payload.Length) bytes payload to $($truePath)" | |
| [System.IO.File]::WriteAllBytes($truePath, $this.Payload) | |
| } | |
| [String] FormatTime() { | |
| return $this.Timestamp.UtcDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ") | |
| } | |
| [void] WriteEfiVar([String]$Name, [Boolean]$AppendWrite) { | |
| $signedFilePath = ".\.signedFile-$($Name).p7s" | |
| Write-Debug "[$($this.GetType().Name)] Set-SecureBootUEFI requires CertData being a standalone file. Exporting to $($signedFilePath)" | |
| $this.AuthInfo.ExportCertData($signedFilePath) | |
| if ($AppendWrite) { | |
| Write-Debug "[$($this.GetType().Name)] Appending to $($Name)" | |
| $result = Set-SecureBootUEFI -Name $Name -AppendWrite -Time $this.FormatTime() -Content $this.Payload -SignedFilePath $signedFilePath | |
| } else { | |
| Write-Debug "[$($this.GetType().Name)] Replacing $($Name)" | |
| $result = Set-SecureBootUEFI -Name $Name -Time $this.FormatTime() -Content $this.Payload -SignedFilePath $signedFilePath | |
| } | |
| Write-Debug "[$($this.GetType().Name)] Removing $($signedFilePath)" | |
| Remove-Item $signedFilePath | |
| } | |
| WriteEfiVar([String]$Name){ | |
| $this.WriteEfiVar($Name, $true) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment