-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmac_tool.py
More file actions
313 lines (266 loc) · 11 KB
/
Copy pathmac_tool.py
File metadata and controls
313 lines (266 loc) · 11 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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
#!/usr/bin/env python3
import os
import stat
import sys
BT_FILE_SIZE = 440
WIFI_FILE_SIZE = 2050
BT_MAC_OFFSET = 0x00
BT_TRAILER_SIG = bytes.fromhex('60002310000007000000050703040000')
BT_TRAILER_SIG_OFFSET = 0x06
WIFI_HDR_VARIANTS = (
bytes.fromhex('01000800'),
bytes.fromhex('01000900'),
)
WIFI_HDR_LEN = 4
WIFI_MAC_OFFSET = 0x04
TRAILER_MAGIC = 0xAA
def die(msg):
print(f"Error: {msg}", file=sys.stderr)
sys.exit(1)
def parse_mac(s):
parts = s.replace('-', ':').split(':')
if len(parts) != 6 or any(len(p) != 2 for p in parts):
die(f"MAC must be six colon-separated hex bytes (e.g. 02:11:22:33:44:55), got {s!r} ({len(parts)} parts)")
try:
return bytes(int(p, 16) for p in parts)
except ValueError:
die(f"MAC contains non-hex characters: {s!r} (expected 0-9, a-f, A-F)")
def format_mac(b):
return ':'.join(f'{x:02x}' for x in b)
def compute_checksum(data):
cs = 0
for i, b in enumerate(data[:-2]):
cs = ((cs + b) if i % 2 == 0 else (cs ^ b)) & 0xff
return cs
def trailer_valid(data):
return data[-2] == TRAILER_MAGIC and data[-1] == compute_checksum(data)
def patch_bt(data, new_mac):
if len(data) != BT_FILE_SIZE:
die(f"BT_Addr size is {len(data)} bytes, expected {BT_FILE_SIZE}. Did you pass the WIFI file by mistake? (WIFI is {WIFI_FILE_SIZE} bytes)")
got = data[BT_TRAILER_SIG_OFFSET:BT_TRAILER_SIG_OFFSET + len(BT_TRAILER_SIG)]
if got != BT_TRAILER_SIG:
die(f"BT_Addr trailer signature mismatch at offset {BT_TRAILER_SIG_OFFSET:#x}: got {got.hex()}, expected {BT_TRAILER_SIG.hex()}. File may be corrupted or not a real BT_Addr.")
out = bytearray(data)
out[BT_MAC_OFFSET:BT_MAC_OFFSET + 6] = new_mac
out[-2] = TRAILER_MAGIC
out[-1] = compute_checksum(bytes(out))
return bytes(out)
def patch_wifi(data, new_mac):
if len(data) != WIFI_FILE_SIZE:
die(f"WIFI size is {len(data)} bytes, expected {WIFI_FILE_SIZE}. Did you pass the BT_Addr file by mistake? (BT_Addr is {BT_FILE_SIZE} bytes)")
got = data[:WIFI_HDR_LEN]
if got not in WIFI_HDR_VARIANTS:
expected = ' or '.join(h.hex() for h in WIFI_HDR_VARIANTS)
die(f"WIFI header magic mismatch: got {got.hex()}, expected {expected}. File may be corrupted or not a real WIFI.")
out = bytearray(data)
out[WIFI_MAC_OFFSET:WIFI_MAC_OFFSET + 6] = new_mac
out[-2] = TRAILER_MAGIC
out[-1] = compute_checksum(bytes(out))
return bytes(out)
def read_bt_mac(data):
if len(data) != BT_FILE_SIZE:
return None
if data[BT_TRAILER_SIG_OFFSET:BT_TRAILER_SIG_OFFSET + len(BT_TRAILER_SIG)] != BT_TRAILER_SIG:
return None
return data[BT_MAC_OFFSET:BT_MAC_OFFSET + 6]
def read_wifi_mac(data):
if len(data) != WIFI_FILE_SIZE:
return None
if data[:WIFI_HDR_LEN] not in WIFI_HDR_VARIANTS:
return None
return data[WIFI_MAC_OFFSET:WIFI_MAC_OFFSET + 6]
def find_bt_copies(img):
out = []
pos = 0
while True:
i = img.find(BT_TRAILER_SIG, pos)
if i < 0:
break
start = i - BT_TRAILER_SIG_OFFSET
if start >= 0 and start + BT_FILE_SIZE <= len(img):
blob = img[start:start + BT_FILE_SIZE]
if trailer_valid(blob):
out.append((start, blob))
pos = i + 1
return out
def find_wifi_copies(img):
found = {}
for hdr in WIFI_HDR_VARIANTS:
pos = 0
while True:
i = img.find(hdr, pos)
if i < 0:
break
if i + WIFI_FILE_SIZE <= len(img):
blob = img[i:i + WIFI_FILE_SIZE]
if trailer_valid(blob):
found[i] = blob
pos = i + 1
return [(off, found[off]) for off in sorted(found)]
def is_partition_image(path):
sz = os.path.getsize(path)
if sz == BT_FILE_SIZE or sz == WIFI_FILE_SIZE:
return False
return sz > 1024 * 1024
def write_output(path, data):
try:
with open(path, 'wb') as f:
f.write(data)
except OSError as e:
die(f"cannot write {path}: {e.strerror or e}")
def is_regular_file(path):
try:
return stat.S_ISREG(os.stat(path).st_mode)
except OSError:
return False
def read_file(path):
with open(path, 'rb') as f:
return f.read()
def cmd_read(args):
if len(args) != 1:
die("usage: mac_tool.py read <file>")
path = args[0]
if not os.path.exists(path):
die(f"file not found: {path}")
sz = os.path.getsize(path)
data = read_file(path)
if is_partition_image(path):
bts = find_bt_copies(data)
wfs = find_wifi_copies(data)
if bts:
mac = bts[0][1][BT_MAC_OFFSET:BT_MAC_OFFSET + 6]
print(f"BT_Addr ({len(bts)} copies, first @ {bts[0][0]:#x}): {format_mac(mac)}")
else:
print("BT_Addr : (not found — no valid 440-byte record with a matching aa+checksum trailer)")
if wfs:
mac = wfs[0][1][WIFI_MAC_OFFSET:WIFI_MAC_OFFSET + 6]
print(f"WIFI MAC ({len(wfs)} copies, first @ {wfs[0][0]:#x}): {format_mac(mac)}")
else:
print("WIFI MAC : (not found — no valid 2050-byte record with a matching aa+checksum trailer)")
return
bt = read_bt_mac(data)
if bt is not None:
print(f"BT_Addr: {format_mac(bt)}")
return
wf = read_wifi_mac(data)
if wf is not None:
print(f"WIFI MAC: {format_mac(wf)}")
return
if sz == BT_FILE_SIZE:
die(f"file is {sz} bytes (BT_Addr-shaped) but the trailer signature at offset {BT_TRAILER_SIG_OFFSET:#x} does not match {BT_TRAILER_SIG.hex()} — file may be corrupted or not a real BT_Addr")
if sz == WIFI_FILE_SIZE:
expected = ' or '.join(h.hex() for h in WIFI_HDR_VARIANTS)
die(f"file is {sz} bytes (WIFI-shaped) but the header magic does not match {expected} — file may be corrupted or not a real WIFI")
die(f"file is {sz} bytes; expected {BT_FILE_SIZE} (BT_Addr), {WIFI_FILE_SIZE} (WIFI), or > 1 MiB (partition image)")
def cmd_write(args):
bt_mac = None
wifi_mac = None
out_path = None
in_path = None
i = 0
while i < len(args):
a = args[i]
if a == '--bt':
if i + 1 >= len(args):
die("--bt requires a MAC argument (e.g. --bt 02:11:22:33:44:55)")
bt_mac = parse_mac(args[i + 1])
i += 2
elif a == '--wifi':
if i + 1 >= len(args):
die("--wifi requires a MAC argument (e.g. --wifi 02:11:22:33:44:66)")
wifi_mac = parse_mac(args[i + 1])
i += 2
elif a == '-o':
if i + 1 >= len(args):
die("-o requires a path argument")
out_path = args[i + 1]
i += 2
elif in_path is None:
in_path = a
i += 1
else:
die(f"unexpected argument {a!r} (already have input file {in_path!r})")
if in_path is None:
die("usage: mac_tool.py write <file> [--bt MAC] [--wifi MAC] [-o output]")
if bt_mac is None and wifi_mac is None:
die("at least one of --bt MAC or --wifi MAC is required")
if not os.path.exists(in_path):
die(f"file not found: {in_path}")
data = read_file(in_path)
sz = len(data)
if is_partition_image(in_path):
if out_path is None:
base, ext = os.path.splitext(in_path)
out_path = f"{base}_patched{ext}" if ext else f"{in_path}.patched"
out = bytearray(data)
bt_count = 0
wifi_count = 0
if bt_mac is not None:
for off, blob in find_bt_copies(data):
out[off:off + BT_FILE_SIZE] = patch_bt(blob, bt_mac)
bt_count += 1
if wifi_mac is not None:
for off, blob in find_wifi_copies(data):
out[off:off + WIFI_FILE_SIZE] = patch_wifi(blob, wifi_mac)
wifi_count += 1
if bt_mac is not None and bt_count == 0:
die(f"no valid BT_Addr record found in {in_path} ({sz} bytes scanned). Looked for {BT_TRAILER_SIG.hex()} at offset 6 of a 440-byte record with valid aa+checksum trailer.")
if wifi_mac is not None and wifi_count == 0:
expected = ' or '.join(h.hex() for h in WIFI_HDR_VARIANTS)
die(f"no valid WIFI record found in {in_path} ({sz} bytes scanned). Looked for header {expected} at the start of a 2050-byte record with valid aa+checksum trailer.")
write_output(out_path, bytes(out))
msg = []
if bt_mac is not None:
msg.append(f"BT={format_mac(bt_mac)} ({bt_count} copies)")
if wifi_mac is not None:
msg.append(f"WIFI={format_mac(wifi_mac)} ({wifi_count} copies)")
print(f"Wrote {out_path}: {', '.join(msg)}")
if is_regular_file(out_path):
cmd_read([out_path])
else:
print(f" (verify skipped: {out_path} is not a regular file)")
return
if sz == BT_FILE_SIZE:
if bt_mac is None:
die(f"input {in_path} is a BT_Addr file ({sz} bytes); pass --bt MAC (got --wifi only)")
if wifi_mac is not None:
die(f"input {in_path} is a BT_Addr file ({sz} bytes); --wifi not applicable. Pass the WIFI file ({WIFI_FILE_SIZE} bytes) instead, or pass a partition image to patch both at once.")
if out_path is None:
base, ext = os.path.splitext(in_path)
out_path = f"{base}_patched{ext}" if ext else f"{in_path}.patched"
write_output(out_path, patch_bt(data, bt_mac))
print(f"Wrote {out_path}: BT={format_mac(bt_mac)}")
if is_regular_file(out_path):
cmd_read([out_path])
else:
print(f" (verify skipped: {out_path} is not a regular file)")
return
if sz == WIFI_FILE_SIZE:
if wifi_mac is None:
die(f"input {in_path} is a WIFI file ({sz} bytes); pass --wifi MAC (got --bt only)")
if bt_mac is not None:
die(f"input {in_path} is a WIFI file ({sz} bytes); --bt not applicable. Pass the BT_Addr file ({BT_FILE_SIZE} bytes) instead, or pass a partition image to patch both at once.")
if out_path is None:
base, ext = os.path.splitext(in_path)
out_path = f"{base}_patched{ext}" if ext else f"{in_path}.patched"
write_output(out_path, patch_wifi(data, wifi_mac))
print(f"Wrote {out_path}: WIFI={format_mac(wifi_mac)}")
if is_regular_file(out_path):
cmd_read([out_path])
else:
print(f" (verify skipped: {out_path} is not a regular file)")
return
die(f"file {in_path} is {sz} bytes; expected {BT_FILE_SIZE} (BT_Addr), {WIFI_FILE_SIZE} (WIFI), or > 1 MiB (partition image)")
def main():
if len(sys.argv) < 2:
die("usage: mac_tool.py read <file> | write <file> [--bt MAC] [--wifi MAC] [-o output]")
cmd = sys.argv[1]
args = sys.argv[2:]
if cmd == 'read':
cmd_read(args)
elif cmd == 'write':
cmd_write(args)
else:
die(f"unknown command {cmd!r}; expected 'read' or 'write'")
if __name__ == '__main__':
main()