mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
Serialize and deserialize DTX packets
This commit is contained in:
41
Cargo.lock
generated
41
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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
388
idevice/src/dvt/message.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
82
tools/src/process_control.rs
Normal file
82
tools/src/process_control.rs
Normal 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));
|
||||
}
|
||||
Reference in New Issue
Block a user