pymobiledevice3 4.27.5__py3-none-any.whl → 4.27.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pymobiledevice3 might be problematic. Click here for more details.

@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '4.27.5'
32
- __version_tuple__ = version_tuple = (4, 27, 5)
31
+ __version__ = version = '4.27.6'
32
+ __version_tuple__ = version_tuple = (4, 27, 6)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -0,0 +1,606 @@
1
+ # mbn.py
2
+ # Support for Qualcomm MBN (Modem Binary) formats — Python port
3
+ # Mirrors the logic of mbn.c (v1/v2/BIN headers, ELF detection, and v7 stitching)
4
+ #
5
+ # Copyright (c) 2012 Martin Szulecki
6
+ # Copyright (c) 2012 Nikias Bassen
7
+ # Copyright (c) 2025 Visual Ehrmanntraut <visual@chefkiss.dev>
8
+ #
9
+ # Ported to Python by DoronZ <doron88@gmail.com>. Licensed under LGPL-2.1-or-later (same as original).
10
+
11
+ import io
12
+ import logging
13
+ from typing import Optional
14
+
15
+ from construct import Bytes, ChecksumError, Int16ul, Int32ul, Int64ul, Struct
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # -----------------------------------------------------------------------------
20
+ # Constants
21
+ # -----------------------------------------------------------------------------
22
+ MBN_V1_MAGIC = b"\x0a\x00\x00\x00"
23
+ MBN_V1_MAGIC_SIZE = 4
24
+
25
+ MBN_V2_MAGIC = b"\xd1\xdc\x4b\x84\x34\x10\xd7\x73"
26
+ MBN_V2_MAGIC_SIZE = 8
27
+
28
+ MBN_BIN_MAGIC = b"\x04\x00\xea\x6c\x69\x48\x55"
29
+ MBN_BIN_MAGIC_SIZE = 7
30
+ MBN_BIN_MAGIC_OFFSET = 1
31
+
32
+ # ELF
33
+ EI_MAG0, EI_MAG1, EI_MAG2, EI_MAG3, EI_CLASS = 0, 1, 2, 3, 4
34
+ EI_NIDENT = 16
35
+ ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3 = 0x7F, ord("E"), ord("L"), ord("F")
36
+ ELFCLASSNONE, ELFCLASS32, ELFCLASS64 = 0, 1, 2
37
+
38
+ # -----------------------------------------------------------------------------
39
+ # Construct Structs (little-endian)
40
+ # -----------------------------------------------------------------------------
41
+
42
+ # MBN v1
43
+ MBN_V1 = Struct(
44
+ "type" / Int32ul,
45
+ "unk_0x04" / Int32ul,
46
+ "unk_0x08" / Int32ul,
47
+ "unk_0x0c" / Int32ul,
48
+ "data_size" / Int32ul, # total - sizeof(header)
49
+ "sig_offset" / Int32ul, # real offset = enc_sig_offset & 0xFFFFFF00 (FYI)
50
+ "unk_0x18" / Int32ul,
51
+ "unk_0x1c" / Int32ul,
52
+ "unk_0x20" / Int32ul,
53
+ "unk_0x24" / Int32ul,
54
+ )
55
+
56
+ # MBN v2
57
+ MBN_V2 = Struct(
58
+ "magic1" / Bytes(8),
59
+ "unk_0x08" / Int32ul,
60
+ "unk_0x0c" / Int32ul, # 0xFFFFFFFF
61
+ "unk_0x10" / Int32ul, # 0xFFFFFFFF
62
+ "header_size" / Int32ul,
63
+ "unk_0x18" / Int32ul,
64
+ "data_size" / Int32ul, # total - sizeof(header)
65
+ "sig_offset" / Int32ul,
66
+ "unk_0x24" / Int32ul,
67
+ "unk_0x28" / Int32ul,
68
+ "unk_0x2c" / Int32ul,
69
+ "unk_0x30" / Int32ul,
70
+ "unk_0x34" / Int32ul, # 0x1
71
+ "unk_0x38" / Int32ul, # 0x1
72
+ "unk_0x3c" / Int32ul, # 0xFFFFFFFF
73
+ "unk_0x40" / Int32ul, # 0xFFFFFFFF
74
+ "unk_0x44" / Int32ul, # 0xFFFFFFFF
75
+ "unk_0x48" / Int32ul, # 0xFFFFFFFF
76
+ "unk_0x4c" / Int32ul, # 0xFFFFFFFF
77
+ )
78
+
79
+ # MBN BIN
80
+ MBN_BIN = Struct(
81
+ "magic" / Bytes(8),
82
+ "unk_0x08" / Int32ul,
83
+ "version" / Int32ul,
84
+ "total_size" / Int32ul, # includes header
85
+ "unk_0x14" / Int32ul,
86
+ )
87
+
88
+ # v7 trailer header used by mbn_mav25_stitch
89
+ MBN_V7 = Struct(
90
+ "reserved" / Int32ul,
91
+ "version" / Int32ul,
92
+ "common_metadata_size" / Int32ul,
93
+ "qti_metadata_size" / Int32ul,
94
+ "oem_metadata_size" / Int32ul,
95
+ "hash_table_size" / Int32ul,
96
+ "qti_signature_size" / Int32ul,
97
+ "qti_certificate_chain_size" / Int32ul,
98
+ "oem_signature_size" / Int32ul,
99
+ "oem_certificate_chain_size" / Int32ul,
100
+ )
101
+
102
+ # ELF (minimal fields we need)
103
+ ELF32_Ehdr = Struct(
104
+ "e_ident" / Bytes(16),
105
+ "e_type" / Int16ul,
106
+ "e_machine" / Int16ul,
107
+ "e_version" / Int32ul,
108
+ "e_entry" / Int32ul,
109
+ "e_phoff" / Int32ul,
110
+ "e_shoff" / Int32ul,
111
+ "e_flags" / Int32ul,
112
+ "e_ehsize" / Int16ul,
113
+ "e_phentsize" / Int16ul,
114
+ "e_phnum" / Int16ul,
115
+ "e_shentsize" / Int16ul,
116
+ "e_shnum" / Int16ul,
117
+ "e_shstrndx" / Int16ul,
118
+ )
119
+
120
+ ELF64_Ehdr = Struct(
121
+ "e_ident" / Bytes(16),
122
+ "e_type" / Int16ul,
123
+ "e_machine" / Int16ul,
124
+ "e_version" / Int32ul,
125
+ "e_entry" / Int64ul,
126
+ "e_phoff" / Int64ul,
127
+ "e_shoff" / Int64ul,
128
+ "e_flags" / Int32ul,
129
+ "e_ehsize" / Int16ul,
130
+ "e_phentsize" / Int16ul,
131
+ "e_phnum" / Int16ul,
132
+ "e_shentsize" / Int16ul,
133
+ "e_shnum" / Int16ul,
134
+ "e_shstrndx" / Int16ul,
135
+ )
136
+
137
+ ELF32_Phdr = Struct(
138
+ "p_type" / Int32ul,
139
+ "p_offset" / Int32ul,
140
+ "p_vaddr" / Int32ul,
141
+ "p_paddr" / Int32ul,
142
+ "p_filesz" / Int32ul,
143
+ "p_memsz" / Int32ul,
144
+ "p_flags" / Int32ul,
145
+ "p_align" / Int32ul,
146
+ )
147
+
148
+ ELF64_Phdr = Struct(
149
+ "p_type" / Int32ul,
150
+ "p_flags" / Int32ul,
151
+ "p_offset" / Int64ul,
152
+ "p_vaddr" / Int64ul,
153
+ "p_paddr" / Int64ul,
154
+ "p_filesz" / Int64ul,
155
+ "p_memsz" / Int64ul,
156
+ "p_align" / Int64ul,
157
+ )
158
+
159
+
160
+ # -----------------------------------------------------------------------------
161
+ # Helpers
162
+ # -----------------------------------------------------------------------------
163
+
164
+
165
+ def _is_valid_elf_ident(e_ident: bytes) -> bool:
166
+ return (
167
+ len(e_ident) >= EI_NIDENT
168
+ and e_ident[EI_MAG0] == ELFMAG0
169
+ and e_ident[EI_MAG1] == ELFMAG1
170
+ and e_ident[EI_MAG2] == ELFMAG2
171
+ and e_ident[EI_MAG3] == ELFMAG3
172
+ and e_ident[EI_CLASS] != ELFCLASSNONE
173
+ )
174
+
175
+
176
+ def _read_elf_headers(data: bytes):
177
+ if len(data) < EI_NIDENT:
178
+ return None, None
179
+ e_ident = data[:EI_NIDENT]
180
+ if not _is_valid_elf_ident(e_ident):
181
+ return None, None
182
+ if e_ident[EI_CLASS] == ELFCLASS64:
183
+ if len(data) < ELF64_Ehdr.sizeof():
184
+ return None, None
185
+ hdr = ELF64_Ehdr.parse(data[: ELF64_Ehdr.sizeof()])
186
+ return "ELF64", hdr
187
+ elif e_ident[EI_CLASS] == ELFCLASS32:
188
+ if len(data) < ELF32_Ehdr.sizeof():
189
+ return None, None
190
+ hdr = ELF32_Ehdr.parse(data[: ELF32_Ehdr.sizeof()])
191
+ return "ELF32", hdr
192
+ return None, None
193
+
194
+
195
+ def _read_program_headers(data: bytes, kind: str, hdr) -> list:
196
+ phdrs = []
197
+ if hdr.e_phnum == 0:
198
+ logger.error("%s: ELF has no program sections", "_read_program_headers")
199
+ return phdrs
200
+
201
+ table_size = hdr.e_phnum * (
202
+ ELF64_Phdr.sizeof() if kind == "ELF64" else ELF32_Phdr.sizeof()
203
+ )
204
+ if hdr.e_phoff + table_size > len(data):
205
+ logger.error(
206
+ "%s: Program header table is out of bounds", "_read_program_headers"
207
+ )
208
+ return []
209
+
210
+ table = data[hdr.e_phoff:hdr.e_phoff + table_size]
211
+ bio = io.BytesIO(table)
212
+ for _ in range(hdr.e_phnum):
213
+ if kind == "ELF64":
214
+ ph = ELF64_Phdr.parse_stream(bio)
215
+ else:
216
+ ph = ELF32_Phdr.parse_stream(bio)
217
+ phdrs.append(ph)
218
+ return phdrs
219
+
220
+
221
+ def _elf_last_segment_end(data: bytes) -> Optional[int]:
222
+ kind, hdr = _read_elf_headers(data)
223
+ if not hdr:
224
+ return None
225
+ phdrs = _read_program_headers(data, kind, hdr)
226
+ if not phdrs:
227
+ return None
228
+ # last by highest p_offset
229
+ last = max(phdrs, key=lambda p: int(p.p_offset))
230
+ return int(last.p_offset + last.p_filesz)
231
+
232
+
233
+ def _mbn_v7_header_sizes_valid(h, sect_size: int) -> bool:
234
+ total = (
235
+ MBN_V7.sizeof()
236
+ + h.common_metadata_size
237
+ + h.qti_metadata_size
238
+ + h.oem_metadata_size
239
+ + h.hash_table_size
240
+ + h.qti_signature_size
241
+ + h.qti_certificate_chain_size
242
+ + h.oem_signature_size
243
+ + h.oem_certificate_chain_size
244
+ )
245
+ return total <= sect_size
246
+
247
+
248
+ def _mbn_v7_header_sizes_expected(h) -> bool:
249
+ return (
250
+ (h.qti_metadata_size in (0, 0xE0))
251
+ and (h.oem_metadata_size in (0, 0xE0))
252
+ and (h.oem_signature_size in (0, 0x68))
253
+ and (h.oem_certificate_chain_size in (0, 0xD20))
254
+ )
255
+
256
+
257
+ def _mbn_v7_log(h, func: str, prefix: str) -> None:
258
+ logger.debug(
259
+ "%s: %s header {version=0x%x, common_metadata_size=0x%x, qti_metadata_size=0x%x, "
260
+ "oem_metadata_size=0x%x, hash_table_size=0x%x, qti_signature_size=0x%x, "
261
+ "qti_certificate_chain_size=0x%x, oem_signature_size=0x%x, oem_certificate_chain_size=0x%x}",
262
+ func,
263
+ prefix,
264
+ h.version,
265
+ h.common_metadata_size,
266
+ h.qti_metadata_size,
267
+ h.oem_metadata_size,
268
+ h.hash_table_size,
269
+ h.qti_signature_size,
270
+ h.qti_certificate_chain_size,
271
+ h.oem_signature_size,
272
+ h.oem_certificate_chain_size,
273
+ )
274
+
275
+
276
+ # -----------------------------------------------------------------------------
277
+ # Public API
278
+ # -----------------------------------------------------------------------------
279
+
280
+
281
+ def mbn_stitch(data: bytes, blob: bytes) -> Optional[bytes]:
282
+ """
283
+ Overwrite the tail of `data` with `blob`. Format-aware size logging/checks.
284
+ Returns new bytes or None.
285
+ """
286
+ if data is None:
287
+ logger.error("%s: data is NULL", "mbn_stitch")
288
+ return None
289
+ if not data:
290
+ logger.error("%s: data size is 0", "mbn_stitch")
291
+ return None
292
+ if blob is None:
293
+ logger.error("%s: blob is NULL", "mbn_stitch")
294
+ return None
295
+ if not blob:
296
+ logger.error("%s: blob size is 0", "mbn_stitch")
297
+ return None
298
+
299
+ data_size = len(data)
300
+ blob_size = len(blob)
301
+ parsed_size = 0
302
+
303
+ try:
304
+ # MBN v2
305
+ if data_size > MBN_V2_MAGIC_SIZE and data[:MBN_V2_MAGIC_SIZE] == MBN_V2_MAGIC:
306
+ if data_size < MBN_V2.sizeof():
307
+ logger.error("%s: truncated MBN v2 header", "mbn_stitch")
308
+ return None
309
+ h = MBN_V2.parse(data[: MBN_V2.sizeof()])
310
+ parsed_size = h.data_size + MBN_V2.sizeof()
311
+ logger.debug(
312
+ "%s: encountered MBN v2 image, parsed_size = 0x%x",
313
+ "mbn_stitch",
314
+ parsed_size,
315
+ )
316
+
317
+ # MBN v1
318
+ elif data_size > MBN_V1_MAGIC_SIZE and data[:MBN_V1_MAGIC_SIZE] == MBN_V1_MAGIC:
319
+ if data_size < MBN_V1.sizeof():
320
+ logger.error("%s: truncated MBN v1 header", "mbn_stitch")
321
+ return None
322
+ h = MBN_V1.parse(data[: MBN_V1.sizeof()])
323
+ parsed_size = h.data_size + MBN_V1.sizeof()
324
+ logger.debug(
325
+ "%s: encountered MBN v1 image, parsed_size = 0x%x",
326
+ "mbn_stitch",
327
+ parsed_size,
328
+ )
329
+
330
+ # BIN
331
+ elif (
332
+ data_size > (MBN_BIN_MAGIC_SIZE + MBN_BIN_MAGIC_OFFSET)
333
+ and data[MBN_BIN_MAGIC_OFFSET:MBN_BIN_MAGIC_OFFSET + MBN_BIN_MAGIC_SIZE]
334
+ == MBN_BIN_MAGIC
335
+ ):
336
+ if data_size < MBN_BIN.sizeof():
337
+ logger.error("%s: truncated MBN BIN header", "mbn_stitch")
338
+ return None
339
+ h = MBN_BIN.parse(data[: MBN_BIN.sizeof()])
340
+ parsed_size = h.total_size
341
+ logger.debug(
342
+ "%s: encountered MBN BIN image, parsed_size = 0x%x",
343
+ "mbn_stitch",
344
+ parsed_size,
345
+ )
346
+
347
+ # ELF
348
+ else:
349
+ end = _elf_last_segment_end(data)
350
+ if end is not None:
351
+ parsed_size = end
352
+ logger.debug(
353
+ "%s: encountered ELF image, parsed_size = 0x%x",
354
+ "mbn_stitch",
355
+ parsed_size,
356
+ )
357
+ else:
358
+ logger.warning("Unknown file format passed to %s", "mbn_stitch")
359
+ parsed_size = data_size
360
+
361
+ if parsed_size != data_size:
362
+ logger.warning(
363
+ "%s: size mismatch for MBN data, expected 0x%x, input size 0x%x",
364
+ "mbn_stitch",
365
+ parsed_size,
366
+ data_size,
367
+ )
368
+
369
+ stitch_offset = data_size - blob_size
370
+ if stitch_offset < 0 or stitch_offset + blob_size > data_size:
371
+ logger.error(
372
+ "%s: stitch offset (0x%x) + size (0x%x) is larger than the destination (0x%x)",
373
+ "mbn_stitch",
374
+ stitch_offset,
375
+ blob_size,
376
+ data_size,
377
+ )
378
+ return None
379
+
380
+ out = bytearray(data)
381
+ logger.debug(
382
+ "%s: stitching mbn at 0x%x, size 0x%x",
383
+ "mbn_stitch",
384
+ stitch_offset,
385
+ blob_size,
386
+ )
387
+ out[stitch_offset: stitch_offset + blob_size] = blob
388
+ return bytes(out)
389
+
390
+ except ChecksumError as e:
391
+ logger.error("%s: construct checksum error: %s", "mbn_stitch", str(e))
392
+ return None
393
+ except Exception as e:
394
+ logger.error("%s: unexpected error: %s", "mbn_stitch", str(e))
395
+ return None
396
+
397
+
398
+ def mbn_mav25_stitch(data: bytes, blob: bytes) -> Optional[bytes]:
399
+ """
400
+ Patch an ELF's last program-section area with a v7 trailer from `blob`.
401
+ - Writes new metadata+hash at section start
402
+ - Writes OEM sig+chain after existing dest qti sig+chain
403
+ Returns new bytes or None.
404
+ """
405
+ if data is None:
406
+ logger.error("%s: data is NULL", "mbn_mav25_stitch")
407
+ return None
408
+ if not data:
409
+ logger.error("%s: data size is 0", "mbn_mav25_stitch")
410
+ return None
411
+ if blob is None:
412
+ logger.error("%s: blob is NULL", "mbn_mav25_stitch")
413
+ return None
414
+ if not blob:
415
+ logger.error("%s: blob size is 0", "mbn_mav25_stitch")
416
+ return None
417
+
418
+ data_size, blob_size = len(data), len(blob)
419
+
420
+ kind, ehdr = _read_elf_headers(data)
421
+ if not ehdr:
422
+ logger.error("%s: data is not a valid ELF", "mbn_mav25_stitch")
423
+ return None
424
+
425
+ if blob_size < MBN_V7.sizeof():
426
+ logger.error("%s: header is bigger than blob", "mbn_mav25_stitch")
427
+ return None
428
+
429
+ src = MBN_V7.parse(blob[: MBN_V7.sizeof()])
430
+ _mbn_v7_log(src, "mbn_mav25_stitch", "src")
431
+ if src.version != 7:
432
+ logger.error(
433
+ "%s: src header version (0x%x) is incorrect",
434
+ "mbn_mav25_stitch",
435
+ src.version,
436
+ )
437
+ return None
438
+ if not _mbn_v7_header_sizes_expected(src):
439
+ logger.warning(
440
+ "%s: header sizes in header are unexpected (qti_metadata_size=0x%x, oem_metadata_size=0x%x, "
441
+ "oem_signature_size=0x%x, oem_certificate_chain_size=0x%x)",
442
+ "mbn_mav25_stitch",
443
+ src.qti_metadata_size,
444
+ src.oem_metadata_size,
445
+ src.oem_signature_size,
446
+ src.oem_certificate_chain_size,
447
+ )
448
+
449
+ # last PHDR (by index)
450
+ phdrs = _read_program_headers(data, kind, ehdr)
451
+ if not phdrs:
452
+ return None
453
+ last_ph = phdrs[-1]
454
+ sect_off, sect_size = int(last_ph.p_offset), int(last_ph.p_filesz)
455
+
456
+ if sect_off == 0:
457
+ logger.error("%s: section has 0 offset", "mbn_mav25_stitch")
458
+ return None
459
+ if sect_size == 0:
460
+ logger.error("%s: section has 0 size", "mbn_mav25_stitch")
461
+ return None
462
+ if sect_off + sect_size > data_size:
463
+ logger.error(
464
+ "%s: section (0x%x+0x%x) is bigger than the data",
465
+ "mbn_mav25_stitch",
466
+ sect_off,
467
+ sect_size,
468
+ )
469
+ return None
470
+ if sect_size < MBN_V7.sizeof():
471
+ logger.error(
472
+ "%s: dest header is bigger than the section (0x%x)",
473
+ "mbn_mav25_stitch",
474
+ sect_size,
475
+ )
476
+ return None
477
+
478
+ dest = MBN_V7.parse(data[sect_off: sect_off + MBN_V7.sizeof()])
479
+ _mbn_v7_log(dest, "mbn_mav25_stitch", "dest")
480
+ if dest.version != 7:
481
+ logger.error(
482
+ "%s: dest header version (0x%x) is incorrect",
483
+ "mbn_mav25_stitch",
484
+ dest.version,
485
+ )
486
+ return None
487
+ if not _mbn_v7_header_sizes_valid(dest, sect_size):
488
+ logger.error(
489
+ (
490
+ "%s: sizes in dest header are invalid (common_metadata_size=0x%x, qti_metadata_size=0x%x, "
491
+ "oem_metadata_size=0x%x, hash_table_size=0x%x, qti_signature_size=0x%x, "
492
+ "qti_certificate_chain_size=0x%x, oem_signature_size=0x%x, "
493
+ "oem_certificate_chain_size=0x%x)"
494
+ ),
495
+ "mbn_mav25_stitch",
496
+ dest.common_metadata_size,
497
+ dest.qti_metadata_size,
498
+ dest.oem_metadata_size,
499
+ dest.hash_table_size,
500
+ dest.qti_signature_size,
501
+ dest.qti_certificate_chain_size,
502
+ dest.oem_signature_size,
503
+ dest.oem_certificate_chain_size,
504
+ )
505
+ return None
506
+ if not _mbn_v7_header_sizes_expected(dest):
507
+ logger.warning(
508
+ "%s: header sizes in dest header are unexpected (qti_metadata_size=0x%x, oem_metadata_size=0x%x, "
509
+ "oem_signature_size=0x%x, oem_certificate_chain_size=0x%x)",
510
+ "mbn_mav25_stitch",
511
+ dest.qti_metadata_size,
512
+ dest.oem_metadata_size,
513
+ dest.oem_signature_size,
514
+ dest.oem_certificate_chain_size,
515
+ )
516
+
517
+ # compute new layout from src
518
+ new_metadata_size = (
519
+ MBN_V7.sizeof()
520
+ + src.common_metadata_size
521
+ + src.qti_metadata_size
522
+ + src.oem_metadata_size
523
+ )
524
+ new_metadata_and_hash_table_size = new_metadata_size + src.hash_table_size
525
+ new_oem_sig_and_cert_chain_size = (
526
+ src.oem_signature_size + src.oem_certificate_chain_size
527
+ )
528
+
529
+ new_oem_sig_and_cert_chain_off = (
530
+ new_metadata_and_hash_table_size
531
+ + dest.qti_signature_size
532
+ + dest.qti_certificate_chain_size
533
+ )
534
+
535
+ # bounds
536
+ if new_metadata_and_hash_table_size > blob_size:
537
+ logger.error(
538
+ "%s: new metadata (0x%x) and hash table (0x%x) are bigger than the source (0x%x)",
539
+ "mbn_mav25_stitch",
540
+ new_metadata_size,
541
+ src.hash_table_size,
542
+ blob_size,
543
+ )
544
+ return None
545
+ if new_metadata_and_hash_table_size > sect_size:
546
+ logger.error(
547
+ "%s: new metadata (0x%x) and hash table (0x%x) are bigger than the destination (0x%x)",
548
+ "mbn_mav25_stitch",
549
+ new_metadata_size,
550
+ src.hash_table_size,
551
+ sect_size,
552
+ )
553
+ return None
554
+ if new_metadata_and_hash_table_size + new_oem_sig_and_cert_chain_size > blob_size:
555
+ logger.error(
556
+ "%s: new OEM signature and certificate chain are bigger than the source",
557
+ "mbn_mav25_stitch",
558
+ )
559
+ return None
560
+ if new_oem_sig_and_cert_chain_off + new_oem_sig_and_cert_chain_size > sect_size:
561
+ logger.error(
562
+ "%s: new OEM signature and certificate chain are outside the bounds of the destination",
563
+ "mbn_mav25_stitch",
564
+ )
565
+ return None
566
+
567
+ out = bytearray(data)
568
+ # write metadata + hash
569
+ logger.debug(
570
+ "%s: stitching mbn at 0x%x (0x%x bytes)",
571
+ "mbn_mav25_stitch",
572
+ sect_off,
573
+ new_metadata_and_hash_table_size,
574
+ )
575
+ out[sect_off: sect_off + new_metadata_and_hash_table_size] = blob[
576
+ :new_metadata_and_hash_table_size
577
+ ]
578
+
579
+ # write OEM sig + chain
580
+ logger.debug(
581
+ "%s: stitching mbn at 0x%x (0x%x bytes)",
582
+ "mbn_mav25_stitch",
583
+ sect_off + new_oem_sig_and_cert_chain_off,
584
+ new_oem_sig_and_cert_chain_size,
585
+ )
586
+ start = new_metadata_and_hash_table_size
587
+ end = start + new_oem_sig_and_cert_chain_size
588
+
589
+ slice_start = sect_off + new_oem_sig_and_cert_chain_off
590
+ slice_end = sect_off + new_oem_sig_and_cert_chain_off + new_oem_sig_and_cert_chain_size
591
+ out[slice_start:slice_end] = blob[start:end]
592
+
593
+ return bytes(out)
594
+
595
+
596
+ # -----------------------------------------------------------------------------
597
+ # Tiny helpers (optional)
598
+ # -----------------------------------------------------------------------------
599
+
600
+
601
+ def mbn_is_valid_elf(buf: bytes) -> bool:
602
+ return len(buf) >= EI_NIDENT and _is_valid_elf_ident(buf[:EI_NIDENT])
603
+
604
+
605
+ def mbn_is_64bit_elf(buf: bytes) -> bool:
606
+ return len(buf) >= EI_NIDENT and buf[EI_CLASS] == ELFCLASS64
@@ -23,12 +23,13 @@ from pymobiledevice3.restore.consts import PROGRESS_BAR_OPERATIONS, lpol_file
23
23
  from pymobiledevice3.restore.device import Device
24
24
  from pymobiledevice3.restore.fdr import FDRClient, fdr_type, start_fdr_thread
25
25
  from pymobiledevice3.restore.ftab import Ftab
26
+ from pymobiledevice3.restore.mbn import mbn_mav25_stitch, mbn_stitch
26
27
  from pymobiledevice3.restore.recovery import Behavior, Recovery
27
28
  from pymobiledevice3.restore.restore_options import RestoreOptions
28
29
  from pymobiledevice3.restore.restored_client import RestoredClient
29
30
  from pymobiledevice3.restore.tss import TSSRequest, TSSResponse
30
31
  from pymobiledevice3.service_connection import ServiceConnection
31
- from pymobiledevice3.utils import plist_access_path
32
+ from pymobiledevice3.utils import asyncio_print_traceback, plist_access_path
32
33
 
33
34
  known_errors = {
34
35
  0xFFFFFFFFFFFFFFFF: 'verification error',
@@ -442,7 +443,7 @@ class Restore(BaseRestore):
442
443
  await service.aio_send_plist(req)
443
444
 
444
445
  @staticmethod
445
- def get_bbfw_fn_for_element(elem):
446
+ def get_bbfw_fn_for_element(elem: str, bb_chip_id: Optional[int] = None) -> str:
446
447
  bbfw_fn_elem = {
447
448
  # ICE3 firmware files
448
449
  'RamPSI': 'psi_ram.fls',
@@ -465,7 +466,16 @@ class Restore(BaseRestore):
465
466
  # Mav20 Firmware file
466
467
  'Misc': 'multi_image.mbn',
467
468
  }
468
- return bbfw_fn_elem.get(elem)
469
+
470
+ bbfw_fn_elem_mav25 = {
471
+ # Mav25 Firmware files
472
+ 'Misc': 'multi_image.mbn',
473
+ 'RestoreSBL1': 'restorexbl_sc.elf',
474
+ 'SBL1': 'xbl_sc.elf',
475
+ 'TME': 'signed_firmware_soc_view.elf',
476
+ }
477
+
478
+ return bbfw_fn_elem_mav25.get(elem) if bb_chip_id == 0x1F30E1 else bbfw_fn_elem.get(elem)
469
479
 
470
480
  def fls_parse(self, buffer):
471
481
  raise NotImplementedError()
@@ -476,7 +486,8 @@ class Restore(BaseRestore):
476
486
  def fls_insert_ticket(self, fls, bbticket):
477
487
  raise NotImplementedError()
478
488
 
479
- def sign_bbfw(self, bbfw_orig, bbtss, bb_nonce):
489
+ def sign_bbfw(self, bbfw_orig: bytes, bbtss: TSSResponse, bb_nonce: Optional[bytes],
490
+ bb_chip_id: Optional[int] = None) -> bytes:
480
491
  # check for BBTicket in result
481
492
  bbticket = bbtss.bb_ticket
482
493
  bbfw_dict = bbtss.get('BasebandFirmware')
@@ -495,7 +506,7 @@ class Restore(BaseRestore):
495
506
  for key, blob in bbfw_dict.items():
496
507
  if key.endswith('-Blob') and isinstance(blob, bytes):
497
508
  key = key.split('-', 1)[0]
498
- signfn = self.get_bbfw_fn_for_element(key)
509
+ signfn = self.get_bbfw_fn_for_element(key, bb_chip_id)
499
510
 
500
511
  if signfn is None:
501
512
  raise PyMobileDevice3Exception(
@@ -507,11 +518,11 @@ class Restore(BaseRestore):
507
518
  buffer = bbfw_orig.read(signfn)
508
519
 
509
520
  if is_fls:
510
- fls = self.fls_parse(buffer)
511
- data = self.fls_update_sig_blob(fls, blob)
521
+ raise NotImplementedError('is_fls')
522
+ elif bb_chip_id == 0x1F30E1: # Mav25 - Qualcomm Snapdragon X80 5G Modem
523
+ data = mbn_mav25_stitch(buffer, blob)
512
524
  else:
513
- parsed_sig_offset = len(buffer) - len(blob)
514
- data = buffer[:parsed_sig_offset] + blob
525
+ data = mbn_stitch(buffer, blob)
515
526
 
516
527
  bbfw_patched.writestr(bbfw_orig.getinfo(signfn), data)
517
528
 
@@ -559,6 +570,7 @@ class Restore(BaseRestore):
559
570
  if tmp_zip_read_name:
560
571
  os.remove(tmp_zip_read_name)
561
572
 
573
+ @asyncio_print_traceback
562
574
  async def send_baseband_data(self, message: dict):
563
575
  self.logger.info(f'About to send BasebandData: {message}')
564
576
  service = await self._get_service_for_data_request(message)
@@ -608,7 +620,7 @@ class Restore(BaseRestore):
608
620
  # extract baseband firmware to temp file
609
621
  bbfw = self.ipsw.read(bbfwpath)
610
622
 
611
- buffer = self.sign_bbfw(bbfw, bbtss, bb_nonce)
623
+ buffer = self.sign_bbfw(bbfw, bbtss, bb_nonce, bb_chip_id)
612
624
 
613
625
  self.logger.info('Sending BasebandData now...')
614
626
  await service.aio_send_plist({'BasebandData': buffer})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymobiledevice3
3
- Version: 4.27.5
3
+ Version: 4.27.6
4
4
  Summary: Pure python3 implementation for working with iDevices (iPhone, etc...)
5
5
  Author-email: doronz88 <doron88@gmail.com>, matan <matan1008@gmail.com>
6
6
  Maintainer-email: doronz88 <doron88@gmail.com>, matan <matan1008@gmail.com>
@@ -8,7 +8,7 @@ misc/understanding_idevice_protocol_layers.md,sha256=8tEqRXWOUPoxOJLZVh7C7H9JGCh
8
8
  misc/usbmux_sniff.sh,sha256=iWtbucOEQ9_UEFXk9x-2VNt48Jg5zrPsnUbZ_LfZxwA,212
9
9
  pymobiledevice3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  pymobiledevice3/__main__.py,sha256=1nv18QRgR_FDq5rE95uhdZGOQ6xkPzNDrcMzQQs8ZZ4,11697
11
- pymobiledevice3/_version.py,sha256=drqBxOP7IVaoCFNTNLbLqRxgVySWqWe06Z2SK5blf6A,706
11
+ pymobiledevice3/_version.py,sha256=5o71dkMA4e-Z6BEkb9K3FNM6kwzbnHi6uML3NUJ4QSg,706
12
12
  pymobiledevice3/bonjour.py,sha256=-Q_TLBGJ6qW3CX_DgBcz-CXfWSwxWVQ2L64hk6PxnDY,5631
13
13
  pymobiledevice3/ca.py,sha256=mTvWdSjTZw6Eb-22-IZ323GyA1G6CXYmdPedImTjm3A,10542
14
14
  pymobiledevice3/common.py,sha256=-PG6oaUkNFlB3jb7E0finMrX8wqhkS-cuTAfmLvZUmc,329
@@ -91,8 +91,9 @@ pymobiledevice3/restore/device.py,sha256=TZahPSKHxfrF7A8DC70fwcoQK9bXD6QV1dAr80Y
91
91
  pymobiledevice3/restore/fdr.py,sha256=KOXZH50oNYseP-PzSkw_uxu1NyGP5d32_DYSoYdbFu8,6269
92
92
  pymobiledevice3/restore/ftab.py,sha256=SWNKZRN1pFWzx_qHCkycvsLaHFAqkqQJYRBJYQ_-Wjw,1507
93
93
  pymobiledevice3/restore/img4.py,sha256=V03nq1L7FMkdmC15MN7W9Wct4MQx472aDpy5IkuNvII,4888
94
+ pymobiledevice3/restore/mbn.py,sha256=3Xb62vrfCROosvcTYAbAwALW_URxm5wEpctjj82CwIQ,19323
94
95
  pymobiledevice3/restore/recovery.py,sha256=1eHQ-I1iJG31TLIAz890fBtGqopBQT3FB--UYESIkEQ,17205
95
- pymobiledevice3/restore/restore.py,sha256=86qTivTKuYT0cwWcYPoRtLKvsFvPoiUyKtq_9YWjXw0,57485
96
+ pymobiledevice3/restore/restore.py,sha256=VKro3s0vm3WDrgVAbdcw4SNSPmgtsJ5yEvpMy_eUfPM,58104
96
97
  pymobiledevice3/restore/restore_options.py,sha256=qeh_wRa_bi0Ccl0p0JuX9EChEQBqwJt94EBImWcEJ3E,7255
97
98
  pymobiledevice3/restore/restored_client.py,sha256=h_yBZ_e1wfaIzi0f9-R8Ky2x6xFTkvDlpWIV561uxoU,3638
98
99
  pymobiledevice3/restore/tss.py,sha256=QHq5OTyCn7iIrYhhgOxT5_gpV7wggcy5Faje5160FHQ,29852
@@ -164,9 +165,9 @@ pymobiledevice3/services/web_protocol/switch_to.py,sha256=hDddJUEePbRN-8xlllOeGh
164
165
  pymobiledevice3/tunneld/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
165
166
  pymobiledevice3/tunneld/api.py,sha256=EfGKXEWhsMSB__menPmRmL9R6dpazVJDUy7B3pn05MM,2357
166
167
  pymobiledevice3/tunneld/server.py,sha256=L_98QatvVuyiXexoHF5rA0V56wC84VLeKhLyiFWwHrc,22960
167
- pymobiledevice3-4.27.5.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
168
- pymobiledevice3-4.27.5.dist-info/METADATA,sha256=JkvGLXeRUphXgo-AmCAyHIS3YWJoR9ftkt0hXLAIJWM,17450
169
- pymobiledevice3-4.27.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
170
- pymobiledevice3-4.27.5.dist-info/entry_points.txt,sha256=jJMlOanHlVwUxcY__JwvKeWPrvBJr_wJyEq4oHIZNKE,66
171
- pymobiledevice3-4.27.5.dist-info/top_level.txt,sha256=MjZoRqcWPOh5banG-BbDOnKEfsS3kCxqV9cv-nzyg2Q,21
172
- pymobiledevice3-4.27.5.dist-info/RECORD,,
168
+ pymobiledevice3-4.27.6.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
169
+ pymobiledevice3-4.27.6.dist-info/METADATA,sha256=9zYKxPeaGYsfbQe6-sf4iQn9lQjw12zx_smTNsWVIVU,17450
170
+ pymobiledevice3-4.27.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
171
+ pymobiledevice3-4.27.6.dist-info/entry_points.txt,sha256=jJMlOanHlVwUxcY__JwvKeWPrvBJr_wJyEq4oHIZNKE,66
172
+ pymobiledevice3-4.27.6.dist-info/top_level.txt,sha256=MjZoRqcWPOh5banG-BbDOnKEfsS3kCxqV9cv-nzyg2Q,21
173
+ pymobiledevice3-4.27.6.dist-info/RECORD,,