diff --git a/src/db.h b/src/db.h index 686980f61ed..30aa301ae4d 100644 --- a/src/db.h +++ b/src/db.h @@ -656,3 +656,15 @@ static uint8_t pe256dbx[] = { 0xff, 0xd7, 0x68, 0x8e, 0x7d, 0x2b, 0x8c, 0x3c, 0x31, 0x40, 0xb4, 0x15, 0xe7, 0x28, 0xbb, 0xe7, 0x66, 0x3c, 0x54, 0xe2, 0x3b, 0xd2, 0x88, 0xff, 0x2c, 0xf4, 0x61, 0x78, 0x35, 0x08, 0x8f, 0x39, 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, }; + +/* + * Extended SBATLevel.txt that merges Linux SBAT with Microsoft's SVN + * See https://github.com/pbatard/rufus/issues/2244#issuecomment-2243661539 + * Use as fallback when https://rufus.ie/sbat_level.txt cannot be accessed. + */ +static const char db_sbat_level_txt[] = + "sbat,1,2024010900\n" + "shim,4\n" + "grub,3\n" + "grub.debian,4\n" + "BOOTMGRSECURITYVERSIONNUMBER,0x30000"; diff --git a/src/hash.c b/src/hash.c index 84ab9c5c6ea..1fd58c1ff0c 100644 --- a/src/hash.c +++ b/src/hash.c @@ -116,6 +116,7 @@ StrArray modified_files = { 0 }; extern int default_thread_priority; extern const char* efi_archname[ARCH_MAX]; +extern char* sbat_level_txt; /* * Rotate 32 or 64 bit integers by n bytes. @@ -2081,25 +2082,16 @@ BOOL IsFileInDB(const char* path) BOOL IsRevokedBySbat(uint8_t* buf, uint32_t len) { - static const char section_name[IMAGE_SIZEOF_SHORT_NAME] = { '.', 's', 'b', 'a', 't', '\0', '\0', '\0' }; - char* sbat = NULL, * version_str; - uint32_t i, j; + char* sbat = NULL, *version_str; + uint32_t i, j, sbat_len; sbat_entry_t entry; - IMAGE_SECTION_HEADER* section_header; - if (buf == NULL || len < 0x100 || sbat_entries == NULL) + if (sbat_entries == NULL) return FALSE; - for (i = 0x40; i < MIN(len, 0x800); i++) { - if (memcmp(section_name, &buf[i], sizeof(section_name)) == 0) { - section_header = (IMAGE_SECTION_HEADER*)&buf[i]; - if (section_header->SizeOfRawData >= len || section_header->PointerToRawData >= len) - return TRUE; - sbat = (char*)&buf[section_header->PointerToRawData]; - break; - } - } - if (sbat == NULL) + // Look for a .sbat section + sbat = GetPeSection(buf, &sbat_len, ".sbat"); + if (sbat == NULL || sbat < buf || sbat >= buf + len) return FALSE; for (i = 0; sbat[i] != '\0'; ) { @@ -2128,11 +2120,66 @@ BOOL IsRevokedBySbat(uint8_t* buf, uint32_t len) return FALSE; } +BOOL IsRevokedBySvn(uint8_t* buf, uint32_t len) +{ + wchar_t* rsrc_name = NULL; + uint8_t *base; + 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_HEADERS64* pe64_header; + IMAGE_DATA_DIRECTORY img_data_dir; + + if (sbat_entries == NULL) + return FALSE; + + for (i = 0; sbat_entries[i].product != NULL; i++) { + // SVN entries are expected to be uppercase + for (j = 0; j < strlen(sbat_entries[i].product) && isupper(sbat_entries[i].product[j]); j++); + if (j < strlen(sbat_entries[i].product)) + continue; + rsrc_name = utf8_to_wchar(sbat_entries[i].product); + if (rsrc_name == NULL) + continue; + + pe_header = (IMAGE_NT_HEADERS*)&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 { + pe64_header = (IMAGE_NT_HEADERS64*)pe_header; + img_data_dir = pe64_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE]; + } + + base = RvaToPhysical(buf, img_data_dir.VirtualAddress); + rsrc_rva = FindResourceRva(FALSE, base, base, rsrc_name, &rsrc_len); + safe_free(rsrc_name); + if (rsrc_rva != 0) { + if (rsrc_len == sizeof(uint32_t)) { + svn_ver = (uint32_t*)RvaToPhysical(buf, rsrc_rva); + if (svn_ver != NULL && *svn_ver < sbat_entries[i].version) + return TRUE; + } else { + uprintf("WARNING: Unexpected Microsoft SVN version size"); + } + } + } + return FALSE; +} + int IsBootloaderRevoked(uint8_t* buf, uint32_t len) { uint32_t i; uint8_t hash[SHA256_HASHSIZE]; + // 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') + return -2; if (!PE256Buffer(buf, len, hash)) return -1; // Check for UEFI DBX revocation @@ -2143,10 +2190,12 @@ int IsBootloaderRevoked(uint8_t* buf, uint32_t len) for (i = 0; i < pe256ssp_size * SHA256_HASHSIZE; i += SHA256_HASHSIZE) if (memcmp(hash, &pe256ssp[i], SHA256_HASHSIZE) == 0) return 2; - // Check for SBAT revocation + // Check for Linux SBAT revocation if (IsRevokedBySbat(buf, len)) return 3; - + // Sheck for Microsoft SVN revocation + if (IsRevokedBySvn(buf, len)) + return 4; return 0; } diff --git a/src/net.c b/src/net.c index 8e486ca08d6..f5fc821546e 100644 --- a/src/net.c +++ b/src/net.c @@ -240,7 +240,7 @@ uint64_t DownloadToFileOrBufferEx(const char* url, const char* file, const char* if (DownloadStatus != 200) { error_code = ERROR_INTERNET_ITEM_NOT_FOUND; SetLastError(RUFUS_ERROR(error_code)); - uprintf("%s: %d", (DownloadStatus == 404) ? "File not found" : "Unable to access file", DownloadStatus); + uprintf("%s '%s': %d", (DownloadStatus == 404) ? "File not found" : "Unable to access file", url, DownloadStatus); goto out; } dwSize = sizeof(strsize); diff --git a/src/parser.c b/src/parser.c index 2cb09ccf013..48905642dc0 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1570,10 +1570,17 @@ sbat_entry_t* GetSbatEntries(char* sbatlevel) num_entries = 0; for (i = 0; sbatlevel[i] != '\0'; ) { - while (sbatlevel[i] == '\n') + // Eliminate blank lines + if (sbatlevel[i] == '\n') { i++; - if (sbatlevel[i] == '\0') - break; + continue; + } + // Eliminate comments + if (sbatlevel[i] == '#') { + while (sbatlevel[i] != '\n' && sbatlevel[i] != '\0') + i++; + continue; + } sbat_entries[num_entries].product = &sbatlevel[i]; for (; sbatlevel[i] != ',' && sbatlevel[i] != '\0' && sbatlevel[i] != '\n'; i++); if (sbatlevel[i] == '\0' || sbatlevel[i] == '\n') @@ -1586,7 +1593,11 @@ sbat_entry_t* GetSbatEntries(char* sbatlevel) sbatlevel[i] = '\0'; if (!eof) i++; - sbat_entries[num_entries].version = atoi(version_str); + // Allow the provision of an hex version + if (version_str[0] == '0' && version_str[1] == 'x') + sbat_entries[num_entries].version = strtoul(version_str, NULL, 16); + else + sbat_entries[num_entries].version = strtoul(version_str, NULL, 10); if (!eol) for (; sbatlevel[i] != '\0' && sbatlevel[i] != '\n'; i++); if (sbat_entries[num_entries].version != 0) @@ -1595,3 +1606,107 @@ sbat_entry_t* GetSbatEntries(char* sbatlevel) return sbat_entries; } + +/* + * PE parsing functions + */ + +// Return the address of a PE section from a PE buffer +uint8_t* GetPeSection(uint8_t* buf, uint32_t* sec_len, const char* name) +{ + 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_HEADERS64* pe64_header; + IMAGE_SECTION_HEADER* section_header; + + static_strcpy(section_name, name); + + pe_header = (IMAGE_NT_HEADERS*)&buf[dos_header->e_lfanew]; + if (pe_header == NULL) + 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; + } else { + pe64_header = (IMAGE_NT_HEADERS64*)pe_header; + section_header = (IMAGE_SECTION_HEADER*)(&pe64_header[1]); + nb_sections = pe64_header->FileHeader.NumberOfSections; + } + for (i = 0; i < nb_sections; i++) { + if (memcmp(section_header[i].Name, section_name, sizeof(section_name)) == 0) { + *sec_len = section_header->SizeOfRawData; + return &buf[section_header[i].PointerToRawData]; + } + } + return NULL; +} + +// Convert an RVA address to a physical address from a PE buffer +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_HEADERS64* pe64_header; + IMAGE_SECTION_HEADER* section_header; + + pe_header = (IMAGE_NT_HEADERS*)&buf[dos_header->e_lfanew]; + if (pe_header == NULL) + 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; + } else { + pe64_header = (IMAGE_NT_HEADERS64*)pe_header; + section_header = (IMAGE_SECTION_HEADER*)(pe64_header + 1); + nb_sections = pe64_header->FileHeader.NumberOfSections; + } + + for (i = 0; i < nb_sections; i++) { + if ((section_header[i].VirtualAddress <= rva && rva < (section_header[i].VirtualAddress + section_header[i].Misc.VirtualSize))) + break; + } + if (i >= nb_sections) + return NULL; + + return &buf[section_header[i].PointerToRawData + (rva - section_header[i].VirtualAddress)]; +} + +// Using the MS APIs to poke the resources of the EFI bootloaders is simply TOO. DAMN. SLOW. +// So, to QUICKLY access the resources we need, we reivent Microsoft's sub-optimal resource parser. +uint32_t FindResourceRva(BOOL found, uint8_t* base, uint8_t* cur, const wchar_t* name, uint32_t* len) +{ + uint32_t rva; + WORD i; + IMAGE_RESOURCE_DIRECTORY* dir = (IMAGE_RESOURCE_DIRECTORY*)cur; + IMAGE_RESOURCE_DIRECTORY_ENTRY* dir_entry = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)&dir[1]; + IMAGE_RESOURCE_DIR_STRING_U* dir_string; + IMAGE_RESOURCE_DATA_ENTRY* data_entry; + + if (base == NULL || cur == NULL || name == NULL) + return 0; + + for (i = 0; i < dir->NumberOfNamedEntries + dir->NumberOfIdEntries; i++) { + if (!found && i < dir->NumberOfNamedEntries) { + dir_string = (IMAGE_RESOURCE_DIR_STRING_U*)(base + dir_entry[i].NameOffset); + if (dir_string->Length != wcslen(name) || + memcmp(name, dir_string->NameString, wcslen(name)) != 0) + continue; + found = TRUE; + } + if (dir_entry[i].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY) { + rva = FindResourceRva(found, base, &base[dir_entry[i].OffsetToDirectory], name, len); + if (rva != 0) + return rva; + } else if (found) { + data_entry = (IMAGE_RESOURCE_DATA_ENTRY*)(base + dir_entry[i].OffsetToData); + if (len != NULL) + *len = data_entry->Size; + return data_entry->OffsetToData; + } + } + return 0; +} diff --git a/src/rufus.c b/src/rufus.c index bb4647c01d4..cdf10b8654f 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -1608,6 +1608,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) const char* msg; 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 SecuritySiPolicy", "Linux SBAT", "Windows SVN" }; 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]); @@ -1616,8 +1617,8 @@ static DWORD WINAPI BootCheckThread(LPVOID param) r = IsBootloaderRevoked(buf, len); safe_free(buf); if (r > 0) { - uprintf("Warning: '%s' is revoked by %s", img_report.efi_boot_path[i], - (r == 1) ? "UEFI DBX" : ((r == 2) ? "Windows SSP" : "Linux SBAT")); + assert(r <= ARRAYSIZE(revocation_type)); + uprintf("Warning: '%s' has been revoked by %s", img_report.efi_boot_path[i], revocation_type[r - 1]); is_bootloader_revoked = TRUE; switch (r) { case 2: diff --git a/src/rufus.h b/src/rufus.h index 3fafca6601a..0ff7f209848 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -831,6 +831,9 @@ extern HANDLE CreatePreallocatedFile(const char* lpFileName, DWORD dwDesiredAcce DWORD dwFlagsAndAttributes, LONGLONG fileSize); extern uint32_t ResolveDllAddress(dll_resolver_t* resolver); extern sbat_entry_t* GetSbatEntries(char* sbatlevel); +extern uint8_t* GetPeSection(uint8_t* buf, uint32_t* sec_len, const char* name); +extern uint8_t* RvaToPhysical(uint8_t* buf, uint32_t rva); +extern uint32_t FindResourceRva(BOOL found, uint8_t* base, uint8_t* cur, const wchar_t* name, uint32_t* len); #define GetTextWidth(hDlg, id) GetTextSize(GetDlgItem(hDlg, id), NULL).cx DWORD WINAPI HashThread(void* param); diff --git a/src/rufus.rc b/src/rufus.rc index 13274611c17..ec45982a6d8 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.2196" +CAPTION "Rufus 4.6.2197" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -397,8 +397,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,6,2196,0 - PRODUCTVERSION 4,6,2196,0 + FILEVERSION 4,6,2197,0 + PRODUCTVERSION 4,6,2197,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -416,13 +416,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.6.2196" + VALUE "FileVersion", "4.6.2197" 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.2196" + VALUE "ProductVersion", "4.6.2197" END END BLOCK "VarFileInfo" diff --git a/src/stdlg.c b/src/stdlg.c index 45c4673e5d7..0be692459cf 100644 --- a/src/stdlg.c +++ b/src/stdlg.c @@ -1393,7 +1393,7 @@ static DWORD WINAPI CheckForFidoThread(LPVOID param) if (sbat_entries != 0) { for (i = 0; sbat_entries[i].product != NULL; i++); if (i > 0) - uprintf("Found %d additional revoked UEFI bootloader filters from remote SBAT", i); + uprintf("Found %d additional UEFI revocation filters from remote SBAT", i); } }