diff --git a/src/db.h b/src/db.h index 30aa301ae4d..52c026598e2 100644 --- a/src/db.h +++ b/src/db.h @@ -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 + * Copyright © 2016-2024 Pete Batard * * 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 @@ -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 diff --git a/src/hash.c b/src/hash.c index d39f68abaad..5ff7df1061b 100644 --- a/src/hash.c +++ b/src/hash.c @@ -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. @@ -2166,10 +2167,35 @@ 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) { @@ -2177,8 +2203,10 @@ int IsBootloaderRevoked(uint8_t* buf, uint32_t len) 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; @@ -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; } diff --git a/src/parser.c b/src/parser.c index ca48c61e4cf..7bc21692ddf 100644 --- a/src/parser.c +++ b/src/parser.c @@ -24,6 +24,8 @@ #endif #include +#include +#include #include #include #include @@ -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; } @@ -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; @@ -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; @@ -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; +} diff --git a/src/pki.c b/src/pki.c index 5c69d3fce8b..09dda43c80f 100644 --- a/src/pki.c +++ b/src/pki.c @@ -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; @@ -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; @@ -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; } @@ -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) { @@ -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; @@ -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; diff --git a/src/rufus.c b/src/rufus.c index 8e0837342a6..09dcc29fc2c 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -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]); diff --git a/src/rufus.h b/src/rufus.h index 9240784a23e..61e26ff8936 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -528,6 +528,12 @@ typedef struct ALIGNED(64) { uint64_t bytecount; } HASH_CONTEXT; +/* Certificate info */ +typedef struct { + char name[256]; + uint8_t thumbprint[SHA1_HASHSIZE]; +} cert_info_t; + /* Hash functions */ typedef void hash_init_t(HASH_CONTEXT* ctx); typedef void hash_write_t(HASH_CONTEXT* ctx, const uint8_t* buf, size_t len); @@ -658,7 +664,7 @@ typedef struct htab_table { uint32_t size; uint32_t filled; } htab_table; -#define HTAB_EMPTY {NULL, 0, 0} +#define HTAB_EMPTY { NULL, 0, 0 } extern BOOL htab_create(uint32_t nel, htab_table* htab); extern void htab_destroy(htab_table* htab); extern uint32_t htab_hash(char* str, htab_table* htab); @@ -799,6 +805,7 @@ extern void* get_data_from_asn1(const uint8_t* buf, size_t buf_len, const char* extern int sanitize_label(char* label); extern int IsHDD(DWORD DriveIndex, uint16_t vid, uint16_t pid, const char* strid); extern char* GetSignatureName(const char* path, const char* country_code, BOOL bSilent); +extern BOOL GetIssuerCertificateInfo(uint8_t* cert, cert_info_t* info); extern uint64_t GetSignatureTimeStamp(const char* path); extern LONG ValidateSignature(HWND hDlg, const char* path); extern BOOL ValidateOpensslSignature(BYTE* pbBuffer, DWORD dwBufferLen, BYTE* pbSignature, DWORD dwSigLen); @@ -841,6 +848,7 @@ extern uint32_t ResolveDllAddress(dll_resolver_t* resolver); extern sbat_entry_t* GetSbatEntries(char* sbatlevel); extern uint16_t GetPeArch(uint8_t* buf); extern uint8_t* GetPeSection(uint8_t* buf, const char* name, uint32_t* len); +extern uint8_t* GetPeSignatureData(uint8_t* buf); extern uint8_t* RvaToPhysical(uint8_t* buf, uint32_t rva); extern uint32_t FindResourceRva(const wchar_t* name, uint8_t* root, uint8_t* dir, uint32_t* len); extern DWORD ListDirectoryContent(StrArray* arr, char* dir, uint8_t type); diff --git a/src/rufus.rc b/src/rufus.rc index 7236b2c2e6c..ac3546960ff 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.6.2201" +CAPTION "Rufus 4.6.2202" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -399,8 +399,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,6,2201,0 - PRODUCTVERSION 4,6,2201,0 + FILEVERSION 4,6,2202,0 + PRODUCTVERSION 4,6,2202,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -418,13 +418,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.6.2201" + VALUE "FileVersion", "4.6.2202" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.6.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.6.2201" + VALUE "ProductVersion", "4.6.2202" END END BLOCK "VarFileInfo" diff --git a/src/wue.c b/src/wue.c index 46db5bfd5be..d7b14cf4e4e 100644 --- a/src/wue.c +++ b/src/wue.c @@ -1026,7 +1026,7 @@ BOOL ApplyWindowsCustomization(char drive_letter, int flags) } // Now that we should be able to write to the destination directories, copy the content. ListDirectoryContent(&files, path, LIST_DIR_TYPE_FILE | LIST_DIR_TYPE_RECURSIVE); - for (i = 0; r && i < (int)files.Index; i++) { + for (i = 0; i < (int)files.Index; i++) { rep = remove_substr(files.String[i], "_EX"); assert(rep != NULL); TakeOwnership(rep);