Skip to content

Commit

Permalink
[iso] add Microsoft SVN revocation validation and reporting
Browse files Browse the repository at this point in the history
* See #2244 (comment)
* Note that we don't use the GUID but the resource name for bootmgr.efi, as trying to figure
  out where the heck the relevant GUID is located in the PE code is not worth the effort.
* Also add internal fallback for sbat_level.txt if the user can't access our remote version.
* Also improve PE section lookup.
  • Loading branch information
pbatard committed Oct 5, 2024
1 parent c5d61f6 commit 15c2843
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 30 deletions.
12 changes: 12 additions & 0 deletions src/db.h
Original file line number Diff line number Diff line change
Expand Up @@ -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";
83 changes: 66 additions & 17 deletions src/hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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'; ) {
Expand Down Expand Up @@ -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
Expand All @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion src/net.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
123 changes: 119 additions & 4 deletions src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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)
Expand All @@ -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;
}
5 changes: 3 additions & 2 deletions src/rufus.c
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand All @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions src/rufus.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 5 additions & 5 deletions src/rufus.rc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/stdlg.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down

0 comments on commit 15c2843

Please sign in to comment.