diff --git a/Cargo.lock b/Cargo.lock index d69a223..b49fcad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,6 +238,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -252,6 +253,18 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_derive" +version = "4.5.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "clap_lex" version = "0.7.4" @@ -636,6 +649,12 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "http" version = "1.2.0" @@ -883,6 +902,7 @@ dependencies = [ "indexmap", "json", "log", + "ns-keyed-archive", "openssl", "plist", "reqwest", @@ -1119,6 +1139,27 @@ dependencies = [ "libc", ] +[[package]] +name = "ns-keyed-archive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82124a464c3fe86dea8ac04edef9438615f680cccfbd7bf08e0a3fd1565b03cc" +dependencies = [ + "nskeyedarchiver_converter", + "plist", +] + +[[package]] +name = "nskeyedarchiver_converter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f237a10fe003123daa55a74b63a0b0a65de1671b2d128711ffe5886891a8f77f" +dependencies = [ + "clap", + "plist", + "thiserror", +] + [[package]] name = "num-conv" version = "0.1.0" diff --git a/idevice/Cargo.toml b/idevice/Cargo.toml index 7151cbe..9621ea1 100644 --- a/idevice/Cargo.toml +++ b/idevice/Cargo.toml @@ -16,6 +16,7 @@ tokio-openssl = { version = "0.6" } plist = { version = "1.7" } serde = { version = "1", features = ["derive"] } +ns-keyed-archive = { version = "0.1.2", optional = true } thiserror = { version = "2" } log = { version = "0.4" } @@ -34,11 +35,14 @@ reqwest = { version = "0.12", features = ["json"], optional = true } sha2 = { version = "0.10", optional = true } +[dev-dependencies] +tokio = { version = "1.43", features = ["fs"] } + [features] core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"] debug_proxy = [] -dvt = ["dep:byteorder"] +dvt = ["dep:byteorder", "dep:ns-keyed-archive"] heartbeat = [] installation_proxy = [] misagent = [] diff --git a/idevice/src/dvt/message.rs b/idevice/src/dvt/message.rs new file mode 100644 index 0000000..344dc11 --- /dev/null +++ b/idevice/src/dvt/message.rs @@ -0,0 +1,388 @@ +// Jackson Coxson +// Messages contain: +// - 32 byte header +// - 16 byte payload header +// - Optional auxiliary +// - 16 byte aux header, useless +// - Aux data +// - Payload (NSKeyedArchive) + +use plist::Value; +use tokio::io::{AsyncRead, AsyncReadExt}; + +use crate::IdeviceError; + +#[derive(Debug, Clone, PartialEq)] +pub struct MessageHeader { + magic: u32, // 0x795b3d1f + header_len: u32, // will always be 32 bytes + fragment_id: u16, + fragment_count: u16, + length: u32, // Length of of the payload + identifier: u32, + conversation_index: u32, + channel: u32, + expects_reply: bool, +} + +#[derive(Debug, Default, Clone, PartialEq)] +pub struct PayloadHeader { + flags: u32, + aux_length: u32, + total_length: u64, +} + +#[derive(Debug, Default, PartialEq)] +pub struct AuxHeader { + buffer_size: u32, + unknown: u32, + aux_size: u32, + unknown2: u32, +} + +#[derive(Debug, PartialEq)] +pub struct Aux { + header: AuxHeader, + values: Vec, +} + +#[derive(PartialEq)] +pub enum AuxValue { + String(String), // 0x01 + Array(Vec), // 0x02 + U32(u32), // 0x03 + I64(i64), // 0x06 +} + +#[derive(Debug, PartialEq)] +pub struct Message { + pub message_header: MessageHeader, + pub payload_header: PayloadHeader, + pub aux: Option, + pub data: Option, +} + +impl Aux { + pub fn from_bytes(bytes: Vec) -> Result { + if bytes.len() < 16 { + return Err(IdeviceError::NotEnoughBytes(bytes.len(), 24)); + } + + let header = AuxHeader { + buffer_size: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), + unknown: u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]), + aux_size: u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]), + unknown2: u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]), + }; + + let mut bytes = &bytes[16..]; + let mut values = Vec::new(); + loop { + if bytes.len() < 8 { + break; + } + let aux_type = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + bytes = &bytes[4..]; + match aux_type { + 0x0a => { + // null, skip + // seems to be in between every value + } + 0x01 => { + let len = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize; + bytes = &bytes[4..]; + if bytes.len() < len { + return Err(IdeviceError::NotEnoughBytes(bytes.len(), len)); + } + values.push(AuxValue::String(String::from_utf8(bytes[..len].to_vec())?)); + bytes = &bytes[len..]; + } + 0x02 => { + let len = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize; + bytes = &bytes[4..]; + if bytes.len() < len { + return Err(IdeviceError::NotEnoughBytes(bytes.len(), len)); + } + values.push(AuxValue::Array(bytes[..len].to_vec())); + bytes = &bytes[len..]; + } + 0x03 => { + values.push(AuxValue::U32(u32::from_le_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], + ]))); + bytes = &bytes[4..]; + } + 0x06 => { + if bytes.len() < 8 { + return Err(IdeviceError::NotEnoughBytes(8, bytes.len())); + } + values.push(AuxValue::I64(i64::from_le_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], + bytes[7], + ]))); + bytes = &bytes[8..]; + } + _ => return Err(IdeviceError::UnknownAuxValueType(aux_type)), + } + } + + Ok(Self { header, values }) + } + + // Creates the default struct + // Note that the header isn't updated until serialization + pub fn from_values(values: Vec) -> Self { + Self { + header: AuxHeader::default(), + values, + } + } + + /// Serializes the values with the correctly sized header + pub fn serialize(&self) -> Vec { + let mut values_payload = Vec::new(); + for v in self.values.iter() { + values_payload.extend_from_slice(&0x0a_u32.to_le_bytes()); + match v { + AuxValue::String(s) => { + values_payload.extend_from_slice(&0x01_u32.to_le_bytes()); + values_payload.extend_from_slice(&(s.len() as u32).to_le_bytes()); + values_payload.extend_from_slice(s.as_bytes()); + } + AuxValue::Array(v) => { + values_payload.extend_from_slice(&0x02_u32.to_le_bytes()); + values_payload.extend_from_slice(&(v.len() as u32).to_le_bytes()); + values_payload.extend_from_slice(v); + } + AuxValue::U32(u) => { + values_payload.extend_from_slice(&0x03_u32.to_le_bytes()); + values_payload.extend_from_slice(&u.to_le_bytes()); + } + AuxValue::I64(i) => { + values_payload.extend_from_slice(&0x06_u32.to_le_bytes()); + values_payload.extend_from_slice(&i.to_le_bytes()); + } + } + } + + let mut res = Vec::new(); + let buffer_size = if values_payload.len() > 496 { + 8688_u32 + } else { + 496 + }; + 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 + // pymobiledevice3 doesn't seem to parse the header at all + res.extend_from_slice(&0_u32.to_le_bytes()); + res.extend_from_slice(&(values_payload.len() as u32).to_le_bytes()); + res.extend_from_slice(&0_u32.to_le_bytes()); + res.extend_from_slice(&values_payload); + res + } +} + +impl MessageHeader { + /// Creates a new header. Note that during serialization, the length will be updated + pub fn new( + fragment_id: u16, + fragment_count: u16, + identifier: u32, + conversation_index: u32, + channel: u32, + expects_reply: bool, + ) -> Self { + Self { + magic: 0x795b3d1f, + header_len: 32, + fragment_id, + fragment_count, + length: 0, + identifier, + conversation_index, + channel, + expects_reply, + } + } + + pub fn serialize(&self) -> Vec { + let mut res = Vec::new(); + res.extend_from_slice(&self.magic.to_le_bytes()); + res.extend_from_slice(&self.header_len.to_le_bytes()); + res.extend_from_slice(&self.fragment_id.to_le_bytes()); + res.extend_from_slice(&self.fragment_count.to_le_bytes()); + res.extend_from_slice(&self.length.to_le_bytes()); + res.extend_from_slice(&self.identifier.to_le_bytes()); + res.extend_from_slice(&self.conversation_index.to_le_bytes()); + res.extend_from_slice(&self.channel.to_le_bytes()); + res.extend_from_slice(&if self.expects_reply { 1_u32 } else { 0 }.to_le_bytes()); + + res + } +} + +impl PayloadHeader { + pub fn new() -> Self { + Self::default() + } + + pub fn serialize(&self) -> Vec { + let mut res = Vec::new(); + res.extend_from_slice(&self.flags.to_le_bytes()); + res.extend_from_slice(&self.aux_length.to_le_bytes()); + res.extend_from_slice(&self.total_length.to_le_bytes()); + + res + } + + // from pymobiledevice3 + pub fn instruments_message_type() -> Self { + Self { + flags: 2, + ..Default::default() + } + } + + pub fn apply_expects_reply_map(&mut self) { + self.flags |= 0x1000 + } +} + +impl Message { + pub async fn from_reader(reader: &mut R) -> Result { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf).await?; + + let mheader = MessageHeader { + magic: u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]), + header_len: u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]), + fragment_id: u16::from_le_bytes([buf[8], buf[9]]), + fragment_count: u16::from_le_bytes([buf[10], buf[11]]), + length: u32::from_le_bytes([buf[12], buf[13], buf[14], buf[15]]), + identifier: u32::from_le_bytes([buf[16], buf[17], buf[18], buf[19]]), + conversation_index: u32::from_le_bytes([buf[20], buf[21], buf[22], buf[23]]), + channel: u32::from_le_bytes([buf[24], buf[25], buf[26], buf[27]]), + expects_reply: u32::from_le_bytes([buf[28], buf[29], buf[30], buf[31]]) == 1, + }; + + let mut buf = [0u8; 16]; + reader.read_exact(&mut buf).await?; + + let pheader = PayloadHeader { + flags: u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]), + aux_length: u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]), + total_length: u64::from_le_bytes([ + buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15], + ]), + }; + + let aux = if pheader.aux_length > 0 { + let mut buf = vec![0u8; pheader.aux_length as usize]; + reader.read_exact(&mut buf).await?; + Some(Aux::from_bytes(buf)?) + } else { + None + }; + + let mut buf = vec![0u8; (pheader.total_length - pheader.aux_length as u64) as usize]; + reader.read_exact(&mut buf).await?; + + let data = if buf.is_empty() { + None + } else { + Some(ns_keyed_archive::decode::from_bytes(&buf)?) + }; + + Ok(Message { + message_header: mheader, + payload_header: pheader, + aux, + data, + }) + } + + pub fn new( + message_header: MessageHeader, + payload_header: PayloadHeader, + aux: Option, + data: Option, + ) -> Self { + Self { + message_header, + payload_header, + aux, + data, + } + } + + pub fn serialize(&self) -> Vec { + let aux = match &self.aux { + Some(a) => a.serialize(), + None => Vec::new(), + }; + let data = match &self.data { + Some(d) => ns_keyed_archive::encode::encode_to_bytes(d.to_owned()) + .expect("Failed to encode value"), + None => Vec::new(), + }; + + // Update the payload header + let mut payload_header = self.payload_header.to_owned(); + payload_header.aux_length = aux.len() as u32; + payload_header.total_length = (aux.len() + data.len()) as u64; + let payload_header = payload_header.serialize(); + + // Update the message header + let mut message_header = self.message_header.to_owned(); + message_header.length = (payload_header.len() + aux.len() + data.len()) as u32; + + let mut res = Vec::new(); + res.extend_from_slice(&message_header.serialize()); + res.extend_from_slice(&payload_header); + res.extend_from_slice(&aux); + res.extend_from_slice(&data); + + res + } +} + +impl std::fmt::Debug for AuxValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AuxValue::String(s) => write!(f, "String({:?})", s), + AuxValue::Array(arr) => write!( + f, + "Array(len={}, first_bytes={:?})", + arr.len(), + &arr[..arr.len().min(10)] + ), // Show only first 10 bytes + AuxValue::U32(n) => write!(f, "U32({})", n), + AuxValue::I64(n) => write!(f, "I64({})", n), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn t1() { + let test = "/Users/jacksoncoxson/Desktop/try1"; + 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/message_aux.rs b/idevice/src/dvt/message_aux.rs deleted file mode 100644 index 7cd640c..0000000 --- a/idevice/src/dvt/message_aux.rs +++ /dev/null @@ -1,290 +0,0 @@ -// Jackson Coxson - -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use plist::Value; -use std::io::{Cursor, Read, Write}; - -const MESSAGE_AUX_MAGIC: u64 = 0x1f0; -const DTX_MESSAGE_MAGIC: u32 = 0x1F3D5B79; -const EMPTY_DICTIONARY: u32 = 0xa; - -#[derive(Debug, Clone)] -pub enum AuxValue { - Object(Vec), - Int(u32), - Long(u64), - Bytes(Vec), - PlistObject(Value), -} - -#[derive(Debug, Clone)] -pub struct AuxItem { - aux_type: u32, - value: AuxValue, -} - -#[derive(Debug, Clone)] -pub struct MessageAux { - magic: u64, - aux: Vec, -} - -#[derive(Debug, Clone)] -pub struct DtxMessageHeader { - magic: u32, - cb: u32, - fragment_id: u16, - fragment_count: u16, - length: u32, - identifier: u32, - conversation_index: u32, - channel_code: i32, - expects_reply: u32, -} - -#[derive(Debug, Clone)] -pub struct DtxMessagePayloadHeader { - flags: u32, - auxiliary_length: u32, - total_length: u64, -} - -impl MessageAux { - pub fn new() -> Self { - MessageAux { - magic: MESSAGE_AUX_MAGIC, - aux: Vec::new(), - } - } - - pub fn append_int(&mut self, value: u32) -> &mut Self { - self.aux.push(AuxItem { - aux_type: 3, - value: AuxValue::Int(value), - }); - self - } - - pub fn append_long(&mut self, value: u64) -> &mut Self { - self.aux.push(AuxItem { - aux_type: 6, - value: AuxValue::Long(value), - }); - self - } - - pub fn append_obj(&mut self, value: Value) -> &mut Self { - // Serialize the plist value to binary format - let mut buf = Vec::new(); - value.to_writer_binary(&mut buf).unwrap(); - - self.aux.push(AuxItem { - aux_type: 2, - value: AuxValue::Object(buf), - }); - self - } - - pub fn serialize(&self) -> Vec { - let mut result = Vec::new(); - - // Write magic number - result.write_u64::(self.magic).unwrap(); - - // Calculate and write the total size of aux data - let mut aux_data = Vec::new(); - for item in &self.aux { - // Write empty dictionary marker - aux_data - .write_u32::(EMPTY_DICTIONARY) - .unwrap(); - - // Write type - aux_data.write_u32::(item.aux_type).unwrap(); - - // Write value based on type - match &item.value { - AuxValue::Object(data) => { - aux_data - .write_u32::(data.len() as u32) - .unwrap(); - aux_data.write_all(data).unwrap(); - } - AuxValue::Int(value) => { - aux_data.write_u32::(*value).unwrap(); - } - AuxValue::Long(value) => { - aux_data.write_u64::(*value).unwrap(); - } - AuxValue::Bytes(data) => { - aux_data.write_all(data).unwrap(); - } - AuxValue::PlistObject(obj) => { - let mut buf = Vec::new(); - obj.to_writer_binary(&mut buf).unwrap(); - aux_data.write_all(&buf).unwrap(); - } - } - } - - // Write the length of aux data - result - .write_u64::(aux_data.len() as u64) - .unwrap(); - - // Write aux data - result.write_all(&aux_data).unwrap(); - - result - } - - pub fn deserialize(mut data: &[u8]) -> Result { - let magic = data.read_u64::()?; - let aux_length = data.read_u64::()?; - - let mut aux_items = Vec::new(); - let mut aux_data = data.take(aux_length); - - while let Ok(empty_dict) = aux_data.read_u32::() { - if empty_dict != EMPTY_DICTIONARY { - // Handle non-standard format - continue; - } - - let aux_type = aux_data.read_u32::()?; - - let value = match aux_type { - 2 => { - // Object (Binary Plist) - let length = aux_data.read_u32::()?; - let mut buffer = vec![0u8; length as usize]; - aux_data.read_exact(&mut buffer)?; - - // You could optionally parse the binary plist here - let cursor = Cursor::new(buffer); - let plist_value: Value = Value::from_reader(cursor).expect("bad plist"); - AuxValue::PlistObject(plist_value) - } - 3 => { - // Int - let value = aux_data.read_u32::()?; - AuxValue::Int(value) - } - 6 => { - // Long - let value = aux_data.read_u64::()?; - AuxValue::Long(value) - } - _ => { - // Default: raw bytes (remaining) - let mut buffer = Vec::new(); - aux_data.read_to_end(&mut buffer)?; - AuxValue::Bytes(buffer) - } - }; - - aux_items.push(AuxItem { aux_type, value }); - } - - Ok(MessageAux { - magic, - aux: aux_items, - }) - } -} - -impl Default for MessageAux { - fn default() -> Self { - Self::new() - } -} - -impl DtxMessageHeader { - pub fn new() -> Self { - DtxMessageHeader { - magic: DTX_MESSAGE_MAGIC, - cb: 0, - fragment_id: 0, - fragment_count: 0, - length: 0, - identifier: 0, - conversation_index: 0, - channel_code: 0, - expects_reply: 0, - } - } - - pub fn serialize(&self) -> Vec { - let mut result = Vec::new(); - result.write_u32::(self.magic).unwrap(); - result.write_u32::(self.cb).unwrap(); - result.write_u16::(self.fragment_id).unwrap(); - result - .write_u16::(self.fragment_count) - .unwrap(); - result.write_u32::(self.length).unwrap(); - result.write_u32::(self.identifier).unwrap(); - result - .write_u32::(self.conversation_index) - .unwrap(); - result.write_i32::(self.channel_code).unwrap(); - result - .write_u32::(self.expects_reply) - .unwrap(); - result - } - - pub fn deserialize(mut data: &[u8]) -> Result { - Ok(DtxMessageHeader { - magic: data.read_u32::()?, - cb: data.read_u32::()?, - fragment_id: data.read_u16::()?, - fragment_count: data.read_u16::()?, - length: data.read_u32::()?, - identifier: data.read_u32::()?, - conversation_index: data.read_u32::()?, - channel_code: data.read_i32::()?, - expects_reply: data.read_u32::()?, - }) - } -} - -impl Default for DtxMessageHeader { - fn default() -> Self { - Self::new() - } -} - -impl DtxMessagePayloadHeader { - pub fn new() -> Self { - DtxMessagePayloadHeader { - flags: 0, - auxiliary_length: 0, - total_length: 0, - } - } - - pub fn serialize(&self) -> Vec { - let mut result = Vec::new(); - result.write_u32::(self.flags).unwrap(); - result - .write_u32::(self.auxiliary_length) - .unwrap(); - result.write_u64::(self.total_length).unwrap(); - result - } - - pub fn deserialize(mut data: &[u8]) -> Result { - Ok(DtxMessagePayloadHeader { - flags: data.read_u32::()?, - auxiliary_length: data.read_u32::()?, - total_length: data.read_u64::()?, - }) - } -} - -impl Default for DtxMessagePayloadHeader { - fn default() -> Self { - Self::new() - } -} diff --git a/idevice/src/dvt/mod.rs b/idevice/src/dvt/mod.rs index 9883b1d..18feb85 100644 --- a/idevice/src/dvt/mod.rs +++ b/idevice/src/dvt/mod.rs @@ -1,5 +1,7 @@ // Jackson Coxson -pub mod message_aux; +pub mod message; pub mod process_control; pub mod remote_server; + +pub const SERVICE_NAME: &str = "com.apple.instruments.dtservicehub"; diff --git a/idevice/src/dvt/remote_server.rs b/idevice/src/dvt/remote_server.rs index b257930..6b74e86 100644 --- a/idevice/src/dvt/remote_server.rs +++ b/idevice/src/dvt/remote_server.rs @@ -2,13 +2,22 @@ use std::collections::HashMap; -use crate::{IdeviceError, ReadWrite}; +use tokio::io::AsyncWriteExt; + +use crate::{ + dvt::message::{Aux, Message, MessageHeader, PayloadHeader}, + IdeviceError, ReadWrite, +}; + +use super::message::AuxValue; + +pub const INSTRUMENTS_MESSAGE_TYPE: u32 = 2; pub struct RemoteServerClient { idevice: Box, - current_message: usize, - last_channel: usize, - channels: HashMap>, + current_message: u32, + new_channel: u32, + channels: HashMap>, } impl RemoteServerClient { @@ -16,10 +25,72 @@ impl RemoteServerClient { Ok(Self { idevice, current_message: 0, - last_channel: 0, + new_channel: 1, channels: HashMap::new(), }) } + + pub async fn make_channel( + &mut self, + identifier: impl Into, + ) -> Result<(), IdeviceError> { + let code = self.new_channel; + let args = vec![AuxValue::U32(code), AuxValue::String(identifier.into())]; + self.send_message( + 0, + Some("_requestChannelWithCode:identifier:"), + Some(args), + true, + ) + .await?; + + let res = self.read_message().await?; + if res.data.is_some() { + return Err(IdeviceError::UnexpectedResponse); + } + + Ok(()) + } + + pub async fn send_message( + &mut self, + channel: u32, + data: Option>, + args: Option>, + expect_reply: bool, + ) -> Result<(), IdeviceError> { + self.current_message += 1; + + let mheader = MessageHeader::new(0, 1, self.current_message, 0, channel, expect_reply); + let mut pheader = PayloadHeader::instruments_message_type(); + if expect_reply { + pheader.apply_expects_reply_map(); + } + let aux = args.map(Aux::from_values); + let data: Option = data.map(Into::into); + + let message = Message::new(mheader, pheader, aux, data); + self.idevice.write_all(&message.serialize()).await?; + + Ok(()) + } + + pub async fn read_message(&mut self) -> Result { + Message::from_reader(&mut self.idevice).await + } } pub struct Channel {} + +#[cfg(test)] +mod tests { + use crate::util::plist_to_archived_bytes; + + #[test] + fn t1() { + let selector: plist::Value = "asdf".into(); + let selector = plist_to_archived_bytes(selector); + + std::fs::write("/Users/jacksoncoxson/code/test/test-rs.plist", selector).unwrap(); + } +} diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index 2a84f02..dfc7e6e 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -300,6 +300,20 @@ pub enum IdeviceError { #[error("xpc message failed")] Xpc(#[from] xpc::error::XPCError), + #[cfg(feature = "dvt")] + #[error("NSKeyedArchive error")] + NsKeyedArchiveError(#[from] ns_keyed_archive::ConverterError), + + #[cfg(feature = "dvt")] + #[error("Unknown aux value type")] + UnknownAuxValueType(u32), + + #[error("not enough bytes, expected {1}, got {0}")] + NotEnoughBytes(usize, usize), + + #[error("failed to parse bytes as valid utf8")] + Utf8Error, + #[cfg(feature = "debug_proxy")] #[error("invalid argument passed")] InvalidArgument, diff --git a/idevice/src/tss.rs b/idevice/src/tss.rs index 3333548..b3eacb4 100644 --- a/idevice/src/tss.rs +++ b/idevice/src/tss.rs @@ -4,7 +4,7 @@ use log::{debug, warn}; use plist::Value; -use crate::{util::plist_to_bytes, IdeviceError}; +use crate::{util::plist_to_xml_bytes, IdeviceError}; const TSS_CLIENT_VERSION_STRING: &str = "libauthinstall-1033.0.2"; const TSS_CONTROLLER_ACTION_URL: &str = "http://gs.apple.com/TSS/controller?action=2"; @@ -45,7 +45,7 @@ impl TSSRequest { .header("Content-type", "text/xml; charset=\"utf-8\"") .header("User-Agent", "InetURL/1.0") .header("Expect", "") - .body(plist_to_bytes(&self.inner)) + .body(plist_to_xml_bytes(&self.inner)) .send() .await? .text() diff --git a/idevice/src/usbmuxd/raw_packet.rs b/idevice/src/usbmuxd/raw_packet.rs index 10794d1..e19f1a0 100644 --- a/idevice/src/usbmuxd/raw_packet.rs +++ b/idevice/src/usbmuxd/raw_packet.rs @@ -1,6 +1,6 @@ // Jackson Coxson -use crate::util::plist_to_bytes; +use crate::util::plist_to_xml_bytes; use log::warn; #[derive(Debug)] @@ -14,7 +14,7 @@ pub struct RawPacket { impl RawPacket { pub fn new(plist: plist::Dictionary, version: u32, message: u32, tag: u32) -> RawPacket { - let plist_bytes = plist_to_bytes(&plist); + let plist_bytes = plist_to_xml_bytes(&plist); let size = plist_bytes.len() as u32 + 16; RawPacket { size, @@ -33,7 +33,7 @@ impl From for Vec { packet.extend_from_slice(&raw_packet.version.to_le_bytes()); packet.extend_from_slice(&raw_packet.message.to_le_bytes()); packet.extend_from_slice(&raw_packet.tag.to_le_bytes()); - packet.extend_from_slice(&plist_to_bytes(&raw_packet.plist)); + packet.extend_from_slice(&plist_to_xml_bytes(&raw_packet.plist)); packet } } diff --git a/idevice/src/util.rs b/idevice/src/util.rs index 7a49d61..b60a667 100644 --- a/idevice/src/util.rs +++ b/idevice/src/util.rs @@ -2,7 +2,7 @@ use plist::Value; -pub fn plist_to_bytes(p: &plist::Dictionary) -> Vec { +pub fn plist_to_xml_bytes(p: &plist::Dictionary) -> Vec { let buf = Vec::new(); let mut writer = std::io::BufWriter::new(buf); plist::to_writer_xml(&mut writer, &p).unwrap(); @@ -10,6 +10,23 @@ pub fn plist_to_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 13244e7..68fc005 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -33,6 +33,10 @@ path = "src/core_device_proxy_tun.rs" name = "idevice_id" path = "src/idevice_id.rs" +[[bin]] +name = "process_control" +path = "src/process_control.rs" + [[bin]] name = "remotexpc" path = "src/remotexpc.rs" diff --git a/tools/src/process_control.rs b/tools/src/process_control.rs new file mode 100644 index 0000000..d115c5a --- /dev/null +++ b/tools/src/process_control.rs @@ -0,0 +1,82 @@ +// Jackson Coxson + +use std::{ + io::Write, + net::{IpAddr, SocketAddr}, + str::FromStr, +}; + +use clap::{Arg, Command}; +use idevice::{debug_proxy::DebugProxyClient, tunneld::get_tunneld_devices, xpc::XPCDevice}; +use tokio::net::TcpStream; + +mod common; + +#[tokio::main] +async fn main() { + env_logger::init(); + + let matches = Command::new("process_control") + .about("Query process control") + .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), + ) + .get_matches(); + + if matches.get_flag("about") { + println!("debug_proxy - connect to the debug proxy and run commands"); + println!("Copyright (c) 2025 Jackson Coxson"); + return; + } + + let udid = matches.get_one::("udid"); + + let socket = SocketAddr::new( + IpAddr::from_str("127.0.0.1").unwrap(), + idevice::tunneld::DEFAULT_PORT, + ); + let mut devices = get_tunneld_devices(socket) + .await + .expect("Failed to get tunneld devices"); + + let (_udid, device) = match udid { + Some(u) => ( + u.to_owned(), + devices.remove(u).expect("Device not in tunneld"), + ), + None => devices.into_iter().next().expect("No devices"), + }; + + // Make the connection to RemoteXPC + let client = XPCDevice::new(Box::new( + TcpStream::connect((device.tunnel_address.as_str(), device.tunnel_port)) + .await + .unwrap(), + )) + .await + .unwrap(); + + // Get the debug proxy + let service = client + .services + .get(idevice::dvt::SERVICE_NAME) + .expect("Client did not contain DVT service"); + + let stream = TcpStream::connect(SocketAddr::new( + IpAddr::from_str(&device.tunnel_address).unwrap(), + service.port, + )) + .await + .expect("Failed to connect"); + + let rs_client = idevice::dvt::remote_server::RemoteServerClient::new(Box::new(stream)); +}