pymobiledevice3 5.0.1__py3-none-any.whl → 5.0.3__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.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- pymobiledevice3/__main__.py +128 -102
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +36 -59
- pymobiledevice3/ca.py +32 -24
- pymobiledevice3/cli/activation.py +7 -7
- pymobiledevice3/cli/afc.py +19 -19
- pymobiledevice3/cli/amfi.py +4 -4
- pymobiledevice3/cli/apps.py +51 -39
- pymobiledevice3/cli/backup.py +58 -32
- pymobiledevice3/cli/bonjour.py +25 -18
- pymobiledevice3/cli/cli_common.py +112 -81
- pymobiledevice3/cli/companion_proxy.py +4 -4
- pymobiledevice3/cli/completions.py +10 -10
- pymobiledevice3/cli/crash.py +37 -31
- pymobiledevice3/cli/developer.py +602 -520
- pymobiledevice3/cli/diagnostics.py +38 -33
- pymobiledevice3/cli/lockdown.py +79 -74
- pymobiledevice3/cli/mounter.py +85 -68
- pymobiledevice3/cli/notification.py +10 -10
- pymobiledevice3/cli/pcap.py +19 -14
- pymobiledevice3/cli/power_assertion.py +12 -10
- pymobiledevice3/cli/processes.py +10 -10
- pymobiledevice3/cli/profile.py +88 -77
- pymobiledevice3/cli/provision.py +17 -17
- pymobiledevice3/cli/remote.py +186 -110
- pymobiledevice3/cli/restore.py +43 -40
- pymobiledevice3/cli/springboard.py +30 -28
- pymobiledevice3/cli/syslog.py +85 -58
- pymobiledevice3/cli/usbmux.py +21 -20
- pymobiledevice3/cli/version.py +3 -2
- pymobiledevice3/cli/webinspector.py +157 -79
- pymobiledevice3/common.py +1 -1
- pymobiledevice3/exceptions.py +154 -60
- pymobiledevice3/irecv.py +49 -53
- pymobiledevice3/irecv_devices.py +1489 -492
- pymobiledevice3/lockdown.py +396 -242
- pymobiledevice3/lockdown_service_provider.py +5 -7
- pymobiledevice3/osu/os_utils.py +18 -9
- pymobiledevice3/osu/posix_util.py +28 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +19 -19
- pymobiledevice3/remote/common.py +4 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +10 -8
- pymobiledevice3/remote/core_device/file_service.py +47 -33
- pymobiledevice3/remote/remote_service_discovery.py +53 -35
- pymobiledevice3/remote/remotexpc.py +64 -42
- pymobiledevice3/remote/tunnel_service.py +371 -293
- pymobiledevice3/remote/utils.py +12 -11
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +16 -16
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +90 -47
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +11 -11
- pymobiledevice3/restore/fdr.py +46 -46
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +130 -133
- pymobiledevice3/restore/mbn.py +35 -54
- pymobiledevice3/restore/recovery.py +125 -135
- pymobiledevice3/restore/restore.py +524 -523
- pymobiledevice3/restore/restore_options.py +122 -115
- pymobiledevice3/restore/restored_client.py +25 -22
- pymobiledevice3/restore/tss.py +378 -270
- pymobiledevice3/service_connection.py +50 -46
- pymobiledevice3/services/accessibilityaudit.py +137 -127
- pymobiledevice3/services/afc.py +350 -291
- pymobiledevice3/services/amfi.py +21 -18
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +60 -46
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +56 -48
- pymobiledevice3/services/diagnostics.py +971 -968
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +442 -421
- pymobiledevice3/services/dvt/instruments/device_info.py +11 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +25 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +8 -7
- pymobiledevice3/services/house_arrest.py +12 -15
- pymobiledevice3/services/installation_proxy.py +119 -100
- pymobiledevice3/services/lockdown_service.py +12 -5
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +84 -72
- pymobiledevice3/services/mobile_config.py +330 -301
- pymobiledevice3/services/mobile_image_mounter.py +137 -116
- pymobiledevice3/services/mobilebackup2.py +188 -150
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +69 -51
- pymobiledevice3/services/pcapd.py +306 -306
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +16 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +13 -10
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +3 -3
- pymobiledevice3/services/web_protocol/automation_session.py +183 -179
- pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +47 -45
- pymobiledevice3/services/web_protocol/element.py +74 -63
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +3 -3
- pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
- pymobiledevice3/services/web_protocol/switch_to.py +11 -12
- pymobiledevice3/services/webinspector.py +129 -117
- pymobiledevice3/tcp_forwarder.py +35 -22
- pymobiledevice3/tunneld/api.py +20 -15
- pymobiledevice3/tunneld/server.py +212 -133
- pymobiledevice3/usbmux.py +183 -138
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/METADATA +1 -1
- pymobiledevice3-5.0.3.dist-info/RECORD +173 -0
- pymobiledevice3-5.0.1.dist-info/RECORD +0 -173
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/top_level.txt +0 -0
pymobiledevice3/bonjour.py
CHANGED
|
@@ -3,22 +3,23 @@
|
|
|
3
3
|
# - Uses ifaddr (optional) to map IPs -> local interfaces; otherwise iface will be None.
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import contextlib
|
|
6
7
|
import ipaddress
|
|
7
8
|
import socket
|
|
8
9
|
import struct
|
|
9
10
|
import sys
|
|
10
11
|
from collections import defaultdict
|
|
11
12
|
from dataclasses import dataclass, field
|
|
12
|
-
from typing import
|
|
13
|
+
from typing import Optional
|
|
13
14
|
|
|
14
15
|
import ifaddr # pip install ifaddr
|
|
15
16
|
|
|
16
17
|
from pymobiledevice3.osu.os_utils import get_os_utils
|
|
17
18
|
|
|
18
|
-
REMOTEPAIRING_SERVICE_NAME =
|
|
19
|
-
REMOTEPAIRING_MANUAL_PAIRING_SERVICE_NAME =
|
|
20
|
-
MOBDEV2_SERVICE_NAME =
|
|
21
|
-
REMOTED_SERVICE_NAME =
|
|
19
|
+
REMOTEPAIRING_SERVICE_NAME = "_remotepairing._tcp.local."
|
|
20
|
+
REMOTEPAIRING_MANUAL_PAIRING_SERVICE_NAME = "_remotepairing-manual-pairing._tcp.local."
|
|
21
|
+
MOBDEV2_SERVICE_NAME = "_apple-mobdev2._tcp.local."
|
|
22
|
+
REMOTED_SERVICE_NAME = "_remoted._tcp.local."
|
|
22
23
|
OSUTILS = get_os_utils()
|
|
23
24
|
DEFAULT_BONJOUR_TIMEOUT = OSUTILS.bonjour_timeout
|
|
24
25
|
|
|
@@ -38,6 +39,7 @@ CLASS_QU = 0x8000 # unicast-response bit (we use multicast queries)
|
|
|
38
39
|
|
|
39
40
|
# ---------------- Dataclasses ----------------
|
|
40
41
|
|
|
42
|
+
|
|
41
43
|
# --- Dataclass decorator shim (adds slots only on 3.10+)
|
|
42
44
|
def dataclass_compat(*d_args, **d_kwargs):
|
|
43
45
|
if sys.version_info < (3, 10):
|
|
@@ -62,8 +64,8 @@ class ServiceInstance:
|
|
|
62
64
|
instance: str # "<Instance Name>._type._proto.local."
|
|
63
65
|
host: Optional[str] # "host.local" (without trailing dot), or None if unresolved
|
|
64
66
|
port: Optional[int] # SRV port
|
|
65
|
-
addresses:
|
|
66
|
-
properties:
|
|
67
|
+
addresses: list[Address] = field(default_factory=list) # IPs with interface names
|
|
68
|
+
properties: dict[str, str] = field(default_factory=dict) # TXT key/values
|
|
67
69
|
|
|
68
70
|
|
|
69
71
|
# ---------------- DNS helpers ----------------
|
|
@@ -82,7 +84,7 @@ def encode_name(name: str) -> bytes:
|
|
|
82
84
|
return bytes(out)
|
|
83
85
|
|
|
84
86
|
|
|
85
|
-
def decode_name(data: bytes, off: int) ->
|
|
87
|
+
def decode_name(data: bytes, off: int) -> tuple[str, int]:
|
|
86
88
|
labels = []
|
|
87
89
|
jumped = False
|
|
88
90
|
orig_end = off
|
|
@@ -123,9 +125,9 @@ def parse_rr(data: bytes, off: int):
|
|
|
123
125
|
name, off = decode_name(data, off)
|
|
124
126
|
if off + 10 > len(data):
|
|
125
127
|
raise ValueError("truncated RR header")
|
|
126
|
-
rtype, rclass, ttl, rdlen = struct.unpack("!HHIH", data[off: off + 10])
|
|
128
|
+
rtype, rclass, ttl, rdlen = struct.unpack("!HHIH", data[off : off + 10])
|
|
127
129
|
off += 10
|
|
128
|
-
rdata = data[off: off + rdlen]
|
|
130
|
+
rdata = data[off : off + rdlen]
|
|
129
131
|
off += rdlen
|
|
130
132
|
|
|
131
133
|
rr = {"name": name, "type": rtype, "class": rclass & 0x7FFF, "ttl": ttl}
|
|
@@ -135,16 +137,14 @@ def parse_rr(data: bytes, off: int):
|
|
|
135
137
|
elif rtype == QTYPE_SRV and rdlen >= 6:
|
|
136
138
|
priority, weight, port = struct.unpack("!HHH", rdata[:6])
|
|
137
139
|
target, _ = decode_name(data, off - rdlen + 6)
|
|
138
|
-
rr.update(
|
|
139
|
-
{"priority": priority, "weight": weight, "port": port, "target": target}
|
|
140
|
-
)
|
|
140
|
+
rr.update({"priority": priority, "weight": weight, "port": port, "target": target})
|
|
141
141
|
elif rtype == QTYPE_TXT:
|
|
142
142
|
kv = {}
|
|
143
143
|
i = 0
|
|
144
144
|
while i < rdlen:
|
|
145
145
|
b = rdata[i]
|
|
146
146
|
i += 1
|
|
147
|
-
seg = rdata[i: i + b]
|
|
147
|
+
seg = rdata[i : i + b]
|
|
148
148
|
i += b
|
|
149
149
|
if not seg:
|
|
150
150
|
continue
|
|
@@ -185,16 +185,13 @@ class _Adapters:
|
|
|
185
185
|
def __init__(self):
|
|
186
186
|
self.adapters = ifaddr.get_adapters() if ifaddr is not None else []
|
|
187
187
|
|
|
188
|
-
def pick_iface_for_ip(
|
|
189
|
-
self, ip_str: str, family: int, v6_scopeid: Optional[int]
|
|
190
|
-
) -> Optional[str]:
|
|
188
|
+
def pick_iface_for_ip(self, ip_str: str, family: int, v6_scopeid: Optional[int]) -> Optional[str]:
|
|
191
189
|
# Prefer scope id for IPv6 link-local
|
|
192
|
-
if family == socket.AF_INET6 and ip_str.lower().startswith("fe80:"):
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
pass
|
|
190
|
+
if family == socket.AF_INET6 and ip_str.lower().startswith("fe80:") and v6_scopeid:
|
|
191
|
+
try:
|
|
192
|
+
return socket.if_indextoname(v6_scopeid)
|
|
193
|
+
except OSError:
|
|
194
|
+
pass
|
|
198
195
|
|
|
199
196
|
# Otherwise, try to match destination ip to local subnet via ifaddr
|
|
200
197
|
if not self.adapters:
|
|
@@ -213,9 +210,7 @@ class _Adapters:
|
|
|
213
210
|
ipn_ip = ipn.ip[0]
|
|
214
211
|
if fam != family:
|
|
215
212
|
continue
|
|
216
|
-
net = ipaddress.ip_network(
|
|
217
|
-
f"{ipn_ip}/{ipn.network_prefix}", strict=False
|
|
218
|
-
)
|
|
213
|
+
net = ipaddress.ip_network(f"{ipn_ip}/{ipn.network_prefix}", strict=False)
|
|
219
214
|
if ip in net and ipn.network_prefix > best[1]:
|
|
220
215
|
best = (ad.nice_name or ad.name, ipn.network_prefix)
|
|
221
216
|
return best[0]
|
|
@@ -237,21 +232,15 @@ async def _bind_ipv4(queue: asyncio.Queue):
|
|
|
237
232
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
238
233
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
239
234
|
if hasattr(socket, "SO_REUSEPORT"):
|
|
240
|
-
|
|
235
|
+
with contextlib.suppress(OSError):
|
|
241
236
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
|
242
|
-
except OSError:
|
|
243
|
-
pass
|
|
244
237
|
s.bind(("0.0.0.0", MDNS_PORT))
|
|
245
238
|
try:
|
|
246
|
-
mreq = struct.pack(
|
|
247
|
-
"=4s4s", socket.inet_aton(MDNS_MCAST_V4), socket.inet_aton("0.0.0.0")
|
|
248
|
-
)
|
|
239
|
+
mreq = struct.pack("=4s4s", socket.inet_aton(MDNS_MCAST_V4), socket.inet_aton("0.0.0.0"))
|
|
249
240
|
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
|
250
241
|
except OSError:
|
|
251
242
|
pass
|
|
252
|
-
transport, _ = await asyncio.get_running_loop().create_datagram_endpoint(
|
|
253
|
-
lambda: _DatagramProtocol(queue), sock=s
|
|
254
|
-
)
|
|
243
|
+
transport, _ = await asyncio.get_running_loop().create_datagram_endpoint(lambda: _DatagramProtocol(queue), sock=s)
|
|
255
244
|
return transport, s
|
|
256
245
|
|
|
257
246
|
|
|
@@ -259,10 +248,8 @@ async def _bind_ipv6_all_ifaces(queue: asyncio.Queue):
|
|
|
259
248
|
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
|
260
249
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
261
250
|
if hasattr(socket, "SO_REUSEPORT"):
|
|
262
|
-
|
|
251
|
+
with contextlib.suppress(OSError):
|
|
263
252
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
|
264
|
-
except OSError:
|
|
265
|
-
pass
|
|
266
253
|
s.bind(("::", MDNS_PORT))
|
|
267
254
|
grp = socket.inet_pton(socket.AF_INET6, MDNS_MCAST_V6)
|
|
268
255
|
for ifindex, _ in socket.if_nameindex():
|
|
@@ -271,15 +258,13 @@ async def _bind_ipv6_all_ifaces(queue: asyncio.Queue):
|
|
|
271
258
|
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq6)
|
|
272
259
|
except OSError:
|
|
273
260
|
continue
|
|
274
|
-
transport, _ = await asyncio.get_running_loop().create_datagram_endpoint(
|
|
275
|
-
lambda: _DatagramProtocol(queue), sock=s
|
|
276
|
-
)
|
|
261
|
+
transport, _ = await asyncio.get_running_loop().create_datagram_endpoint(lambda: _DatagramProtocol(queue), sock=s)
|
|
277
262
|
return transport, s
|
|
278
263
|
|
|
279
264
|
|
|
280
265
|
async def _open_mdns_sockets():
|
|
281
266
|
queue = asyncio.Queue()
|
|
282
|
-
transports:
|
|
267
|
+
transports: list[tuple[asyncio.BaseTransport, socket.socket]] = []
|
|
283
268
|
t4, s4 = await _bind_ipv4(queue)
|
|
284
269
|
transports.append((t4, s4))
|
|
285
270
|
t6, s6 = await _bind_ipv6_all_ifaces(queue)
|
|
@@ -302,9 +287,7 @@ async def _send_query_all(transports, pkt: bytes):
|
|
|
302
287
|
# ---------------- Public API ----------------
|
|
303
288
|
|
|
304
289
|
|
|
305
|
-
async def browse_service(
|
|
306
|
-
service_type: str, timeout: float = 4.0
|
|
307
|
-
) -> List[ServiceInstance]:
|
|
290
|
+
async def browse_service(service_type: str, timeout: float = 4.0) -> list[ServiceInstance]:
|
|
308
291
|
"""
|
|
309
292
|
Discover a DNS-SD/mDNS service type (e.g. "_remoted._tcp.local.") on the local network.
|
|
310
293
|
|
|
@@ -316,11 +299,11 @@ async def browse_service(
|
|
|
316
299
|
transports, queue = await _open_mdns_sockets()
|
|
317
300
|
adapters = _Adapters()
|
|
318
301
|
|
|
319
|
-
ptr_targets:
|
|
320
|
-
srv_map:
|
|
321
|
-
txt_map:
|
|
302
|
+
ptr_targets: set[str] = set()
|
|
303
|
+
srv_map: dict[str, dict] = {}
|
|
304
|
+
txt_map: dict[str, dict] = {}
|
|
322
305
|
# host -> list[(ip, iface)]
|
|
323
|
-
host_addrs:
|
|
306
|
+
host_addrs: dict[str, list[Address]] = defaultdict(list)
|
|
324
307
|
|
|
325
308
|
def _record_addr(rr_name: str, ip_str: str, pkt_addr):
|
|
326
309
|
# Determine family and possible scopeid from the packet that delivered this RR
|
|
@@ -335,16 +318,12 @@ async def browse_service(
|
|
|
335
318
|
existing.append(Address(ip=ip_str, iface=iface))
|
|
336
319
|
|
|
337
320
|
try:
|
|
338
|
-
await _send_query_all(
|
|
339
|
-
transports, build_query(service_type, QTYPE_PTR, unicast=False)
|
|
340
|
-
)
|
|
321
|
+
await _send_query_all(transports, build_query(service_type, QTYPE_PTR, unicast=False))
|
|
341
322
|
loop = asyncio.get_running_loop()
|
|
342
323
|
end = loop.time() + timeout
|
|
343
324
|
while loop.time() < end:
|
|
344
325
|
try:
|
|
345
|
-
data, pkt_addr = await asyncio.wait_for(
|
|
346
|
-
queue.get(), timeout=end - loop.time()
|
|
347
|
-
)
|
|
326
|
+
data, pkt_addr = await asyncio.wait_for(queue.get(), timeout=end - loop.time())
|
|
348
327
|
except asyncio.TimeoutError:
|
|
349
328
|
break
|
|
350
329
|
for rr in parse_mdns_message(data):
|
|
@@ -358,16 +337,14 @@ async def browse_service(
|
|
|
358
337
|
}
|
|
359
338
|
elif t == QTYPE_TXT:
|
|
360
339
|
txt_map[rr["name"]] = rr.get("txt", {})
|
|
361
|
-
elif t == QTYPE_A and rr.get("address"):
|
|
362
|
-
_record_addr(rr["name"], rr["address"], pkt_addr)
|
|
363
|
-
elif t == QTYPE_AAAA and rr.get("address"):
|
|
340
|
+
elif (t == QTYPE_A and rr.get("address")) or (t == QTYPE_AAAA and rr.get("address")):
|
|
364
341
|
_record_addr(rr["name"], rr["address"], pkt_addr)
|
|
365
342
|
finally:
|
|
366
343
|
for transport, _ in transports:
|
|
367
344
|
transport.close()
|
|
368
345
|
|
|
369
346
|
# Assemble dataclasses
|
|
370
|
-
results:
|
|
347
|
+
results: list[ServiceInstance] = []
|
|
371
348
|
for inst in sorted(ptr_targets):
|
|
372
349
|
srv = srv_map.get(inst, {})
|
|
373
350
|
target = srv.get("target")
|
pymobiledevice3/ca.py
CHANGED
|
@@ -23,10 +23,7 @@ def select_hash_algorithm(device_version: Union[tuple[int, int, int], str, None]
|
|
|
23
23
|
"""
|
|
24
24
|
if device_version is None:
|
|
25
25
|
return hashes.SHA256()
|
|
26
|
-
if isinstance(device_version, str)
|
|
27
|
-
parts = tuple(int(x) for x in device_version.split("."))
|
|
28
|
-
else:
|
|
29
|
-
parts = device_version
|
|
26
|
+
parts = tuple(int(x) for x in device_version.split(".")) if isinstance(device_version, str) else device_version
|
|
30
27
|
return hashes.SHA1() if parts < (4, 0, 0) else hashes.SHA256()
|
|
31
28
|
|
|
32
29
|
|
|
@@ -65,6 +62,7 @@ def serialize_private_key_pkcs8_pem(key: RSAPrivateKey) -> bytes:
|
|
|
65
62
|
# Certificate builders (empty DN, v3, KU)
|
|
66
63
|
# =======================================
|
|
67
64
|
|
|
65
|
+
|
|
68
66
|
def build_root_certificate(root_key: RSAPrivateKey, alg: hashes.HashAlgorithm) -> Certificate:
|
|
69
67
|
"""
|
|
70
68
|
Build a self-signed root (CA) certificate:
|
|
@@ -93,10 +91,10 @@ def build_root_certificate(root_key: RSAPrivateKey, alg: hashes.HashAlgorithm) -
|
|
|
93
91
|
|
|
94
92
|
|
|
95
93
|
def build_host_certificate(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
host_key: RSAPrivateKey,
|
|
95
|
+
root_cert: Certificate,
|
|
96
|
+
root_key: RSAPrivateKey,
|
|
97
|
+
alg: hashes.HashAlgorithm,
|
|
100
98
|
) -> Certificate:
|
|
101
99
|
"""
|
|
102
100
|
Build the host (leaf) certificate signed by the root:
|
|
@@ -127,9 +125,13 @@ def build_host_certificate(
|
|
|
127
125
|
x509.KeyUsage(
|
|
128
126
|
digital_signature=True,
|
|
129
127
|
key_encipherment=True,
|
|
130
|
-
key_cert_sign=False,
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
key_cert_sign=False,
|
|
129
|
+
crl_sign=False,
|
|
130
|
+
content_commitment=False,
|
|
131
|
+
data_encipherment=False,
|
|
132
|
+
key_agreement=False,
|
|
133
|
+
encipher_only=False,
|
|
134
|
+
decipher_only=False,
|
|
133
135
|
),
|
|
134
136
|
critical=True,
|
|
135
137
|
)
|
|
@@ -138,10 +140,10 @@ def build_host_certificate(
|
|
|
138
140
|
|
|
139
141
|
|
|
140
142
|
def build_device_certificate(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
device_public_key: RSAPublicKey,
|
|
144
|
+
root_cert: Certificate,
|
|
145
|
+
root_key: RSAPrivateKey,
|
|
146
|
+
alg: hashes.HashAlgorithm,
|
|
145
147
|
) -> Certificate:
|
|
146
148
|
"""
|
|
147
149
|
Build the device certificate (leaf) signed by the root:
|
|
@@ -173,9 +175,13 @@ def build_device_certificate(
|
|
|
173
175
|
x509.KeyUsage(
|
|
174
176
|
digital_signature=True,
|
|
175
177
|
key_encipherment=True,
|
|
176
|
-
key_cert_sign=False,
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
key_cert_sign=False,
|
|
179
|
+
crl_sign=False,
|
|
180
|
+
content_commitment=False,
|
|
181
|
+
data_encipherment=False,
|
|
182
|
+
key_agreement=False,
|
|
183
|
+
encipher_only=False,
|
|
184
|
+
decipher_only=False,
|
|
179
185
|
),
|
|
180
186
|
critical=True,
|
|
181
187
|
)
|
|
@@ -188,10 +194,11 @@ def build_device_certificate(
|
|
|
188
194
|
# Public API for your pairing flow (renamed)
|
|
189
195
|
# ==========================================
|
|
190
196
|
|
|
197
|
+
|
|
191
198
|
def generate_pairing_cert_chain(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
device_public_key_pem: bytes,
|
|
200
|
+
private_key: Optional[RSAPrivateKey] = None,
|
|
201
|
+
device_version: Union[tuple[int, int, int], str, None] = (4, 0, 0),
|
|
195
202
|
) -> tuple[bytes, bytes, bytes, bytes, bytes]:
|
|
196
203
|
"""
|
|
197
204
|
Generate a root→host certificate chain and a device certificate that mirror the
|
|
@@ -215,7 +222,7 @@ def generate_pairing_cert_chain(
|
|
|
215
222
|
# Device leaf (public key provided by the device)
|
|
216
223
|
dev_pub = load_pem_public_key(device_public_key_pem)
|
|
217
224
|
if not isinstance(dev_pub, RSAPublicKey):
|
|
218
|
-
raise
|
|
225
|
+
raise TypeError("device_public_key_pem must be an RSA PUBLIC KEY in PEM format")
|
|
219
226
|
device_cert = build_device_certificate(dev_pub, root_cert, root_key, alg)
|
|
220
227
|
|
|
221
228
|
return (
|
|
@@ -277,6 +284,7 @@ def create_keybag_file(file: Path, common_name: str) -> None:
|
|
|
277
284
|
private_key.private_bytes(
|
|
278
285
|
encoding=serialization.Encoding.PEM,
|
|
279
286
|
format=PrivateFormat.TraditionalOpenSSL,
|
|
280
|
-
encryption_algorithm=serialization.NoEncryption()
|
|
281
|
-
)
|
|
287
|
+
encryption_algorithm=serialization.NoEncryption(),
|
|
288
|
+
)
|
|
289
|
+
+ cer.public_bytes(encoding=serialization.Encoding.PEM)
|
|
282
290
|
)
|
|
@@ -12,20 +12,20 @@ def cli() -> None:
|
|
|
12
12
|
|
|
13
13
|
@cli.group()
|
|
14
14
|
def activation() -> None:
|
|
15
|
-
"""
|
|
15
|
+
"""Perform iCloud activation/deactivation or query the current state"""
|
|
16
16
|
pass
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
@activation.command(cls=Command)
|
|
20
20
|
def state(service_provider: LockdownClient):
|
|
21
|
-
"""
|
|
21
|
+
"""Get current activation state"""
|
|
22
22
|
print(MobileActivationService(service_provider).state)
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
@activation.command(cls=Command)
|
|
26
|
-
@click.option(
|
|
26
|
+
@click.option("--now", is_flag=True, help="do not wait for next nonce cycle")
|
|
27
27
|
def activate(service_provider: LockdownClient, now):
|
|
28
|
-
"""
|
|
28
|
+
"""Activate device"""
|
|
29
29
|
activation_service = MobileActivationService(service_provider)
|
|
30
30
|
if not now:
|
|
31
31
|
activation_service.wait_for_activation_session()
|
|
@@ -34,11 +34,11 @@ def activate(service_provider: LockdownClient, now):
|
|
|
34
34
|
|
|
35
35
|
@activation.command(cls=Command)
|
|
36
36
|
def deactivate(service_provider: LockdownClient):
|
|
37
|
-
"""
|
|
37
|
+
"""Deactivate device"""
|
|
38
38
|
MobileActivationService(service_provider).deactivate()
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
@activation.command(cls=Command)
|
|
42
42
|
def itunes(service_provider: LockdownClient):
|
|
43
|
-
"""
|
|
44
|
-
service_provider.set_value(True, key=
|
|
43
|
+
"""Tell the device that it has been connected to iTunes (useful for < iOS 4)"""
|
|
44
|
+
service_provider.set_value(True, key="iTunesHasConnected")
|
pymobiledevice3/cli/afc.py
CHANGED
|
@@ -13,44 +13,44 @@ def cli() -> None:
|
|
|
13
13
|
|
|
14
14
|
@cli.group()
|
|
15
15
|
def afc() -> None:
|
|
16
|
-
"""
|
|
16
|
+
"""Manage device multimedia files"""
|
|
17
17
|
pass
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
@afc.command(
|
|
20
|
+
@afc.command("shell", cls=Command)
|
|
21
21
|
def afc_shell(service_provider: LockdownClient):
|
|
22
|
-
"""
|
|
22
|
+
"""open an AFC shell rooted at /var/mobile/Media"""
|
|
23
23
|
AfcShell.create(service_provider)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
@afc.command(
|
|
27
|
-
@click.option(
|
|
28
|
-
@click.argument(
|
|
29
|
-
@click.argument(
|
|
26
|
+
@afc.command("pull", cls=Command)
|
|
27
|
+
@click.option("-i", "--ignore-errors", is_flag=True, help="Ignore AFC pull errors")
|
|
28
|
+
@click.argument("remote_file", type=click.Path(exists=False))
|
|
29
|
+
@click.argument("local_file", type=click.Path(exists=False))
|
|
30
30
|
def afc_pull(service_provider: LockdownServiceProvider, remote_file: str, local_file: str, ignore_errors: bool) -> None:
|
|
31
|
-
"""
|
|
31
|
+
"""pull remote file from /var/mobile/Media"""
|
|
32
32
|
AfcService(lockdown=service_provider).pull(remote_file, local_file, ignore_errors=ignore_errors)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
@afc.command(
|
|
36
|
-
@click.argument(
|
|
37
|
-
@click.argument(
|
|
35
|
+
@afc.command("push", cls=Command)
|
|
36
|
+
@click.argument("local_file", type=click.Path(exists=False))
|
|
37
|
+
@click.argument("remote_file", type=click.Path(exists=False))
|
|
38
38
|
def afc_push(service_provider: LockdownServiceProvider, local_file: str, remote_file: str) -> None:
|
|
39
|
-
"""
|
|
39
|
+
"""push local file into /var/mobile/Media"""
|
|
40
40
|
AfcService(lockdown=service_provider).push(local_file, remote_file)
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
@afc.command(
|
|
44
|
-
@click.argument(
|
|
45
|
-
@click.option(
|
|
43
|
+
@afc.command("ls", cls=Command)
|
|
44
|
+
@click.argument("remote_file", type=click.Path(exists=False))
|
|
45
|
+
@click.option("-r", "--recursive", is_flag=True)
|
|
46
46
|
def afc_ls(service_provider: LockdownClient, remote_file, recursive):
|
|
47
|
-
"""
|
|
47
|
+
"""perform a dirlist rooted at /var/mobile/Media"""
|
|
48
48
|
for path in AfcService(lockdown=service_provider).dirlist(remote_file, -1 if recursive else 1):
|
|
49
49
|
print(path)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
@afc.command(
|
|
53
|
-
@click.argument(
|
|
52
|
+
@afc.command("rm", cls=Command)
|
|
53
|
+
@click.argument("remote_file", type=click.Path(exists=False))
|
|
54
54
|
def afc_rm(service_provider: LockdownClient, remote_file):
|
|
55
|
-
"""
|
|
55
|
+
"""remove a file rooted at /var/mobile/Media"""
|
|
56
56
|
AfcService(lockdown=service_provider).rm(remote_file)
|
pymobiledevice3/cli/amfi.py
CHANGED
|
@@ -16,23 +16,23 @@ def cli() -> None:
|
|
|
16
16
|
|
|
17
17
|
@cli.group()
|
|
18
18
|
def amfi() -> None:
|
|
19
|
-
"""
|
|
19
|
+
"""Enable developer-mode or query its state"""
|
|
20
20
|
pass
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@amfi.command(cls=Command)
|
|
24
24
|
def reveal_developer_mode(service_provider: LockdownClient):
|
|
25
|
-
"""
|
|
25
|
+
"""reveal developer mode option in device's UI"""
|
|
26
26
|
AmfiService(service_provider).reveal_developer_mode_option_in_ui()
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@amfi.command(cls=Command)
|
|
30
30
|
def enable_developer_mode(service_provider: LockdownClient):
|
|
31
|
-
"""
|
|
31
|
+
"""enable developer mode"""
|
|
32
32
|
AmfiService(service_provider).enable_developer_mode()
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
@amfi.command(cls=Command)
|
|
36
36
|
def developer_mode_status(service_provider: LockdownClient):
|
|
37
|
-
"""
|
|
37
|
+
"""query developer mode status"""
|
|
38
38
|
print_json(service_provider.developer_mode_status)
|
pymobiledevice3/cli/apps.py
CHANGED
|
@@ -14,73 +14,85 @@ def cli() -> None:
|
|
|
14
14
|
|
|
15
15
|
@cli.group()
|
|
16
16
|
def apps() -> None:
|
|
17
|
-
"""
|
|
17
|
+
"""Manage installed applications"""
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
@apps.command(
|
|
22
|
-
@click.option(
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
@apps.command("list", cls=Command)
|
|
22
|
+
@click.option(
|
|
23
|
+
"app_type",
|
|
24
|
+
"-t",
|
|
25
|
+
"--type",
|
|
26
|
+
type=click.Choice(["System", "User", "Hidden", "Any"]),
|
|
27
|
+
default="Any",
|
|
28
|
+
help="include only applications of given type",
|
|
29
|
+
)
|
|
30
|
+
@click.option("--calculate-sizes/--no-calculate-size", default=False)
|
|
25
31
|
def apps_list(service_provider: LockdownServiceProvider, app_type: str, calculate_sizes: bool) -> None:
|
|
26
|
-
"""
|
|
27
|
-
print_json(
|
|
28
|
-
|
|
32
|
+
"""list installed apps"""
|
|
33
|
+
print_json(
|
|
34
|
+
InstallationProxyService(lockdown=service_provider).get_apps(
|
|
35
|
+
application_type=app_type, calculate_sizes=calculate_sizes
|
|
36
|
+
)
|
|
37
|
+
)
|
|
29
38
|
|
|
30
39
|
|
|
31
|
-
@apps.command(
|
|
32
|
-
@click.argument(
|
|
33
|
-
@click.option(
|
|
40
|
+
@apps.command("query", cls=Command)
|
|
41
|
+
@click.argument("bundle_identifiers", nargs=-1)
|
|
42
|
+
@click.option("--calculate-sizes/--no-calculate-size", default=False)
|
|
34
43
|
def apps_query(service_provider: LockdownServiceProvider, bundle_identifiers: list[str], calculate_sizes: bool) -> None:
|
|
35
|
-
"""
|
|
36
|
-
print_json(
|
|
37
|
-
|
|
44
|
+
"""query installed apps"""
|
|
45
|
+
print_json(
|
|
46
|
+
InstallationProxyService(lockdown=service_provider).get_apps(
|
|
47
|
+
calculate_sizes=calculate_sizes, bundle_identifiers=bundle_identifiers
|
|
48
|
+
)
|
|
49
|
+
)
|
|
38
50
|
|
|
39
51
|
|
|
40
|
-
@apps.command(
|
|
41
|
-
@click.argument(
|
|
52
|
+
@apps.command("uninstall", cls=Command)
|
|
53
|
+
@click.argument("bundle_id")
|
|
42
54
|
def uninstall(service_provider: LockdownClient, bundle_id):
|
|
43
|
-
"""
|
|
55
|
+
"""uninstall app by given bundle_id"""
|
|
44
56
|
InstallationProxyService(lockdown=service_provider).uninstall(bundle_id)
|
|
45
57
|
|
|
46
58
|
|
|
47
|
-
@apps.command(
|
|
48
|
-
@click.option(
|
|
49
|
-
@click.argument(
|
|
59
|
+
@apps.command("install", cls=Command)
|
|
60
|
+
@click.option("--developer", is_flag=True, help="Install developer package")
|
|
61
|
+
@click.argument("package", type=click.Path(exists=True))
|
|
50
62
|
def install(service_provider: LockdownServiceProvider, package: str, developer: bool) -> None:
|
|
51
|
-
"""
|
|
63
|
+
"""install given .ipa/.app/.ipcc"""
|
|
52
64
|
InstallationProxyService(lockdown=service_provider).install_from_local(package, developer=developer)
|
|
53
65
|
|
|
54
66
|
|
|
55
|
-
@apps.command(
|
|
56
|
-
@click.option(
|
|
57
|
-
@click.argument(
|
|
67
|
+
@apps.command("afc", cls=Command)
|
|
68
|
+
@click.option("--documents", is_flag=True)
|
|
69
|
+
@click.argument("bundle_id")
|
|
58
70
|
def afc(service_provider: LockdownClient, bundle_id: str, documents: bool):
|
|
59
|
-
"""
|
|
71
|
+
"""open an AFC shell for given bundle_id, assuming its profile is installed"""
|
|
60
72
|
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id, documents_only=documents).shell()
|
|
61
73
|
|
|
62
74
|
|
|
63
|
-
@apps.command(
|
|
64
|
-
@click.argument(
|
|
65
|
-
@click.argument(
|
|
66
|
-
@click.argument(
|
|
75
|
+
@apps.command("pull", cls=Command)
|
|
76
|
+
@click.argument("bundle_id")
|
|
77
|
+
@click.argument("remote_file", type=click.Path(exists=False))
|
|
78
|
+
@click.argument("local_file", type=click.Path(exists=False))
|
|
67
79
|
def pull(service_provider: LockdownClient, bundle_id: str, remote_file: str, local_file: str):
|
|
68
|
-
"""
|
|
80
|
+
"""pull remote file from specified bundle_id"""
|
|
69
81
|
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).pull(remote_file, local_file)
|
|
70
82
|
|
|
71
83
|
|
|
72
|
-
@apps.command(
|
|
73
|
-
@click.argument(
|
|
74
|
-
@click.argument(
|
|
75
|
-
@click.argument(
|
|
84
|
+
@apps.command("push", cls=Command)
|
|
85
|
+
@click.argument("bundle_id")
|
|
86
|
+
@click.argument("local_file", type=click.Path(exists=False))
|
|
87
|
+
@click.argument("remote_file", type=click.Path(exists=False))
|
|
76
88
|
def push(service_provider: LockdownClient, bundle_id: str, local_file: str, remote_file: str):
|
|
77
|
-
"""
|
|
89
|
+
"""push local file into specified bundle_id"""
|
|
78
90
|
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).push(local_file, remote_file)
|
|
79
91
|
|
|
80
92
|
|
|
81
|
-
@apps.command(
|
|
82
|
-
@click.argument(
|
|
83
|
-
@click.argument(
|
|
93
|
+
@apps.command("rm", cls=Command)
|
|
94
|
+
@click.argument("bundle_id")
|
|
95
|
+
@click.argument("remote_file", type=click.Path(exists=False))
|
|
84
96
|
def rm(service_provider: LockdownClient, bundle_id: str, remote_file: str):
|
|
85
|
-
"""
|
|
97
|
+
"""remove remote file from specified bundle_id"""
|
|
86
98
|
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).rm(remote_file)
|