Skip to content

Commit

Permalink
[iso] add DBX certificate revocation validation and reporting
Browse files Browse the repository at this point in the history
* This is currently only used to detect the use of 'Microsoft Windows Production PCA 2011'
  signed bootloaders.
* Because the cert is still in the process of being revoked, and Windows 11 24H2 still uses
  'Microsoft Windows Production PCA 2011' signed bootloaders by default, only report this if
  running in expert mode.
* Also fix non-reachable code in wue.c.
  • Loading branch information
pbatard committed Oct 9, 2024
1 parent fd5c366 commit 6b5837d
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 27 deletions.
10 changes: 9 additions & 1 deletion src/db.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Rufus: The Reliable USB Formatting Utility
* DB of the known SHA256 hash values for Rufus downloadable content (GRUB, Syslinux, etc.)
* as well PE256 hash values for UEFI revoked content (DBX, SkuSiPolicy.p7b)
* Copyright © 2016-2023 Pete Batard <[email protected]>
* Copyright © 2016-2024 Pete Batard <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -657,6 +657,14 @@ static uint8_t pe256dbx[] = {
0xff, 0xf4, 0x21, 0xa9, 0xdc, 0xd3, 0xef, 0x38, 0xad, 0x58, 0x5e, 0x8b, 0xac, 0xa4, 0x08, 0xac, 0x2e, 0x4c, 0xdb, 0xdf, 0xa6, 0x79, 0x90, 0x0e, 0xc1, 0x70, 0x89, 0x62, 0x4e, 0x31, 0x0a, 0xda,
};

/*
* Contains the SHA-1 thumbprint of certificates that are being revoked by DBX.
* This only includes the 'Microsoft Windows Production PCA 2011' for now.
*/
static uint8_t certdbx[] = {
0x58, 0x0a, 0x6f, 0x4c, 0xc4, 0xe4, 0xb6, 0x69, 0xb9, 0xeb, 0xdc, 0x1b, 0x2b, 0x3e, 0x08, 0x7b, 0x80, 0xd0, 0x67, 0x8d
};

/*
* Extended SBATLevel.txt that merges Linux SBAT with Microsoft's SVN
* See https://github.com/pbatard/rufus/issues/2244#issuecomment-2243661539
Expand Down
37 changes: 34 additions & 3 deletions src/hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ StrArray modified_files = { 0 };
extern int default_thread_priority;
extern const char* efi_archname[ARCH_MAX];
extern char* sbat_level_txt;
extern BOOL expert_mode;

/*
* Rotate 32 or 64 bit integers by n bytes.
Expand Down Expand Up @@ -2166,19 +2167,46 @@ BOOL IsRevokedBySvn(uint8_t* buf, uint32_t len)
return FALSE;
}

BOOL IsRevokedByCert(uint8_t* buf, uint32_t len)
{
int i;
uint8_t* cert;
cert_info_t info;

cert = GetPeSignatureData(buf);
if (cert == NULL)
return FALSE;
if (!GetIssuerCertificateInfo(cert, &info))
return FALSE;

for (i = 0; i < ARRAYSIZE(certdbx); i += SHA1_HASHSIZE) {
if (!expert_mode)
continue;
if (memcmp(info.thumbprint, &certdbx[i], SHA1_HASHSIZE) == 0) {
uprintf("Found '%s' revoked certificate", info.name);
return TRUE;
}
}
return FALSE;
}

int IsBootloaderRevoked(uint8_t* buf, uint32_t len)
{
uint32_t i;
uint8_t hash[SHA256_HASHSIZE];
IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)buf;
IMAGE_NT_HEADERS* pe_header;

// Fall back to embedded sbat_level.txt if we couldn't access remote
if (sbat_entries == NULL) {
sbat_level_txt = safe_strdup(db_sbat_level_txt);
sbat_entries = GetSbatEntries(sbat_level_txt);
}

// TODO: More elaborate PE checks?
if (buf == NULL || len < 0x100 || buf[0] != 'M' || buf[1] != 'Z')
if (buf == NULL || len < 0x100 || dos_header->e_magic != IMAGE_DOS_SIGNATURE)
return -2;
pe_header = (IMAGE_NT_HEADERS*)&buf[dos_header->e_lfanew];
if (pe_header->Signature != IMAGE_NT_SIGNATURE)
return -2;
if (!PE256Buffer(buf, len, hash))
return -1;
Expand All @@ -2193,9 +2221,12 @@ int IsBootloaderRevoked(uint8_t* buf, uint32_t len)
// Check for Linux SBAT revocation
if (IsRevokedBySbat(buf, len))
return 3;
// Sheck for Microsoft SVN revocation
// Check for Microsoft SVN revocation
if (IsRevokedBySvn(buf, len))
return 4;
// Check for DBX certificate revocation
if (IsRevokedByCert(buf, len))
return 5;
return 0;
}

Expand Down
39 changes: 36 additions & 3 deletions src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#endif

#include <windows.h>
#include <wincrypt.h>
#include <wintrust.h>
#include <stdio.h>
#include <wchar.h>
#include <string.h>
Expand Down Expand Up @@ -1643,10 +1645,12 @@ uint16_t GetPeArch(uint8_t* buf)
IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)buf;
IMAGE_NT_HEADERS* pe_header;

if (buf == NULL)
if (buf == NULL || dos_header->e_magic != IMAGE_DOS_SIGNATURE)
return IMAGE_FILE_MACHINE_UNKNOWN;

pe_header = (IMAGE_NT_HEADERS*)&buf[dos_header->e_lfanew];
if (pe_header->Signature != IMAGE_NT_SIGNATURE)
return IMAGE_FILE_MACHINE_UNKNOWN;
return pe_header->FileHeader.Machine;
}

Expand All @@ -1662,10 +1666,12 @@ uint8_t* GetPeSection(uint8_t* buf, const char* name, uint32_t* len)

static_strcpy(section_name, name);

if (buf == NULL || name == NULL)
if (buf == NULL || name == NULL || dos_header->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;

pe_header = (IMAGE_NT_HEADERS*)&buf[dos_header->e_lfanew];
if (pe_header->Signature != IMAGE_NT_SIGNATURE)
return NULL;
if (pe_header->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 || pe_header->FileHeader.Machine == IMAGE_FILE_MACHINE_ARM) {
section_header = (IMAGE_SECTION_HEADER*)(&pe_header[1]);
nb_sections = pe_header->FileHeader.NumberOfSections;
Expand Down Expand Up @@ -1693,10 +1699,12 @@ uint8_t* RvaToPhysical(uint8_t* buf, uint32_t rva)
IMAGE_NT_HEADERS64* pe64_header;
IMAGE_SECTION_HEADER* section_header;

if (buf == NULL)
if (buf == NULL || dos_header->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;

pe_header = (IMAGE_NT_HEADERS*)&buf[dos_header->e_lfanew];
if (pe_header->Signature != IMAGE_NT_SIGNATURE)
return NULL;
if (pe_header->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 || pe_header->FileHeader.Machine == IMAGE_FILE_MACHINE_ARM) {
section_header = (IMAGE_SECTION_HEADER*)(pe_header + 1);
nb_sections = pe_header->FileHeader.NumberOfSections;
Expand Down Expand Up @@ -1755,3 +1763,28 @@ uint32_t FindResourceRva(const wchar_t* name, uint8_t* root, uint8_t* dir, uint3
}
return 0;
}

uint8_t* GetPeSignatureData(uint8_t* buf)
{
IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)buf;
IMAGE_NT_HEADERS* pe_header;
IMAGE_DATA_DIRECTORY sec_dir;
WIN_CERTIFICATE* cert;

if (buf == NULL || dos_header->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;

pe_header = (IMAGE_NT_HEADERS*)&buf[dos_header->e_lfanew];
if (pe_header->Signature != IMAGE_NT_SIGNATURE)
return NULL;

sec_dir = pe_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
if (sec_dir.VirtualAddress == 0 || sec_dir.Size == 0)
return NULL;

cert = (WIN_CERTIFICATE*)&buf[sec_dir.VirtualAddress];
if (cert->dwLength == 0 || cert->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA)
return NULL;

return (uint8_t*)cert;
}
121 changes: 109 additions & 12 deletions src/pki.c
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,9 @@ char* GetSignatureName(const char* path, const char* country_code, BOOL bSilent)
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;
PCCERT_CONTEXT pCertContext = NULL;
DWORD dwSize, dwEncoding, dwContentType, dwFormatType;
DWORD dwSize, dwEncoding, dwContentType, dwFormatType, dwSignerInfoSize = 0;
PCMSG_SIGNER_INFO pSignerInfo = NULL;
DWORD dwSignerInfo = 0;
// TODO: Do we really need CertInfo? Or can we just reference pSignerInfo?
CERT_INFO CertInfo = { 0 };
SPROG_PUBLISHERINFO ProgPubInfo = { 0 };
wchar_t *szFileName;
Expand Down Expand Up @@ -311,21 +311,21 @@ char* GetSignatureName(const char* path, const char* country_code, BOOL bSilent)
goto out;

// Get signer information size.
r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo);
r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfoSize);
if (!r) {
uprintf("PKI: Failed to get signer size: %s", WinPKIErrorString());
goto out;
}

// Allocate memory for signer information.
pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSignerInfo, 1);
pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSignerInfoSize, 1);
if (!pSignerInfo) {
uprintf("PKI: Could not allocate memory for signer information");
goto out;
}

// Get Signer Information.
r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pSignerInfo, &dwSignerInfo);
r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pSignerInfo, &dwSignerInfoSize);
if (!r) {
uprintf("PKI: Failed to get signer information: %s", WinPKIErrorString());
goto out;
Expand All @@ -334,10 +334,9 @@ char* GetSignatureName(const char* path, const char* country_code, BOOL bSilent)
// Search for the signer certificate in the temporary certificate store.
CertInfo.Issuer = pSignerInfo->Issuer;
CertInfo.SerialNumber = pSignerInfo->SerialNumber;

pCertContext = CertFindCertificateInStore(hStore, ENCODING, 0, CERT_FIND_SUBJECT_CERT, (PVOID)&CertInfo, NULL);
if (!pCertContext) {
uprintf("PKI: Failed to locate signer certificate in temporary store: %s", WinPKIErrorString());
uprintf("PKI: Failed to locate signer certificate in store: %s", WinPKIErrorString());
goto out;
}

Expand Down Expand Up @@ -385,6 +384,105 @@ char* GetSignatureName(const char* path, const char* country_code, BOOL bSilent)
return p;
}

// Return the issuer certificate's name and thumbprint
BOOL GetIssuerCertificateInfo(uint8_t* cert, cert_info_t* info)
{
BOOL ret = FALSE;
DWORD dwSize, dwEncoding, dwContentType, dwFormatType, dwSignerInfoSize = 0;
WIN_CERTIFICATE* pWinCert = (WIN_CERTIFICATE*)cert;
CRYPT_DATA_BLOB signedDataBlob;
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;
PCMSG_SIGNER_INFO pSignerInfo = NULL;
PCCERT_CONTEXT pCertContext = NULL, pIssuerCertContext = NULL;
PCCERT_CHAIN_CONTEXT pChainContext = NULL;
CERT_CHAIN_PARA chainPara;

if (cert == NULL || info == NULL || pWinCert->dwLength == 0)
return FALSE;

signedDataBlob.cbData = pWinCert->dwLength - sizeof(WIN_CERTIFICATE);
signedDataBlob.pbData = pWinCert->bCertificate;

// Get message handle and store handle from the signed file.
if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &signedDataBlob,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED, CERT_QUERY_FORMAT_FLAG_BINARY,
0, &dwEncoding, &dwContentType, &dwFormatType, &hStore, &hMsg, NULL)) {
uprintf("PKI: Failed to get signature: %s", WinPKIErrorString());
goto out;
}

// Get signer information size.
if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, NULL, &dwSignerInfoSize)) {
uprintf("PKI: Failed to get signer size: %s", WinPKIErrorString());
goto out;
}

// Allocate memory for signer information.
pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSignerInfoSize, 1);
if (!pSignerInfo) {
uprintf("PKI: Could not allocate memory for signer information");
goto out;
}

// Get Signer Information.
if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, pSignerInfo, &dwSignerInfoSize)) {
uprintf("PKI: Failed to get signer info: %s", WinPKIErrorString());
goto out;
}

// Search for the signer certificate in the temporary certificate store.
pCertContext = CertFindCertificateInStore(hStore, ENCODING, 0, CERT_FIND_SUBJECT_CERT, (PVOID)pSignerInfo, NULL);
if (!pCertContext) {
uprintf("PKI: Failed to locate signer certificate in store: %s", WinPKIErrorString());
goto out;
}

// Build a certificate chain to get the issuer (CA) certificate.
memset(&chainPara, 0, sizeof(chainPara));
chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
if (!CertGetCertificateChain(NULL, pCertContext, NULL, hStore, &chainPara, 0, NULL, &pChainContext)) {
uprintf("PKI: Failed to build certificate chain. Error code: %s", WinPKIErrorString());
goto out;
}

// Get the issuer's certificate (second certificate in the chain)
if (pChainContext->cChain > 0 && pChainContext->rgpChain[0]->cElement > 1) {
pIssuerCertContext = pChainContext->rgpChain[0]->rgpElement[1]->pCertContext;
} else {
uprintf("PKI: Failed to retrieve issuer's certificate: %s", WinPKIErrorString());
goto out;
}

// Isolate the signing certificate subject name
dwSize = CertGetNameStringA(pIssuerCertContext, CERT_NAME_ATTR_TYPE, 0, szOID_COMMON_NAME,
info->name, sizeof(info->name));
if (dwSize <= 1) {
uprintf("PKI: Failed to get Subject Name");
goto out;
}

dwSize = SHA1_HASHSIZE;
if (!CryptHashCertificate(0, CALG_SHA1, 0, pIssuerCertContext->pbCertEncoded,
pIssuerCertContext->cbCertEncoded, info->thumbprint, &dwSize)) {
uprintf("PKI: Failed to compute the thumbprint: %s", WinPKIErrorString());
goto out;
}
ret = TRUE;

out:
safe_free(pSignerInfo);
if (pIssuerCertContext != NULL)
CertFreeCertificateContext(pIssuerCertContext);
if (pCertContext != NULL)
CertFreeCertificateContext(pCertContext);
if (hStore != NULL)
CertCloseStore(hStore, 0);
if (hMsg != NULL)
CryptMsgClose(hMsg);
return ret;
}

// The timestamping authorities we use are RFC 3161 compliant
static uint64_t GetRFC3161TimeStamp(PCMSG_SIGNER_INFO pSignerInfo)
{
Expand Down Expand Up @@ -523,9 +621,8 @@ uint64_t GetSignatureTimeStamp(const char* path)
HMODULE hm;
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;
DWORD dwSize, dwEncoding, dwContentType, dwFormatType;
DWORD dwSize, dwEncoding, dwContentType, dwFormatType, dwSignerInfoSize = 0;
PCMSG_SIGNER_INFO pSignerInfo = NULL;
DWORD dwSignerInfo = 0;
wchar_t *szFileName;
uint64_t timestamp = 0ULL, nested_timestamp;

Expand Down Expand Up @@ -559,21 +656,21 @@ uint64_t GetSignatureTimeStamp(const char* path)
}

// Get signer information size.
r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo);
r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfoSize);
if (!r) {
uprintf("PKI: Failed to get signer size: %s", WinPKIErrorString());
goto out;
}

// Allocate memory for signer information.
pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSignerInfo, 1);
pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSignerInfoSize, 1);
if (!pSignerInfo) {
uprintf("PKI: Could not allocate memory for signer information");
goto out;
}

// Get Signer Information.
r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pSignerInfo, &dwSignerInfo);
r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pSignerInfo, &dwSignerInfoSize);
if (!r) {
uprintf("PKI: Failed to get signer information: %s", WinPKIErrorString());
goto out;
Expand Down
2 changes: 1 addition & 1 deletion src/rufus.c
Original file line number Diff line number Diff line change
Expand Up @@ -1610,7 +1610,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param)
// Check UEFI bootloaders for revocation
if (IS_EFI_BOOTABLE(img_report)) {
for (i = 0; i < ARRAYSIZE(img_report.efi_boot_path) && img_report.efi_boot_path[i][0] != 0; i++) {
static const char* revocation_type[] = { "UEFI DBX", "Windows SSP", "Linux SBAT", "Windows SVN" };
static const char* revocation_type[] = { "UEFI DBX", "Windows SSP", "Linux SBAT", "Windows SVN", "Cert DBX" };
len = ReadISOFileToBuffer(image_path, img_report.efi_boot_path[i], &buf);
if (len == 0) {
uprintf("Warning: Failed to extract '%s' to check for UEFI revocation", img_report.efi_boot_path[i]);
Expand Down
Loading

0 comments on commit 6b5837d

Please sign in to comment.