pymobiledevice3 5.0.1__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 +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 +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.1.dist-info → pymobiledevice3-5.0.2.dist-info}/METADATA +1 -1
- pymobiledevice3-5.0.2.dist-info/RECORD +173 -0
- pymobiledevice3-5.0.1.dist-info/RECORD +0 -173
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/top_level.txt +0 -0
|
@@ -17,16 +17,18 @@ class DiagnosticsServiceService(CoreDeviceService):
|
|
|
17
17
|
Obtain device diagnostics
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
SERVICE_NAME =
|
|
20
|
+
SERVICE_NAME = "com.apple.coredevice.diagnosticsservice"
|
|
21
21
|
|
|
22
22
|
def __init__(self, rsd: RemoteServiceDiscoveryService):
|
|
23
23
|
super().__init__(rsd, self.SERVICE_NAME)
|
|
24
24
|
|
|
25
25
|
async def capture_sysdiagnose(self, is_dry_run: bool) -> SysDiagnoseResponse:
|
|
26
|
-
response = await self.invoke(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return SysDiagnoseResponse(
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
response = await self.invoke(
|
|
27
|
+
"com.apple.coredevice.feature.capturesysdiagnose",
|
|
28
|
+
{"options": {"collectFullLogs": True}, "isDryRun": is_dry_run},
|
|
29
|
+
)
|
|
30
|
+
return SysDiagnoseResponse(
|
|
31
|
+
file_size=response["fileTransfer"]["expectedLength"],
|
|
32
|
+
preferred_filename=response["preferredFilename"],
|
|
33
|
+
generator=self.service.iter_file_chunks(response["fileTransfer"]["expectedLength"]),
|
|
34
|
+
)
|
|
@@ -20,10 +20,10 @@ class Domain(IntEnum):
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
APPLE_DOMAIN_DICT = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
"appDataContainer": Domain.APP_DATA_CONTAINER,
|
|
24
|
+
"appGroupDataContainer": Domain.APP_GROUP_DATA_CONTAINER,
|
|
25
|
+
"temporary": Domain.TEMPORARY,
|
|
26
|
+
"systemCrashLogs": Domain.SYSTEM_CRASH_LOGS,
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
|
|
@@ -32,9 +32,9 @@ class FileServiceService(CoreDeviceService):
|
|
|
32
32
|
Filesystem control
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
-
CTRL_SERVICE_NAME =
|
|
35
|
+
CTRL_SERVICE_NAME = "com.apple.coredevice.fileservice.control"
|
|
36
36
|
|
|
37
|
-
def __init__(self, rsd: RemoteServiceDiscoveryService, domain: Domain, identifier: str =
|
|
37
|
+
def __init__(self, rsd: RemoteServiceDiscoveryService, domain: Domain, identifier: str = "") -> None:
|
|
38
38
|
super().__init__(rsd, self.CTRL_SERVICE_NAME)
|
|
39
39
|
self.domain: Domain = domain
|
|
40
40
|
self.session: Optional[str] = None
|
|
@@ -43,45 +43,59 @@ class FileServiceService(CoreDeviceService):
|
|
|
43
43
|
async def connect(self) -> None:
|
|
44
44
|
await super().connect()
|
|
45
45
|
response = await self.send_receive_request({
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
"Cmd": "CreateSession",
|
|
47
|
+
"Domain": XpcUInt64Type(self.domain),
|
|
48
|
+
"Identifier": self.identifier,
|
|
49
|
+
"Session": "",
|
|
50
|
+
"User": "mobile",
|
|
51
|
+
})
|
|
52
|
+
self.session = response["NewSessionID"]
|
|
49
53
|
|
|
50
|
-
async def retrieve_directory_list(self, path: str =
|
|
51
|
-
return (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
async def retrieve_directory_list(self, path: str = ".") -> AsyncGenerator[list[str], None]:
|
|
55
|
+
return (
|
|
56
|
+
await self.send_receive_request({
|
|
57
|
+
"Cmd": "RetrieveDirectoryList",
|
|
58
|
+
"MessageUUID": str(uuid.uuid4()),
|
|
59
|
+
"Path": path,
|
|
60
|
+
"SessionID": self.session,
|
|
61
|
+
})
|
|
62
|
+
)["FileList"]
|
|
54
63
|
|
|
55
|
-
async def retrieve_file(self, path: str =
|
|
56
|
-
response = await self.send_receive_request({
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
data_service = self.rsd.get_service_port('com.apple.coredevice.fileservice.data')
|
|
64
|
+
async def retrieve_file(self, path: str = ".") -> bytes:
|
|
65
|
+
response = await self.send_receive_request({"Cmd": "RetrieveFile", "Path": path, "SessionID": self.session})
|
|
66
|
+
data_service = self.rsd.get_service_port("com.apple.coredevice.fileservice.data")
|
|
60
67
|
reader, writer = await asyncio.open_connection(self.service.address[0], data_service)
|
|
61
|
-
writer.write(b
|
|
68
|
+
writer.write(b"rwb!FILE" + struct.pack(">QQQQ", response["Response"], 0, response["NewFileID"], 0))
|
|
62
69
|
await writer.drain()
|
|
63
70
|
await reader.readexactly(0x24)
|
|
64
|
-
return await reader.readexactly(struct.unpack(
|
|
71
|
+
return await reader.readexactly(struct.unpack(">I", await reader.readexactly(4))[0])
|
|
65
72
|
|
|
66
|
-
async def propose_empty_file(
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
async def propose_empty_file(
|
|
74
|
+
self,
|
|
75
|
+
path: str = ".",
|
|
76
|
+
file_permissions: int = 0o644,
|
|
77
|
+
uid: int = 501,
|
|
78
|
+
gid: int = 501,
|
|
79
|
+
creation_time: int = time.time(),
|
|
80
|
+
last_modification_time: int = time.time(),
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Request to write an empty file at given path."""
|
|
69
83
|
await self.send_receive_request({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
"Cmd": "ProposeEmptyFile",
|
|
85
|
+
"FileCreationTime": XpcInt64Type(creation_time),
|
|
86
|
+
"FileLastModificationTime": XpcInt64Type(last_modification_time),
|
|
87
|
+
"FilePermissions": XpcInt64Type(file_permissions),
|
|
88
|
+
"FileOwnerUserID": XpcInt64Type(uid),
|
|
89
|
+
"FileOwnerGroupID": XpcInt64Type(gid),
|
|
90
|
+
"Path": path,
|
|
91
|
+
"SessionID": self.session,
|
|
78
92
|
})
|
|
79
93
|
|
|
80
94
|
async def send_receive_request(self, request: dict) -> dict:
|
|
81
95
|
response = await self.service.send_receive_request(request)
|
|
82
|
-
encoded_error = response.get(
|
|
96
|
+
encoded_error = response.get("EncodedError")
|
|
83
97
|
if encoded_error is not None:
|
|
84
|
-
localized_description = response.get(
|
|
98
|
+
localized_description = response.get("LocalizedDescription")
|
|
85
99
|
if localized_description is not None:
|
|
86
100
|
raise CoreDeviceError(localized_description)
|
|
87
101
|
raise CoreDeviceError(encoded_error)
|
|
@@ -6,8 +6,12 @@ from typing import Any, Optional, Union
|
|
|
6
6
|
|
|
7
7
|
from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remoted
|
|
8
8
|
from pymobiledevice3.common import get_home_folder
|
|
9
|
-
from pymobiledevice3.exceptions import
|
|
10
|
-
|
|
9
|
+
from pymobiledevice3.exceptions import (
|
|
10
|
+
InvalidServiceError,
|
|
11
|
+
NoDeviceConnectedError,
|
|
12
|
+
PyMobileDevice3Exception,
|
|
13
|
+
StartServiceError,
|
|
14
|
+
)
|
|
11
15
|
from pymobiledevice3.lockdown import LockdownClient, create_using_remote
|
|
12
16
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
13
17
|
from pymobiledevice3.pair_records import get_local_pairing_record, get_remote_pairing_record_filename
|
|
@@ -38,11 +42,11 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
|
|
|
38
42
|
|
|
39
43
|
@property
|
|
40
44
|
def product_version(self) -> str:
|
|
41
|
-
return self.peer_info[
|
|
45
|
+
return self.peer_info["Properties"]["OSVersion"]
|
|
42
46
|
|
|
43
47
|
@property
|
|
44
48
|
def ecid(self) -> int:
|
|
45
|
-
return self.peer_info[
|
|
49
|
+
return self.peer_info["Properties"]["UniqueChipID"]
|
|
46
50
|
|
|
47
51
|
@property
|
|
48
52
|
def developer_mode_status(self) -> bool:
|
|
@@ -59,15 +63,18 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
|
|
|
59
63
|
await self.service.connect()
|
|
60
64
|
try:
|
|
61
65
|
self.peer_info = await self.service.receive_response()
|
|
62
|
-
self.udid = self.peer_info[
|
|
63
|
-
self.product_type = self.peer_info[
|
|
66
|
+
self.udid = self.peer_info["Properties"]["UniqueDeviceID"]
|
|
67
|
+
self.product_type = self.peer_info["Properties"]["ProductType"]
|
|
64
68
|
try:
|
|
65
|
-
self.lockdown = create_using_remote(
|
|
69
|
+
self.lockdown = create_using_remote(
|
|
70
|
+
self.start_lockdown_service("com.apple.mobile.lockdown.remote.trusted")
|
|
71
|
+
)
|
|
66
72
|
except InvalidServiceError:
|
|
67
73
|
self.lockdown = create_using_remote(
|
|
68
|
-
self.start_lockdown_service(
|
|
74
|
+
self.start_lockdown_service("com.apple.mobile.lockdown.remote.untrusted")
|
|
75
|
+
)
|
|
69
76
|
self.all_values = self.lockdown.all_values
|
|
70
|
-
except Exception:
|
|
77
|
+
except Exception:
|
|
71
78
|
await self.close()
|
|
72
79
|
raise
|
|
73
80
|
|
|
@@ -80,23 +87,26 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
|
|
|
80
87
|
def start_lockdown_service(self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
|
|
81
88
|
service = self.start_lockdown_service_without_checkin(name)
|
|
82
89
|
try:
|
|
83
|
-
checkin = {
|
|
90
|
+
checkin = {"Label": "pymobiledevice3", "ProtocolVersion": "2", "Request": "RSDCheckin"}
|
|
84
91
|
if include_escrow_bag:
|
|
85
|
-
pairing_record = get_local_pairing_record(
|
|
86
|
-
|
|
92
|
+
pairing_record = get_local_pairing_record(
|
|
93
|
+
get_remote_pairing_record_filename(self.udid), get_home_folder()
|
|
94
|
+
)
|
|
95
|
+
checkin["EscrowBag"] = base64.b64decode(pairing_record["remote_unlock_host_key"])
|
|
87
96
|
response = service.send_recv_plist(checkin)
|
|
88
|
-
if response[
|
|
97
|
+
if response["Request"] != "RSDCheckin":
|
|
89
98
|
raise PyMobileDevice3Exception(f'Invalid response for RSDCheckIn: {response}. Expected "RSDCheckIn"')
|
|
90
99
|
response = service.recv_plist()
|
|
91
|
-
if response[
|
|
92
|
-
raise PyMobileDevice3Exception(
|
|
93
|
-
|
|
100
|
+
if response["Request"] != "StartService":
|
|
101
|
+
raise PyMobileDevice3Exception(
|
|
102
|
+
f'Invalid response for RSDCheckIn: {response}. Expected "ServiceService"'
|
|
103
|
+
)
|
|
104
|
+
except Exception:
|
|
94
105
|
service.close()
|
|
95
106
|
raise
|
|
96
107
|
return service
|
|
97
108
|
|
|
98
|
-
async def aio_start_lockdown_service(
|
|
99
|
-
self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
|
|
109
|
+
async def aio_start_lockdown_service(self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
|
|
100
110
|
service = self.start_lockdown_service(name, include_escrow_bag=include_escrow_bag)
|
|
101
111
|
await service.aio_start()
|
|
102
112
|
return service
|
|
@@ -105,9 +115,9 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
|
|
|
105
115
|
try:
|
|
106
116
|
return self.start_lockdown_service_without_checkin(name)
|
|
107
117
|
except StartServiceError:
|
|
108
|
-
logging.getLogger(self.__module__).
|
|
109
|
-
|
|
110
|
-
|
|
118
|
+
logging.getLogger(self.__module__).exception(
|
|
119
|
+
"Failed to connect to required service. Make sure DeveloperDiskImage.dmg has been mounted. "
|
|
120
|
+
"You can do so using: pymobiledevice3 mounter mount"
|
|
111
121
|
)
|
|
112
122
|
raise
|
|
113
123
|
|
|
@@ -116,24 +126,24 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
|
|
|
116
126
|
return service
|
|
117
127
|
|
|
118
128
|
def start_service(self, name: str) -> Union[RemoteXPCConnection, ServiceConnection]:
|
|
119
|
-
service = self.peer_info[
|
|
120
|
-
service_properties = service.get(
|
|
121
|
-
use_remote_xpc = service_properties.get(
|
|
129
|
+
service = self.peer_info["Services"][name]
|
|
130
|
+
service_properties = service.get("Properties", {})
|
|
131
|
+
use_remote_xpc = service_properties.get("UsesRemoteXPC", False)
|
|
122
132
|
return self.start_remote_service(name) if use_remote_xpc else self.start_lockdown_service(name)
|
|
123
133
|
|
|
124
134
|
def get_service_port(self, name: str) -> int:
|
|
125
135
|
"""takes a service name and returns the port that service is running on if the service exists"""
|
|
126
|
-
service = self.peer_info[
|
|
136
|
+
service = self.peer_info["Services"].get(name)
|
|
127
137
|
if service is None:
|
|
128
|
-
raise InvalidServiceError(f
|
|
129
|
-
return int(service[
|
|
138
|
+
raise InvalidServiceError(f"No such service: {name}")
|
|
139
|
+
return int(service["Port"])
|
|
130
140
|
|
|
131
141
|
async def close(self) -> None:
|
|
132
142
|
if self.lockdown is not None:
|
|
133
143
|
self.lockdown.close()
|
|
134
144
|
await self.service.close()
|
|
135
145
|
|
|
136
|
-
async def __aenter__(self) ->
|
|
146
|
+
async def __aenter__(self) -> "RemoteServiceDiscoveryService":
|
|
137
147
|
await self.connect()
|
|
138
148
|
return self
|
|
139
149
|
|
|
@@ -141,20 +151,28 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
|
|
|
141
151
|
await self.close()
|
|
142
152
|
|
|
143
153
|
def __repr__(self) -> str:
|
|
144
|
-
name_str =
|
|
154
|
+
name_str = ""
|
|
145
155
|
if self.name:
|
|
146
|
-
name_str = f
|
|
147
|
-
return (
|
|
148
|
-
|
|
156
|
+
name_str = f" NAME:{self.name}"
|
|
157
|
+
return (
|
|
158
|
+
f"<{self.__class__.__name__} PRODUCT:{self.product_type} VERSION:{self.product_version} "
|
|
159
|
+
f"UDID:{self.udid}{name_str}>"
|
|
160
|
+
)
|
|
149
161
|
|
|
150
162
|
|
|
151
163
|
async def get_remoted_devices(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[RSDDevice]:
|
|
152
164
|
result = []
|
|
153
165
|
for hostname in await browse_remoted(timeout):
|
|
154
166
|
with RemoteServiceDiscoveryService((hostname, RSD_PORT)) as rsd:
|
|
155
|
-
properties = rsd.peer_info[
|
|
156
|
-
result.append(
|
|
157
|
-
|
|
167
|
+
properties = rsd.peer_info["Properties"]
|
|
168
|
+
result.append(
|
|
169
|
+
RSDDevice(
|
|
170
|
+
hostname=hostname,
|
|
171
|
+
udid=properties["UniqueDeviceID"],
|
|
172
|
+
product_type=properties["ProductType"],
|
|
173
|
+
os_version=properties["OSVersion"],
|
|
174
|
+
)
|
|
175
|
+
)
|
|
158
176
|
return result
|
|
159
177
|
|
|
160
178
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
import sys
|
|
3
4
|
from asyncio import IncompleteReadError
|
|
4
5
|
from typing import AsyncIterable, Optional
|
|
@@ -6,14 +7,27 @@ from typing import AsyncIterable, Optional
|
|
|
6
7
|
import IPython
|
|
7
8
|
import nest_asyncio
|
|
8
9
|
from construct import StreamError
|
|
9
|
-
from hyperframe.frame import
|
|
10
|
-
|
|
10
|
+
from hyperframe.frame import (
|
|
11
|
+
DataFrame,
|
|
12
|
+
Frame,
|
|
13
|
+
GoAwayFrame,
|
|
14
|
+
HeadersFrame,
|
|
15
|
+
RstStreamFrame,
|
|
16
|
+
SettingsFrame,
|
|
17
|
+
WindowUpdateFrame,
|
|
18
|
+
)
|
|
11
19
|
from pygments import formatters, highlight, lexers
|
|
12
20
|
from traitlets.config import Config
|
|
13
21
|
|
|
14
22
|
from pymobiledevice3.exceptions import ProtocolError, StreamClosedError
|
|
15
|
-
from pymobiledevice3.remote.xpc_message import
|
|
16
|
-
|
|
23
|
+
from pymobiledevice3.remote.xpc_message import (
|
|
24
|
+
XpcFlags,
|
|
25
|
+
XpcInt64Type,
|
|
26
|
+
XpcUInt64Type,
|
|
27
|
+
XpcWrapper,
|
|
28
|
+
create_xpc_wrapper,
|
|
29
|
+
decode_xpc_object,
|
|
30
|
+
)
|
|
17
31
|
|
|
18
32
|
# Extracted by sniffing `remoted` traffic via Wireshark
|
|
19
33
|
DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS = 100
|
|
@@ -21,7 +35,7 @@ DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 1048576
|
|
|
21
35
|
DEFAULT_WIN_SIZE_INCR = 983041
|
|
22
36
|
|
|
23
37
|
FRAME_HEADER_SIZE = 9
|
|
24
|
-
HTTP2_MAGIC = b
|
|
38
|
+
HTTP2_MAGIC = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
|
25
39
|
|
|
26
40
|
ROOT_CHANNEL = 1
|
|
27
41
|
REPLY_CHANNEL = 3
|
|
@@ -38,14 +52,14 @@ resp = await client.send_receive_request({"Command": "DoSomething"})
|
|
|
38
52
|
|
|
39
53
|
class RemoteXPCConnection:
|
|
40
54
|
def __init__(self, address: tuple[str, int]):
|
|
41
|
-
self._previous_frame_data = b
|
|
55
|
+
self._previous_frame_data = b""
|
|
42
56
|
self.address = address
|
|
43
57
|
self.next_message_id: dict[int, int] = {ROOT_CHANNEL: 0, REPLY_CHANNEL: 0}
|
|
44
58
|
self.peer_info = None
|
|
45
59
|
self._reader: Optional[asyncio.StreamReader] = None
|
|
46
60
|
self._writer: Optional[asyncio.StreamWriter] = None
|
|
47
61
|
|
|
48
|
-
async def __aenter__(self) ->
|
|
62
|
+
async def __aenter__(self) -> "RemoteXPCConnection":
|
|
49
63
|
await self.connect()
|
|
50
64
|
return self
|
|
51
65
|
|
|
@@ -56,7 +70,7 @@ class RemoteXPCConnection:
|
|
|
56
70
|
self._reader, self._writer = await asyncio.open_connection(self.address[0], self.address[1])
|
|
57
71
|
try:
|
|
58
72
|
await self._do_handshake()
|
|
59
|
-
except Exception:
|
|
73
|
+
except Exception:
|
|
60
74
|
await self.close()
|
|
61
75
|
raise
|
|
62
76
|
|
|
@@ -64,16 +78,15 @@ class RemoteXPCConnection:
|
|
|
64
78
|
if self._writer is None:
|
|
65
79
|
return
|
|
66
80
|
self._writer.close()
|
|
67
|
-
|
|
81
|
+
with contextlib.suppress(ConnectionResetError):
|
|
68
82
|
await self._writer.wait_closed()
|
|
69
|
-
except ConnectionResetError:
|
|
70
|
-
pass
|
|
71
83
|
self._writer = None
|
|
72
84
|
self._reader = None
|
|
73
85
|
|
|
74
86
|
async def send_request(self, data: dict, wanting_reply: bool = False) -> None:
|
|
75
87
|
xpc_wrapper = create_xpc_wrapper(
|
|
76
|
-
data, message_id=self.next_message_id[ROOT_CHANNEL], wanting_reply=wanting_reply
|
|
88
|
+
data, message_id=self.next_message_id[ROOT_CHANNEL], wanting_reply=wanting_reply
|
|
89
|
+
)
|
|
77
90
|
self._writer.write(DataFrame(stream_id=ROOT_CHANNEL, data=xpc_wrapper).serialize())
|
|
78
91
|
await self._writer.drain()
|
|
79
92
|
|
|
@@ -84,7 +97,7 @@ class RemoteXPCConnection:
|
|
|
84
97
|
while size < total_size:
|
|
85
98
|
frame = await self._receive_next_data_frame()
|
|
86
99
|
|
|
87
|
-
if
|
|
100
|
+
if "END_STREAM" in frame.flags:
|
|
88
101
|
continue
|
|
89
102
|
|
|
90
103
|
if frame.stream_id != stream_id:
|
|
@@ -92,12 +105,12 @@ class RemoteXPCConnection:
|
|
|
92
105
|
if xpc_wrapper.flags.FILE_TX_STREAM_REQUEST:
|
|
93
106
|
continue
|
|
94
107
|
|
|
95
|
-
assert frame.stream_id == stream_id, f
|
|
108
|
+
assert frame.stream_id == stream_id, f"got {frame.stream_id} instead of {stream_id}"
|
|
96
109
|
size += len(frame.data)
|
|
97
110
|
yield frame.data
|
|
98
111
|
|
|
99
112
|
async def receive_file(self, total_size: int) -> bytes:
|
|
100
|
-
buf = b
|
|
113
|
+
buf = b""
|
|
101
114
|
async for chunk in self.iter_file_chunks(total_size):
|
|
102
115
|
buf += chunk
|
|
103
116
|
return buf
|
|
@@ -107,7 +120,7 @@ class RemoteXPCConnection:
|
|
|
107
120
|
frame = await self._receive_next_data_frame()
|
|
108
121
|
try:
|
|
109
122
|
xpc_message = XpcWrapper.parse(self._previous_frame_data + frame.data).message
|
|
110
|
-
self._previous_frame_data = b
|
|
123
|
+
self._previous_frame_data = b""
|
|
111
124
|
except StreamError:
|
|
112
125
|
self._previous_frame_data += frame.data
|
|
113
126
|
continue
|
|
@@ -124,48 +137,56 @@ class RemoteXPCConnection:
|
|
|
124
137
|
|
|
125
138
|
def shell(self) -> None:
|
|
126
139
|
nest_asyncio.apply(asyncio.get_running_loop())
|
|
127
|
-
sys.argv = [
|
|
140
|
+
sys.argv = ["a"]
|
|
128
141
|
config = Config()
|
|
129
|
-
config.InteractiveShellApp.exec_lines = [
|
|
130
|
-
print(highlight(SHELL_USAGE, lexers.PythonLexer(),
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
config.InteractiveShellApp.exec_lines = ["%autoawait asyncio"]
|
|
143
|
+
print(highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")))
|
|
144
|
+
IPython.start_ipython(
|
|
145
|
+
config=config,
|
|
146
|
+
user_ns={
|
|
147
|
+
"client": self,
|
|
148
|
+
"XpcInt64Type": XpcInt64Type,
|
|
149
|
+
"XpcUInt64Type": XpcUInt64Type,
|
|
150
|
+
},
|
|
151
|
+
)
|
|
137
152
|
|
|
138
153
|
async def _do_handshake(self) -> None:
|
|
139
154
|
self._writer.write(HTTP2_MAGIC)
|
|
140
155
|
await self._writer.drain()
|
|
141
156
|
|
|
142
157
|
# send h2 headers
|
|
143
|
-
await self._send_frame(
|
|
144
|
-
SettingsFrame
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
await self._send_frame(
|
|
159
|
+
SettingsFrame(
|
|
160
|
+
settings={
|
|
161
|
+
SettingsFrame.MAX_CONCURRENT_STREAMS: DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS,
|
|
162
|
+
SettingsFrame.INITIAL_WINDOW_SIZE: DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE,
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
)
|
|
147
166
|
await self._send_frame(WindowUpdateFrame(stream_id=0, window_increment=DEFAULT_WIN_SIZE_INCR))
|
|
148
|
-
await self._send_frame(HeadersFrame(stream_id=ROOT_CHANNEL, flags=[
|
|
167
|
+
await self._send_frame(HeadersFrame(stream_id=ROOT_CHANNEL, flags=["END_HEADERS"]))
|
|
149
168
|
|
|
150
169
|
# send first actual requests
|
|
151
170
|
await self.send_request({})
|
|
152
|
-
await self._send_frame(
|
|
153
|
-
|
|
171
|
+
await self._send_frame(
|
|
172
|
+
DataFrame(stream_id=ROOT_CHANNEL, data=XpcWrapper.build({"size": 0, "flags": 0x0201, "payload": None}))
|
|
173
|
+
)
|
|
154
174
|
self.next_message_id[ROOT_CHANNEL] += 1
|
|
155
175
|
await self._open_channel(REPLY_CHANNEL, XpcFlags.INIT_HANDSHAKE)
|
|
156
176
|
self.next_message_id[REPLY_CHANNEL] += 1
|
|
157
177
|
|
|
158
178
|
settings_frame = await asyncio.wait_for(self._receive_frame(), FIRST_REPLY_TIMEOUT)
|
|
159
179
|
if not isinstance(settings_frame, SettingsFrame):
|
|
160
|
-
raise ProtocolError(f
|
|
180
|
+
raise ProtocolError(f"Got unexpected frame: {settings_frame} instead of a SettingsFrame")
|
|
161
181
|
|
|
162
|
-
await self._send_frame(SettingsFrame(flags=[
|
|
182
|
+
await self._send_frame(SettingsFrame(flags=["ACK"]))
|
|
163
183
|
|
|
164
184
|
async def _open_channel(self, stream_id: int, flags: int) -> None:
|
|
165
185
|
flags |= XpcFlags.ALWAYS_SET
|
|
166
|
-
await self._send_frame(HeadersFrame(stream_id=stream_id, flags=[
|
|
186
|
+
await self._send_frame(HeadersFrame(stream_id=stream_id, flags=["END_HEADERS"]))
|
|
167
187
|
await self._send_frame(
|
|
168
|
-
DataFrame(stream_id=stream_id, data=XpcWrapper.build({
|
|
188
|
+
DataFrame(stream_id=stream_id, data=XpcWrapper.build({"size": 0, "flags": flags, "payload": None}))
|
|
189
|
+
)
|
|
169
190
|
|
|
170
191
|
async def _send_frame(self, frame: Frame) -> None:
|
|
171
192
|
self._writer.write(frame.serialize())
|
|
@@ -176,9 +197,9 @@ class RemoteXPCConnection:
|
|
|
176
197
|
frame = await self._receive_frame()
|
|
177
198
|
|
|
178
199
|
if isinstance(frame, GoAwayFrame):
|
|
179
|
-
raise StreamClosedError(f
|
|
200
|
+
raise StreamClosedError(f"Got {frame}")
|
|
180
201
|
if isinstance(frame, RstStreamFrame):
|
|
181
|
-
raise StreamClosedError(f
|
|
202
|
+
raise StreamClosedError(f"Got {frame}")
|
|
182
203
|
if not isinstance(frame, DataFrame):
|
|
183
204
|
continue
|
|
184
205
|
|
|
@@ -195,11 +216,11 @@ class RemoteXPCConnection:
|
|
|
195
216
|
return frame
|
|
196
217
|
|
|
197
218
|
async def _recvall(self, size: int) -> bytes:
|
|
198
|
-
data = b
|
|
219
|
+
data = b""
|
|
199
220
|
while len(data) < size:
|
|
200
221
|
try:
|
|
201
222
|
chunk = await self._reader.readexactly(size - len(data))
|
|
202
|
-
except IncompleteReadError:
|
|
203
|
-
raise ConnectionAbortedError()
|
|
223
|
+
except IncompleteReadError as e:
|
|
224
|
+
raise ConnectionAbortedError() from e
|
|
204
225
|
data += chunk
|
|
205
226
|
return data
|