diff --git a/idevice/Cargo.toml b/idevice/Cargo.toml index c4c0598..f172e4b 100644 --- a/idevice/Cargo.toml +++ b/idevice/Cargo.toml @@ -86,6 +86,7 @@ mobilebackup2 = [] location_simulation = [] pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"] pcapd = [] +preboard_service = [] obfuscate = ["dep:obfstr"] restore_service = [] rsd = ["xpc"] @@ -122,6 +123,7 @@ full = [ "mobilebackup2", "pair", "pcapd", + "preboard_service", "restore_service", "rsd", "springboardservices", diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index 009cf32..33fb862 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -379,7 +379,20 @@ impl Idevice { debug!("Received plist: {}", pretty_print_dictionary(&res)); if let Some(e) = res.get("Error") { - let e: String = plist::from_value(e)?; + let e = match e { + plist::Value::String(e) => e.to_string(), + plist::Value::Integer(e) => { + if let Some(error_string) = res.get("ErrorString").and_then(|x| x.as_string()) { + error_string.to_string() + } else { + e.to_string() + } + } + _ => { + log::error!("Error is not a string or integer from read_plist: {e:?}"); + return Err(IdeviceError::UnexpectedResponse); + } + }; if let Some(e) = IdeviceError::from_device_error_type(e.as_str(), &res) { return Err(e); } else { @@ -698,6 +711,8 @@ pub enum IdeviceError { MalformedCommand = -64, #[error("integer overflow")] IntegerOverflow = -65, + #[error("canceled by user")] + CanceledByUser = -66, } impl IdeviceError { @@ -710,6 +725,9 @@ impl IdeviceError { /// # Returns /// Some(IdeviceError) if the string maps to a known error type, None otherwise fn from_device_error_type(e: &str, context: &plist::Dictionary) -> Option { + if e.contains("NSDebugDescription=Canceled by user.") { + return Some(Self::CanceledByUser); + } match e { "GetProhibited" => Some(Self::GetProhibited), "InvalidHostID" => Some(Self::InvalidHostID), @@ -849,6 +867,7 @@ impl IdeviceError { IdeviceError::UnsupportedWatchKey => -63, IdeviceError::MalformedCommand => -64, IdeviceError::IntegerOverflow => -65, + IdeviceError::CanceledByUser => -66, } } } diff --git a/idevice/src/services/mod.rs b/idevice/src/services/mod.rs index 852f2ab..24afbd6 100644 --- a/idevice/src/services/mod.rs +++ b/idevice/src/services/mod.rs @@ -35,6 +35,8 @@ pub mod mobilebackup2; pub mod os_trace_relay; #[cfg(feature = "pcapd")] pub mod pcapd; +#[cfg(feature = "preboard_service")] +pub mod preboard_service; #[cfg(feature = "restore_service")] pub mod restore_service; #[cfg(feature = "rsd")] diff --git a/idevice/src/services/preboard_service.rs b/idevice/src/services/preboard_service.rs new file mode 100644 index 0000000..8f5d7f9 --- /dev/null +++ b/idevice/src/services/preboard_service.rs @@ -0,0 +1,72 @@ +//! Abstraction for preboard + +use crate::{Idevice, IdeviceError, IdeviceService, RsdService, obf}; + +/// Client for interacting with the preboard service on the device. +pub struct PreboardServiceClient { + /// The underlying device connection with established service + pub idevice: Idevice, +} + +impl IdeviceService for PreboardServiceClient { + fn service_name() -> std::borrow::Cow<'static, str> { + obf!("com.apple.preboardservice_v2") + } + + async fn from_stream(idevice: Idevice) -> Result { + Ok(Self::new(idevice)) + } +} + +impl RsdService for PreboardServiceClient { + fn rsd_service_name() -> std::borrow::Cow<'static, str> { + obf!("com.apple.preboardservice_v2.shim.remote") + } + + async fn from_stream(stream: Box) -> Result { + let mut idevice = Idevice::new(stream, ""); + idevice.rsd_checkin().await?; + Ok(Self::new(idevice)) + } +} + +impl PreboardServiceClient { + pub fn new(idevice: Idevice) -> Self { + Self { idevice } + } + + pub async fn create_stashbag(&mut self, manifest: &[u8]) -> Result<(), IdeviceError> { + let req = crate::plist!({ + "Command": "CreateStashbag", + "Manifest": manifest + }); + self.idevice.send_plist(req).await?; + let res = self.idevice.read_plist().await?; + if let Some(res) = res.get("ShowDialog").and_then(|x| x.as_boolean()) { + if !res { + log::warn!("ShowDialog is not true"); + return Err(IdeviceError::UnexpectedResponse); + } + } else { + log::warn!("No ShowDialog in response from service"); + return Err(IdeviceError::UnexpectedResponse); + } + + self.idevice.read_plist().await?; + Ok(()) + } + + pub async fn commit_stashbag(&mut self, manifest: &[u8]) -> Result<(), IdeviceError> { + let req = crate::plist!({ + "Command": "CommitStashbag", + "Manifest": manifest + }); + self.idevice.send_plist(req).await?; + self.idevice.read_plist().await?; + Ok(()) + } + + pub async fn clear_system_token(&mut self) -> Result<(), IdeviceError> { + todo!() + } +} diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 99d1978..57b5828 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -121,6 +121,10 @@ path = "src/bt_packet_logger.rs" name = "pcapd" path = "src/pcapd.rs" +[[bin]] +name = "preboard" +path = "src/preboard.rs" + [dependencies] idevice = { path = "../idevice", features = ["full"], default-features = false } tokio = { version = "1.43", features = ["full"] } diff --git a/tools/src/preboard.rs b/tools/src/preboard.rs new file mode 100644 index 0000000..b245037 --- /dev/null +++ b/tools/src/preboard.rs @@ -0,0 +1,76 @@ +// Jackson Coxson + +use clap::{Arg, Command}; +use idevice::{IdeviceService, preboard_service::PreboardServiceClient}; + +mod common; + +#[tokio::main] +async fn main() { + env_logger::init(); + + let matches = Command::new("preboard") + .about("Mess with developer mode") + .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("create").about("Create a stashbag??")) + .subcommand(Command::new("commit").about("Commit a stashbag??")) + .get_matches(); + + if matches.get_flag("about") { + println!("preboard - no idea what this does"); + 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 mut pc = PreboardServiceClient::connect(&*provider) + .await + .expect("Failed to connect to Preboard"); + + if matches.subcommand_matches("create").is_some() { + pc.create_stashbag(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) + .await + .expect("Failed to create"); + } else if matches.subcommand_matches("commit").is_some() { + pc.commit_stashbag(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) + .await + .expect("Failed to create"); + } else { + eprintln!("Invalid usage, pass -h for help"); + } + return; +}