diff --git a/Cargo.lock b/Cargo.lock index ccb56be..0d67243 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -266,33 +266,13 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -[[package]] -name = "c2rust-bitfields" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "367e5d1b30f28be590b6b3868da1578361d29d9bfac516d22f497d28ed7c9055" -dependencies = [ - "c2rust-bitfields-derive 0.19.0", -] - [[package]] name = "c2rust-bitfields" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46dc7d2bffa0d0b3d47eb2dc69973466858281446c2ac9f6d8a10e92ab1017df" dependencies = [ - "c2rust-bitfields-derive 0.20.0", -] - -[[package]] -name = "c2rust-bitfields-derive" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a279db9c50c4024eeca1a763b6e0f033848ce74e83e47454bcf8a8a98f7b0b56" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "c2rust-bitfields-derive", ] [[package]] @@ -866,17 +846,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getifaddrs" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba121d81ab5ea05b0cd5858516266800bf965531a794f7ac58e3eeb804f364f" -dependencies = [ - "bitflags", - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "getifaddrs" version = "0.2.0" @@ -1166,7 +1135,7 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tokio-rustls", - "tun-rs 2.5.3", + "tun-rs", "uuid", "x509-cert", ] @@ -1198,7 +1167,6 @@ dependencies = [ "plist", "sha2", "tokio", - "tun-rs 1.5.0", "ureq", "uuid", ] @@ -2645,34 +2613,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tun-rs" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53141e64197ff7e758b8152615e50bb4a3b18c970738876e7906d31f242c7d6e" -dependencies = [ - "bitflags", - "blocking", - "byteorder", - "bytes", - "c2rust-bitfields 0.19.0", - "cfg-if", - "encoding_rs", - "getifaddrs 0.1.5", - "ipnet", - "libc", - "libloading", - "log", - "mac_address", - "nix 0.29.0", - "scopeguard", - "thiserror 2.0.12", - "tokio", - "windows-sys 0.59.0", - "winreg 0.52.0", - "wintun-bindings", -] - [[package]] name = "tun-rs" version = "2.5.3" @@ -2683,9 +2623,9 @@ dependencies = [ "blocking", "byteorder", "bytes", - "c2rust-bitfields 0.20.0", + "c2rust-bitfields", "encoding_rs", - "getifaddrs 0.2.0", + "getifaddrs", "ipnet", "libc", "libloading", @@ -2697,7 +2637,7 @@ dependencies = [ "tokio", "widestring", "windows-sys 0.60.2", - "winreg 0.55.0", + "winreg", ] [[package]] @@ -3026,15 +2966,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -3062,21 +2993,6 @@ dependencies = [ "windows-targets 0.53.3", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -3110,12 +3026,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3128,12 +3038,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3146,12 +3050,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3176,12 +3074,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3194,12 +3086,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3212,12 +3098,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3230,12 +3110,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3257,16 +3131,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "winreg" version = "0.55.0" @@ -3277,19 +3141,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "wintun-bindings" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88303b411e20a1319b368dcd04db1480003ed46ac35193e139f542720b15fbf" -dependencies = [ - "c2rust-bitfields 0.20.0", - "libloading", - "log", - "thiserror 2.0.12", - "windows-sys 0.60.2", -] - [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/idevice/Cargo.toml b/idevice/Cargo.toml index 765a3b5..113cc75 100644 --- a/idevice/Cargo.toml +++ b/idevice/Cargo.toml @@ -3,7 +3,7 @@ name = "idevice" description = "A Rust library to interact with services on iOS devices." authors = ["Jackson Coxson"] version = "0.1.37" -edition = "2021" +edition = "2024" license = "MIT" documentation = "https://docs.rs/idevice" repository = "https://github.com/jkcoxson/idevice" @@ -62,6 +62,7 @@ ring = ["rustls/ring", "tokio-rustls/ring"] afc = ["dep:chrono"] amfi = [] +companion_proxy = [] core_device = ["xpc", "dep:uuid"] core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"] crashreportcopymobile = ["afc"] @@ -95,6 +96,7 @@ xpc = ["dep:indexmap", "dep:uuid"] full = [ "afc", "amfi", + "companion_proxy", "core_device", "core_device_proxy", "crashreportcopymobile", diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index 9320b91..b9fca66 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -660,6 +660,10 @@ pub enum IdeviceError { FfiInvalidString = -61, #[error("buffer passed is too small - needs {0}, got {1}")] FfiBufferTooSmall(usize, usize) = -62, + #[error("unsupported watch key")] + UnsupportedWatchKey = -63, + #[error("malformed command")] + MalformedCommand = -64, } impl IdeviceError { @@ -683,6 +687,8 @@ impl IdeviceError { "UserDeniedPairing" => Some(Self::UserDeniedPairing), #[cfg(feature = "pair")] "PasswordProtected" => Some(Self::PasswordProtected), + "UnsupportedWatchKey" => Some(Self::UnsupportedWatchKey), + "MalformedCommand" => Some(Self::MalformedCommand), "InternalError" => { let detailed_error = context .get("DetailedError") @@ -806,6 +812,8 @@ impl IdeviceError { IdeviceError::FfiInvalidArg => -60, IdeviceError::FfiInvalidString => -61, IdeviceError::FfiBufferTooSmall(_, _) => -62, + IdeviceError::UnsupportedWatchKey => -63, + IdeviceError::MalformedCommand => -64, } } } diff --git a/idevice/src/services/companion_proxy.rs b/idevice/src/services/companion_proxy.rs new file mode 100644 index 0000000..462966d --- /dev/null +++ b/idevice/src/services/companion_proxy.rs @@ -0,0 +1,150 @@ +//! Companion Proxy is Apple's bridge to connect to the Apple Watch + +use log::warn; + +use crate::{Idevice, IdeviceError, IdeviceService, RsdService, obf}; + +pub struct CompanionProxy { + idevice: Idevice, +} + +pub struct CompanionProxyStream { + proxy: CompanionProxy, +} + +impl IdeviceService for CompanionProxy { + fn service_name() -> std::borrow::Cow<'static, str> { + obf!("com.apple.companion_proxy") + } + + async fn from_stream(idevice: Idevice) -> Result { + Ok(Self::new(idevice)) + } +} + +impl RsdService for CompanionProxy { + fn rsd_service_name() -> std::borrow::Cow<'static, str> { + obf!("com.apple.companion_proxy.shim.remote") + } + + async fn from_stream(stream: Box) -> Result { + let mut idevice = Idevice::new(stream, ""); + idevice.rsd_checkin().await?; + Ok(Self::new(idevice)) + } +} + +impl CompanionProxy { + pub fn new(idevice: Idevice) -> Self { + Self { idevice } + } + + pub async fn get_device_registry(&mut self) -> Result, IdeviceError> { + let command = crate::plist!({ + "Command": "GetDeviceRegistry" + }); + + self.idevice.send_plist(command).await?; + let res = self.idevice.read_plist().await?; + let list = match res.get("PairedDevicesArray").and_then(|x| x.as_array()) { + Some(l) => l, + None => { + warn!("Didn't get PairedDevicesArray array"); + return Err(IdeviceError::UnexpectedResponse); + } + }; + + let mut res = Vec::new(); + for l in list { + if let plist::Value::String(l) = l { + res.push(l.to_owned()); + } + } + + Ok(res) + } + + pub async fn listen_for_devices(mut self) -> Result { + let command = crate::plist!({ + "Command": "StartListeningForDevices" + }); + self.idevice.send_plist(command).await?; + + Ok(CompanionProxyStream { proxy: self }) + } + + pub async fn get_value( + &mut self, + udid: impl Into, + key: impl Into, + ) -> Result { + let udid = udid.into(); + let key = key.into(); + let command = crate::plist!({ + "Command": "GetValueFromRegistry", + "GetValueGizmoUDIDKey": udid, + "GetValueKeyKey": key.clone() + }); + self.idevice.send_plist(command).await?; + let mut res = self.idevice.read_plist().await?; + if let Some(v) = res + .remove("RetrievedValueDictionary") + .and_then(|x| x.into_dictionary()) + .and_then(|mut x| x.remove(&key)) + { + Ok(v) + } else { + Err(IdeviceError::NotFound) + } + } + + pub async fn start_forwarding_service_port( + &mut self, + port: u16, + service_name: Option<&str>, + options: Option, + ) -> Result { + let command = crate::plist!({ + "Command": "StartForwardingServicePort", + "GizmoRemotePortNumber": port, + "IsServiceLowPriority": false, + "PreferWifi": false, + "ForwardedServiceName":? service_name, + : Result<(), IdeviceError> { + let command = crate::plist!({ + "Command": "StopForwardingServicePort", + "GizmoRemotePortNumber": port + }); + + self.idevice.send_plist(command).await?; + let res = self.idevice.read_plist().await?; + if let Some(c) = res.get("Command").and_then(|x| x.as_string()) + && (c == "ComandSuccess" || c == "CommandSuccess") + // Apple you spelled this wrong, adding the right spelling just in case you fix it smh + { + Ok(()) + } else { + Err(IdeviceError::UnexpectedResponse) + } + } +} + +impl CompanionProxyStream { + pub async fn next(&mut self) -> Result { + self.proxy.idevice.read_plist().await + } +} diff --git a/idevice/src/services/mod.rs b/idevice/src/services/mod.rs index 17bdcb2..f0fb2c6 100644 --- a/idevice/src/services/mod.rs +++ b/idevice/src/services/mod.rs @@ -2,6 +2,8 @@ pub mod afc; #[cfg(feature = "amfi")] pub mod amfi; +#[cfg(feature = "companion_proxy")] +pub mod companion_proxy; #[cfg(feature = "core_device")] pub mod core_device; #[cfg(feature = "core_device_proxy")] diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 049c48a..a2dc6ac 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -25,9 +25,9 @@ path = "src/instproxy.rs" name = "mounter" path = "src/mounter.rs" -[[bin]] -name = "core_device_proxy_tun" -path = "src/core_device_proxy_tun.rs" +# [[bin]] +# name = "core_device_proxy_tun" +# path = "src/core_device_proxy_tun.rs" [[bin]] name = "idevice_id" @@ -93,12 +93,16 @@ path = "src/lockdown.rs" name = "restore_service" path = "src/restore_service.rs" +[[bin]] +name = "companion_proxy" +path = "src/companion_proxy.rs" + [dependencies] idevice = { path = "../idevice", features = ["full"], default-features = false } tokio = { version = "1.43", features = ["full"] } log = { version = "0.4" } env_logger = { version = "0.11" } -tun-rs = { version = "1.5", features = ["async"] } +# tun-rs = { version = "1.5", features = ["async"] } sha2 = { version = "0.10" } ureq = { version = "3" } clap = { version = "4.5" } diff --git a/tools/src/companion_proxy.rs b/tools/src/companion_proxy.rs new file mode 100644 index 0000000..4c1df7c --- /dev/null +++ b/tools/src/companion_proxy.rs @@ -0,0 +1,150 @@ +// Jackson Coxson + +use clap::{arg, Arg, Command}; +use idevice::{ + companion_proxy::CompanionProxy, core_device_proxy::CoreDeviceProxy, pretty_print_dictionary, + pretty_print_plist, rsd::RsdHandshake, IdeviceService, RsdService, +}; + +mod common; + +#[tokio::main] +async fn main() { + env_logger::init(); + + let matches = Command::new("companion_proxy") + .about("Apple Watch things") + .arg( + Arg::new("host") + .long("host") + .value_name("HOST") + .help("IP address of the device"), + ) + .arg( + Arg::new("pairing_file") + .long("pairing-file") + .value_name("PATH") + .help("Path to the pairing file"), + ) + .arg( + Arg::new("udid") + .value_name("UDID") + .help("UDID of the device (overrides host/pairing file)") + .index(1), + ) + .arg( + Arg::new("about") + .long("about") + .help("Show about information") + .action(clap::ArgAction::SetTrue), + ) + .subcommand(Command::new("list").about("List the companions on the device")) + .subcommand(Command::new("listen").about("Listen for devices")) + .subcommand( + Command::new("get") + .about("Gets a value") + .arg(arg!(-d --device_udid "the device udid to get from").required(true)) + .arg(arg!(-v --value "the value to get").required(true)), + ) + .subcommand( + Command::new("start") + .about("Starts a service") + .arg(arg!(-p --port "the port").required(true)) + .arg(arg!(-n --name "the optional service name").required(false)), + ) + .subcommand( + Command::new("stop") + .about("Starts a service") + .arg(arg!(-p --port "the port").required(true)), + ) + .get_matches(); + + if matches.get_flag("about") { + println!("companion_proxy"); + println!("Copyright (c) 2025 Jackson Coxson"); + return; + } + + let udid = matches.get_one::("udid"); + let host = matches.get_one::("host"); + let pairing_file = matches.get_one::("pairing_file"); + + let provider = match common::get_provider(udid, host, pairing_file, "amfi-jkcoxson").await { + Ok(p) => p, + Err(e) => { + eprintln!("{e}"); + return; + } + }; + + let proxy = CoreDeviceProxy::connect(&*provider) + .await + .expect("no core_device_proxy"); + let rsd_port = proxy.handshake.server_rsd_port; + let mut provider = proxy + .create_software_tunnel() + .expect("no tunnel") + .to_async_handle(); + let mut handshake = RsdHandshake::new(provider.connect(rsd_port).await.unwrap()) + .await + .unwrap(); + let mut proxy = CompanionProxy::connect_rsd(&mut provider, &mut handshake) + .await + .expect("no companion proxy connect"); + + // let mut proxy = CompanionProxy::connect(&*provider) + // .await + // .expect("Failed to connect to companion proxy"); + + if matches.subcommand_matches("list").is_some() { + proxy.get_device_registry().await.expect("Failed to show"); + } else if matches.subcommand_matches("listen").is_some() { + let mut stream = proxy.listen_for_devices().await.expect("Failed to show"); + while let Ok(v) = stream.next().await { + println!("{}", pretty_print_dictionary(&v)); + } + } else if let Some(matches) = matches.subcommand_matches("get") { + let key = matches.get_one::("value").expect("no value passed"); + let udid = matches + .get_one::("device_udid") + .expect("no AW udid passed"); + + match proxy.get_value(udid, key).await { + Ok(value) => { + println!("{}", pretty_print_plist(&value)); + } + Err(e) => { + eprintln!("Error getting value: {e}"); + } + } + } else if let Some(matches) = matches.subcommand_matches("start") { + let port: u16 = matches + .get_one::("port") + .expect("no port passed") + .parse() + .expect("not a number"); + let name = matches.get_one::("name").map(|x| x.as_str()); + + match proxy.start_forwarding_service_port(port, name, None).await { + Ok(value) => { + println!("started on port {value}"); + } + Err(e) => { + eprintln!("Error starting: {e}"); + } + } + } else if let Some(matches) = matches.subcommand_matches("stop") { + let port: u16 = matches + .get_one::("port") + .expect("no port passed") + .parse() + .expect("not a number"); + + if let Err(e) = proxy.stop_forwarding_service_port(port).await { + eprintln!("Error starting: {e}"); + } + } else { + eprintln!("Invalid usage, pass -h for help"); + } + return; +}