pymobiledevice3 4.27.4__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.4'
32
- __version_tuple__ = version_tuple = (4, 27, 4)
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})
@@ -277,7 +277,7 @@ class AfcService(LockdownService):
277
277
  except Exception as afc_exception:
278
278
  if not ignore_errors:
279
279
  raise
280
- self.logger.warning("(Ignoring) Error:", afc_exception, "occured during the copy of", src_filename)
280
+ self.logger.warning(f"(Ignoring) Error: {afc_exception} occurred during the copy of {src_filename}")
281
281
 
282
282
  @path_to_str()
283
283
  def exists(self, filename):
@@ -620,7 +620,7 @@ class AfcService(LockdownService):
620
620
  pass
621
621
  return status, data
622
622
 
623
- def _do_operation(self, opcode: afc_opcode_t, data: bytes = b''):
623
+ def _do_operation(self, opcode, data: bytes = b''):
624
624
  self._dispatch_packet(opcode, data)
625
625
  status, data = self._receive_data()
626
626
 
@@ -887,6 +887,7 @@ class AfcShell:
887
887
  progress_bar : --progress_bar
888
888
  Show progress bar
889
889
  """
890
+
890
891
  def log(src, dst):
891
892
  print(f'{src} --> {dst}')
892
893
 
@@ -99,7 +99,7 @@ class CrashReportsManager:
99
99
  self.afc.rm_single(src, force=True)
100
100
 
101
101
  match = None if match is None else re.compile(match)
102
- self.afc.pull(entry, out, match, callback=log, progress_bar=progress_bar)
102
+ self.afc.pull(entry, out, match, callback=log, progress_bar=progress_bar, ignore_errors=True)
103
103
 
104
104
  def flush(self) -> None:
105
105
  """ Trigger com.apple.crashreportmover to flush all products into CrashReports directory """
@@ -4,7 +4,7 @@ import logging
4
4
  import uuid
5
5
  from dataclasses import dataclass, fields
6
6
  from enum import Enum
7
- from typing import Optional, Union
7
+ from typing import Any, Coroutine, Optional, Union
8
8
 
9
9
  import nest_asyncio
10
10
 
@@ -157,7 +157,7 @@ class WebinspectorService:
157
157
  self.service = self.await_(self.lockdown.aio_start_lockdown_service(self.service_name))
158
158
  self.await_(self._report_identifier())
159
159
  try:
160
- self._handle_recv(self.await_(asyncio.wait_for(self._recv_message(), timeout)))
160
+ self._handle_recv(self._await_with_timeout(self._recv_message(), timeout))
161
161
  except asyncio.TimeoutError as e:
162
162
  raise WebInspectorNotEnabledError from e
163
163
  self._recv_task = self.loop.create_task(self._receiving_task())
@@ -225,7 +225,7 @@ class WebinspectorService:
225
225
  self.await_(self._request_application_launch(bundle))
226
226
  self.get_open_pages()
227
227
  try:
228
- return self.await_(asyncio.wait_for(self._wait_for_application(bundle), timeout=timeout))
228
+ return self._await_with_timeout(self._wait_for_application(bundle), timeout)
229
229
  except TimeoutError:
230
230
  raise LaunchingApplicationError()
231
231
 
@@ -244,8 +244,22 @@ class WebinspectorService:
244
244
  def flush_input(self, duration: Union[float, int] = 0):
245
245
  return self.await_(asyncio.sleep(duration))
246
246
 
247
- def await_(self, awaitable):
248
- return self.loop.run_until_complete(asyncio.ensure_future(awaitable, loop=self.loop))
247
+ def await_(self, awaitable: Coroutine) -> Any:
248
+ return self.loop.run_until_complete(awaitable)
249
+
250
+ def _await_with_timeout(self, coro: Coroutine, timeout: float = None) -> Any:
251
+ # Create a task explicitly so we're definitely inside a Task
252
+ task = self.loop.create_task(coro)
253
+ done, pending = self.loop.run_until_complete(asyncio.wait({task}, timeout=timeout))
254
+ if not done:
255
+ task.cancel()
256
+ # Give the task a chance to cancel cleanly
257
+ try:
258
+ self.loop.run_until_complete(task)
259
+ except asyncio.CancelledError:
260
+ pass
261
+ raise WebInspectorNotEnabledError()
262
+ return task.result()
249
263
 
250
264
  def _handle_recv(self, plist):
251
265
  self.receive_handlers[plist['__selector']](plist['__argument'])
@@ -15,6 +15,7 @@ with warnings.catch_warnings():
15
15
  # Ignore: "Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater."
16
16
  warnings.simplefilter('ignore', category=UserWarning)
17
17
  import fastapi
18
+
18
19
  import uvicorn
19
20
  from construct import StreamError
20
21
  from fastapi import FastAPI
@@ -23,7 +24,7 @@ from packaging.version import Version
23
24
  from pymobiledevice3 import usbmux
24
25
  from pymobiledevice3.bonjour import REMOTED_SERVICE_NAMES, browse
25
26
  from pymobiledevice3.exceptions import ConnectionFailedError, ConnectionFailedToUsbmuxdError, DeviceNotFoundError, \
26
- GetProhibitedError, InvalidServiceError, LockdownError, MuxException, PairingError
27
+ GetProhibitedError, IncorrectModeError, InvalidServiceError, LockdownError, MuxException, PairingError
27
28
  from pymobiledevice3.lockdown import create_using_usbmux, get_mobdev2_lockdowns
28
29
  from pymobiledevice3.osu.os_utils import get_os_utils
29
30
  from pymobiledevice3.remote.common import TunnelProtocol
@@ -148,7 +149,7 @@ class TunneldCore:
148
149
  try:
149
150
  service = CoreDeviceTunnelProxy(create_using_usbmux(mux_device.serial))
150
151
  except (MuxException, InvalidServiceError, GetProhibitedError, construct.core.StreamError,
151
- ConnectionAbortedError, DeviceNotFoundError, LockdownError):
152
+ ConnectionAbortedError, DeviceNotFoundError, LockdownError, IncorrectModeError):
152
153
  continue
153
154
  self.tunnel_tasks[task_identifier] = TunnelTask(
154
155
  udid=mux_device.serial,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymobiledevice3
3
- Version: 4.27.4
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=kZd1Lfu1YfMYyRyfVzYa9lLoPGYCKckLquTqRZzL7hE,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,17 +91,18 @@ 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
99
100
  pymobiledevice3/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
101
  pymobiledevice3/services/accessibilityaudit.py,sha256=PtQujwFLL3E1OgOSUjkKHvgmk2e8Ij2XZIp8JicAphs,15547
101
- pymobiledevice3/services/afc.py,sha256=xbUsUBW8Sue4weuaar5QnjulncyYiBxitZYLNzfif7Y,36539
102
+ pymobiledevice3/services/afc.py,sha256=bENRiozw5qAqMatXeqQZ1JmT8OZBH2R1UmN3hJSrKo4,36527
102
103
  pymobiledevice3/services/amfi.py,sha256=CmH4tbWpwLhfZlX0ukOD7sX8sYhr0exlczQ8tY2NnPw,2511
103
104
  pymobiledevice3/services/companion.py,sha256=uKsIqDHIdMkx6QjRd55NE6w8ksXhT_aN2z2cQAcIif0,2694
104
- pymobiledevice3/services/crash_reports.py,sha256=KXi_LVyvLGt2AXz-t5wI6bS4ybMpZ4ilvDBxorHHXAM,10788
105
+ pymobiledevice3/services/crash_reports.py,sha256=vHIDCjlJTz26X6e2rpxb2K9sASNHlvms_mM2eXglxjU,10808
105
106
  pymobiledevice3/services/debugserver_applist.py,sha256=YE6mWfEb4IiKmdATOCESto1nI-_uovrbqrkByGektLI,548
106
107
  pymobiledevice3/services/device_arbitration.py,sha256=FNcWydgueBqNUVZXwYfRF-EKS3vvQQHQwVzKzc-O8u4,1134
107
108
  pymobiledevice3/services/device_link.py,sha256=KCpHjOBvvzIz87JClMpRmK4fAARftaHHK1cT-YSDcXY,8435
@@ -129,7 +130,7 @@ pymobiledevice3/services/screenshot.py,sha256=sulQnyVGmq_3MJZGQ_GPJYOCj_tmE_d9ZG
129
130
  pymobiledevice3/services/simulate_location.py,sha256=WsnxaHIeRa0-9LkHbfnfOG0I9GK7D7xgksl91hOfCkI,1166
130
131
  pymobiledevice3/services/springboard.py,sha256=-3tqSuRx4p2j-rWRkDl_icvxftyuEkESWANKAS1L97M,2510
131
132
  pymobiledevice3/services/syslog.py,sha256=dccKS8sWgPCGOzK2-3f08gyF9UrN5-iHeqHxtpt6P-0,1563
132
- pymobiledevice3/services/webinspector.py,sha256=MioTJCUZPWUcNCTqzDuNp7J8s_P8bkDGMiKSTD64EB4,15913
133
+ pymobiledevice3/services/webinspector.py,sha256=p-wazgczgVh07dTuPNtYv9RHB6HgwxLrQMFX_dms96k,16488
133
134
  pymobiledevice3/services/dvt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
134
135
  pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py,sha256=2lhxkZVxV3U3tDKrx2AxX5Ui2QXBoz8_WEY6xbrkkTc,1072
135
136
  pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py,sha256=dsaResRZem1ezLkQNfMXvFJoaE0lsbznhYs7q8BLVAw,1179
@@ -163,10 +164,10 @@ pymobiledevice3/services/web_protocol/session_protocol.py,sha256=7dJkFyivu554K6I
163
164
  pymobiledevice3/services/web_protocol/switch_to.py,sha256=hDddJUEePbRN-8xlllOeGhnYvE4NEnd8JJIlosLMB9c,2880
164
165
  pymobiledevice3/tunneld/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
165
166
  pymobiledevice3/tunneld/api.py,sha256=EfGKXEWhsMSB__menPmRmL9R6dpazVJDUy7B3pn05MM,2357
166
- pymobiledevice3/tunneld/server.py,sha256=IFgRHBEoM-DXUAz83VhGfP1b0oJbnsxgrZix29t_rk4,22919
167
- pymobiledevice3-4.27.4.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
168
- pymobiledevice3-4.27.4.dist-info/METADATA,sha256=af7BO2wUPogLF9E6Ex_PWDMKYAkMvCD6XzyymAXg6VU,17450
169
- pymobiledevice3-4.27.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
170
- pymobiledevice3-4.27.4.dist-info/entry_points.txt,sha256=jJMlOanHlVwUxcY__JwvKeWPrvBJr_wJyEq4oHIZNKE,66
171
- pymobiledevice3-4.27.4.dist-info/top_level.txt,sha256=MjZoRqcWPOh5banG-BbDOnKEfsS3kCxqV9cv-nzyg2Q,21
172
- pymobiledevice3-4.27.4.dist-info/RECORD,,
167
+ pymobiledevice3/tunneld/server.py,sha256=L_98QatvVuyiXexoHF5rA0V56wC84VLeKhLyiFWwHrc,22960
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,,