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.
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/restore/mbn.py +606 -0
- pymobiledevice3/restore/restore.py +22 -10
- {pymobiledevice3-4.27.5.dist-info → pymobiledevice3-4.27.6.dist-info}/METADATA +1 -1
- {pymobiledevice3-4.27.5.dist-info → pymobiledevice3-4.27.6.dist-info}/RECORD +9 -8
- {pymobiledevice3-4.27.5.dist-info → pymobiledevice3-4.27.6.dist-info}/WHEEL +0 -0
- {pymobiledevice3-4.27.5.dist-info → pymobiledevice3-4.27.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.27.5.dist-info → pymobiledevice3-4.27.6.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-4.27.5.dist-info → pymobiledevice3-4.27.6.dist-info}/top_level.txt +0 -0
pymobiledevice3/_version.py
CHANGED
|
@@ -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.
|
|
32
|
-
__version_tuple__ = version_tuple = (4, 27,
|
|
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
|
-
|
|
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
|
-
|
|
511
|
-
|
|
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
|
-
|
|
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.
|
|
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=
|
|
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=
|
|
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.
|
|
168
|
-
pymobiledevice3-4.27.
|
|
169
|
-
pymobiledevice3-4.27.
|
|
170
|
-
pymobiledevice3-4.27.
|
|
171
|
-
pymobiledevice3-4.27.
|
|
172
|
-
pymobiledevice3-4.27.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|