mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
Implement process control
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -923,6 +923,7 @@ dependencies = [
|
|||||||
"env_logger",
|
"env_logger",
|
||||||
"idevice",
|
"idevice",
|
||||||
"log",
|
"log",
|
||||||
|
"ns-keyed-archive",
|
||||||
"plist",
|
"plist",
|
||||||
"sha2",
|
"sha2",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ pub struct AuxHeader {
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Aux {
|
pub struct Aux {
|
||||||
header: AuxHeader,
|
pub header: AuxHeader,
|
||||||
values: Vec<AuxValue>,
|
pub values: Vec<AuxValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
@@ -166,11 +166,7 @@ impl Aux {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
let buffer_size = if values_payload.len() > 496 {
|
let buffer_size = 496_u32;
|
||||||
8688_u32
|
|
||||||
} else {
|
|
||||||
496
|
|
||||||
};
|
|
||||||
res.extend_from_slice(&buffer_size.to_le_bytes()); // TODO: find what
|
res.extend_from_slice(&buffer_size.to_le_bytes()); // TODO: find what
|
||||||
// this means and how to actually serialize it
|
// this means and how to actually serialize it
|
||||||
// go-ios just uses 496
|
// go-ios just uses 496
|
||||||
@@ -183,6 +179,13 @@ impl Aux {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AuxValue {
|
||||||
|
// Returns an array AuxType
|
||||||
|
pub fn archived_value(v: impl Into<plist::Value>) -> Self {
|
||||||
|
Self::Array(ns_keyed_archive::encode::encode_to_bytes(v.into()).expect("Failed to encode"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MessageHeader {
|
impl MessageHeader {
|
||||||
/// Creates a new header. Note that during serialization, the length will be updated
|
/// Creates a new header. Note that during serialization, the length will be updated
|
||||||
pub fn new(
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
use crate::IdeviceError;
|
use log::warn;
|
||||||
|
use plist::{Dictionary, Value};
|
||||||
|
|
||||||
|
use crate::{dvt::message::AuxValue, IdeviceError};
|
||||||
|
|
||||||
use super::remote_server::{Channel, RemoteServerClient};
|
use super::remote_server::{Channel, RemoteServerClient};
|
||||||
|
|
||||||
@@ -16,4 +19,104 @@ impl<'a> ProcessControlClient<'a> {
|
|||||||
|
|
||||||
Ok(Self { channel })
|
Ok(Self { channel })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn launch_app(
|
||||||
|
&mut self,
|
||||||
|
bundle_id: impl Into<String>,
|
||||||
|
env_vars: Option<Dictionary>,
|
||||||
|
arguments: Option<Dictionary>,
|
||||||
|
start_suspended: bool,
|
||||||
|
kill_existing: bool,
|
||||||
|
) -> Result<u64, IdeviceError> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,14 +28,23 @@ pub struct Channel<'a> {
|
|||||||
|
|
||||||
impl RemoteServerClient {
|
impl RemoteServerClient {
|
||||||
pub fn new(idevice: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
|
pub fn new(idevice: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
|
||||||
|
let mut channels = HashMap::new();
|
||||||
|
channels.insert(0, VecDeque::new());
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
idevice,
|
idevice,
|
||||||
current_message: 0,
|
current_message: 0,
|
||||||
new_channel: 1,
|
new_channel: 1,
|
||||||
channels: HashMap::new(),
|
channels,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn root_channel(&mut self) -> Channel {
|
||||||
|
Channel {
|
||||||
|
client: self,
|
||||||
|
channel: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn make_channel(
|
pub async fn make_channel(
|
||||||
&mut self,
|
&mut self,
|
||||||
identifier: impl Into<String>,
|
identifier: impl Into<String>,
|
||||||
@@ -50,19 +59,22 @@ impl RemoteServerClient {
|
|||||||
.expect("Failed to encode"),
|
.expect("Failed to encode"),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
self.send_message(
|
|
||||||
0,
|
let mut root = self.root_channel();
|
||||||
|
root.call_method(
|
||||||
Some("_requestChannelWithCode:identifier:"),
|
Some("_requestChannelWithCode:identifier:"),
|
||||||
Some(args),
|
Some(args),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let res = self.read_message(0).await?;
|
let res = root.read_message().await?;
|
||||||
if res.data.is_some() {
|
if res.data.is_some() {
|
||||||
return Err(IdeviceError::UnexpectedResponse);
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.channels.insert(code, VecDeque::new());
|
||||||
|
|
||||||
self.build_channel(code)
|
self.build_channel(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +85,7 @@ impl RemoteServerClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_message(
|
pub async fn call_method(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel: u32,
|
channel: u32,
|
||||||
data: Option<impl Into<plist::Value>>,
|
data: Option<impl Into<plist::Value>>,
|
||||||
@@ -89,11 +101,6 @@ impl RemoteServerClient {
|
|||||||
|
|
||||||
let message = Message::new(mheader, pheader, aux, data);
|
let message = Message::new(mheader, pheader, aux, data);
|
||||||
debug!("Sending message: {message:#?}");
|
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?;
|
self.idevice.write_all(&message.serialize()).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -133,14 +140,14 @@ impl Channel<'_> {
|
|||||||
self.client.read_message(self.channel).await
|
self.client.read_message(self.channel).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_message(
|
pub async fn call_method(
|
||||||
&mut self,
|
&mut self,
|
||||||
data: Option<impl Into<plist::Value>>,
|
method: Option<impl Into<plist::Value>>,
|
||||||
args: Option<Vec<AuxValue>>,
|
args: Option<Vec<AuxValue>>,
|
||||||
expect_reply: bool,
|
expect_reply: bool,
|
||||||
) -> Result<(), IdeviceError> {
|
) -> Result<(), IdeviceError> {
|
||||||
self.client
|
self.client
|
||||||
.send_message(self.channel, data, args, expect_reply)
|
.call_method(self.channel, method, args, expect_reply)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,10 @@ pub enum IdeviceError {
|
|||||||
#[error("unknown channel")]
|
#[error("unknown channel")]
|
||||||
UnknownChannel(u32),
|
UnknownChannel(u32),
|
||||||
|
|
||||||
|
#[cfg(feature = "dvt")]
|
||||||
|
#[error("disable memory limit failed")]
|
||||||
|
DisableMemoryLimitFailed,
|
||||||
|
|
||||||
#[error("not enough bytes, expected {1}, got {0}")]
|
#[error("not enough bytes, expected {1}, got {0}")]
|
||||||
NotEnoughBytes(usize, usize),
|
NotEnoughBytes(usize, usize),
|
||||||
|
|
||||||
|
|||||||
@@ -10,23 +10,6 @@ pub fn plist_to_xml_bytes(p: &plist::Dictionary) -> Vec<u8> {
|
|||||||
writer.into_inner().unwrap()
|
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 {
|
pub fn pretty_print_plist(p: &Value) -> String {
|
||||||
print_plist(p, 0)
|
print_plist(p, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ path = "src/idevice_id.rs"
|
|||||||
name = "process_control"
|
name = "process_control"
|
||||||
path = "src/process_control.rs"
|
path = "src/process_control.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "dvt_packet_parser"
|
||||||
|
path = "src/dvt_packet_parser.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "remotexpc"
|
name = "remotexpc"
|
||||||
path = "src/remotexpc.rs"
|
path = "src/remotexpc.rs"
|
||||||
@@ -59,3 +63,4 @@ sha2 = { version = "0.10" }
|
|||||||
ureq = { version = "3" }
|
ureq = { version = "3" }
|
||||||
clap = { version = "4.5" }
|
clap = { version = "4.5" }
|
||||||
plist = { version = "1.7" }
|
plist = { version = "1.7" }
|
||||||
|
ns-keyed-archive = "0.1.2"
|
||||||
|
|||||||
33
tools/src/dvt_packet_parser.rs
Normal file
33
tools/src/dvt_packet_parser.rs
Normal file
@@ -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:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ async fn main() {
|
|||||||
Arg::new("udid")
|
Arg::new("udid")
|
||||||
.value_name("UDID")
|
.value_name("UDID")
|
||||||
.help("UDID of the device (overrides host/pairing file)")
|
.help("UDID of the device (overrides host/pairing file)")
|
||||||
.index(1),
|
.index(2),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("about")
|
Arg::new("about")
|
||||||
@@ -29,15 +29,24 @@ async fn main() {
|
|||||||
.help("Show about information")
|
.help("Show about information")
|
||||||
.action(clap::ArgAction::SetTrue),
|
.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();
|
.get_matches();
|
||||||
|
|
||||||
if matches.get_flag("about") {
|
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");
|
println!("Copyright (c) 2025 Jackson Coxson");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let udid = matches.get_one::<String>("udid");
|
let udid = matches.get_one::<String>("udid");
|
||||||
|
let bundle_id = matches
|
||||||
|
.get_one::<String>("bundle_id")
|
||||||
|
.expect("No bundle ID specified");
|
||||||
|
|
||||||
let socket = SocketAddr::new(
|
let socket = SocketAddr::new(
|
||||||
IpAddr::from_str("127.0.0.1").unwrap(),
|
IpAddr::from_str("127.0.0.1").unwrap(),
|
||||||
@@ -80,7 +89,17 @@ async fn main() {
|
|||||||
let mut rs_client =
|
let mut rs_client =
|
||||||
idevice::dvt::remote_server::RemoteServerClient::new(Box::new(stream)).unwrap();
|
idevice::dvt::remote_server::RemoteServerClient::new(Box::new(stream)).unwrap();
|
||||||
rs_client.read_message(0).await.expect("no read??");
|
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
|
.await
|
||||||
.unwrap();
|
.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}");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user