[wue] add experimental option to replace Windows bootloaders with the 24H2 _EX versions

* This aims at creating installation media that is compatible with systems where
  'Microsoft Windows Production PCA 2011' has been revoked.
* Doesn't work, since the bootloaders being applied by the first stage installer come
  from \sources\install.wim[#]\windows\system32\Recovery\Winre.wim[#]\Windows\Boot\
  (instead of \sources\boot.wim[#]\Windows\Boot\ as one would naturally expect) and
  Microsoft botched the ones they included there by using completely vulnerable (and
  therefore revoked) ones.
  See https://github.com/pbatard/rufus/issues/2244#issuecomment-2400380839.
* Still, I sure haven't gone through this excruciating ACL bullshit for nothing, so
  you get an experimental option, behind the expert mode curtain.
This commit is contained in:
Pete Batard 2024-10-09 00:40:15 +01:00
parent c800448c62
commit fd5c366938
No known key found for this signature in database
GPG key ID: 38E0CF5E69EDD671
9 changed files with 360 additions and 49 deletions

View file

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

View file

@ -26,6 +26,8 @@
#include <stdio.h>
#include <shlobj.h>
#include <ctype.h>
#include <aclapi.h>
#include <accctrl.h>
#include <commdlg.h>
#include <shellapi.h>
#include <shlwapi.h>
@ -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;

View file

@ -1267,7 +1267,7 @@ out:
}
/*
* 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) ||

View file

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

View file

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

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

View file

@ -25,6 +25,8 @@
#include <sddl.h>
#include <gpedit.h>
#include <assert.h>
#include <accctrl.h>
#include <aclapi.h>
#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;
}

View file

@ -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;
}

143
src/wue.c
View file

@ -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];
/// <summary>
/// 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, " <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.
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: