pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.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.
Files changed (164) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. misc/understanding_idevice_protocol_layers.md +15 -10
  4. pymobiledevice3/__main__.py +317 -127
  5. pymobiledevice3/_version.py +22 -4
  6. pymobiledevice3/bonjour.py +358 -113
  7. pymobiledevice3/ca.py +253 -16
  8. pymobiledevice3/cli/activation.py +31 -23
  9. pymobiledevice3/cli/afc.py +49 -40
  10. pymobiledevice3/cli/amfi.py +16 -21
  11. pymobiledevice3/cli/apps.py +87 -42
  12. pymobiledevice3/cli/backup.py +160 -90
  13. pymobiledevice3/cli/bonjour.py +44 -40
  14. pymobiledevice3/cli/cli_common.py +204 -198
  15. pymobiledevice3/cli/companion_proxy.py +14 -14
  16. pymobiledevice3/cli/crash.py +105 -56
  17. pymobiledevice3/cli/developer/__init__.py +62 -0
  18. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  19. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  20. pymobiledevice3/cli/developer/arbitration.py +50 -0
  21. pymobiledevice3/cli/developer/condition.py +33 -0
  22. pymobiledevice3/cli/developer/core_device.py +294 -0
  23. pymobiledevice3/cli/developer/debugserver.py +244 -0
  24. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  25. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  26. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  27. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  28. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  29. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  30. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  31. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  32. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  33. pymobiledevice3/cli/idam.py +42 -0
  34. pymobiledevice3/cli/lockdown.py +108 -103
  35. pymobiledevice3/cli/mounter.py +158 -99
  36. pymobiledevice3/cli/notification.py +38 -26
  37. pymobiledevice3/cli/pcap.py +45 -24
  38. pymobiledevice3/cli/power_assertion.py +18 -17
  39. pymobiledevice3/cli/processes.py +17 -23
  40. pymobiledevice3/cli/profile.py +165 -109
  41. pymobiledevice3/cli/provision.py +35 -34
  42. pymobiledevice3/cli/remote.py +217 -129
  43. pymobiledevice3/cli/restore.py +159 -143
  44. pymobiledevice3/cli/springboard.py +63 -53
  45. pymobiledevice3/cli/syslog.py +193 -86
  46. pymobiledevice3/cli/usbmux.py +73 -33
  47. pymobiledevice3/cli/version.py +5 -7
  48. pymobiledevice3/cli/webinspector.py +376 -214
  49. pymobiledevice3/common.py +3 -1
  50. pymobiledevice3/exceptions.py +182 -58
  51. pymobiledevice3/irecv.py +52 -53
  52. pymobiledevice3/irecv_devices.py +1489 -464
  53. pymobiledevice3/lockdown.py +473 -275
  54. pymobiledevice3/lockdown_service_provider.py +15 -8
  55. pymobiledevice3/osu/os_utils.py +27 -9
  56. pymobiledevice3/osu/posix_util.py +34 -15
  57. pymobiledevice3/osu/win_util.py +14 -8
  58. pymobiledevice3/pair_records.py +102 -21
  59. pymobiledevice3/remote/common.py +8 -4
  60. pymobiledevice3/remote/core_device/app_service.py +94 -67
  61. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  62. pymobiledevice3/remote/core_device/device_info.py +5 -5
  63. pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
  64. pymobiledevice3/remote/core_device/file_service.py +53 -23
  65. pymobiledevice3/remote/remote_service_discovery.py +79 -45
  66. pymobiledevice3/remote/remotexpc.py +73 -44
  67. pymobiledevice3/remote/tunnel_service.py +442 -317
  68. pymobiledevice3/remote/utils.py +14 -13
  69. pymobiledevice3/remote/xpc_message.py +145 -125
  70. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  71. pymobiledevice3/resources/firmware_notifications.py +20 -16
  72. pymobiledevice3/resources/notifications.txt +144 -0
  73. pymobiledevice3/restore/asr.py +27 -27
  74. pymobiledevice3/restore/base_restore.py +110 -21
  75. pymobiledevice3/restore/consts.py +87 -66
  76. pymobiledevice3/restore/device.py +59 -12
  77. pymobiledevice3/restore/fdr.py +46 -48
  78. pymobiledevice3/restore/ftab.py +19 -19
  79. pymobiledevice3/restore/img4.py +163 -0
  80. pymobiledevice3/restore/mbn.py +587 -0
  81. pymobiledevice3/restore/recovery.py +151 -151
  82. pymobiledevice3/restore/restore.py +562 -544
  83. pymobiledevice3/restore/restore_options.py +131 -110
  84. pymobiledevice3/restore/restored_client.py +51 -31
  85. pymobiledevice3/restore/tss.py +385 -267
  86. pymobiledevice3/service_connection.py +252 -59
  87. pymobiledevice3/services/accessibilityaudit.py +202 -120
  88. pymobiledevice3/services/afc.py +962 -365
  89. pymobiledevice3/services/amfi.py +24 -30
  90. pymobiledevice3/services/companion.py +23 -19
  91. pymobiledevice3/services/crash_reports.py +71 -47
  92. pymobiledevice3/services/debugserver_applist.py +3 -3
  93. pymobiledevice3/services/device_arbitration.py +8 -8
  94. pymobiledevice3/services/device_link.py +101 -79
  95. pymobiledevice3/services/diagnostics.py +973 -967
  96. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  97. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  98. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  99. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  100. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  101. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  102. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  103. pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
  104. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  105. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  106. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  107. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  108. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  109. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  110. pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
  111. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  112. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  113. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  114. pymobiledevice3/services/file_relay.py +10 -10
  115. pymobiledevice3/services/heartbeat.py +9 -8
  116. pymobiledevice3/services/house_arrest.py +16 -15
  117. pymobiledevice3/services/idam.py +20 -0
  118. pymobiledevice3/services/installation_proxy.py +173 -81
  119. pymobiledevice3/services/lockdown_service.py +20 -10
  120. pymobiledevice3/services/misagent.py +22 -19
  121. pymobiledevice3/services/mobile_activation.py +147 -64
  122. pymobiledevice3/services/mobile_config.py +331 -294
  123. pymobiledevice3/services/mobile_image_mounter.py +141 -113
  124. pymobiledevice3/services/mobilebackup2.py +203 -145
  125. pymobiledevice3/services/notification_proxy.py +11 -11
  126. pymobiledevice3/services/os_trace.py +134 -74
  127. pymobiledevice3/services/pcapd.py +314 -302
  128. pymobiledevice3/services/power_assertion.py +10 -9
  129. pymobiledevice3/services/preboard.py +4 -4
  130. pymobiledevice3/services/remote_fetch_symbols.py +21 -14
  131. pymobiledevice3/services/remote_server.py +176 -146
  132. pymobiledevice3/services/restore_service.py +16 -16
  133. pymobiledevice3/services/screenshot.py +15 -12
  134. pymobiledevice3/services/simulate_location.py +7 -7
  135. pymobiledevice3/services/springboard.py +15 -15
  136. pymobiledevice3/services/syslog.py +5 -5
  137. pymobiledevice3/services/web_protocol/alert.py +11 -11
  138. pymobiledevice3/services/web_protocol/automation_session.py +251 -239
  139. pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
  140. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  141. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  142. pymobiledevice3/services/web_protocol/driver.py +114 -111
  143. pymobiledevice3/services/web_protocol/element.py +124 -111
  144. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  145. pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
  146. pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
  147. pymobiledevice3/services/web_protocol/switch_to.py +30 -27
  148. pymobiledevice3/services/webinspector.py +189 -155
  149. pymobiledevice3/tcp_forwarder.py +87 -69
  150. pymobiledevice3/tunneld/__init__.py +0 -0
  151. pymobiledevice3/tunneld/api.py +63 -0
  152. pymobiledevice3/tunneld/server.py +603 -0
  153. pymobiledevice3/usbmux.py +198 -147
  154. pymobiledevice3/utils.py +14 -11
  155. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
  156. pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
  157. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
  158. pymobiledevice3/cli/developer.py +0 -1215
  159. pymobiledevice3/cli/diagnostics.py +0 -99
  160. pymobiledevice3/tunneld.py +0 -524
  161. pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
  162. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  163. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
  164. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.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