pymobiledevice3 4.27.0__py3-none-any.whl → 5.1.2__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.
Files changed (143) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. pymobiledevice3/__main__.py +123 -98
  4. pymobiledevice3/_version.py +2 -2
  5. pymobiledevice3/bonjour.py +351 -117
  6. pymobiledevice3/ca.py +32 -24
  7. pymobiledevice3/cli/activation.py +7 -7
  8. pymobiledevice3/cli/afc.py +19 -19
  9. pymobiledevice3/cli/amfi.py +4 -4
  10. pymobiledevice3/cli/apps.py +51 -39
  11. pymobiledevice3/cli/backup.py +58 -32
  12. pymobiledevice3/cli/bonjour.py +27 -20
  13. pymobiledevice3/cli/cli_common.py +112 -81
  14. pymobiledevice3/cli/companion_proxy.py +4 -4
  15. pymobiledevice3/cli/completions.py +10 -10
  16. pymobiledevice3/cli/crash.py +37 -31
  17. pymobiledevice3/cli/developer.py +601 -519
  18. pymobiledevice3/cli/diagnostics.py +38 -33
  19. pymobiledevice3/cli/lockdown.py +82 -72
  20. pymobiledevice3/cli/mounter.py +84 -67
  21. pymobiledevice3/cli/notification.py +10 -10
  22. pymobiledevice3/cli/pcap.py +19 -14
  23. pymobiledevice3/cli/power_assertion.py +12 -10
  24. pymobiledevice3/cli/processes.py +10 -10
  25. pymobiledevice3/cli/profile.py +88 -77
  26. pymobiledevice3/cli/provision.py +17 -17
  27. pymobiledevice3/cli/remote.py +188 -111
  28. pymobiledevice3/cli/restore.py +43 -40
  29. pymobiledevice3/cli/springboard.py +30 -28
  30. pymobiledevice3/cli/syslog.py +85 -58
  31. pymobiledevice3/cli/usbmux.py +21 -20
  32. pymobiledevice3/cli/version.py +3 -2
  33. pymobiledevice3/cli/webinspector.py +156 -78
  34. pymobiledevice3/common.py +1 -1
  35. pymobiledevice3/exceptions.py +154 -60
  36. pymobiledevice3/irecv.py +49 -53
  37. pymobiledevice3/irecv_devices.py +1489 -492
  38. pymobiledevice3/lockdown.py +400 -251
  39. pymobiledevice3/lockdown_service_provider.py +5 -7
  40. pymobiledevice3/osu/os_utils.py +18 -9
  41. pymobiledevice3/osu/posix_util.py +28 -15
  42. pymobiledevice3/osu/win_util.py +14 -8
  43. pymobiledevice3/pair_records.py +19 -19
  44. pymobiledevice3/remote/common.py +4 -4
  45. pymobiledevice3/remote/core_device/app_service.py +94 -67
  46. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  47. pymobiledevice3/remote/core_device/device_info.py +5 -5
  48. pymobiledevice3/remote/core_device/diagnostics_service.py +10 -8
  49. pymobiledevice3/remote/core_device/file_service.py +47 -33
  50. pymobiledevice3/remote/remote_service_discovery.py +53 -35
  51. pymobiledevice3/remote/remotexpc.py +64 -42
  52. pymobiledevice3/remote/tunnel_service.py +383 -297
  53. pymobiledevice3/remote/utils.py +14 -13
  54. pymobiledevice3/remote/xpc_message.py +145 -125
  55. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  56. pymobiledevice3/resources/firmware_notifications.py +16 -16
  57. pymobiledevice3/restore/asr.py +27 -27
  58. pymobiledevice3/restore/base_restore.py +90 -47
  59. pymobiledevice3/restore/consts.py +87 -66
  60. pymobiledevice3/restore/device.py +11 -11
  61. pymobiledevice3/restore/fdr.py +46 -46
  62. pymobiledevice3/restore/ftab.py +19 -19
  63. pymobiledevice3/restore/img4.py +130 -133
  64. pymobiledevice3/restore/mbn.py +587 -0
  65. pymobiledevice3/restore/recovery.py +125 -135
  66. pymobiledevice3/restore/restore.py +535 -523
  67. pymobiledevice3/restore/restore_options.py +122 -115
  68. pymobiledevice3/restore/restored_client.py +25 -22
  69. pymobiledevice3/restore/tss.py +378 -270
  70. pymobiledevice3/service_connection.py +50 -46
  71. pymobiledevice3/services/accessibilityaudit.py +137 -127
  72. pymobiledevice3/services/afc.py +363 -293
  73. pymobiledevice3/services/amfi.py +21 -18
  74. pymobiledevice3/services/companion.py +23 -19
  75. pymobiledevice3/services/crash_reports.py +61 -47
  76. pymobiledevice3/services/debugserver_applist.py +3 -3
  77. pymobiledevice3/services/device_arbitration.py +8 -8
  78. pymobiledevice3/services/device_link.py +56 -48
  79. pymobiledevice3/services/diagnostics.py +971 -968
  80. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  81. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  82. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  83. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  84. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  85. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  86. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  87. pymobiledevice3/services/dvt/instruments/device_info.py +11 -11
  88. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  89. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  90. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  91. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  92. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  93. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  94. pymobiledevice3/services/dvt/instruments/process_control.py +25 -10
  95. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  96. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  97. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  98. pymobiledevice3/services/file_relay.py +10 -10
  99. pymobiledevice3/services/heartbeat.py +8 -7
  100. pymobiledevice3/services/house_arrest.py +12 -15
  101. pymobiledevice3/services/installation_proxy.py +119 -100
  102. pymobiledevice3/services/lockdown_service.py +12 -5
  103. pymobiledevice3/services/misagent.py +22 -19
  104. pymobiledevice3/services/mobile_activation.py +84 -72
  105. pymobiledevice3/services/mobile_config.py +331 -301
  106. pymobiledevice3/services/mobile_image_mounter.py +137 -116
  107. pymobiledevice3/services/mobilebackup2.py +188 -150
  108. pymobiledevice3/services/notification_proxy.py +11 -11
  109. pymobiledevice3/services/os_trace.py +128 -74
  110. pymobiledevice3/services/pcapd.py +306 -306
  111. pymobiledevice3/services/power_assertion.py +10 -9
  112. pymobiledevice3/services/preboard.py +4 -4
  113. pymobiledevice3/services/remote_fetch_symbols.py +16 -14
  114. pymobiledevice3/services/remote_server.py +176 -146
  115. pymobiledevice3/services/restore_service.py +16 -16
  116. pymobiledevice3/services/screenshot.py +13 -10
  117. pymobiledevice3/services/simulate_location.py +7 -7
  118. pymobiledevice3/services/springboard.py +15 -15
  119. pymobiledevice3/services/syslog.py +5 -5
  120. pymobiledevice3/services/web_protocol/alert.py +3 -3
  121. pymobiledevice3/services/web_protocol/automation_session.py +183 -179
  122. pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
  123. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  124. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  125. pymobiledevice3/services/web_protocol/driver.py +47 -45
  126. pymobiledevice3/services/web_protocol/element.py +74 -63
  127. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  128. pymobiledevice3/services/web_protocol/selenium_api.py +3 -3
  129. pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
  130. pymobiledevice3/services/web_protocol/switch_to.py +11 -12
  131. pymobiledevice3/services/webinspector.py +142 -116
  132. pymobiledevice3/tcp_forwarder.py +64 -50
  133. pymobiledevice3/tunneld/api.py +20 -15
  134. pymobiledevice3/tunneld/server.py +315 -193
  135. pymobiledevice3/usbmux.py +197 -148
  136. pymobiledevice3/utils.py +14 -11
  137. {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/METADATA +2 -6
  138. pymobiledevice3-5.1.2.dist-info/RECORD +173 -0
  139. pymobiledevice3-4.27.0.dist-info/RECORD +0 -172
  140. {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/WHEEL +0 -0
  141. {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/entry_points.txt +0 -0
  142. {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/licenses/LICENSE +0 -0
  143. {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,587 @@
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 * (ELF64_Phdr.sizeof() if kind == "ELF64" else ELF32_Phdr.sizeof())
202
+ if hdr.e_phoff + table_size > len(data):
203
+ logger.error("%s: Program header table is out of bounds", "_read_program_headers")
204
+ return []
205
+
206
+ table = data[hdr.e_phoff : hdr.e_phoff + table_size]
207
+ bio = io.BytesIO(table)
208
+ for _ in range(hdr.e_phnum):
209
+ ph = ELF64_Phdr.parse_stream(bio) if kind == "ELF64" else ELF32_Phdr.parse_stream(bio)
210
+ phdrs.append(ph)
211
+ return phdrs
212
+
213
+
214
+ def _elf_last_segment_end(data: bytes) -> Optional[int]:
215
+ kind, hdr = _read_elf_headers(data)
216
+ if not hdr:
217
+ return None
218
+ phdrs = _read_program_headers(data, kind, hdr)
219
+ if not phdrs:
220
+ return None
221
+ # last by highest p_offset
222
+ last = max(phdrs, key=lambda p: int(p.p_offset))
223
+ return int(last.p_offset + last.p_filesz)
224
+
225
+
226
+ def _mbn_v7_header_sizes_valid(h, sect_size: int) -> bool:
227
+ total = (
228
+ MBN_V7.sizeof()
229
+ + h.common_metadata_size
230
+ + h.qti_metadata_size
231
+ + h.oem_metadata_size
232
+ + h.hash_table_size
233
+ + h.qti_signature_size
234
+ + h.qti_certificate_chain_size
235
+ + h.oem_signature_size
236
+ + h.oem_certificate_chain_size
237
+ )
238
+ return total <= sect_size
239
+
240
+
241
+ def _mbn_v7_header_sizes_expected(h) -> bool:
242
+ return (
243
+ (h.qti_metadata_size in (0, 0xE0))
244
+ and (h.oem_metadata_size in (0, 0xE0))
245
+ and (h.oem_signature_size in (0, 0x68))
246
+ and (h.oem_certificate_chain_size in (0, 0xD20))
247
+ )
248
+
249
+
250
+ def _mbn_v7_log(h, func: str, prefix: str) -> None:
251
+ logger.debug(
252
+ "%s: %s header {version=0x%x, common_metadata_size=0x%x, qti_metadata_size=0x%x, "
253
+ "oem_metadata_size=0x%x, hash_table_size=0x%x, qti_signature_size=0x%x, "
254
+ "qti_certificate_chain_size=0x%x, oem_signature_size=0x%x, oem_certificate_chain_size=0x%x}",
255
+ func,
256
+ prefix,
257
+ h.version,
258
+ h.common_metadata_size,
259
+ h.qti_metadata_size,
260
+ h.oem_metadata_size,
261
+ h.hash_table_size,
262
+ h.qti_signature_size,
263
+ h.qti_certificate_chain_size,
264
+ h.oem_signature_size,
265
+ h.oem_certificate_chain_size,
266
+ )
267
+
268
+
269
+ # -----------------------------------------------------------------------------
270
+ # Public API
271
+ # -----------------------------------------------------------------------------
272
+
273
+
274
+ def mbn_stitch(data: bytes, blob: bytes) -> Optional[bytes]:
275
+ """
276
+ Overwrite the tail of `data` with `blob`. Format-aware size logging/checks.
277
+ Returns new bytes or None.
278
+ """
279
+ if data is None:
280
+ logger.error("%s: data is NULL", "mbn_stitch")
281
+ return None
282
+ if not data:
283
+ logger.error("%s: data size is 0", "mbn_stitch")
284
+ return None
285
+ if blob is None:
286
+ logger.error("%s: blob is NULL", "mbn_stitch")
287
+ return None
288
+ if not blob:
289
+ logger.error("%s: blob size is 0", "mbn_stitch")
290
+ return None
291
+
292
+ data_size = len(data)
293
+ blob_size = len(blob)
294
+ parsed_size = 0
295
+
296
+ try:
297
+ # MBN v2
298
+ if data_size > MBN_V2_MAGIC_SIZE and data[:MBN_V2_MAGIC_SIZE] == MBN_V2_MAGIC:
299
+ if data_size < MBN_V2.sizeof():
300
+ logger.error("%s: truncated MBN v2 header", "mbn_stitch")
301
+ return None
302
+ h = MBN_V2.parse(data[: MBN_V2.sizeof()])
303
+ parsed_size = h.data_size + MBN_V2.sizeof()
304
+ logger.debug(
305
+ "%s: encountered MBN v2 image, parsed_size = 0x%x",
306
+ "mbn_stitch",
307
+ parsed_size,
308
+ )
309
+
310
+ # MBN v1
311
+ elif data_size > MBN_V1_MAGIC_SIZE and data[:MBN_V1_MAGIC_SIZE] == MBN_V1_MAGIC:
312
+ if data_size < MBN_V1.sizeof():
313
+ logger.error("%s: truncated MBN v1 header", "mbn_stitch")
314
+ return None
315
+ h = MBN_V1.parse(data[: MBN_V1.sizeof()])
316
+ parsed_size = h.data_size + MBN_V1.sizeof()
317
+ logger.debug(
318
+ "%s: encountered MBN v1 image, parsed_size = 0x%x",
319
+ "mbn_stitch",
320
+ parsed_size,
321
+ )
322
+
323
+ # BIN
324
+ elif (
325
+ data_size > (MBN_BIN_MAGIC_SIZE + MBN_BIN_MAGIC_OFFSET)
326
+ and data[MBN_BIN_MAGIC_OFFSET : MBN_BIN_MAGIC_OFFSET + MBN_BIN_MAGIC_SIZE] == MBN_BIN_MAGIC
327
+ ):
328
+ if data_size < MBN_BIN.sizeof():
329
+ logger.error("%s: truncated MBN BIN header", "mbn_stitch")
330
+ return None
331
+ h = MBN_BIN.parse(data[: MBN_BIN.sizeof()])
332
+ parsed_size = h.total_size
333
+ logger.debug(
334
+ "%s: encountered MBN BIN image, parsed_size = 0x%x",
335
+ "mbn_stitch",
336
+ parsed_size,
337
+ )
338
+
339
+ # ELF
340
+ else:
341
+ end = _elf_last_segment_end(data)
342
+ if end is not None:
343
+ parsed_size = end
344
+ logger.debug(
345
+ "%s: encountered ELF image, parsed_size = 0x%x",
346
+ "mbn_stitch",
347
+ parsed_size,
348
+ )
349
+ else:
350
+ logger.warning("Unknown file format passed to %s", "mbn_stitch")
351
+ parsed_size = data_size
352
+
353
+ if parsed_size != data_size:
354
+ logger.warning(
355
+ "%s: size mismatch for MBN data, expected 0x%x, input size 0x%x",
356
+ "mbn_stitch",
357
+ parsed_size,
358
+ data_size,
359
+ )
360
+
361
+ stitch_offset = data_size - blob_size
362
+ if stitch_offset < 0 or stitch_offset + blob_size > data_size:
363
+ logger.error(
364
+ "%s: stitch offset (0x%x) + size (0x%x) is larger than the destination (0x%x)",
365
+ "mbn_stitch",
366
+ stitch_offset,
367
+ blob_size,
368
+ data_size,
369
+ )
370
+ return None
371
+
372
+ out = bytearray(data)
373
+ logger.debug(
374
+ "%s: stitching mbn at 0x%x, size 0x%x",
375
+ "mbn_stitch",
376
+ stitch_offset,
377
+ blob_size,
378
+ )
379
+ out[stitch_offset : stitch_offset + blob_size] = blob
380
+ return bytes(out)
381
+
382
+ except ChecksumError:
383
+ logger.exception("mbn_stitch: construct checksum error")
384
+ return None
385
+ except Exception:
386
+ logger.exception("mbn_stitch")
387
+ return None
388
+
389
+
390
+ def mbn_mav25_stitch(data: bytes, blob: bytes) -> Optional[bytes]:
391
+ """
392
+ Patch an ELF's last program-section area with a v7 trailer from `blob`.
393
+ - Writes new metadata+hash at section start
394
+ - Writes OEM sig+chain after existing dest qti sig+chain
395
+ Returns new bytes or None.
396
+ """
397
+ if data is None:
398
+ logger.error("%s: data is NULL", "mbn_mav25_stitch")
399
+ return None
400
+ if not data:
401
+ logger.error("%s: data size is 0", "mbn_mav25_stitch")
402
+ return None
403
+ if blob is None:
404
+ logger.error("%s: blob is NULL", "mbn_mav25_stitch")
405
+ return None
406
+ if not blob:
407
+ logger.error("%s: blob size is 0", "mbn_mav25_stitch")
408
+ return None
409
+
410
+ data_size, blob_size = len(data), len(blob)
411
+
412
+ kind, ehdr = _read_elf_headers(data)
413
+ if not ehdr:
414
+ logger.error("%s: data is not a valid ELF", "mbn_mav25_stitch")
415
+ return None
416
+
417
+ if blob_size < MBN_V7.sizeof():
418
+ logger.error("%s: header is bigger than blob", "mbn_mav25_stitch")
419
+ return None
420
+
421
+ src = MBN_V7.parse(blob[: MBN_V7.sizeof()])
422
+ _mbn_v7_log(src, "mbn_mav25_stitch", "src")
423
+ if src.version != 7:
424
+ logger.error(
425
+ "%s: src header version (0x%x) is incorrect",
426
+ "mbn_mav25_stitch",
427
+ src.version,
428
+ )
429
+ return None
430
+ if not _mbn_v7_header_sizes_expected(src):
431
+ logger.warning(
432
+ "%s: header sizes in header are unexpected (qti_metadata_size=0x%x, oem_metadata_size=0x%x, "
433
+ "oem_signature_size=0x%x, oem_certificate_chain_size=0x%x)",
434
+ "mbn_mav25_stitch",
435
+ src.qti_metadata_size,
436
+ src.oem_metadata_size,
437
+ src.oem_signature_size,
438
+ src.oem_certificate_chain_size,
439
+ )
440
+
441
+ # last PHDR (by index)
442
+ phdrs = _read_program_headers(data, kind, ehdr)
443
+ if not phdrs:
444
+ return None
445
+ last_ph = phdrs[-1]
446
+ sect_off, sect_size = int(last_ph.p_offset), int(last_ph.p_filesz)
447
+
448
+ if sect_off == 0:
449
+ logger.error("%s: section has 0 offset", "mbn_mav25_stitch")
450
+ return None
451
+ if sect_size == 0:
452
+ logger.error("%s: section has 0 size", "mbn_mav25_stitch")
453
+ return None
454
+ if sect_off + sect_size > data_size:
455
+ logger.error(
456
+ "%s: section (0x%x+0x%x) is bigger than the data",
457
+ "mbn_mav25_stitch",
458
+ sect_off,
459
+ sect_size,
460
+ )
461
+ return None
462
+ if sect_size < MBN_V7.sizeof():
463
+ logger.error(
464
+ "%s: dest header is bigger than the section (0x%x)",
465
+ "mbn_mav25_stitch",
466
+ sect_size,
467
+ )
468
+ return None
469
+
470
+ dest = MBN_V7.parse(data[sect_off : sect_off + MBN_V7.sizeof()])
471
+ _mbn_v7_log(dest, "mbn_mav25_stitch", "dest")
472
+ if dest.version != 7:
473
+ logger.error(
474
+ "%s: dest header version (0x%x) is incorrect",
475
+ "mbn_mav25_stitch",
476
+ dest.version,
477
+ )
478
+ return None
479
+ if not _mbn_v7_header_sizes_valid(dest, sect_size):
480
+ logger.error(
481
+ (
482
+ "%s: sizes in dest header are invalid (common_metadata_size=0x%x, qti_metadata_size=0x%x, "
483
+ "oem_metadata_size=0x%x, hash_table_size=0x%x, qti_signature_size=0x%x, "
484
+ "qti_certificate_chain_size=0x%x, oem_signature_size=0x%x, "
485
+ "oem_certificate_chain_size=0x%x)"
486
+ ),
487
+ "mbn_mav25_stitch",
488
+ dest.common_metadata_size,
489
+ dest.qti_metadata_size,
490
+ dest.oem_metadata_size,
491
+ dest.hash_table_size,
492
+ dest.qti_signature_size,
493
+ dest.qti_certificate_chain_size,
494
+ dest.oem_signature_size,
495
+ dest.oem_certificate_chain_size,
496
+ )
497
+ return None
498
+ if not _mbn_v7_header_sizes_expected(dest):
499
+ logger.warning(
500
+ "%s: header sizes in dest header are unexpected (qti_metadata_size=0x%x, oem_metadata_size=0x%x, "
501
+ "oem_signature_size=0x%x, oem_certificate_chain_size=0x%x)",
502
+ "mbn_mav25_stitch",
503
+ dest.qti_metadata_size,
504
+ dest.oem_metadata_size,
505
+ dest.oem_signature_size,
506
+ dest.oem_certificate_chain_size,
507
+ )
508
+
509
+ # compute new layout from src
510
+ new_metadata_size = MBN_V7.sizeof() + src.common_metadata_size + src.qti_metadata_size + src.oem_metadata_size
511
+ new_metadata_and_hash_table_size = new_metadata_size + src.hash_table_size
512
+ new_oem_sig_and_cert_chain_size = src.oem_signature_size + src.oem_certificate_chain_size
513
+
514
+ new_oem_sig_and_cert_chain_off = (
515
+ new_metadata_and_hash_table_size + dest.qti_signature_size + dest.qti_certificate_chain_size
516
+ )
517
+
518
+ # bounds
519
+ if new_metadata_and_hash_table_size > blob_size:
520
+ logger.error(
521
+ "%s: new metadata (0x%x) and hash table (0x%x) are bigger than the source (0x%x)",
522
+ "mbn_mav25_stitch",
523
+ new_metadata_size,
524
+ src.hash_table_size,
525
+ blob_size,
526
+ )
527
+ return None
528
+ if new_metadata_and_hash_table_size > sect_size:
529
+ logger.error(
530
+ "%s: new metadata (0x%x) and hash table (0x%x) are bigger than the destination (0x%x)",
531
+ "mbn_mav25_stitch",
532
+ new_metadata_size,
533
+ src.hash_table_size,
534
+ sect_size,
535
+ )
536
+ return None
537
+ if new_metadata_and_hash_table_size + new_oem_sig_and_cert_chain_size > blob_size:
538
+ logger.error(
539
+ "%s: new OEM signature and certificate chain are bigger than the source",
540
+ "mbn_mav25_stitch",
541
+ )
542
+ return None
543
+ if new_oem_sig_and_cert_chain_off + new_oem_sig_and_cert_chain_size > sect_size:
544
+ logger.error(
545
+ "%s: new OEM signature and certificate chain are outside the bounds of the destination",
546
+ "mbn_mav25_stitch",
547
+ )
548
+ return None
549
+
550
+ out = bytearray(data)
551
+ # write metadata + hash
552
+ logger.debug(
553
+ "%s: stitching mbn at 0x%x (0x%x bytes)",
554
+ "mbn_mav25_stitch",
555
+ sect_off,
556
+ new_metadata_and_hash_table_size,
557
+ )
558
+ out[sect_off : sect_off + new_metadata_and_hash_table_size] = blob[:new_metadata_and_hash_table_size]
559
+
560
+ # write OEM sig + chain
561
+ logger.debug(
562
+ "%s: stitching mbn at 0x%x (0x%x bytes)",
563
+ "mbn_mav25_stitch",
564
+ sect_off + new_oem_sig_and_cert_chain_off,
565
+ new_oem_sig_and_cert_chain_size,
566
+ )
567
+ start = new_metadata_and_hash_table_size
568
+ end = start + new_oem_sig_and_cert_chain_size
569
+
570
+ slice_start = sect_off + new_oem_sig_and_cert_chain_off
571
+ slice_end = sect_off + new_oem_sig_and_cert_chain_off + new_oem_sig_and_cert_chain_size
572
+ out[slice_start:slice_end] = blob[start:end]
573
+
574
+ return bytes(out)
575
+
576
+
577
+ # -----------------------------------------------------------------------------
578
+ # Tiny helpers (optional)
579
+ # -----------------------------------------------------------------------------
580
+
581
+
582
+ def mbn_is_valid_elf(buf: bytes) -> bool:
583
+ return len(buf) >= EI_NIDENT and _is_valid_elf_ident(buf[:EI_NIDENT])
584
+
585
+
586
+ def mbn_is_64bit_elf(buf: bytes) -> bool:
587
+ return len(buf) >= EI_NIDENT and buf[EI_CLASS] == ELFCLASS64