diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 14cf92f..185596e 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -23,6 +23,8 @@ pub mod logging; pub mod misagent; #[cfg(feature = "mobile_image_mounter")] pub mod mobile_image_mounter; +#[cfg(feature = "os_trace_relay")] +pub mod os_trace_relay; mod pairing_file; #[cfg(feature = "dvt")] pub mod process_control; diff --git a/ffi/src/os_trace_relay.rs b/ffi/src/os_trace_relay.rs new file mode 100644 index 0000000..79d9ce6 --- /dev/null +++ b/ffi/src/os_trace_relay.rs @@ -0,0 +1,217 @@ +use std::os::raw::c_char; +use std::ffi::CString; + +use idevice::{os_trace_relay::OsTraceRelayClient, IdeviceError, IdeviceService}; + +use crate::{ + provider::TcpProviderHandle, IdeviceErrorCode, RUNTIME +}; + +pub struct OsTraceRelayClientHandle(pub OsTraceRelayClient); +pub struct OsTraceRelayReceiverHandle(pub idevice::os_trace_relay::OsTraceRelayReceiver); + +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OsTraceLog { + pub pid: u32, + pub timestamp: i64, + pub level: u8, + pub image_name: *const c_char, + pub filename: *const c_char, + pub message: *const c_char, + pub label: *const SyslogLabel, +} + +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SyslogLabel { + pub subsystem: *const c_char, + pub category: *const c_char, +} + +#[unsafe(no_mangle)] +pub extern "C" fn os_trace_relay_connect_tcp( + provider: *mut TcpProviderHandle, + client: *mut *mut OsTraceRelayClientHandle +) -> IdeviceErrorCode { + if provider.is_null() { + log::error!("Null pointer provided"); + return IdeviceErrorCode::InvalidArg; + } + + let res: Result = RUNTIME.block_on(async move { + let provider_box = unsafe { Box::from_raw(provider) }; + + let provider_ref = &provider_box.0; + + let result = OsTraceRelayClient::connect(provider_ref).await; + + std::mem::forget(provider_box); + result + }); + + match res { + Ok(c) => { + let boxed = Box::new(OsTraceRelayClientHandle(c)); + unsafe { *client = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => { + let _ = unsafe { Box::from_raw(provider) }; + e.into() + } + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn os_trace_relay_free( + handle: *mut OsTraceRelayClientHandle +) { + if !handle.is_null() { + log::debug!("Freeing os trace relay client"); + let _ = unsafe { Box::from_raw(handle) }; + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn os_trace_relay_start_trace( + client: *mut OsTraceRelayClientHandle, + receiver: *mut *mut OsTraceRelayReceiverHandle, + pid: *const u32, +) -> IdeviceErrorCode { + if receiver.is_null() || client.is_null() { + log::error!("Null pointer provided"); + return IdeviceErrorCode::InvalidArg; + } + + let pid_option = if pid.is_null() { + None + } else { + Some(unsafe { *pid }) + }; + + let client_owned = unsafe { Box::from_raw(client) }; + + let res= RUNTIME.block_on(async { + client_owned + .0 + .start_trace(pid_option) + .await + }); + + match res { + Ok(relay) => { + let boxed = Box::new(OsTraceRelayReceiverHandle(relay)); + unsafe { *receiver = Box::into_raw(boxed) }; + + IdeviceErrorCode::IdeviceSuccess + }, + Err(e) => e.into(), + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn os_trace_relay_receiver_free( + handle: *mut OsTraceRelayReceiverHandle +) { + if !handle.is_null() { + log::debug!("Freeing syslog relay client"); + let _ = unsafe { Box::from_raw(handle) }; + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn os_trace_relay_get_pid_list( + client: *mut OsTraceRelayClientHandle, + list: *mut *mut Vec, +) -> IdeviceErrorCode { + let res = RUNTIME.block_on(async { + unsafe { &mut *client } + .0 + .get_pid_list() + .await + }); + + match res { + Ok(r) => { + unsafe { *list = Box::into_raw(Box::new(r)) }; + IdeviceErrorCode::IdeviceSuccess + }, + Err(e) => e.into(), + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn os_trace_relay_next( + client: *mut OsTraceRelayReceiverHandle, + log: *mut *mut OsTraceLog +) -> IdeviceErrorCode { + if client.is_null() { + log::error!("Null pointer provided"); + return IdeviceErrorCode::InvalidArg; + } + + let res = RUNTIME.block_on(async { + unsafe { &mut *client } + .0 + .next() + .await + }); + + match res { + Ok(r) => { + let log_entry = Box::new(OsTraceLog { + pid: r.pid, + timestamp: r.timestamp.and_utc().timestamp(), + level: r.level as u8, + image_name: CString::new(r.image_name).unwrap().into_raw(), + filename: CString::new(r.filename).unwrap().into_raw(), + message: CString::new(r.message).unwrap().into_raw(), + label: if let Some(label) = r.label { + Box::into_raw(Box::new(SyslogLabel { + subsystem: CString::new(label.subsystem).unwrap().into_raw(), + category: CString::new(label.category).unwrap().into_raw(), + })) + } else { + std::ptr::null() + }, + }); + + unsafe { *log = Box::into_raw(log_entry) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn os_trace_relay_free_log(log: *mut OsTraceLog) { + if !log.is_null() { + unsafe { + if !(*log).image_name.is_null() { + let _ = CString::from_raw((*log).image_name as *mut c_char); + } + if !(*log).filename.is_null() { + let _ = CString::from_raw((*log).filename as *mut c_char); + } + if !(*log).message.is_null() { + let _ = CString::from_raw((*log).message as *mut c_char); + } + if !(*log).label.is_null() { + let label = &*(*log).label; + + if !label.subsystem.is_null() { + let _ = CString::from_raw(label.subsystem as *mut c_char); + } + + if !label.category.is_null() { + let _ = CString::from_raw(label.category as *mut c_char); + } + + let _ = Box::from_raw((*log).label as *mut SyslogLabel); + } + + let _ = Box::from_raw(log); + } + } +} \ No newline at end of file diff --git a/idevice/src/services/crashreportcopymobile.rs b/idevice/src/services/crashreportcopymobile.rs index 329b41a..bcd4cd2 100644 --- a/idevice/src/services/crashreportcopymobile.rs +++ b/idevice/src/services/crashreportcopymobile.rs @@ -79,13 +79,17 @@ impl CrashReportCopyMobileClient { /// Lists crash report files in the root of the crash logs directory. /// + /// # Arguments + /// * `dir_path` - The directory to pull logs from. Default is / + /// /// # Returns /// A list of filenames. /// /// # Errors /// Returns `IdeviceError` if listing the directory fails. - pub async fn ls(&mut self) -> Result, IdeviceError> { - let mut res = self.afc_client.list_dir("/").await?; + pub async fn ls(&mut self, dir_path: Option<&str>) -> Result, IdeviceError> { + let path = dir_path.unwrap_or("/"); + let mut res = self.afc_client.list_dir(path).await?; if res.len() > 2 { if &res[0] == "." { res.swap_remove(0); diff --git a/idevice/src/services/os_trace_relay.rs b/idevice/src/services/os_trace_relay.rs index 822369a..4d9c990 100644 --- a/idevice/src/services/os_trace_relay.rs +++ b/idevice/src/services/os_trace_relay.rs @@ -66,19 +66,19 @@ pub struct OsTraceRelayReceiver { #[derive(Debug, Clone, PartialEq, Eq)] pub struct OsTraceLog { - pid: u32, - timestamp: NaiveDateTime, - level: LogLevel, - image_name: String, - filename: String, - message: String, - label: Option, + pub pid: u32, + pub timestamp: NaiveDateTime, + pub level: LogLevel, + pub image_name: String, + pub filename: String, + pub message: String, + pub label: Option, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct SyslogLabel { - subsystem: String, - category: String, + pub subsystem: String, + pub category: String, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/tools/src/crash_logs.rs b/tools/src/crash_logs.rs index 8c545bc..cda1535 100644 --- a/tools/src/crash_logs.rs +++ b/tools/src/crash_logs.rs @@ -43,7 +43,8 @@ async fn main() { Command::new("pull") .about("Pulls a log") .arg(Arg::new("path").required(true).index(1)) - .arg(Arg::new("save").required(true).index(2)), + .arg(Arg::new("save").required(true).index(2)) + .arg(Arg::new("dir").required(false).index(3)), ) .get_matches(); @@ -68,8 +69,12 @@ async fn main() { .await .expect("Unable to connect to misagent"); - if let Some(_matches) = matches.subcommand_matches("list") { - let res = crash_client.ls().await.expect("Failed to read dir"); + if let Some(matches) = matches.subcommand_matches("list") { + let dir_path: Option<&String> = matches.get_one("dir"); + let res = crash_client + .ls(dir_path.map(|x| x.as_str())) + .await + .expect("Failed to read dir"); println!("{res:#?}"); } else if matches.subcommand_matches("flush").is_some() { flush_reports(&*provider).await.expect("Failed to flush"); diff --git a/tools/src/instproxy.rs b/tools/src/instproxy.rs index 8c7b7b4..e5f1980 100644 --- a/tools/src/instproxy.rs +++ b/tools/src/instproxy.rs @@ -39,6 +39,11 @@ async fn main() { .subcommand(Command::new("lookup").about("Gets the apps on the device")) .subcommand(Command::new("browse").about("Browses the apps on the device")) .subcommand(Command::new("check_capabilities").about("Check the capabilities")) + .subcommand( + Command::new("install") + .about("Install an app in the AFC jail") + .arg(Arg::new("path")), + ) .get_matches(); if matches.get_flag("about") { @@ -78,6 +83,26 @@ async fn main() { .check_capabilities_match(Vec::new(), None) .await .expect("check failed"); + } else if let Some(matches) = matches.subcommand_matches("install") { + let path: &String = match matches.get_one("path") { + Some(p) => p, + None => { + eprintln!("No path passed, pass -h for help"); + return; + } + }; + + instproxy_client + .install_with_callback( + path, + None, + async |(percentage, _)| { + println!("Installing: {percentage}"); + }, + (), + ) + .await + .expect("Failed to install") } else { eprintln!("Invalid usage, pass -h for help"); }