diff --git a/res/loc/rufus.loc b/res/loc/rufus.loc index 0849e65b2d0..327443281b0 100644 --- a/res/loc/rufus.loc +++ b/res/loc/rufus.loc @@ -609,6 +609,7 @@ t MSG_346 "Restrict Windows to S-Mode (INCOMPATIBLE with online account bypass)" 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]" # 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" diff --git a/src/msapi_utf8.h b/src/msapi_utf8.h index 7532c2624d4..e0510f410f4 100644 --- a/src/msapi_utf8.h +++ b/src/msapi_utf8.h @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -89,6 +91,9 @@ static __inline char* wchar_to_utf8(const wchar_t* wstr) int size = 0; char* str = NULL; + if (wstr == NULL) + return NULL; + // Convert the empty string too if (wstr[0] == 0) return (char*)calloc(1, 1); @@ -568,6 +573,52 @@ static __inline const char* PathFindFileNameU(const char* szPath) return &szPath[i]; } +static __inline char* PathCombineU(char* lpDest, char* lpDir, char* lpFile) +{ + wchar_t* wret = NULL; + DWORD err = ERROR_INVALID_DATA; + wchar_t wlpDest[MAX_PATH]; + wconvert(lpDir); + wconvert(lpFile); + wret = PathCombineW(wlpDest, wlpDir, wlpFile); + err = GetLastError(); + wfree(lpDir); + wfree(lpFile); + if (wret == NULL) + return NULL; + wchar_to_utf8_no_alloc(wlpDest, lpDest, MAX_PATH); + SetLastError(err); + return lpDest; +} + +static __inline HANDLE FindFirstFileU(char* lpFileName, LPWIN32_FIND_DATAA lpFindFileData) +{ + HANDLE ret = INVALID_HANDLE_VALUE; + WIN32_FIND_DATAW wFindFileData = { 0 }; + wconvert(lpFileName); + ret = FindFirstFileW(wlpFileName, &wFindFileData); + if (ret != INVALID_HANDLE_VALUE) { + memcpy(lpFindFileData, &wFindFileData, offsetof(WIN32_FIND_DATAW, cFileName)); + wchar_to_utf8_no_alloc(wFindFileData.cFileName, lpFindFileData->cFileName, sizeof(lpFindFileData->cFileName)); + wchar_to_utf8_no_alloc(wFindFileData.cAlternateFileName, lpFindFileData->cAlternateFileName, sizeof(lpFindFileData->cAlternateFileName)); + } + wfree(lpFileName); + return ret; +} + +static __inline BOOL FindNextFileU(HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData) +{ + BOOL ret = FALSE; + WIN32_FIND_DATAW wFindFileData = { 0 }; + ret = FindNextFileW(hFindFile, &wFindFileData); + if (ret) { + memcpy(lpFindFileData, &wFindFileData, offsetof(WIN32_FIND_DATAW, cFileName)); + wchar_to_utf8_no_alloc(wFindFileData.cFileName, lpFindFileData->cFileName, sizeof(lpFindFileData->cFileName)); + wchar_to_utf8_no_alloc(wFindFileData.cAlternateFileName, lpFindFileData->cAlternateFileName, sizeof(lpFindFileData->cAlternateFileName)); + } + return ret; +} + // This function differs from regular GetTextExtentPoint in that it uses a zero terminated string static __inline BOOL GetTextExtentPointU(HDC hdc, const char* lpString, LPSIZE lpSize) { @@ -819,6 +870,28 @@ static __inline BOOL SetFileAttributesU(const char* lpFileName, DWORD dwFileAttr return ret; } +static __inline DWORD GetNamedSecurityInfoU(const char* lpObjectName, SE_OBJECT_TYPE ObjectType, + SECURITY_INFORMATION SecurityInfo, PSID* ppsidOwner, PSID* ppsidGroup, PACL* ppDacl, + PACL* ppSacl, PSECURITY_DESCRIPTOR* ppSecurityDescriptor) +{ + DWORD ret; + wconvert(lpObjectName); + ret = GetNamedSecurityInfoW(wlpObjectName, ObjectType, SecurityInfo, ppsidOwner, ppsidGroup, + ppDacl, ppSacl, ppSecurityDescriptor); + wfree(lpObjectName); + return ret; +} + +static __inline DWORD SetNamedSecurityInfoU(const char* lpObjectName, SE_OBJECT_TYPE ObjectType, + SECURITY_INFORMATION SecurityInfo, PSID psidOwner, PSID psidGroup, PACL pDacl, PACL pSacl) +{ + DWORD ret; + wconvert(lpObjectName); + ret = SetNamedSecurityInfoW(wlpObjectName, ObjectType, SecurityInfo, psidOwner, psidGroup, pDacl, pSacl); + wfree(lpObjectName); + return ret; +} + static __inline int SHCreateDirectoryExU(HWND hwnd, const char* pszPath, SECURITY_ATTRIBUTES *psa) { int ret = ERROR_INVALID_DATA; diff --git a/src/parser.c b/src/parser.c index 94ece7998bb..ca48c61e4cf 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1267,7 +1267,7 @@ char* replace_in_token_data(const char* filename, const char* token, const char* } /* - * Replace all 'c' characters in string 'src' with the substring 'rep' + * Replace all 'c' characters in string 'src' with the substring 'rep'. * The returned string is allocated and must be freed by the caller. */ char* replace_char(const char* src, const char c, const char* rep) @@ -1300,6 +1300,30 @@ char* replace_char(const char* src, const char c, const char* rep) return res; } +/* + * Remove all instances of substring 'sub' form string 'src. + * The returned string is allocated and must be freed by the caller. + */ +char* remove_substr(const char* src, const char* sub) +{ + size_t i, j, str_len = safe_strlen(src), sub_len = safe_strlen(sub); + char* res; + + if ((src == NULL) || (sub == NULL) || (sub_len > str_len)) + return NULL; + + res = (char*)calloc(str_len + 1, 1); + if (res == NULL) + return NULL; + for (i = 0, j = 0; i <= str_len; ) { + if (i <= str_len - sub_len && memcmp(&src[i], sub, sub_len) == 0) + i += sub_len; + else + res[j++] = src[i++]; + } + return res; +} + /* * Internal recursive call for get_data_from_asn1(). Returns FALSE on error, TRUE otherwise. */ @@ -1697,8 +1721,7 @@ uint8_t* RvaToPhysical(uint8_t* buf, uint32_t rva) static BOOL FoundResourceRva = FALSE; uint32_t FindResourceRva(const wchar_t* name, uint8_t* root, uint8_t* dir, uint32_t* len) { - uint32_t rva; - WORD i; + uint32_t i, rva; IMAGE_RESOURCE_DIRECTORY* _dir = (IMAGE_RESOURCE_DIRECTORY*)dir; IMAGE_RESOURCE_DIRECTORY_ENTRY* dir_entry = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)&_dir[1]; IMAGE_RESOURCE_DIR_STRING_U* dir_string; @@ -1711,7 +1734,7 @@ uint32_t FindResourceRva(const wchar_t* name, uint8_t* root, uint8_t* dir, uint3 if (root == dir) FoundResourceRva = FALSE; - for (i = 0; i < _dir->NumberOfNamedEntries + _dir->NumberOfIdEntries; i++) { + for (i = 0; i < (uint32_t)_dir->NumberOfNamedEntries + _dir->NumberOfIdEntries; i++) { if (!FoundResourceRva && i < _dir->NumberOfNamedEntries) { dir_string = (IMAGE_RESOURCE_DIR_STRING_U*)(root + dir_entry[i].NameOffset); if (dir_string->Length != wcslen(name) || diff --git a/src/rufus.c b/src/rufus.c index 4ba8c3785a4..8e0837342a6 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -1586,6 +1586,10 @@ static DWORD WINAPI BootCheckThread(LPVOID param) StrArrayAdd(&options, lmprintf(MSG_335), TRUE); MAP_BIT(UNATTEND_DISABLE_BITLOCKER); if (expert_mode) { + if (!appstore_version && img_report.win_version.build >= 26100) { + StrArrayAdd(&options, lmprintf(MSG_350), TRUE); + MAP_BIT(UNATTEND_USE_MS2023_BOOTLOADERS); + } StrArrayAdd(&options, lmprintf(MSG_346), TRUE); MAP_BIT(UNATTEND_FORCE_S_MODE); } @@ -1597,7 +1601,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) i = remap16(i, map, TRUE); unattend_xml_path = CreateUnattendXml(arch, i); // Remember the user preferences for the current session. - unattend_xml_mask &= ~(remap16(0x1ff, map, TRUE)); + unattend_xml_mask &= ~(remap16(UNATTEND_FULL_MASK, map, TRUE)); unattend_xml_mask |= i; WriteSetting32(SETTING_WUE_OPTIONS, (UNATTEND_DEFAULT_MASK << 16) | unattend_xml_mask); } diff --git a/src/rufus.h b/src/rufus.h index 24a4dfb023f..9240784a23e 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -629,6 +629,8 @@ typedef struct { #define UNATTEND_SET_USER 0x00040 #define UNATTEND_DISABLE_BITLOCKER 0x00080 #define UNATTEND_FORCE_S_MODE 0x00100 +#define UNATTEND_USE_MS2023_BOOTLOADERS 0x00200 +#define UNATTEND_FULL_MASK 0x003FF #define UNATTEND_DEFAULT_MASK 0x000FF #define UNATTEND_WINDOWS_TO_GO 0x10000 // Special flag for Windows To Go @@ -636,10 +638,15 @@ typedef struct { #define UNATTEND_SPECIALIZE_DEPLOYMENT_MASK (UNATTEND_NO_ONLINE_ACCOUNT) #define UNATTEND_OOBE_SHELL_SETUP_MASK (UNATTEND_NO_DATA_COLLECTION | UNATTEND_SET_USER | UNATTEND_DUPLICATE_LOCALE) #define UNATTEND_OOBE_INTERNATIONAL_MASK (UNATTEND_DUPLICATE_LOCALE) -#define UNATTEND_OOBE_MASK (UNATTEND_OOBE_SHELL_SETUP_MASK | UNATTEND_OOBE_INTERNATIONAL_MASK | UNATTEND_DISABLE_BITLOCKER) +#define UNATTEND_OOBE_MASK (UNATTEND_OOBE_SHELL_SETUP_MASK | UNATTEND_OOBE_INTERNATIONAL_MASK | UNATTEND_DISABLE_BITLOCKER | UNATTEND_USE_MS2023_BOOTLOADERS) #define UNATTEND_OFFLINE_SERVICING_MASK (UNATTEND_OFFLINE_INTERNAL_DRIVES | UNATTEND_FORCE_S_MODE) #define UNATTEND_DEFAULT_SELECTION_MASK (UNATTEND_SECUREBOOT_TPM_MINRAM | UNATTEND_NO_ONLINE_ACCOUNT | UNATTEND_OFFLINE_INTERNAL_DRIVES) +/* Used with ListDirectoryContent */ +#define LIST_DIR_TYPE_FILE 0x01 +#define LIST_DIR_TYPE_DIRECTORY 0x02 +#define LIST_DIR_TYPE_RECURSIVE 0x80 + /* Hash tables */ typedef struct htab_entry { uint32_t used; @@ -786,6 +793,7 @@ extern char* get_token_data_buffer(const char* token, unsigned int n, const char extern char* insert_section_data(const char* filename, const char* section, const char* data, BOOL dos2unix); extern char* replace_in_token_data(const char* filename, const char* token, const char* src, const char* rep, BOOL dos2unix); extern char* replace_char(const char* src, const char c, const char* rep); +extern char* remove_substr(const char* src, const char* sub); extern void parse_update(char* buf, size_t len); extern void* get_data_from_asn1(const uint8_t* buf, size_t buf_len, const char* oid_str, uint8_t asn1_type, size_t* data_len); extern int sanitize_label(char* label); @@ -835,6 +843,8 @@ extern uint16_t GetPeArch(uint8_t* buf); extern uint8_t* GetPeSection(uint8_t* buf, const char* name, uint32_t* len); 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); +extern BOOL TakeOwnership(LPCSTR lpszOwnFile); #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 f63f929483c..7236b2c2e6c 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.2200" +CAPTION "Rufus 4.6.2201" 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,2200,0 - PRODUCTVERSION 4,6,2200,0 + FILEVERSION 4,6,2201,0 + PRODUCTVERSION 4,6,2201,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.2200" + VALUE "FileVersion", "4.6.2201" 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.2200" + VALUE "ProductVersion", "4.6.2201" END END BLOCK "VarFileInfo" diff --git a/src/stdfn.c b/src/stdfn.c index ec4933a03ca..c299967b8ee 100644 --- a/src/stdfn.c +++ b/src/stdfn.c @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include "re.h" #include "rufus.h" @@ -1242,3 +1244,73 @@ BOOL UnmountRegistryHive(const HKEY key, const char* pszHiveName) return (status == ERROR_SUCCESS); } + +/* + * Take administrative ownership of a file or directory, and grant all access rights. + */ +BOOL TakeOwnership(LPCSTR lpszOwnFile) +{ + BOOL ret = FALSE; + HANDLE hToken = NULL; + PSID pSIDAdmin = NULL; + PACL pOldDACL = NULL, pNewDACL = NULL; + PSECURITY_DESCRIPTOR pSD = NULL; + SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; + EXPLICIT_ACCESS ea = { 0 }; + + if (lpszOwnFile == NULL) + return FALSE; + + // Create a SID for the BUILTIN\Administrators group. + if (!AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pSIDAdmin)) + goto out; + + // Open a handle to the access token for the calling process. + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) + goto out; + + // Enable the SE_TAKE_OWNERSHIP_NAME privilege. + if (!SetPrivilege(hToken, SE_TAKE_OWNERSHIP_NAME, TRUE)) + goto out; + + // Set the owner in the object's security descriptor. + if (SetNamedSecurityInfoU(lpszOwnFile, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, + pSIDAdmin, NULL, NULL, NULL) != ERROR_SUCCESS) + goto out; + + // Disable the SE_TAKE_OWNERSHIP_NAME privilege. + if (!SetPrivilege(hToken, SE_TAKE_OWNERSHIP_NAME, FALSE)) + goto out; + + // Get a pointer to the existing DACL. + if (GetNamedSecurityInfoU(lpszOwnFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, + NULL, NULL, &pOldDACL, NULL, &pSD) != ERROR_SUCCESS) + goto out; + + // Initialize an EXPLICIT_ACCESS structure for the new ACE + // with full control for Administrators. + ea.grfAccessPermissions = GENERIC_ALL; + ea.grfAccessMode = GRANT_ACCESS; + ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea.Trustee.TrusteeType = TRUSTEE_IS_GROUP; + ea.Trustee.ptstrName = (LPTSTR)pSIDAdmin; + + // Create a new ACL that merges the new ACE into the existing DACL. + if (SetEntriesInAcl(1, &ea, pOldDACL, &pNewDACL) != ERROR_SUCCESS) + goto out; + + // Try to modify the object's DACL. + if (SetNamedSecurityInfoU(lpszOwnFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, + NULL, NULL, pNewDACL, NULL) != ERROR_SUCCESS) + goto out; + + ret = TRUE; + +out: + FreeSid(pSIDAdmin); + LocalFree(pNewDACL); + safe_closehandle(hToken); + return ret; +} diff --git a/src/stdio.c b/src/stdio.c index 4004575189f..a6c257b403e 100644 --- a/src/stdio.c +++ b/src/stdio.c @@ -203,7 +203,7 @@ void DumpBufferHex(void *buf, size_t size) uprintf("%s\n", line); line[0] = 0; sprintf(&line[strlen(line)], " %08x ", (unsigned int)i); - for(j=0,k=0; k<16; j++,k++) { + for(j = 0,k = 0; k < 16; j++,k++) { if (i+j < size) { sprintf(&line[strlen(line)], "%02x", buffer[i+j]); } else { @@ -212,7 +212,7 @@ void DumpBufferHex(void *buf, size_t size) sprintf(&line[strlen(line)], " "); } sprintf(&line[strlen(line)], " "); - for(j=0,k=0; k<16; j++,k++) { + for(j = 0,k = 0; k < 16; j++,k++) { if (i+j < size) { if ((buffer[i+j] < 32) || (buffer[i+j] > 126)) { sprintf(&line[strlen(line)], "."); @@ -920,3 +920,60 @@ BOOL ExtractZip(const char* src_zip, const char* dest_dir) bled_exit(); return (extracted_bytes > 0); } + +// Returns a list of all the files or folders from a directory +DWORD ListDirectoryContent(StrArray* arr, char* dir, uint8_t type) +{ + WIN32_FIND_DATAA FindFileData = { 0 }; + HANDLE hFind; + DWORD dwError, dwResult; + char mask[MAX_PATH + 1], path[MAX_PATH + 1]; + + if (arr == NULL || dir == NULL || (type & 0x03) == 0) + return ERROR_INVALID_PARAMETER; + + if (PathCombineU(mask, dir, "*") == NULL) + return GetLastError(); + + hFind = FindFirstFileU(mask, &FindFileData); + if (hFind == INVALID_HANDLE_VALUE) + return GetLastError(); + + dwResult = ERROR_FILE_NOT_FOUND; + do { + if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (strcmp(FindFileData.cFileName, ".") == 0 || + strcmp(FindFileData.cFileName, "..") == 0 || + (type & LIST_DIR_TYPE_RECURSIVE) == 0) + continue; + if (PathCombineU(path, dir, FindFileData.cFileName) == NULL) + break; + // Append a trailing backslash to directories + if (path[strlen(path) - 1] != '\\') { + path[strlen(path) + 1] = '\0'; + path[strlen(path)] = '\\'; + } + if (type & LIST_DIR_TYPE_DIRECTORY) + StrArrayAdd(arr, path, TRUE); + dwError = ListDirectoryContent(arr, path, type); + if (dwError != NO_ERROR && dwError != ERROR_FILE_NOT_FOUND) { + SetLastError(dwError); + break; + } + } else { + if (type & LIST_DIR_TYPE_FILE) { + if (PathCombineU(path, dir, FindFileData.cFileName) == NULL) + break; + StrArrayAdd(arr, path, TRUE); + } + dwResult = NO_ERROR; + } + } while (FindNextFileU(hFind, &FindFileData)); + + dwError = GetLastError(); + FindClose(hFind); + + if (dwError != ERROR_NO_MORE_FILES) + return dwError; + return dwResult; +} diff --git a/src/wue.c b/src/wue.c index 6318aedee7d..46db5bfd5be 100644 --- a/src/wue.c +++ b/src/wue.c @@ -51,6 +51,7 @@ extern uint32_t wim_nb_files, wim_proc_files, wim_extra_files; extern BOOL validate_md5sum; extern uint64_t md5sum_totalbytes; extern StrArray modified_files; +extern const char* efi_archname[ARCH_MAX]; /// /// Create an installation answer file containing the sections specified by the flags. @@ -162,43 +163,56 @@ char* CreateUnattendXml(int arch, int flags) free(tzstr); } } - if (flags & UNATTEND_SET_USER) { - for (i = 0; (i < ARRAYSIZE(unallowed_account_names)) && (stricmp(unattend_username, unallowed_account_names[i]) != 0); i++); - if (i < ARRAYSIZE(unallowed_account_names)) { - uprintf("WARNING: '%s' is not allowed as local account name - Option ignored", unattend_username); - } else if (unattend_username[0] != 0) { - uprintf("• Use '%s' for local account name", unattend_username); - // If we create a local account in unattend.xml, then we can get Windows 11 - // 22H2 to skip MSA even if the network is connected during installation. - fprintf(fd, " \n"); - fprintf(fd, " \n"); - fprintf(fd, " \n"); - fprintf(fd, " %s\n", unattend_username); - fprintf(fd, " %s\n", unattend_username); - fprintf(fd, " Administrators;Power Users\n"); - // Sets an empty password for the account (which, in Microsoft's convoluted ways, - // needs to be initialized to the Base64 encoded UTF-16 string "Password"). - // The use of an empty password has both the advantage of not having to ask users - // to type in a password in Rufus (which they might be weary of) as well as allowing - // automated logon during setup. - fprintf(fd, " \n"); - fprintf(fd, " UABhAHMAcwB3AG8AcgBkAA==\n"); - fprintf(fd, " false</PlainText>\n"); - fprintf(fd, " </Password>\n"); - fprintf(fd, " </LocalAccount>\n"); - fprintf(fd, " </LocalAccounts>\n"); - fprintf(fd, " </UserAccounts>\n"); - // Since we set a blank password, we'll ask the user to change it at next logon. + if (flags & UNATTEND_SET_USER || flags & UNATTEND_USE_MS2023_BOOTLOADERS) { + if (flags & UNATTEND_SET_USER) { + for (i = 0; (i < ARRAYSIZE(unallowed_account_names)) && (stricmp(unattend_username, unallowed_account_names[i]) != 0); i++); + if (i < ARRAYSIZE(unallowed_account_names)) { + uprintf("WARNING: '%s' is not allowed as local account name - Option ignored", unattend_username); + } else if (unattend_username[0] != 0) { + uprintf("• Use '%s' for local account name", unattend_username); + // If we create a local account in unattend.xml, then we can get Windows 11 + // 22H2 to skip MSA even if the network is connected during installation. + fprintf(fd, " <UserAccounts>\n"); + fprintf(fd, " <LocalAccounts>\n"); + fprintf(fd, " <LocalAccount wcm:action=\"add\">\n"); + fprintf(fd, " <Name>%s</Name>\n", unattend_username); + fprintf(fd, " <DisplayName>%s</DisplayName>\n", unattend_username); + fprintf(fd, " <Group>Administrators;Power Users</Group>\n"); + // Sets an empty password for the account (which, in Microsoft's convoluted ways, + // needs to be initialized to the Base64 encoded UTF-16 string "Password"). + // The use of an empty password has both the advantage of not having to ask users + // to type in a password in Rufus (which they might be weary of) as well as allowing + // automated logon during setup. + fprintf(fd, " <Password>\n"); + fprintf(fd, " <Value>UABhAHMAcwB3AG8AcgBkAA==</Value>\n"); + fprintf(fd, " <PlainText>false</PlainText>\n"); + fprintf(fd, " </Password>\n"); + fprintf(fd, " </LocalAccount>\n"); + fprintf(fd, " </LocalAccounts>\n"); + fprintf(fd, " </UserAccounts>\n"); + // Since we set a blank password, we'll ask the user to change it at next logon. + fprintf(fd, " <FirstLogonCommands>\n"); + fprintf(fd, " <SynchronousCommand wcm:action=\"add\">\n"); + fprintf(fd, " <Order>%d</Order>\n", order++); + fprintf(fd, " <CommandLine>net user &quot;%s&quot; /logonpasswordchg:yes</CommandLine>\n", unattend_username); + fprintf(fd, " </SynchronousCommand>\n"); + // Some people report that using the `net user` command above might reset the password expiration to 90 days... + // To alleviate that, blanket set passwords on the target machine to never expire. + fprintf(fd, " <SynchronousCommand wcm:action=\"add\">\n"); + fprintf(fd, " <Order>%d</Order>\n", order++); + fprintf(fd, " <CommandLine>net accounts /maxpwage:unlimited</CommandLine>\n"); + fprintf(fd, " </SynchronousCommand>\n"); + fprintf(fd, " </FirstLogonCommands>\n"); + } + } + if (flags & UNATTEND_USE_MS2023_BOOTLOADERS) { + uprintf("• Use 'Windows UEFI CA 2023' signed bootloaders"); + // TODO: Validate that we can have multiple <FirstLogonCommands> sections fprintf(fd, " <FirstLogonCommands>\n"); fprintf(fd, " <SynchronousCommand wcm:action=\"add\">\n"); fprintf(fd, " <Order>%d</Order>\n", order++); - fprintf(fd, " <CommandLine>net user &quot;%s&quot; /logonpasswordchg:yes</CommandLine>\n", unattend_username); - fprintf(fd, " </SynchronousCommand>\n"); - // Some people report that using the `net user` command above might reset the password expiration to 90 days... - // To alleviate that, blanket set passwords on the target machine to never expire. - fprintf(fd, " <SynchronousCommand wcm:action=\"add\">\n"); - fprintf(fd, " <Order>%d</Order>\n", order++); - fprintf(fd, " <CommandLine>net accounts /maxpwage:unlimited</CommandLine>\n"); + // TODO: Validate the actual value on a machine where updates have been applied + fprintf(fd, " <CommandLine>reg add HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Secureboot /v AvailableUpdates /t REG_DWORD /d 0x3c0 /f\n"); fprintf(fd, " </SynchronousCommand>\n"); fprintf(fd, " </FirstLogonCommands>\n"); } @@ -845,7 +859,7 @@ BOOL ApplyWindowsCustomization(char drive_letter, int flags) setup_arch = GetPeArch(buf); free(buf); if (setup_arch != IMAGE_FILE_MACHINE_AMD64 && setup_arch != IMAGE_FILE_MACHINE_ARM64) { - uprintf("WARNING: Unsupported arch 0x%x -- in-place upgrade wrapper can not be added"); + uprintf("WARNING: Unsupported arch 0x%x -- in-place upgrade wrapper will not be added", setup_arch); } else if (!MoveFileExU(setup_exe, setup_dll, 0)) { uprintf("Could not rename '%s': %s", setup_exe, WindowsErrorString()); } else { @@ -865,7 +879,8 @@ BOOL ApplyWindowsCustomization(char drive_letter, int flags) UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 0, PATCH_PROGRESS_TOTAL); // We only need to mount boot.wim if we have windowsPE data to deal with. If // not, we can just copy our unattend.xml in \sources\$OEM$\$$\Panther\. - if (flags & UNATTEND_WINPE_SETUP_MASK) { + // We also need to mount it if we use the 'Windows UEFI CA 2023' signed bootloaders. + if (flags & UNATTEND_WINPE_SETUP_MASK || flags & UNATTEND_USE_MS2023_BOOTLOADERS) { if (validate_md5sum) md5sum_totalbytes -= _filesizeU(boot_wim_path); uprintf("Mounting '%s[%d]'...", boot_wim_path, wim_index); @@ -969,6 +984,62 @@ BOOL ApplyWindowsCustomization(char drive_letter, int flags) } UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 103, PATCH_PROGRESS_TOTAL); } + + if (flags & UNATTEND_USE_MS2023_BOOTLOADERS) { + if_not_assert(mount_path != NULL) + goto out; + static_sprintf(path, "%s\\Windows\\Boot\\EFI_EX\\bootmgfw_EX.efi", mount_path); + if (!PathFileExistsU(path)) { + uprintf("Could not find 2023 signed UEFI bootloader - Ignoring option"); + } else { + char path2[MAX_PATH], *rep; + StrArray files, dirs; + // Replace /EFI/Boot/boot###.efi + for (i = 1; i < ARRAYSIZE(efi_archname); i++) { + static_sprintf(path2, "%c:\\efi\\boot\\boot%s.efi", drive_letter, efi_archname[i]); + if (!PathFileExistsA(path2)) + continue; + if (!CopyFileU(path, path2, FALSE)) + uprintf("WARNING: Could not replace 'boot%s.efi': %s", efi_archname[i], WindowsErrorString()); + break; + } + // Replace /bootmgr.efi + static_sprintf(path, "%s\\Windows\\Boot\\EFI_EX\\bootmgr_EX.efi", mount_path); + static_sprintf(path2, "%c:\\bootmgr.efi", drive_letter); + if (!CopyFileU(path, path2, FALSE)) + uprintf("WARNING: Could not replace 'bootmgr.efi': %s", WindowsErrorString()); + // Microsoft "secures" the Windows\Boot\ dir through their SUPER OBNOXIOUS AND + // WORTHLESS use of DACLs + read-only flags, so we first need to re-take control + // of all directories under there recursively. + StrArrayCreate(&dirs, 64); + StrArrayCreate(&files, 64); + static_sprintf(path, "%s\\Windows\\Boot\\", mount_path); + StrArrayAdd(&dirs, path, TRUE); + static_sprintf(path, "%s\\Windows\\Boot\\EFI_EX\\", mount_path); + StrArrayAdd(&dirs, path, TRUE); + ListDirectoryContent(&dirs, path, LIST_DIR_TYPE_DIRECTORY | LIST_DIR_TYPE_RECURSIVE); + for (i = 0; i < (int)dirs.Index; i++) { + rep = remove_substr(dirs.String[i], "_EX"); + assert(rep != NULL); + TakeOwnership(rep); + safe_free(rep); + } + // 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++) { + rep = remove_substr(files.String[i], "_EX"); + assert(rep != NULL); + TakeOwnership(rep); + if (!CopyFileU(files.String[i], rep, FALSE) && rep != NULL) + uprintf("WARNING: Could not replace '%s': %s", &rep[strlen(mount_path) + 1], WindowsErrorString()); + safe_free(rep); + } + StrArrayDestroy(&dirs); + StrArrayDestroy(&files); + uprintf("Replaced EFI bootloader files with 'Windows UEFI CA 2023' signed versions"); + } + } + r = TRUE; out: