Implement syslog relay

This commit is contained in:
Jackson Coxson
2025-05-09 16:19:09 -06:00
parent b60e3dc0fb
commit 1556aaf1d3
8 changed files with 189 additions and 4 deletions

2
Cargo.lock generated
View File

@@ -1184,7 +1184,7 @@ dependencies = [
[[package]] [[package]]
name = "idevice" name = "idevice"
version = "0.1.30" version = "0.1.31"
dependencies = [ dependencies = [
"async-recursion", "async-recursion",
"base64", "base64",

View File

@@ -30,12 +30,14 @@ To keep dependency bloat and compile time down, everything is contained in featu
| `debug_proxy` | Send GDB commands to the device.| | `debug_proxy` | Send GDB commands to the device.|
| `dvt` | Access Apple developer tools (e.g. Instruments).| | `dvt` | Access Apple developer tools (e.g. Instruments).|
| `heartbeat` | Maintain a heartbeat connection.| | `heartbeat` | Maintain a heartbeat connection.|
| `house_arrest` | Manage files in app containers |
| `installation_proxy` | Manage app installation and uninstallation.| | `installation_proxy` | Manage app installation and uninstallation.|
| `springboardservices` | Control SpringBoard (e.g. UI interactions). Partial support.| | `springboardservices` | Control SpringBoard (e.g. UI interactions). Partial support.|
| `misagent` | Manage provisioning profiles on the device.| | `misagent` | Manage provisioning profiles on the device.|
| `mobile_image_mounter` | Manage DDI images.| | `mobile_image_mounter` | Manage DDI images.|
| `location_simulation` | Simulate GPS locations on the device.| | `location_simulation` | Simulate GPS locations on the device.|
| `pair` | Pair the device.| | `pair` | Pair the device.|
| `syslog_relay` | Relay system logs from the device |
| `tcp` | Connect to devices over TCP.| | `tcp` | Connect to devices over TCP.|
| `tunnel_tcp_stack` | Naive in-process TCP stack for `core_device_proxy`.| | `tunnel_tcp_stack` | Naive in-process TCP stack for `core_device_proxy`.|
| `tss` | Make requests to Apples TSS servers. Partial support.| | `tss` | Make requests to Apples TSS servers. Partial support.|
@@ -53,11 +55,9 @@ Implement the following:
- companion_proxy - companion_proxy
- diagnostics - diagnostics
- house_arrest
- mobilebackup2 - mobilebackup2
- notification_proxy - notification_proxy
- screenshot - screenshot
- syslog_relay
- webinspector - webinspector
As this project is done in my free time within my busy schedule, there As this project is done in my free time within my busy schedule, there

View File

@@ -2,7 +2,7 @@
name = "idevice" name = "idevice"
description = "A Rust library to interact with services on iOS devices." description = "A Rust library to interact with services on iOS devices."
authors = ["Jackson Coxson"] authors = ["Jackson Coxson"]
version = "0.1.30" version = "0.1.31"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
documentation = "https://docs.rs/idevice" documentation = "https://docs.rs/idevice"
@@ -32,6 +32,7 @@ chrono = { version = "0.4.40", optional = true, default-features = false }
serde_json = { version = "1", optional = true } serde_json = { version = "1", optional = true }
json = { version = "0.12", optional = true } json = { version = "0.12", optional = true }
byteorder = { version = "1.5", optional = true } byteorder = { version = "1.5", optional = true }
bytes = { version = "1.10", optional = true }
reqwest = { version = "0.12", features = [ reqwest = { version = "0.12", features = [
"json", "json",
@@ -68,6 +69,7 @@ misagent = []
mobile_image_mounter = ["dep:sha2"] mobile_image_mounter = ["dep:sha2"]
location_simulation = [] location_simulation = []
pair = ["chrono/default", "dep:sha2", "dep:rsa", "dep:x509-cert"] pair = ["chrono/default", "dep:sha2", "dep:rsa", "dep:x509-cert"]
syslog_relay = ["dep:bytes"]
tcp = ["tokio/net"] tcp = ["tokio/net"]
tunnel_tcp_stack = ["dep:rand", "dep:futures", "tokio/fs", "tokio/sync"] tunnel_tcp_stack = ["dep:rand", "dep:futures", "tokio/fs", "tokio/sync"]
tss = ["dep:uuid", "dep:reqwest"] tss = ["dep:uuid", "dep:reqwest"]
@@ -101,6 +103,7 @@ full = [
"tss", "tss",
"tunneld", "tunneld",
"springboardservices", "springboardservices",
"syslog_relay",
] ]
[package.metadata.docs.rs] [package.metadata.docs.rs]

View File

@@ -295,6 +295,38 @@ impl Idevice {
} }
} }
#[cfg(feature = "syslog_relay")]
async fn read_until_delim(
&mut self,
delimiter: &[u8],
) -> Result<Option<bytes::BytesMut>, IdeviceError> {
if let Some(socket) = &mut self.socket {
let mut buffer = bytes::BytesMut::with_capacity(1024);
let mut temp = [0u8; 1024];
loop {
let n = socket.read(&mut temp).await?;
if n == 0 {
if buffer.is_empty() {
return Ok(None); // EOF and no data
} else {
return Ok(Some(buffer)); // EOF but return partial data
}
}
buffer.extend_from_slice(&temp[..n]);
if let Some(pos) = buffer.windows(delimiter.len()).position(|w| w == delimiter) {
let mut line = buffer.split_to(pos + delimiter.len());
line.truncate(line.len() - delimiter.len()); // remove delimiter
return Ok(Some(line));
}
}
} else {
Err(IdeviceError::NoEstablishedConnection)
}
}
/// Upgrades the connection to TLS using device pairing credentials /// Upgrades the connection to TLS using device pairing credentials
/// ///
/// # Arguments /// # Arguments

View File

@@ -23,5 +23,7 @@ pub mod misagent;
pub mod mobile_image_mounter; pub mod mobile_image_mounter;
#[cfg(feature = "springboardservices")] #[cfg(feature = "springboardservices")]
pub mod springboardservices; pub mod springboardservices;
#[cfg(feature = "syslog_relay")]
pub mod syslog_relay;
#[cfg(feature = "xpc")] #[cfg(feature = "xpc")]
pub mod xpc; pub mod xpc;

View File

@@ -0,0 +1,78 @@
//! iOS Device SyslogRelay Service Abstraction
use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService};
/// Client for interacting with the iOS device SyslogRelay service
pub struct SyslogRelayClient {
/// The underlying device connection with established SyslogRelay service
pub idevice: Idevice,
}
impl IdeviceService for SyslogRelayClient {
/// Returns the SyslogRelay service name as registered with lockdownd
fn service_name() -> &'static str {
"com.apple.syslog_relay"
}
/// Establishes a connection to the SyslogRelay service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `SyslogRelayClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connects to lockdownd service
/// 2. Starts a lockdown session
/// 3. Requests the SyslogRelay service port
/// 4. Establishes connection to the SyslogRelay port
/// 5. Optionally starts TLS if required by service
async fn connect(
provider: &dyn crate::provider::IdeviceProvider,
) -> Result<Self, IdeviceError> {
let mut lockdown = LockdownClient::connect(provider).await?;
lockdown
.start_session(&provider.get_pairing_file().await?)
.await?;
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
let mut idevice = provider.connect(port).await?;
if ssl {
idevice
.start_session(&provider.get_pairing_file().await?)
.await?;
}
Ok(Self { idevice })
}
}
impl SyslogRelayClient {
/// Creates a new SyslogRelay client from an existing device connection
///
/// # Arguments
/// * `idevice` - Pre-established device connection
pub fn new(idevice: Idevice) -> Self {
Self { idevice }
}
/// Get the next log from the relay
///
/// # Returns
/// A string containing the log
///
/// # Errors
/// UnexpectedResponse if the service sends an EOF
pub async fn next(&mut self) -> Result<String, IdeviceError> {
let res = self.idevice.read_until_delim(b"\n\x00").await?;
match res {
Some(res) => Ok(String::from_utf8_lossy(&res).to_string()),
None => Err(IdeviceError::UnexpectedResponse),
}
}
}

View File

@@ -73,6 +73,10 @@ path = "src/amfi.rs"
name = "pair" name = "pair"
path = "src/pair.rs" path = "src/pair.rs"
[[bin]]
name = "syslog_relay"
path = "src/syslog_relay.rs"
[dependencies] [dependencies]
idevice = { path = "../idevice", features = ["full"] } idevice = { path = "../idevice", features = ["full"] }
tokio = { version = "1.43", features = ["io-util", "macros", "time", "full"] } tokio = { version = "1.43", features = ["io-util", "macros", "time", "full"] }

66
tools/src/syslog_relay.rs Normal file
View File

@@ -0,0 +1,66 @@
// Jackson Coxson
use clap::{Arg, Command};
use idevice::{syslog_relay::SyslogRelayClient, IdeviceService};
mod common;
#[tokio::main]
async fn main() {
env_logger::init();
let matches = Command::new("syslog_relay")
.about("Relay system logs")
.arg(
Arg::new("host")
.long("host")
.value_name("HOST")
.help("IP address of the device"),
)
.arg(
Arg::new("pairing_file")
.long("pairing-file")
.value_name("PATH")
.help("Path to the pairing file"),
)
.arg(
Arg::new("udid")
.value_name("UDID")
.help("UDID of the device (overrides host/pairing file)"),
)
.arg(
Arg::new("about")
.long("about")
.help("Show about information")
.action(clap::ArgAction::SetTrue),
)
.get_matches();
if matches.get_flag("about") {
println!("Relay logs on the device");
println!("Copyright (c) 2025 Jackson Coxson");
return;
}
let udid = matches.get_one::<String>("udid");
let host = matches.get_one::<String>("host");
let pairing_file = matches.get_one::<String>("pairing_file");
let provider = match common::get_provider(udid, host, pairing_file, "misagent-jkcoxson").await {
Ok(p) => p,
Err(e) => {
eprintln!("{e}");
return;
}
};
let mut log_client = SyslogRelayClient::connect(&*provider)
.await
.expect("Unable to connect to misagent");
loop {
println!(
"{}",
log_client.next().await.expect("Failed to read next log")
);
}
}