pymobiledevice3 5.0.0__py3-none-any.whl → 5.0.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.
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 +26 -49
- 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 +394 -241
- 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 +62 -41
- 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 +136 -126
- 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 +55 -47
- 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 +466 -384
- 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 +40 -50
- 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 +180 -176
- 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 +2 -2
- pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
- pymobiledevice3/services/web_protocol/switch_to.py +11 -12
- pymobiledevice3/services/webinspector.py +127 -116
- 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.0.dist-info → pymobiledevice3-5.0.2.dist-info}/METADATA +1 -1
- pymobiledevice3-5.0.2.dist-info/RECORD +173 -0
- pymobiledevice3-5.0.0.dist-info/RECORD +0 -173
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/top_level.txt +0 -0
pymobiledevice3/cli/remote.py
CHANGED
|
@@ -9,16 +9,25 @@ from typing import Optional, TextIO
|
|
|
9
9
|
import click
|
|
10
10
|
|
|
11
11
|
from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remotepairing_manual_pairing
|
|
12
|
-
from pymobiledevice3.cli.cli_common import
|
|
13
|
-
|
|
12
|
+
from pymobiledevice3.cli.cli_common import (
|
|
13
|
+
BaseCommand,
|
|
14
|
+
RSDCommand,
|
|
15
|
+
print_json,
|
|
16
|
+
prompt_device_list,
|
|
17
|
+
sudo_required,
|
|
18
|
+
user_requested_colored_output,
|
|
19
|
+
)
|
|
14
20
|
from pymobiledevice3.common import get_home_folder
|
|
15
21
|
from pymobiledevice3.exceptions import NoDeviceConnectedError
|
|
16
22
|
from pymobiledevice3.pair_records import PAIRING_RECORD_EXT, get_remote_pairing_record_filename
|
|
17
23
|
from pymobiledevice3.remote.common import ConnectionType, TunnelProtocol
|
|
18
24
|
from pymobiledevice3.remote.module_imports import MAX_IDLE_TIMEOUT, start_tunnel, verify_tunnel_imports
|
|
19
25
|
from pymobiledevice3.remote.remote_service_discovery import RSD_PORT, RemoteServiceDiscoveryService
|
|
20
|
-
from pymobiledevice3.remote.tunnel_service import
|
|
21
|
-
|
|
26
|
+
from pymobiledevice3.remote.tunnel_service import (
|
|
27
|
+
RemotePairingManualPairingService,
|
|
28
|
+
get_core_device_tunnel_services,
|
|
29
|
+
get_remote_pairing_tunnel_services,
|
|
30
|
+
)
|
|
22
31
|
from pymobiledevice3.remote.utils import get_rsds
|
|
23
32
|
from pymobiledevice3.tunneld.api import TUNNELD_DEFAULT_ADDRESS
|
|
24
33
|
from pymobiledevice3.tunneld.server import TunneldRunner
|
|
@@ -29,27 +38,31 @@ logger = logging.getLogger(__name__)
|
|
|
29
38
|
async def browse_rsd(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[dict]:
|
|
30
39
|
devices = []
|
|
31
40
|
for rsd in await get_rsds(timeout):
|
|
32
|
-
devices.append({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
devices.append({
|
|
42
|
+
"address": rsd.service.address[0],
|
|
43
|
+
"port": RSD_PORT,
|
|
44
|
+
"UniqueDeviceID": rsd.peer_info["Properties"]["UniqueDeviceID"],
|
|
45
|
+
"ProductType": rsd.peer_info["Properties"]["ProductType"],
|
|
46
|
+
"OSVersion": rsd.peer_info["Properties"]["OSVersion"],
|
|
47
|
+
})
|
|
37
48
|
return devices
|
|
38
49
|
|
|
39
50
|
|
|
40
51
|
async def browse_remotepairing(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[dict]:
|
|
41
52
|
devices = []
|
|
42
53
|
for remotepairing in await get_remote_pairing_tunnel_services(timeout):
|
|
43
|
-
devices.append({
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
devices.append({
|
|
55
|
+
"address": remotepairing.hostname,
|
|
56
|
+
"port": remotepairing.port,
|
|
57
|
+
"identifier": remotepairing.remote_identifier,
|
|
58
|
+
})
|
|
46
59
|
return devices
|
|
47
60
|
|
|
48
61
|
|
|
49
62
|
async def cli_browse(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> None:
|
|
50
63
|
print_json({
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
"usb": await browse_rsd(timeout),
|
|
65
|
+
"wifi": await browse_remotepairing(timeout),
|
|
53
66
|
})
|
|
54
67
|
|
|
55
68
|
|
|
@@ -58,105 +71,139 @@ def cli() -> None:
|
|
|
58
71
|
pass
|
|
59
72
|
|
|
60
73
|
|
|
61
|
-
@cli.group(
|
|
74
|
+
@cli.group("remote")
|
|
62
75
|
def remote_cli() -> None:
|
|
63
|
-
"""
|
|
76
|
+
"""Create RemoteXPC tunnels"""
|
|
64
77
|
pass
|
|
65
78
|
|
|
66
79
|
|
|
67
|
-
@remote_cli.command(
|
|
68
|
-
@click.option(
|
|
69
|
-
@click.option(
|
|
70
|
-
@click.option(
|
|
71
|
-
@click.option(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
@remote_cli.command("tunneld", cls=BaseCommand)
|
|
81
|
+
@click.option("--host", default=TUNNELD_DEFAULT_ADDRESS[0])
|
|
82
|
+
@click.option("--port", type=click.INT, default=TUNNELD_DEFAULT_ADDRESS[1])
|
|
83
|
+
@click.option("-d", "--daemonize", is_flag=True)
|
|
84
|
+
@click.option(
|
|
85
|
+
"-p",
|
|
86
|
+
"--protocol",
|
|
87
|
+
type=click.Choice([e.value for e in TunnelProtocol]),
|
|
88
|
+
help="Transport protocol. If python version >= 3.13 will default to TCP. Otherwise will default to QUIC",
|
|
89
|
+
default=TunnelProtocol.DEFAULT.value,
|
|
90
|
+
)
|
|
91
|
+
@click.option("--usb/--no-usb", default=True, help="Enable usb monitoring")
|
|
92
|
+
@click.option("--wifi/--no-wifi", default=True, help="Enable wifi monitoring")
|
|
93
|
+
@click.option("--usbmux/--no-usbmux", default=True, help="Enable usbmux monitoring")
|
|
94
|
+
@click.option("--mobdev2/--no-mobdev2", default=True, help="Enable mobdev2 monitoring")
|
|
78
95
|
@sudo_required
|
|
79
96
|
def cli_tunneld(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"""
|
|
97
|
+
host: str, port: int, daemonize: bool, protocol: str, usb: bool, wifi: bool, usbmux: bool, mobdev2: bool
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Start Tunneld service for remote tunneling"""
|
|
83
100
|
if not verify_tunnel_imports():
|
|
84
101
|
return
|
|
85
102
|
protocol = TunnelProtocol(protocol)
|
|
86
|
-
tunneld_runner = partial(
|
|
87
|
-
|
|
103
|
+
tunneld_runner = partial(
|
|
104
|
+
TunneldRunner.create,
|
|
105
|
+
host,
|
|
106
|
+
port,
|
|
107
|
+
protocol=protocol,
|
|
108
|
+
usb_monitor=usb,
|
|
109
|
+
wifi_monitor=wifi,
|
|
110
|
+
usbmux_monitor=usbmux,
|
|
111
|
+
mobdev2_monitor=mobdev2,
|
|
112
|
+
)
|
|
88
113
|
if daemonize:
|
|
89
114
|
try:
|
|
90
115
|
from daemonize import Daemonize
|
|
91
|
-
except ImportError:
|
|
92
|
-
raise NotImplementedError(
|
|
93
|
-
with tempfile.NamedTemporaryFile(
|
|
94
|
-
daemon = Daemonize(app=f
|
|
95
|
-
|
|
96
|
-
logger.info(f'starting Tunneld {host}:{port}')
|
|
116
|
+
except ImportError as e:
|
|
117
|
+
raise NotImplementedError("daemonizing is only supported on unix platforms") from e
|
|
118
|
+
with tempfile.NamedTemporaryFile("wt") as pid_file:
|
|
119
|
+
daemon = Daemonize(app=f"Tunneld {host}:{port}", pid=pid_file.name, action=tunneld_runner)
|
|
120
|
+
logger.info(f"starting Tunneld {host}:{port}")
|
|
97
121
|
daemon.start()
|
|
98
122
|
else:
|
|
99
123
|
tunneld_runner()
|
|
100
124
|
|
|
101
125
|
|
|
102
|
-
@remote_cli.command(
|
|
103
|
-
@click.option(
|
|
126
|
+
@remote_cli.command("browse", cls=BaseCommand)
|
|
127
|
+
@click.option("--timeout", type=click.FLOAT, default=DEFAULT_BONJOUR_TIMEOUT, help="Bonjour timeout (in seconds)")
|
|
104
128
|
def browse(timeout: float) -> None:
|
|
105
|
-
"""
|
|
129
|
+
"""browse RemoteXPC devices using bonjour"""
|
|
106
130
|
asyncio.run(cli_browse(timeout), debug=True)
|
|
107
131
|
|
|
108
132
|
|
|
109
|
-
@remote_cli.command(
|
|
133
|
+
@remote_cli.command("rsd-info", cls=RSDCommand)
|
|
110
134
|
def rsd_info(service_provider: RemoteServiceDiscoveryService):
|
|
111
|
-
"""
|
|
135
|
+
"""show info extracted from RSD peer"""
|
|
112
136
|
print_json(service_provider.peer_info)
|
|
113
137
|
|
|
114
138
|
|
|
115
139
|
async def tunnel_task(
|
|
116
|
-
|
|
117
|
-
|
|
140
|
+
service,
|
|
141
|
+
secrets: Optional[TextIO] = None,
|
|
142
|
+
script_mode: bool = False,
|
|
143
|
+
max_idle_timeout: float = MAX_IDLE_TIMEOUT,
|
|
144
|
+
protocol: TunnelProtocol = TunnelProtocol.DEFAULT,
|
|
145
|
+
) -> None:
|
|
118
146
|
async with start_tunnel(
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
service, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol
|
|
148
|
+
) as tunnel_result:
|
|
149
|
+
logger.info("tunnel created")
|
|
121
150
|
if script_mode:
|
|
122
|
-
print(f
|
|
151
|
+
print(f"{tunnel_result.address} {tunnel_result.port}")
|
|
123
152
|
else:
|
|
124
153
|
if user_requested_colored_output():
|
|
125
154
|
if secrets is not None:
|
|
126
|
-
print(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
print(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
print(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
print(
|
|
139
|
-
|
|
155
|
+
print(
|
|
156
|
+
click.style("Secrets: ", bold=True, fg="magenta")
|
|
157
|
+
+ click.style(secrets.name, bold=True, fg="white")
|
|
158
|
+
)
|
|
159
|
+
print(
|
|
160
|
+
click.style("Identifier: ", bold=True, fg="yellow")
|
|
161
|
+
+ click.style(service.remote_identifier, bold=True, fg="white")
|
|
162
|
+
)
|
|
163
|
+
print(
|
|
164
|
+
click.style("Interface: ", bold=True, fg="yellow")
|
|
165
|
+
+ click.style(tunnel_result.interface, bold=True, fg="white")
|
|
166
|
+
)
|
|
167
|
+
print(
|
|
168
|
+
click.style("Protocol: ", bold=True, fg="yellow")
|
|
169
|
+
+ click.style(tunnel_result.protocol, bold=True, fg="white")
|
|
170
|
+
)
|
|
171
|
+
print(
|
|
172
|
+
click.style("RSD Address: ", bold=True, fg="yellow")
|
|
173
|
+
+ click.style(tunnel_result.address, bold=True, fg="white")
|
|
174
|
+
)
|
|
175
|
+
print(
|
|
176
|
+
click.style("RSD Port: ", bold=True, fg="yellow")
|
|
177
|
+
+ click.style(tunnel_result.port, bold=True, fg="white")
|
|
178
|
+
)
|
|
179
|
+
print(
|
|
180
|
+
click.style("Use the follow connection option:\n", bold=True, fg="yellow")
|
|
181
|
+
+ click.style(f"--rsd {tunnel_result.address} {tunnel_result.port}", bold=True, fg="cyan")
|
|
182
|
+
)
|
|
140
183
|
else:
|
|
141
184
|
if secrets is not None:
|
|
142
|
-
print(f
|
|
143
|
-
print(f
|
|
144
|
-
print(f
|
|
145
|
-
print(f
|
|
146
|
-
print(f
|
|
147
|
-
print(f
|
|
148
|
-
print(f
|
|
149
|
-
f'--rsd {tunnel_result.address} {tunnel_result.port}')
|
|
185
|
+
print(f"Secrets: {secrets.name}")
|
|
186
|
+
print(f"Identifier: {service.remote_identifier}")
|
|
187
|
+
print(f"Interface: {tunnel_result.interface}")
|
|
188
|
+
print(f"Protocol: {tunnel_result.protocol}")
|
|
189
|
+
print(f"RSD Address: {tunnel_result.address}")
|
|
190
|
+
print(f"RSD Port: {tunnel_result.port}")
|
|
191
|
+
print(f"Use the follow connection option:\n--rsd {tunnel_result.address} {tunnel_result.port}")
|
|
150
192
|
sys.stdout.flush()
|
|
151
193
|
await tunnel_result.client.wait_closed()
|
|
152
|
-
logger.info(
|
|
194
|
+
logger.info("tunnel was closed")
|
|
153
195
|
|
|
154
196
|
|
|
155
197
|
async def start_tunnel_task(
|
|
156
|
-
|
|
157
|
-
|
|
198
|
+
connection_type: ConnectionType,
|
|
199
|
+
secrets: TextIO,
|
|
200
|
+
udid: Optional[str] = None,
|
|
201
|
+
script_mode: bool = False,
|
|
202
|
+
max_idle_timeout: float = MAX_IDLE_TIMEOUT,
|
|
203
|
+
protocol: TunnelProtocol = TunnelProtocol.DEFAULT,
|
|
204
|
+
) -> None:
|
|
158
205
|
if start_tunnel is None:
|
|
159
|
-
raise NotImplementedError(
|
|
206
|
+
raise NotImplementedError("failed to start the tunnel on your platform")
|
|
160
207
|
get_tunnel_services = {
|
|
161
208
|
connection_type.USB: get_core_device_tunnel_services,
|
|
162
209
|
connection_type.WIFI: get_remote_pairing_tunnel_services,
|
|
@@ -172,33 +219,57 @@ async def start_tunnel_task(
|
|
|
172
219
|
# several devices were found, show prompt if none explicitly selected
|
|
173
220
|
service = prompt_device_list(tunnel_services)
|
|
174
221
|
|
|
175
|
-
await tunnel_task(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
@
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
@click.option(
|
|
189
|
-
|
|
190
|
-
|
|
222
|
+
await tunnel_task(
|
|
223
|
+
service, secrets=secrets, script_mode=script_mode, max_idle_timeout=max_idle_timeout, protocol=protocol
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@remote_cli.command("start-tunnel", cls=BaseCommand)
|
|
228
|
+
@click.option(
|
|
229
|
+
"-t",
|
|
230
|
+
"--connection-type",
|
|
231
|
+
type=click.Choice([e.value for e in ConnectionType], case_sensitive=False),
|
|
232
|
+
default=ConnectionType.USB.value,
|
|
233
|
+
)
|
|
234
|
+
@click.option("--udid", help="UDID for a specific device to look for")
|
|
235
|
+
@click.option("--secrets", type=click.File("wt"), help="TLS keyfile for decrypting with Wireshark")
|
|
236
|
+
@click.option(
|
|
237
|
+
"--script-mode",
|
|
238
|
+
is_flag=True,
|
|
239
|
+
help="Show only HOST and port number to allow easy parsing from external shell scripts",
|
|
240
|
+
)
|
|
241
|
+
@click.option(
|
|
242
|
+
"--max-idle-timeout", type=click.FLOAT, default=MAX_IDLE_TIMEOUT, help="Maximum QUIC idle time (ping interval)"
|
|
243
|
+
)
|
|
244
|
+
@click.option(
|
|
245
|
+
"-p",
|
|
246
|
+
"--protocol",
|
|
247
|
+
type=click.Choice([e.value for e in TunnelProtocol], case_sensitive=False),
|
|
248
|
+
default=TunnelProtocol.DEFAULT.value,
|
|
249
|
+
)
|
|
191
250
|
@sudo_required
|
|
192
251
|
def cli_start_tunnel(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
252
|
+
connection_type: ConnectionType,
|
|
253
|
+
udid: Optional[str],
|
|
254
|
+
secrets: TextIO,
|
|
255
|
+
script_mode: bool,
|
|
256
|
+
max_idle_timeout: float,
|
|
257
|
+
protocol: str,
|
|
258
|
+
) -> None:
|
|
259
|
+
"""start tunnel"""
|
|
196
260
|
if not verify_tunnel_imports():
|
|
197
261
|
return
|
|
198
262
|
asyncio.run(
|
|
199
263
|
start_tunnel_task(
|
|
200
|
-
ConnectionType(connection_type),
|
|
201
|
-
|
|
264
|
+
ConnectionType(connection_type),
|
|
265
|
+
secrets,
|
|
266
|
+
udid,
|
|
267
|
+
script_mode,
|
|
268
|
+
max_idle_timeout=max_idle_timeout,
|
|
269
|
+
protocol=TunnelProtocol(protocol),
|
|
270
|
+
),
|
|
271
|
+
debug=True,
|
|
272
|
+
)
|
|
202
273
|
|
|
203
274
|
|
|
204
275
|
@dataclasses.dataclass
|
|
@@ -211,43 +282,48 @@ class RemotePairingManualPairingDevice:
|
|
|
211
282
|
|
|
212
283
|
async def start_remote_pair_task(device_name: str) -> None:
|
|
213
284
|
if start_tunnel is None:
|
|
214
|
-
raise NotImplementedError(
|
|
285
|
+
raise NotImplementedError("failed to start the tunnel on your platform")
|
|
215
286
|
|
|
216
287
|
devices: list[RemotePairingManualPairingDevice] = []
|
|
217
288
|
for answer in await browse_remotepairing_manual_pairing():
|
|
218
|
-
current_device_name = answer.properties[b
|
|
289
|
+
current_device_name = answer.properties[b"name"].decode()
|
|
219
290
|
|
|
220
291
|
if device_name is not None and current_device_name != device_name:
|
|
221
292
|
continue
|
|
222
293
|
|
|
223
294
|
for address in answer.addresses:
|
|
224
295
|
devices.append(
|
|
225
|
-
RemotePairingManualPairingDevice(
|
|
226
|
-
|
|
296
|
+
RemotePairingManualPairingDevice(
|
|
297
|
+
ip=address.full_ip,
|
|
298
|
+
port=answer.port,
|
|
299
|
+
device_name=current_device_name,
|
|
300
|
+
identifier=answer.properties[b"identifier"].decode(),
|
|
301
|
+
)
|
|
302
|
+
)
|
|
227
303
|
|
|
228
304
|
if len(devices) > 0:
|
|
229
305
|
device = prompt_device_list(devices)
|
|
230
306
|
else:
|
|
231
|
-
logger.error(
|
|
307
|
+
logger.error("No devices were found during bonjour browse")
|
|
232
308
|
return
|
|
233
309
|
|
|
234
310
|
async with RemotePairingManualPairingService(device.identifier, device.ip, device.port) as service:
|
|
235
311
|
await service.connect(autopair=True)
|
|
236
312
|
|
|
237
313
|
|
|
238
|
-
@remote_cli.command(
|
|
239
|
-
@click.option(
|
|
314
|
+
@remote_cli.command("pair", cls=BaseCommand)
|
|
315
|
+
@click.option("--name", help="Device name for a specific device to look for")
|
|
240
316
|
def cli_pair(name: Optional[str]) -> None:
|
|
241
|
-
"""
|
|
317
|
+
"""start remote pairing for devices which allow"""
|
|
242
318
|
asyncio.run(start_remote_pair_task(name), debug=True)
|
|
243
319
|
|
|
244
320
|
|
|
245
|
-
@remote_cli.command(
|
|
246
|
-
@click.argument(
|
|
321
|
+
@remote_cli.command("delete-pair", cls=BaseCommand)
|
|
322
|
+
@click.argument("udid")
|
|
247
323
|
@sudo_required
|
|
248
324
|
def cli_delete_pair(udid: str):
|
|
249
|
-
"""
|
|
250
|
-
pair_record_path = get_home_folder() / f
|
|
325
|
+
"""delete a pairing record"""
|
|
326
|
+
pair_record_path = get_home_folder() / f"{get_remote_pairing_record_filename(udid)}.{PAIRING_RECORD_EXT}"
|
|
251
327
|
pair_record_path.unlink()
|
|
252
328
|
|
|
253
329
|
|
|
@@ -256,8 +332,8 @@ async def cli_service_task(service_provider: RemoteServiceDiscoveryService, serv
|
|
|
256
332
|
service.shell()
|
|
257
333
|
|
|
258
334
|
|
|
259
|
-
@remote_cli.command(
|
|
260
|
-
@click.argument(
|
|
335
|
+
@remote_cli.command("service", cls=RSDCommand)
|
|
336
|
+
@click.argument("service_name")
|
|
261
337
|
def cli_service(service_provider: RemoteServiceDiscoveryService, service_name: str) -> None:
|
|
262
|
-
"""
|
|
338
|
+
"""start an ipython shell for interacting with given service"""
|
|
263
339
|
asyncio.run(cli_service_task(service_provider, service_name), debug=True)
|
pymobiledevice3/cli/restore.py
CHANGED
|
@@ -32,15 +32,15 @@ print(irecv.getenv('build-version'))
|
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
34
|
logger = logging.getLogger(__name__)
|
|
35
|
-
IPSWME_API =
|
|
35
|
+
IPSWME_API = "https://api.ipsw.me/v4/device/"
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class Command(click.Command):
|
|
39
39
|
def __init__(self, *args, **kwargs):
|
|
40
40
|
super().__init__(*args, **kwargs)
|
|
41
41
|
self.params[:0] = [
|
|
42
|
-
click.Option((
|
|
43
|
-
click.Option((
|
|
42
|
+
click.Option(("device", "--ecid"), type=click.INT, callback=self.device),
|
|
43
|
+
click.Option(("verbosity", "-v", "--verbose"), count=True, callback=set_verbosity, expose_value=False),
|
|
44
44
|
]
|
|
45
45
|
|
|
46
46
|
@staticmethod
|
|
@@ -50,32 +50,32 @@ class Command(click.Command):
|
|
|
50
50
|
return
|
|
51
51
|
|
|
52
52
|
ecid = value
|
|
53
|
-
logger.debug(
|
|
54
|
-
devices = [dev for dev in usbmux.list_devices() if dev.connection_type ==
|
|
53
|
+
logger.debug("searching among connected devices via lockdownd")
|
|
54
|
+
devices = [dev for dev in usbmux.list_devices() if dev.connection_type == "USB"]
|
|
55
55
|
if len(devices) > 1:
|
|
56
|
-
raise click.ClickException(
|
|
56
|
+
raise click.ClickException("Multiple device detected")
|
|
57
57
|
try:
|
|
58
58
|
for device in devices:
|
|
59
59
|
try:
|
|
60
|
-
lockdown = create_using_usbmux(serial=device.serial, connection_type=
|
|
60
|
+
lockdown = create_using_usbmux(serial=device.serial, connection_type="USB")
|
|
61
61
|
except (ConnectionFailedError, IncorrectModeError):
|
|
62
62
|
continue
|
|
63
63
|
if (ecid is None) or (lockdown.ecid == value):
|
|
64
|
-
logger.debug(
|
|
64
|
+
logger.debug("found device")
|
|
65
65
|
return lockdown
|
|
66
66
|
else:
|
|
67
67
|
continue
|
|
68
68
|
except ConnectionFailedToUsbmuxdError:
|
|
69
69
|
pass
|
|
70
70
|
|
|
71
|
-
logger.debug(
|
|
71
|
+
logger.debug("waiting for device to be available in Recovery mode")
|
|
72
72
|
return IRecv(ecid=ecid)
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
@contextlib.contextmanager
|
|
76
76
|
def tempzip_download_ctx(url: str) -> Generator[ZipFile, None, None]:
|
|
77
77
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
78
|
-
tmpzip = Path(tmpdir) / url.split(
|
|
78
|
+
tmpzip = Path(tmpdir) / url.split("/")[-1]
|
|
79
79
|
file_download(url, tmpzip)
|
|
80
80
|
yield ZipFile(tmpzip)
|
|
81
81
|
|
|
@@ -88,18 +88,19 @@ def zipfile_ctx(path: str) -> Generator[ZipFile, None, None]:
|
|
|
88
88
|
class IPSWCommand(Command):
|
|
89
89
|
def __init__(self, *args, **kwargs):
|
|
90
90
|
super().__init__(*args, **kwargs)
|
|
91
|
-
self.params.extend([
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
self.params.extend([
|
|
92
|
+
click.Option(("ipsw_ctx", "-i", "--ipsw"), required=False, callback=self.ipsw_ctx, help="local IPSW file"),
|
|
93
|
+
click.Option(("tss", "--tss"), type=click.File("rb"), callback=self.tss),
|
|
94
|
+
])
|
|
94
95
|
|
|
95
96
|
@staticmethod
|
|
96
97
|
def ipsw_ctx(ctx, param, value) -> Generator[ZipFile, None, None]:
|
|
97
|
-
if value and not value.startswith((
|
|
98
|
+
if value and not value.startswith(("http://", "https://")):
|
|
98
99
|
return zipfile_ctx(value)
|
|
99
100
|
|
|
100
101
|
url = value
|
|
101
102
|
if url is None:
|
|
102
|
-
url = query_ipswme(ctx.params[
|
|
103
|
+
url = query_ipswme(ctx.params["device"].product_type)
|
|
103
104
|
return tempzip_download_ctx(url)
|
|
104
105
|
|
|
105
106
|
@staticmethod
|
|
@@ -110,11 +111,11 @@ class IPSWCommand(Command):
|
|
|
110
111
|
|
|
111
112
|
|
|
112
113
|
def query_ipswme(identifier: str) -> str:
|
|
113
|
-
resp = requests.get(IPSWME_API + identifier, headers={
|
|
114
|
-
firmwares = resp.json()[
|
|
115
|
-
display_list = [f
|
|
116
|
-
idx = prompt_selection(display_list,
|
|
117
|
-
return firmwares[idx][
|
|
114
|
+
resp = requests.get(IPSWME_API + identifier, headers={"Accept": "application/json"})
|
|
115
|
+
firmwares = resp.json()["firmwares"]
|
|
116
|
+
display_list = [f"{entry['version']}: {entry['buildid']}" for entry in firmwares if entry["signed"]]
|
|
117
|
+
idx = prompt_selection(display_list, "Choose version", idx=True)
|
|
118
|
+
return firmwares[idx]["url"]
|
|
118
119
|
|
|
119
120
|
|
|
120
121
|
async def restore_update_task(device: Device, ipsw: ZipFile, tss: Optional[IO], erase: bool, ignore_fdr: bool) -> None:
|
|
@@ -145,38 +146,39 @@ def cli() -> None:
|
|
|
145
146
|
|
|
146
147
|
@cli.group()
|
|
147
148
|
def restore() -> None:
|
|
148
|
-
"""
|
|
149
|
+
"""Restore an IPSW or access device in recovery mode"""
|
|
149
150
|
pass
|
|
150
151
|
|
|
151
152
|
|
|
152
|
-
@restore.command(
|
|
153
|
+
@restore.command("shell", cls=Command)
|
|
153
154
|
def restore_shell(device):
|
|
154
|
-
"""
|
|
155
|
+
"""create an IPython shell for interacting with iBoot"""
|
|
155
156
|
IPython.embed(
|
|
156
|
-
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style=
|
|
157
|
+
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")),
|
|
157
158
|
user_ns={
|
|
158
|
-
|
|
159
|
-
}
|
|
159
|
+
"irecv": device,
|
|
160
|
+
},
|
|
161
|
+
)
|
|
160
162
|
|
|
161
163
|
|
|
162
|
-
@restore.command(
|
|
164
|
+
@restore.command("enter", cls=Command)
|
|
163
165
|
def restore_enter(device):
|
|
164
|
-
"""
|
|
166
|
+
"""enter Recovery mode"""
|
|
165
167
|
if isinstance(device, LockdownClient):
|
|
166
168
|
device.enter_recovery()
|
|
167
169
|
|
|
168
170
|
|
|
169
|
-
@restore.command(
|
|
171
|
+
@restore.command("exit")
|
|
170
172
|
def restore_exit():
|
|
171
|
-
"""
|
|
173
|
+
"""exit Recovery mode"""
|
|
172
174
|
irecv = IRecv()
|
|
173
175
|
irecv.set_autoboot(True)
|
|
174
176
|
irecv.reboot()
|
|
175
177
|
|
|
176
178
|
|
|
177
|
-
@restore.command(
|
|
179
|
+
@restore.command("restart", cls=Command)
|
|
178
180
|
def restore_restart(device):
|
|
179
|
-
"""
|
|
181
|
+
"""restarts device"""
|
|
180
182
|
if isinstance(device, LockdownClient):
|
|
181
183
|
with DiagnosticsService(device) as diagnostics:
|
|
182
184
|
diagnostics.restart()
|
|
@@ -200,10 +202,10 @@ async def restore_tss_task(device: Device, ipsw_ctx: Generator, tss: IO, out: Op
|
|
|
200
202
|
print_json(tss)
|
|
201
203
|
|
|
202
204
|
|
|
203
|
-
@restore.command(
|
|
204
|
-
@click.argument(
|
|
205
|
+
@restore.command("tss", cls=IPSWCommand)
|
|
206
|
+
@click.argument("out", type=click.File("wb"), required=False)
|
|
205
207
|
def restore_tss(device: Device, ipsw_ctx: Generator, tss: IO, out: Optional[IO]) -> None:
|
|
206
|
-
"""
|
|
208
|
+
"""query SHSH blobs"""
|
|
207
209
|
asyncio.run(restore_tss_task(device, ipsw_ctx, tss, out), debug=True)
|
|
208
210
|
|
|
209
211
|
|
|
@@ -220,7 +222,7 @@ async def restore_ramdisk_task(device: Device, ipsw_ctx: Generator) -> None:
|
|
|
220
222
|
await Recovery(ipsw, device).boot_ramdisk()
|
|
221
223
|
|
|
222
224
|
|
|
223
|
-
@restore.command(
|
|
225
|
+
@restore.command("ramdisk", cls=IPSWCommand)
|
|
224
226
|
def restore_ramdisk(device: Device, ipsw_ctx: Generator, tss: IO) -> None:
|
|
225
227
|
"""
|
|
226
228
|
don't perform an actual restore. just enter the update ramdisk
|
|
@@ -230,10 +232,11 @@ def restore_ramdisk(device: Device, ipsw_ctx: Generator, tss: IO) -> None:
|
|
|
230
232
|
asyncio.run(restore_ramdisk_task(device, ipsw_ctx), debug=True)
|
|
231
233
|
|
|
232
234
|
|
|
233
|
-
@restore.command(
|
|
234
|
-
@click.option(
|
|
235
|
-
@click.option(
|
|
236
|
-
|
|
235
|
+
@restore.command("update", cls=IPSWCommand)
|
|
236
|
+
@click.option("--erase", is_flag=True, help="use the Erase BuildIdentity (full factory-reset)")
|
|
237
|
+
@click.option(
|
|
238
|
+
"--ignore-fdr", is_flag=True, help="only establish an FDR service connection, but don't proxy any traffic"
|
|
239
|
+
)
|
|
237
240
|
def restore_update(device: Device, ipsw_ctx: Generator, tss: IO, erase: bool, ignore_fdr: bool) -> None:
|
|
238
241
|
"""
|
|
239
242
|
perform an update
|