Skip to content

Commit

Permalink
[misc] improve revoked UEFI bootloader reporting
Browse files Browse the repository at this point in the history
* Also fix SBAT not being properly parsed for PE32 executables.
* Also fix signature truncation in GetIssuerCertificateInfo() and fall back to
  returning signer data if issuer is not available (which is typically the case
  for GRUB signed bootloaders).
* Also fix status messages on user cancellation/proceeding.
  • Loading branch information
pbatard committed Oct 9, 2024
1 parent 6b5837d commit ede52c5
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 71 deletions.
1 change: 1 addition & 0 deletions res/loc/rufus.loc
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ t MSG_347 "Expert Mode"
t MSG_348 "Extracting archive files: %s"
t MSG_349 "Use Rufus MBR"
t MSG_350 "Use 'Windows UEFI CA 2023' signed bootloaders [EXPERIMENTAL]"
t MSG_351 "Checking for UEFI bootloader revocation..."
# The following messages are for the Windows Store listing only and are not used by the application
t MSG_900 "Rufus is a utility that helps format and create bootable USB flash drives, such as USB keys/pendrives, memory sticks, etc."
t MSG_901 "Official site: %s"
Expand Down
33 changes: 19 additions & 14 deletions src/hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +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;
extern BOOL expert_mode, usb_debug;

/*
* Rotate 32 or 64 bit integers by n bytes.
Expand Down Expand Up @@ -2081,7 +2081,7 @@ BOOL IsFileInDB(const char* path)
return FALSE;
}

BOOL IsRevokedBySbat(uint8_t* buf, uint32_t len)
static BOOL IsRevokedBySbat(uint8_t* buf, uint32_t len)
{
char* sbat = NULL, *version_str;
uint32_t i, j, sbat_len;
Expand Down Expand Up @@ -2109,6 +2109,7 @@ BOOL IsRevokedBySbat(uint8_t* buf, uint32_t len)
for (; sbat[i] != ',' && sbat[i] != '\0' && sbat[i] != '\n'; i++);
sbat[i++] = '\0';
entry.version = atoi(version_str);
uuprintf(" SBAT: %s,%d", entry.product, entry.version);
for (; sbat[i] != '\0' && sbat[i] != '\n'; i++);
if (entry.version == 0)
continue;
Expand All @@ -2121,13 +2122,13 @@ BOOL IsRevokedBySbat(uint8_t* buf, uint32_t len)
return FALSE;
}

BOOL IsRevokedBySvn(uint8_t* buf, uint32_t len)
static BOOL IsRevokedBySvn(uint8_t* buf, uint32_t len)
{
wchar_t* rsrc_name = NULL;
uint8_t *root;
uint32_t i, j, rsrc_rva, rsrc_len, *svn_ver;
IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)buf;
IMAGE_NT_HEADERS* pe_header;
IMAGE_NT_HEADERS32* pe_header;
IMAGE_NT_HEADERS64* pe64_header;
IMAGE_DATA_DIRECTORY img_data_dir;

Expand All @@ -2143,7 +2144,7 @@ BOOL IsRevokedBySvn(uint8_t* buf, uint32_t len)
if (rsrc_name == NULL)
continue;

pe_header = (IMAGE_NT_HEADERS*)&buf[dos_header->e_lfanew];
pe_header = (IMAGE_NT_HEADERS32*)&buf[dos_header->e_lfanew];
if (pe_header->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 || pe_header->FileHeader.Machine == IMAGE_FILE_MACHINE_ARM) {
img_data_dir = pe_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE];
} else {
Expand All @@ -2157,6 +2158,7 @@ BOOL IsRevokedBySvn(uint8_t* buf, uint32_t len)
if (rsrc_rva != 0) {
if (rsrc_len == sizeof(uint32_t)) {
svn_ver = (uint32_t*)RvaToPhysical(buf, rsrc_rva);
uuprintf(" SVN version: %d.%d", *svn_ver >> 16, *svn_ver & 0xffff);
if (svn_ver != NULL && *svn_ver < sbat_entries[i].version)
return TRUE;
} else {
Expand All @@ -2167,18 +2169,20 @@ BOOL IsRevokedBySvn(uint8_t* buf, uint32_t len)
return FALSE;
}

BOOL IsRevokedByCert(uint8_t* buf, uint32_t len)
static 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))
i = GetIssuerCertificateInfo(cert, &info);
if (i == 0)
uuprintf(" (Unsigned Bootloader)");
if (i <= 0)
return FALSE;

uuprintf(" Signed by: %s", info.name);
for (i = 0; i < ARRAYSIZE(certdbx); i += SHA1_HASHSIZE) {
if (!expert_mode)
continue;
Expand All @@ -2195,7 +2199,7 @@ 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;
IMAGE_NT_HEADERS32* pe_header;

// Fall back to embedded sbat_level.txt if we couldn't access remote
if (sbat_entries == NULL) {
Expand All @@ -2205,11 +2209,15 @@ int IsBootloaderRevoked(uint8_t* buf, uint32_t len)

if (buf == NULL || len < 0x100 || dos_header->e_magic != IMAGE_DOS_SIGNATURE)
return -2;
pe_header = (IMAGE_NT_HEADERS*)&buf[dos_header->e_lfanew];
pe_header = (IMAGE_NT_HEADERS32*)&buf[dos_header->e_lfanew];
if (pe_header->Signature != IMAGE_NT_SIGNATURE)
return -2;
if (!PE256Buffer(buf, len, hash))
return -1;
// Check for UEFI DBX certificate revocation
// This also displays the name of the signer/issuer cert if available
if (IsRevokedByCert(buf, len))
return 5;
// Check for UEFI DBX revocation
for (i = 0; i < ARRAYSIZE(pe256dbx); i += SHA256_HASHSIZE)
if (memcmp(hash, &pe256dbx[i], SHA256_HASHSIZE) == 0)
Expand All @@ -2224,9 +2232,6 @@ int IsBootloaderRevoked(uint8_t* buf, uint32_t len)
// 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
24 changes: 15 additions & 9 deletions src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1643,12 +1643,12 @@ sbat_entry_t* GetSbatEntries(char* sbatlevel)
uint16_t GetPeArch(uint8_t* buf)
{
IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)buf;
IMAGE_NT_HEADERS* pe_header;
IMAGE_NT_HEADERS32* pe_header;

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];
pe_header = (IMAGE_NT_HEADERS32*)&buf[dos_header->e_lfanew];
if (pe_header->Signature != IMAGE_NT_SIGNATURE)
return IMAGE_FILE_MACHINE_UNKNOWN;
return pe_header->FileHeader.Machine;
Expand All @@ -1660,7 +1660,7 @@ uint8_t* GetPeSection(uint8_t* buf, const char* name, uint32_t* len)
char section_name[IMAGE_SIZEOF_SHORT_NAME] = { 0 };
uint32_t i, nb_sections;
IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)buf;
IMAGE_NT_HEADERS* pe_header;
IMAGE_NT_HEADERS32* pe_header;
IMAGE_NT_HEADERS64* pe64_header;
IMAGE_SECTION_HEADER* section_header;

Expand All @@ -1669,7 +1669,7 @@ uint8_t* GetPeSection(uint8_t* buf, const char* name, uint32_t* len)
if (buf == NULL || name == NULL || dos_header->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;

pe_header = (IMAGE_NT_HEADERS*)&buf[dos_header->e_lfanew];
pe_header = (IMAGE_NT_HEADERS32*)&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) {
Expand All @@ -1695,14 +1695,14 @@ uint8_t* RvaToPhysical(uint8_t* buf, uint32_t rva)
{
uint32_t i, nb_sections;
IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)buf;
IMAGE_NT_HEADERS* pe_header;
IMAGE_NT_HEADERS32* pe_header;
IMAGE_NT_HEADERS64* pe64_header;
IMAGE_SECTION_HEADER* section_header;

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

pe_header = (IMAGE_NT_HEADERS*)&buf[dos_header->e_lfanew];
pe_header = (IMAGE_NT_HEADERS32*)&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) {
Expand Down Expand Up @@ -1767,18 +1767,24 @@ uint32_t FindResourceRva(const wchar_t* name, uint8_t* root, uint8_t* dir, uint3
uint8_t* GetPeSignatureData(uint8_t* buf)
{
IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)buf;
IMAGE_NT_HEADERS* pe_header;
IMAGE_NT_HEADERS32* pe_header;
IMAGE_NT_HEADERS64* pe64_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];
pe_header = (IMAGE_NT_HEADERS32*)&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 (pe_header->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 || pe_header->FileHeader.Machine == IMAGE_FILE_MACHINE_ARM) {
sec_dir = pe_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
} else {
pe64_header = (IMAGE_NT_HEADERS64*)pe_header;
sec_dir = pe64_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
}
if (sec_dir.VirtualAddress == 0 || sec_dir.Size == 0)
return NULL;

Expand Down
54 changes: 28 additions & 26 deletions src/pki.c
Original file line number Diff line number Diff line change
Expand Up @@ -384,27 +384,31 @@ 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)
// Fills the certificate's name and thumbprint.
// Tries the issuer first, and if none is available, falls back to current cert.
// Returns 0 for unsigned, -1 on error, 1 for signer or 2 for issuer.
int GetIssuerCertificateInfo(uint8_t* cert, cert_info_t* info)
{
BOOL ret = FALSE;
int ret = 0;
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_CONTEXT pCertContext[2] = { NULL, NULL };
PCCERT_CHAIN_CONTEXT pChainContext = NULL;
CERT_CHAIN_PARA chainPara;
int CertIndex = 0;

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

signedDataBlob.cbData = pWinCert->dwLength - sizeof(WIN_CERTIFICATE);
signedDataBlob.pbData = pWinCert->bCertificate;
if (info == NULL)
return -1;
if (pWinCert == NULL || pWinCert->dwLength == 0)
return 0;

// Get message handle and store handle from the signed file.
signedDataBlob.cbData = pWinCert->dwLength;
signedDataBlob.pbData = pWinCert->bCertificate;
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)) {
Expand All @@ -420,7 +424,7 @@ BOOL GetIssuerCertificateInfo(uint8_t* cert, cert_info_t* info)

// Allocate memory for signer information.
pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSignerInfoSize, 1);
if (!pSignerInfo) {
if (pSignerInfo == NULL) {
uprintf("PKI: Could not allocate memory for signer information");
goto out;
}
Expand All @@ -432,50 +436,48 @@ BOOL GetIssuerCertificateInfo(uint8_t* cert, cert_info_t* info)
}

// Search for the signer certificate in the temporary certificate store.
pCertContext = CertFindCertificateInStore(hStore, ENCODING, 0, CERT_FIND_SUBJECT_CERT, (PVOID)pSignerInfo, NULL);
if (!pCertContext) {
pCertContext[0] = CertFindCertificateInStore(hStore, ENCODING, 0, CERT_FIND_SUBJECT_CERT, (PVOID)pSignerInfo, NULL);
if (pCertContext[0] == NULL) {
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)) {
if (!CertGetCertificateChain(NULL, pCertContext[0], 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)
// Try to get the issuer's certificate (second certificate in the chain) if available
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;
pCertContext[1] = pChainContext->rgpChain[0]->rgpElement[1]->pCertContext;
CertIndex = 1;
}

// Isolate the signing certificate subject name
dwSize = CertGetNameStringA(pIssuerCertContext, CERT_NAME_ATTR_TYPE, 0, szOID_COMMON_NAME,
dwSize = CertGetNameStringA(pCertContext[CertIndex], 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)) {
if (!CryptHashCertificate(0, CALG_SHA1, 0, pCertContext[CertIndex]->pbCertEncoded,
pCertContext[CertIndex]->cbCertEncoded, info->thumbprint, &dwSize)) {
uprintf("PKI: Failed to compute the thumbprint: %s", WinPKIErrorString());
goto out;
}
ret = TRUE;
ret = CertIndex + 1;

out:
safe_free(pSignerInfo);
if (pIssuerCertContext != NULL)
CertFreeCertificateContext(pIssuerCertContext);
if (pCertContext != NULL)
CertFreeCertificateContext(pCertContext);
if (pCertContext[1] != NULL)
CertFreeCertificateContext(pCertContext[1]);
if (pCertContext[0] != NULL)
CertFreeCertificateContext(pCertContext[0]);
if (hStore != NULL)
CertCloseStore(hStore, 0);
if (hMsg != NULL)
Expand Down
Loading

0 comments on commit ede52c5

Please sign in to comment.