diff --git a/Cargo.lock b/Cargo.lock index b49fcad..21a60dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -923,6 +923,7 @@ dependencies = [ "env_logger", "idevice", "log", + "ns-keyed-archive", "plist", "sha2", "tokio", diff --git a/idevice/src/dvt/message.rs b/idevice/src/dvt/message.rs index 2d1155c..26b6e69 100644 --- a/idevice/src/dvt/message.rs +++ b/idevice/src/dvt/message.rs @@ -42,8 +42,8 @@ pub struct AuxHeader { #[derive(Debug, PartialEq)] pub struct Aux { - header: AuxHeader, - values: Vec, + pub header: AuxHeader, + pub values: Vec, } #[derive(PartialEq)] @@ -166,11 +166,7 @@ impl Aux { } let mut res = Vec::new(); - let buffer_size = if values_payload.len() > 496 { - 8688_u32 - } else { - 496 - }; + let buffer_size = 496_u32; res.extend_from_slice(&buffer_size.to_le_bytes()); // TODO: find what // this means and how to actually serialize it // go-ios just uses 496 @@ -183,6 +179,13 @@ impl Aux { } } +impl AuxValue { + // Returns an array AuxType + pub fn archived_value(v: impl Into) -> Self { + Self::Array(ns_keyed_archive::encode::encode_to_bytes(v.into()).expect("Failed to encode")) + } +} + impl MessageHeader { /// Creates a new header. Note that during serialization, the length will be updated pub fn new( @@ -361,27 +364,3 @@ impl std::fmt::Debug for AuxValue { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn t1() { - let test = "/Users/jacksoncoxson/Desktop/try2"; - let mut bytes = tokio::fs::File::open(test).await.unwrap(); - - let message = Message::from_reader(&mut bytes).await.unwrap(); - let bytes = message.serialize(); - let mut cursor = std::io::Cursor::new(bytes); - let message2 = Message::from_reader(&mut cursor).await.unwrap(); - - println!("{message:#?}"); - println!("{message2:#?}"); - assert_eq!(message, message2); - - let og_bytes = tokio::fs::read(test).await.unwrap(); - let new_bytes = message.serialize(); - assert_eq!(og_bytes, new_bytes); - } -} diff --git a/idevice/src/dvt/process_control.rs b/idevice/src/dvt/process_control.rs index 0c9bf25..0dcea36 100644 --- a/idevice/src/dvt/process_control.rs +++ b/idevice/src/dvt/process_control.rs @@ -1,6 +1,9 @@ // Jackson Coxson -use crate::IdeviceError; +use log::warn; +use plist::{Dictionary, Value}; + +use crate::{dvt::message::AuxValue, IdeviceError}; use super::remote_server::{Channel, RemoteServerClient}; @@ -16,4 +19,104 @@ impl<'a> ProcessControlClient<'a> { Ok(Self { channel }) } + + pub async fn launch_app( + &mut self, + bundle_id: impl Into, + env_vars: Option, + arguments: Option, + start_suspended: bool, + kill_existing: bool, + ) -> Result { + let method = Value::String( + "launchSuspendedProcessWithDevicePath:bundleIdentifier:environment:arguments:options:" + .into(), + ); + let mut options = Dictionary::new(); + options.insert( + "StartSuspendedKey".into(), + if start_suspended { 0_u64 } else { 1 }.into(), + ); + options.insert( + "KillExisting".into(), + if kill_existing { 0_u64 } else { 1 }.into(), + ); + + let env_vars = match env_vars { + Some(e) => e, + None => Dictionary::new(), + }; + let arguments = match arguments { + Some(a) => a, + None => Dictionary::new(), + }; + + self.channel + .call_method( + Some(method), + Some(vec![ + AuxValue::archived_value("/private/"), + AuxValue::archived_value(bundle_id.into()), + AuxValue::archived_value(env_vars), + AuxValue::archived_value(arguments), + AuxValue::archived_value(options), + ]), + true, + ) + .await?; + + let res = self.channel.read_message().await?; + + match res.data { + Some(Value::Integer(p)) => match p.as_unsigned() { + Some(p) => Ok(p), + None => { + warn!("PID wasn't unsigned"); + Err(IdeviceError::UnexpectedResponse) + } + }, + _ => { + warn!("Did not get integer response"); + Err(IdeviceError::UnexpectedResponse) + } + } + } + + pub async fn kill_app(&mut self, pid: u64) -> Result<(), IdeviceError> { + self.channel + .call_method( + "killPid:".into(), + Some(vec![AuxValue::U32(pid as u32)]), + false, + ) + .await?; + + Ok(()) + } + + pub async fn disable_memory_limit(&mut self, pid: u64) -> Result<(), IdeviceError> { + self.channel + .call_method( + "requestDisableMemoryLimitsForPid:".into(), + Some(vec![AuxValue::U32(pid as u32)]), + true, + ) + .await?; + + let res = self.channel.read_message().await?; + match res.data { + Some(Value::Boolean(b)) => { + if b { + Ok(()) + } else { + warn!("Failed to disable memory limit"); + Err(IdeviceError::DisableMemoryLimitFailed) + } + } + _ => { + warn!("Did not receive bool response"); + Err(IdeviceError::UnexpectedResponse) + } + } + } } diff --git a/idevice/src/dvt/remote_server.rs b/idevice/src/dvt/remote_server.rs index 7b4d576..7031066 100644 --- a/idevice/src/dvt/remote_server.rs +++ b/idevice/src/dvt/remote_server.rs @@ -28,14 +28,23 @@ pub struct Channel<'a> { impl RemoteServerClient { pub fn new(idevice: Box) -> Result { + let mut channels = HashMap::new(); + channels.insert(0, VecDeque::new()); Ok(Self { idevice, current_message: 0, new_channel: 1, - channels: HashMap::new(), + channels, }) } + pub fn root_channel(&mut self) -> Channel { + Channel { + client: self, + channel: 0, + } + } + pub async fn make_channel( &mut self, identifier: impl Into, @@ -50,19 +59,22 @@ impl RemoteServerClient { .expect("Failed to encode"), ), ]; - self.send_message( - 0, + + let mut root = self.root_channel(); + root.call_method( Some("_requestChannelWithCode:identifier:"), Some(args), true, ) .await?; - let res = self.read_message(0).await?; + let res = root.read_message().await?; if res.data.is_some() { return Err(IdeviceError::UnexpectedResponse); } + self.channels.insert(code, VecDeque::new()); + self.build_channel(code) } @@ -73,7 +85,7 @@ impl RemoteServerClient { }) } - pub async fn send_message( + pub async fn call_method( &mut self, channel: u32, data: Option>, @@ -89,11 +101,6 @@ impl RemoteServerClient { let message = Message::new(mheader, pheader, aux, data); debug!("Sending message: {message:#?}"); - let bytes = message.serialize(); - debug!( - "Re serde: {:#?}", - Message::from_reader(&mut std::io::Cursor::new(bytes)).await - ); self.idevice.write_all(&message.serialize()).await?; Ok(()) @@ -133,14 +140,14 @@ impl Channel<'_> { self.client.read_message(self.channel).await } - pub async fn send_message( + pub async fn call_method( &mut self, - data: Option>, + method: Option>, args: Option>, expect_reply: bool, ) -> Result<(), IdeviceError> { self.client - .send_message(self.channel, data, args, expect_reply) + .call_method(self.channel, method, args, expect_reply) .await } } diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index 2347837..5677801 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -312,6 +312,10 @@ pub enum IdeviceError { #[error("unknown channel")] UnknownChannel(u32), + #[cfg(feature = "dvt")] + #[error("disable memory limit failed")] + DisableMemoryLimitFailed, + #[error("not enough bytes, expected {1}, got {0}")] NotEnoughBytes(usize, usize), diff --git a/idevice/src/util.rs b/idevice/src/util.rs index b60a667..45e6253 100644 --- a/idevice/src/util.rs +++ b/idevice/src/util.rs @@ -10,23 +10,6 @@ pub fn plist_to_xml_bytes(p: &plist::Dictionary) -> Vec { writer.into_inner().unwrap() } -pub fn plist_to_archived_bytes(p: plist::Value) -> Vec { - let mut root = plist::Dictionary::new(); - root.insert("$version".into(), 100_000.into()); - root.insert( - "$objects".into(), - plist::Value::Array(vec!["$null".into(), p]), - ); - root.insert("$archiver".into(), "NSKeyedArchiver".into()); - root.insert("$top".into(), plist::Dictionary::new().into()); - - let buf = Vec::new(); - let mut writer = std::io::BufWriter::new(buf); - plist::to_writer_binary(&mut writer, &root).unwrap(); - - writer.into_inner().unwrap() -} - pub fn pretty_print_plist(p: &Value) -> String { print_plist(p, 0) } diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 68fc005..25cd064 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -37,6 +37,10 @@ path = "src/idevice_id.rs" name = "process_control" path = "src/process_control.rs" +[[bin]] +name = "dvt_packet_parser" +path = "src/dvt_packet_parser.rs" + [[bin]] name = "remotexpc" path = "src/remotexpc.rs" @@ -59,3 +63,4 @@ sha2 = { version = "0.10" } ureq = { version = "3" } clap = { version = "4.5" } plist = { version = "1.7" } +ns-keyed-archive = "0.1.2" diff --git a/tools/src/dvt_packet_parser.rs b/tools/src/dvt_packet_parser.rs new file mode 100644 index 0000000..7249520 --- /dev/null +++ b/tools/src/dvt_packet_parser.rs @@ -0,0 +1,33 @@ +// Jackson Coxson + +use idevice::dvt::message::Message; + +#[tokio::main] +async fn main() { + let file = std::env::args().nth(1).expect("No file passed"); + let mut bytes = tokio::fs::File::open(file).await.unwrap(); + + let message = Message::from_reader(&mut bytes).await.unwrap(); + println!("{message:#?}"); + + println!("----- AUX -----"); + if let Some(aux) = message.aux { + for v in aux.values { + match v { + idevice::dvt::message::AuxValue::Array(a) => { + match ns_keyed_archive::decode::from_bytes(&a) { + Ok(a) => { + println!("{a:#?}"); + } + Err(_) => { + println!("{a:?}"); + } + } + } + _ => { + println!("{v:?}"); + } + } + } + } +} diff --git a/tools/src/process_control.rs b/tools/src/process_control.rs index 36c0e5c..bcfa49a 100644 --- a/tools/src/process_control.rs +++ b/tools/src/process_control.rs @@ -21,7 +21,7 @@ async fn main() { Arg::new("udid") .value_name("UDID") .help("UDID of the device (overrides host/pairing file)") - .index(1), + .index(2), ) .arg( Arg::new("about") @@ -29,15 +29,24 @@ async fn main() { .help("Show about information") .action(clap::ArgAction::SetTrue), ) + .arg( + Arg::new("bundle_id") + .value_name("Bundle ID") + .help("Bundle ID of the app to launch") + .index(1), + ) .get_matches(); if matches.get_flag("about") { - println!("debug_proxy - connect to the debug proxy and run commands"); + println!("process_control - launch and manage processes on the device"); println!("Copyright (c) 2025 Jackson Coxson"); return; } let udid = matches.get_one::("udid"); + let bundle_id = matches + .get_one::("bundle_id") + .expect("No bundle ID specified"); let socket = SocketAddr::new( IpAddr::from_str("127.0.0.1").unwrap(), @@ -80,7 +89,17 @@ async fn main() { let mut rs_client = idevice::dvt::remote_server::RemoteServerClient::new(Box::new(stream)).unwrap(); rs_client.read_message(0).await.expect("no read??"); - let pc_client = idevice::dvt::process_control::ProcessControlClient::new(&mut rs_client) + let mut pc_client = idevice::dvt::process_control::ProcessControlClient::new(&mut rs_client) .await .unwrap(); + + let pid = pc_client + .launch_app(bundle_id, None, None, true, false) + .await + .expect("no launch??"); + pc_client + .disable_memory_limit(pid) + .await + .expect("no disable??"); + println!("PID: {pid}"); }