Serialize and deserialize DTX packets

This commit is contained in:
Jackson Coxson
2025-03-11 16:45:14 -06:00
parent 4eeab8f498
commit dca957ccee
12 changed files with 636 additions and 303 deletions

41
Cargo.lock generated
View File

@@ -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"

View File

@@ -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 = []

388
idevice/src/dvt/message.rs Normal file
View File

@@ -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<AuxValue>,
}
#[derive(PartialEq)]
pub enum AuxValue {
String(String), // 0x01
Array(Vec<u8>), // 0x02
U32(u32), // 0x03
I64(i64), // 0x06
}
#[derive(Debug, PartialEq)]
pub struct Message {
pub message_header: MessageHeader,
pub payload_header: PayloadHeader,
pub aux: Option<Aux>,
pub data: Option<Value>,
}
impl Aux {
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, IdeviceError> {
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<AuxValue>) -> Self {
Self {
header: AuxHeader::default(),
values,
}
}
/// Serializes the values with the correctly sized header
pub fn serialize(&self) -> Vec<u8> {
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<u8> {
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<u8> {
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<R: AsyncRead + Unpin>(reader: &mut R) -> Result<Self, IdeviceError> {
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<Aux>,
data: Option<Value>,
) -> Self {
Self {
message_header,
payload_header,
aux,
data,
}
}
pub fn serialize(&self) -> Vec<u8> {
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);
}
}

View File

@@ -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<u8>),
Int(u32),
Long(u64),
Bytes(Vec<u8>),
PlistObject(Value),
}
#[derive(Debug, Clone)]
pub struct AuxItem {
aux_type: u32,
value: AuxValue,
}
#[derive(Debug, Clone)]
pub struct MessageAux {
magic: u64,
aux: Vec<AuxItem>,
}
#[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<u8> {
let mut result = Vec::new();
// Write magic number
result.write_u64::<LittleEndian>(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::<LittleEndian>(EMPTY_DICTIONARY)
.unwrap();
// Write type
aux_data.write_u32::<LittleEndian>(item.aux_type).unwrap();
// Write value based on type
match &item.value {
AuxValue::Object(data) => {
aux_data
.write_u32::<LittleEndian>(data.len() as u32)
.unwrap();
aux_data.write_all(data).unwrap();
}
AuxValue::Int(value) => {
aux_data.write_u32::<LittleEndian>(*value).unwrap();
}
AuxValue::Long(value) => {
aux_data.write_u64::<LittleEndian>(*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::<LittleEndian>(aux_data.len() as u64)
.unwrap();
// Write aux data
result.write_all(&aux_data).unwrap();
result
}
pub fn deserialize(mut data: &[u8]) -> Result<Self, std::io::Error> {
let magic = data.read_u64::<LittleEndian>()?;
let aux_length = data.read_u64::<LittleEndian>()?;
let mut aux_items = Vec::new();
let mut aux_data = data.take(aux_length);
while let Ok(empty_dict) = aux_data.read_u32::<LittleEndian>() {
if empty_dict != EMPTY_DICTIONARY {
// Handle non-standard format
continue;
}
let aux_type = aux_data.read_u32::<LittleEndian>()?;
let value = match aux_type {
2 => {
// Object (Binary Plist)
let length = aux_data.read_u32::<LittleEndian>()?;
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::<LittleEndian>()?;
AuxValue::Int(value)
}
6 => {
// Long
let value = aux_data.read_u64::<LittleEndian>()?;
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<u8> {
let mut result = Vec::new();
result.write_u32::<LittleEndian>(self.magic).unwrap();
result.write_u32::<LittleEndian>(self.cb).unwrap();
result.write_u16::<LittleEndian>(self.fragment_id).unwrap();
result
.write_u16::<LittleEndian>(self.fragment_count)
.unwrap();
result.write_u32::<LittleEndian>(self.length).unwrap();
result.write_u32::<LittleEndian>(self.identifier).unwrap();
result
.write_u32::<LittleEndian>(self.conversation_index)
.unwrap();
result.write_i32::<LittleEndian>(self.channel_code).unwrap();
result
.write_u32::<LittleEndian>(self.expects_reply)
.unwrap();
result
}
pub fn deserialize(mut data: &[u8]) -> Result<Self, std::io::Error> {
Ok(DtxMessageHeader {
magic: data.read_u32::<LittleEndian>()?,
cb: data.read_u32::<LittleEndian>()?,
fragment_id: data.read_u16::<LittleEndian>()?,
fragment_count: data.read_u16::<LittleEndian>()?,
length: data.read_u32::<LittleEndian>()?,
identifier: data.read_u32::<LittleEndian>()?,
conversation_index: data.read_u32::<LittleEndian>()?,
channel_code: data.read_i32::<LittleEndian>()?,
expects_reply: data.read_u32::<LittleEndian>()?,
})
}
}
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<u8> {
let mut result = Vec::new();
result.write_u32::<LittleEndian>(self.flags).unwrap();
result
.write_u32::<LittleEndian>(self.auxiliary_length)
.unwrap();
result.write_u64::<LittleEndian>(self.total_length).unwrap();
result
}
pub fn deserialize(mut data: &[u8]) -> Result<Self, std::io::Error> {
Ok(DtxMessagePayloadHeader {
flags: data.read_u32::<LittleEndian>()?,
auxiliary_length: data.read_u32::<LittleEndian>()?,
total_length: data.read_u64::<LittleEndian>()?,
})
}
}
impl Default for DtxMessagePayloadHeader {
fn default() -> Self {
Self::new()
}
}

View File

@@ -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";

View File

@@ -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<dyn ReadWrite>,
current_message: usize,
last_channel: usize,
channels: HashMap<u8, Vec<super::message_aux::MessageAux>>,
current_message: u32,
new_channel: u32,
channels: HashMap<u8, Vec<Message>>,
}
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<String>,
) -> 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<impl Into<plist::Value>>,
args: Option<Vec<AuxValue>>,
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<plist::Value> = 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, IdeviceError> {
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();
}
}

View File

@@ -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,

View File

@@ -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()

View File

@@ -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<RawPacket> for Vec<u8> {
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
}
}

View File

@@ -2,7 +2,7 @@
use plist::Value;
pub fn plist_to_bytes(p: &plist::Dictionary) -> Vec<u8> {
pub fn plist_to_xml_bytes(p: &plist::Dictionary) -> Vec<u8> {
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<u8> {
writer.into_inner().unwrap()
}
pub fn plist_to_archived_bytes(p: plist::Value) -> Vec<u8> {
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)
}

View File

@@ -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"

View File

@@ -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::<String>("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));
}