forked from Hogjects/Lufus
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtweaks.py
More file actions
165 lines (149 loc) · 6.7 KB
/
Copy pathtweaks.py
File metadata and controls
165 lines (149 loc) · 6.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""Windows installation customization functions.
These modify Windows installation media (boot.wim, autounattend.xml)
to bypass hardware requirements, skip privacy questions, and create
local accounts.
"""
import html
import re
import subprocess
import os
from lufus.utils import get_mount_and_drive
from lufus import state
from lufus.lufus_logging import get_logger
log = get_logger(__name__)
# Windows username restrictions: no \/ [ ] : ; | = , + * ? < > " @
# Max 20 characters, cannot be all spaces or empty.
_WIN_USERNAME_RE = re.compile(r'^[^\\\/\[\]:;|=,+*?<>"@\x00-\x1f]{1,20}$')
def _validate_windows_username(name: str) -> str | None:
"""Return a stripped, validated Windows username or None if invalid."""
name = name.strip()
if not name:
log.error("Windows username is empty after stripping.")
return None
if not _WIN_USERNAME_RE.match(name):
log.error("Windows username %r contains forbidden characters or exceeds 20 chars.", name)
return None
return name
def _get_mount_and_drive():
return get_mount_and_drive()
def win_hardware_bypass():
mount, _, _ = _get_mount_and_drive()
if not mount:
log.error("win_hardware_bypass: no USB mount found")
return
commands = [
"cd Setup",
"newkey LabConfig",
"cd LabConfig",
"addvalue BypassTPMCheck 4 1",
"addvalue BypassSecureBootCheck 4 1",
"addvalue BypassRAMCheck 4 1",
"save",
"exit",
]
cmd_string = "\n".join(commands) + "\n"
log.info("win_hardware_bypass: injecting registry keys into boot.wim at %s...", mount)
try:
subprocess.run(["mkdir", "/media/tempwinmnt"], check=True)
subprocess.run(["wimmountrw", f"{mount}/sources/boot.wim", "2", "/media/tempwinmnt"], check=True)
subprocess.run(
["chntpw", "e", "/media/tempwinmnt/Windows/System32/config/SYSTEM"],
input=cmd_string,
text=True,
capture_output=True,
check=True,
)
subprocess.run(["wimunmount", "/media/tempwinmnt", "--commit"], check=True)
subprocess.run(["rm", "-rf", "/media/tempwinmnt"], check=True)
log.info("win_hardware_bypass: registry keys injected successfully.")
except subprocess.CalledProcessError as e:
log.error("win_hardware_bypass: CalledProcessError: %s", e.stderr)
def win_local_acc():
mount, _, _ = _get_mount_and_drive()
if not mount:
log.error("win_local_acc: no USB mount found")
return
commands = ["cd Microsoft\\Windows\\CurrentVersion\\OOBE\naddvalue BypassNRO 4 1\nsave\nexit\n"]
cmd_string = "\n".join(commands) + "\n"
log.info("win_local_acc: bypassing online account requirement at %s...", mount)
try:
subprocess.run(["mkdir", "/media/tempwinmnt"], check=True)
subprocess.run(["wimmountrw", f"{mount}/sources/boot.wim", "2", "/media/tempwinmnt"], check=True)
subprocess.run(
["chntpw", "e", "/media/tempwinmnt/Windows/System32/config/SOFTWARE"],
input=cmd_string,
text=True,
capture_output=True,
check=True,
)
subprocess.run(["wimunmount", "/media/tempwinmnt", "--commit"], check=True)
subprocess.run(["rm", "-rf", "/media/tempwinmnt"], check=True)
log.info("win_local_acc: online account bypass applied successfully.")
except subprocess.CalledProcessError as e:
log.error("win_local_acc: CalledProcessError: %s", e.stderr)
def win_skip_privacy_questions():
mount, _, _ = _get_mount_and_drive()
if not mount:
log.error("win_skip_privacy_questions: no USB mount found")
return
xml_content = """<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HidePrivacyExperience>true</HidePrivacyExperience>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<ProtectYourPC>3</ProtectYourPC>
</OOBE>
</component>
</settings>
</unattend>"""
xml_path = os.path.join(mount, "autounattend.xml")
log.info("win_skip_privacy_questions: writing autounattend.xml to %s...", xml_path)
with open(xml_path, "w") as f:
f.write(xml_content)
log.info("win_skip_privacy_questions: autounattend.xml created to skip privacy screens.")
def win_local_acc_name():
mount, _, _ = _get_mount_and_drive()
if not mount:
log.error("win_local_acc_name: no USB mount found")
return
user_name = _validate_windows_username(state.win_local_acc)
if user_name is None:
log.error("win_local_acc_name: invalid username %r, aborting", state.win_local_acc)
return
# html.escape converts < > & " ' so the value is safe to embed in XML.
safe_name = html.escape(user_name, quote=True)
xml_template = f"""<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HidePrivacyExperience>true</HidePrivacyExperience>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<ProtectYourPC>3</ProtectYourPC>
</OOBE>
<UserAccounts>
<LocalAccounts>
<LocalAccount wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<Password><Value></Value><PlainText>true</PlainText></Password>
<Description>Primary Local Account</Description>
<DisplayName>{safe_name}</DisplayName>
<Group>Administrators</Group>
<Name>{safe_name}</Name>
</LocalAccount>
</LocalAccounts>
</UserAccounts>
</component>
</settings>
</unattend>"""
xml_path = os.path.join(mount, "autounattend.xml")
log.info("win_local_acc_name: writing autounattend.xml for local account %r to %s...", user_name, xml_path)
with open(xml_path, "w") as f:
f.write(xml_template)
log.info(
"win_local_acc_name: autounattend.xml created — privacy screens skipped, local account %r created.",
user_name,
)