[misc] improve revoked UEFI bootloader reporting

* 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.
This commit is contained in:
Pete Batard 2024-10-09 20:18:42 +01:00
parent 6b5837dbb5
commit ede52c57e6
No known key found for this signature in database
GPG key ID: 38E0CF5E69EDD671
7 changed files with 98 additions and 71 deletions

View file

@ -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"

View file

@ -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.
@ -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;
@ -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;
@ -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;
@ -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 {
@ -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 {
@ -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;
@ -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) {
@ -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)
@ -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;
}

View file

@ -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;
@ -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;
@ -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) {
@ -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) {
@ -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;

View file

@ -384,27 +384,31 @@ out:
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)) {
@ -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;
}
@ -432,8 +436,8 @@ 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;
}
@ -441,21 +445,19 @@ BOOL GetIssuerCertificateInfo(uint8_t* cert, cert_info_t* info)
// 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");
@ -463,19 +465,19 @@ BOOL GetIssuerCertificateInfo(uint8_t* cert, cert_info_t* info)
}
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)

View file

@ -1399,7 +1399,7 @@ out:
// Likewise, boot check will block message processing => use a thread
static DWORD WINAPI BootCheckThread(LPVOID param)
{
int i, r, username_index = -1;
int i, r, rr, username_index = -1;
FILE *fd;
uint32_t len;
uint8_t* buf = NULL;
@ -1609,33 +1609,43 @@ static DWORD WINAPI BootCheckThread(LPVOID param)
// Check UEFI bootloaders for revocation
if (IS_EFI_BOOTABLE(img_report)) {
assert(ARRAYSIZE(img_report.efi_boot_path) > 0);
PrintStatus(0, MSG_351);
uuprintf("UEFI Secure Boot revocation checks:");
rr = 0;
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", "Cert DBX" };
cdio_loglevel_default = CDIO_LOG_WARN;
len = ReadISOFileToBuffer(image_path, img_report.efi_boot_path[i], &buf);
cdio_loglevel_default = usb_debug ? CDIO_LOG_DEBUG : CDIO_LOG_WARN;
if (len == 0) {
uprintf("Warning: Failed to extract '%s' to check for UEFI revocation", img_report.efi_boot_path[i]);
continue;
}
uuprintf("• %s", img_report.efi_boot_path[i]);
r = IsBootloaderRevoked(buf, len);
safe_free(buf);
if (r > 0) {
assert(r <= ARRAYSIZE(revocation_type));
if (rr == 0)
rr = r;
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:
msg = lmprintf(MSG_341, "Error code: 0xc0000428");
break;
default:
msg = lmprintf(MSG_340);
break;
}
r = MessageBoxExU(hMainDialog, lmprintf(MSG_339, msg), lmprintf(MSG_338),
MB_OKCANCEL | MB_ICONWARNING | MB_IS_RTL, selected_langid);
if (r == IDCANCEL)
goto out;
}
}
if (rr > 0) {
switch (rr) {
case 2:
msg = lmprintf(MSG_341, "Error code: 0xc0000428");
break;
default:
msg = lmprintf(MSG_340);
break;
}
r = MessageBoxExU(hMainDialog, lmprintf(MSG_339, msg), lmprintf(MSG_338),
MB_OKCANCEL | MB_ICONWARNING | MB_IS_RTL, selected_langid);
if (r == IDCANCEL)
goto out;
}
}
@ -1691,7 +1701,6 @@ static DWORD WINAPI BootCheckThread(LPVOID param)
IGNORE_RETVAL(_chdir(tmp));
// The following loops through the grub2 version (which may have the ISO label appended)
// and breaks it according to '.' or '-' until it finds a match on the server.
//
for (i = (int)strlen(img_report.grub2_version), grub2_len = 0; i > 0 && grub2_len <= 0; i--) {
c = img_report.grub2_version[i];
if (c != 0 && c != '.' && c != '-')
@ -1761,7 +1770,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param)
IGNORE_RETVAL(_chdirU(app_data_dir));
IGNORE_RETVAL(_mkdir(FILES_DIR));
IGNORE_RETVAL(_chdir(FILES_DIR));
for (i=0; i<2; i++) {
for (i = 0; i < 2; i++) {
// Check if we already have the relevant ldlinux_v#.##.sys & ldlinux_v#.##.bss files
static_sprintf(tmp, "%s-%s%s\\%s.%s", syslinux, img_report.sl_version_str,
img_report.sl_version_ext, ldlinux, ldlinux_ext[i]);
@ -1783,7 +1792,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param)
lmprintf(MSG_115), MB_YESNO | MB_ICONWARNING | MB_IS_RTL, selected_langid);
if (r != IDYES)
goto out;
for (i=0; i<2; i++) {
for (i = 0; i < 2; i++) {
static_sprintf(tmp, "%s-%s", syslinux, img_report.sl_version_str);
IGNORE_RETVAL(_mkdir(tmp));
if (*img_report.sl_version_ext != 0) {
@ -2970,6 +2979,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA
if (!Notification(MSG_WARNING_QUESTION, NULL, NULL, title, lmprintf(MSG_132)))
goto aborted_start;
}
PrintStatus(0, MSG_142);
GetWindowTextU(hDeviceList, tmp, ARRAYSIZE(tmp));
if (MessageBoxExU(hMainDialog, lmprintf(MSG_003, tmp),
@ -3005,6 +3015,9 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA
if (queued_hotplug_event)
SendMessage(hDlg, UM_MEDIA_CHANGE, 0, 0);
if (wParam == BOOTCHECK_CANCEL) {
nb_devices = ComboBox_GetCount(hDeviceList);
PrintStatus(0, (nb_devices == 1) ? MSG_208 : MSG_209, nb_devices);
PrintStatus(5000, MSG_041);
if (unattend_xml_path != NULL) {
DeleteFileU(unattend_xml_path);
unattend_xml_path = NULL;

View file

@ -805,7 +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 int 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);

View file

@ -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.2202"
CAPTION "Rufus 4.6.2203"
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,2202,0
PRODUCTVERSION 4,6,2202,0
FILEVERSION 4,6,2203,0
PRODUCTVERSION 4,6,2203,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.2202"
VALUE "FileVersion", "4.6.2203"
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.2202"
VALUE "ProductVersion", "4.6.2203"
END
END
BLOCK "VarFileInfo"