diff --git a/ffi/examples/process_control.c b/ffi/examples/process_control.c new file mode 100644 index 0000000..3be8c20 --- /dev/null +++ b/ffi/examples/process_control.c @@ -0,0 +1,210 @@ +// Jackson Coxson + +#include "idevice.h" +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) { + // Initialize logger + idevice_init_logger(Debug, Disabled, NULL); + + if (argc < 3) { + fprintf(stderr, "Usage: %s [pairing_file]\n", + argv[0]); + return 1; + } + + const char *device_ip = argv[1]; + const char *bundle_id = argv[2]; + const char *pairing_file = argc > 3 ? argv[3] : "pairing_file.plist"; + + // Create the socket address + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCKDOWN_PORT); + inet_pton(AF_INET, device_ip, &addr.sin_addr); + + // Read pairing file + IdevicePairingFile *pairing = NULL; + IdeviceErrorCode err = idevice_pairing_file_read(pairing_file, &pairing); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to read pairing file: %d\n", err); + return 1; + } + + /***************************************************************** + * CoreDeviceProxy Setup + *****************************************************************/ + printf("=== Setting up CoreDeviceProxy ===\n"); + + // Create TCP provider + TcpProviderHandle *tcp_provider = NULL; + err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing, + "ProcessControlTest", &tcp_provider); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to create TCP provider: %d\n", err); + idevice_pairing_file_free(pairing); + return 1; + } + + // Connect to CoreDeviceProxy + CoreDeviceProxyHandle *core_device = NULL; + err = core_device_proxy_connect_tcp(tcp_provider, &core_device); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err); + tcp_provider_free(tcp_provider); + idevice_pairing_file_free(pairing); + return 1; + } + tcp_provider_free(tcp_provider); + + // Get server RSD port + uint16_t rsd_port; + err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to get server RSD port: %d\n", err); + core_device_proxy_free(core_device); + idevice_pairing_file_free(pairing); + return 1; + } + printf("Server RSD Port: %d\n", rsd_port); + + /***************************************************************** + * Create TCP Tunnel Adapter + *****************************************************************/ + printf("\n=== Creating TCP Tunnel Adapter ===\n"); + + AdapterHandle *adapter = NULL; + err = core_device_proxy_create_tcp_adapter(core_device, &adapter); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to create TCP adapter: %d\n", err); + core_device_proxy_free(core_device); + idevice_pairing_file_free(pairing); + return 1; + } + + // Connect to RSD port + err = adapter_connect(adapter, rsd_port); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to connect to RSD port: %d\n", err); + adapter_free(adapter); + core_device_proxy_free(core_device); + idevice_pairing_file_free(pairing); + return 1; + } + printf("Successfully connected to RSD port\n"); + + /***************************************************************** + * XPC Device Setup + *****************************************************************/ + printf("\n=== Setting up XPC Device ===\n"); + + XPCDeviceAdapterHandle *xpc_device = NULL; + err = xpc_device_new(adapter, &xpc_device); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to create XPC device: %d\n", err); + adapter_free(adapter); + core_device_proxy_free(core_device); + idevice_pairing_file_free(pairing); + return 1; + } + + /***************************************************************** + * Get DVT Service + *****************************************************************/ + printf("\n=== Getting Debug Proxy Service ===\n"); + + XPCServiceHandle *dvt_service = NULL; + err = xpc_device_get_service(xpc_device, "com.apple.instruments.dtservicehub", + &dvt_service); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to get DVT service: %d\n", err); + xpc_device_free(xpc_device); + return 1; + } + printf("Debug Proxy Service Port: %d\n", dvt_service->port); + + /***************************************************************** + * Remote Server Setup + *****************************************************************/ + printf("\n=== Setting up Remote Server ===\n"); + + // Get the adapter back from XPC device + AdapterHandle *debug_adapter = NULL; + err = xpc_device_adapter_into_inner(xpc_device, &debug_adapter); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to extract adapter: %d\n", err); + xpc_service_free(dvt_service); + xpc_device_free(xpc_device); + return 1; + } + + // Connect to debug proxy port + adapter_close(debug_adapter); + err = adapter_connect(debug_adapter, dvt_service->port); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to connect to debug proxy port: %d\n", err); + adapter_free(debug_adapter); + xpc_service_free(dvt_service); + return 1; + } + printf("Successfully connected to debug proxy port\n"); + + // Create RemoteServerClient + RemoteServerAdapterHandle *remote_server = NULL; + err = remote_server_adapter_new(debug_adapter, &remote_server); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to create remote server: %d\n", err); + adapter_free(debug_adapter); + xpc_service_free(dvt_service); + return 1; + } + + /***************************************************************** + * Process Control Test + *****************************************************************/ + printf("\n=== Testing Process Control ===\n"); + + // Create ProcessControlClient + ProcessControlAdapterHandle *process_control = NULL; + err = process_control_new(remote_server, &process_control); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to create process control client: %d\n", err); + remote_server_free(remote_server); + return 1; + } + + // Launch application + uint64_t pid; + err = process_control_launch_app(process_control, bundle_id, NULL, 0, NULL, 0, + true, false, &pid); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to launch app: %d\n", err); + process_control_free(process_control); + remote_server_free(remote_server); + return 1; + } + printf("Successfully launched app with PID: %llu\n", pid); + + // Disable memory limits + err = process_control_disable_memory_limit(process_control, pid); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to disable memory limits: %d\n", err); + } else { + printf("Successfully disabled memory limits\n"); + } + + /***************************************************************** + * Cleanup + *****************************************************************/ + process_control_free(process_control); + remote_server_free(remote_server); + + printf("\nAll tests completed successfully!\n"); + return 0; +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index e7a012a..4cba47b 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -8,7 +8,9 @@ pub mod installation_proxy; pub mod logging; pub mod mounter; mod pairing_file; +pub mod process_control; pub mod provider; +pub mod remote_server; pub mod remotexpc; pub mod usbmuxd; pub mod util; diff --git a/ffi/src/process_control.rs b/ffi/src/process_control.rs new file mode 100644 index 0000000..d8eae35 --- /dev/null +++ b/ffi/src/process_control.rs @@ -0,0 +1,205 @@ +// Jackson Coxson + +use std::ffi::{CStr, c_char}; + +use idevice::{dvt::process_control::ProcessControlClient, tcp::adapter::Adapter}; +use plist::{Dictionary, Value}; + +use crate::{IdeviceErrorCode, RUNTIME, remote_server::RemoteServerAdapterHandle}; + +/// Opaque handle to a ProcessControlClient +pub struct ProcessControlAdapterHandle<'a>(pub ProcessControlClient<'a, Adapter>); + +/// Creates a new ProcessControlClient from a RemoteServerClient +/// +/// # Arguments +/// * [`server`] - The RemoteServerClient to use +/// * [`handle`] - Pointer to store the newly created ProcessControlClient handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `server` must be a valid pointer to a handle allocated by this library +/// `handle` must be a valid pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn process_control_new( + server: *mut RemoteServerAdapterHandle, + handle: *mut *mut ProcessControlAdapterHandle<'static>, +) -> IdeviceErrorCode { + if server.is_null() || handle.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let server = unsafe { &mut (*server).0 }; + let res = RUNTIME.block_on(async move { ProcessControlClient::new(server).await }); + + match res { + Ok(client) => { + let boxed = Box::new(ProcessControlAdapterHandle(client)); + unsafe { *handle = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Frees a ProcessControlClient handle +/// +/// # Arguments +/// * [`handle`] - The handle to free +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library or NULL +#[unsafe(no_mangle)] +pub unsafe extern "C" fn process_control_free(handle: *mut ProcessControlAdapterHandle<'static>) { + if !handle.is_null() { + let _ = unsafe { Box::from_raw(handle) }; + } +} + +/// Launches an application on the device +/// +/// # Arguments +/// * [`handle`] - The ProcessControlClient handle +/// * [`bundle_id`] - The bundle identifier of the app to launch +/// * [`env_vars`] - NULL-terminated array of environment variables (format "KEY=VALUE") +/// * [`arguments`] - NULL-terminated array of arguments +/// * [`start_suspended`] - Whether to start the app suspended +/// * [`kill_existing`] - Whether to kill existing instances of the app +/// * [`pid`] - Pointer to store the process ID of the launched app +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// All pointers must be valid or NULL where appropriate +#[unsafe(no_mangle)] +pub unsafe extern "C" fn process_control_launch_app( + handle: *mut ProcessControlAdapterHandle<'static>, + bundle_id: *const c_char, + env_vars: *const *const c_char, + env_vars_count: usize, + arguments: *const *const c_char, + arguments_count: usize, + start_suspended: bool, + kill_existing: bool, + pid: *mut u64, +) -> IdeviceErrorCode { + if handle.is_null() || bundle_id.is_null() || pid.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let bundle_id = unsafe { CStr::from_ptr(bundle_id) }; + let bundle_id = match bundle_id.to_str() { + Ok(s) => s.to_string(), + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let mut env_dict = Dictionary::new(); + if !env_vars.is_null() { + let env_vars_slice = unsafe { std::slice::from_raw_parts(env_vars, env_vars_count) }; + for &env_var in env_vars_slice { + if !env_var.is_null() { + let env_var = unsafe { CStr::from_ptr(env_var) }; + if let Ok(env_var) = env_var.to_str() { + if let Some((key, value)) = env_var.split_once('=') { + env_dict.insert(key.to_string(), Value::String(value.to_string())); + } + } + } + } + } + + let mut args_dict = Dictionary::new(); + if !arguments.is_null() { + let args_slice = unsafe { std::slice::from_raw_parts(arguments, arguments_count) }; + for (i, &arg) in args_slice.iter().enumerate() { + if !arg.is_null() { + let arg = unsafe { CStr::from_ptr(arg) }; + if let Ok(arg) = arg.to_str() { + args_dict.insert(i.to_string(), Value::String(arg.to_string())); + } + } + } + } + + let client = unsafe { &mut (*handle).0 }; + let res = RUNTIME.block_on(async move { + client + .launch_app( + bundle_id, + Some(env_dict), + Some(args_dict), + start_suspended, + kill_existing, + ) + .await + }); + + match res { + Ok(p) => { + unsafe { *pid = p }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Kills a running process +/// +/// # Arguments +/// * [`handle`] - The ProcessControlClient handle +/// * [`pid`] - The process ID to kill +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library +#[unsafe(no_mangle)] +pub unsafe extern "C" fn process_control_kill_app( + handle: *mut ProcessControlAdapterHandle<'static>, + pid: u64, +) -> IdeviceErrorCode { + if handle.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let client = unsafe { &mut (*handle).0 }; + let res = RUNTIME.block_on(async move { client.kill_app(pid).await }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Disables memory limits for a process +/// +/// # Arguments +/// * [`handle`] - The ProcessControlClient handle +/// * [`pid`] - The process ID to modify +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library +#[unsafe(no_mangle)] +pub unsafe extern "C" fn process_control_disable_memory_limit( + handle: *mut ProcessControlAdapterHandle<'static>, + pid: u64, +) -> IdeviceErrorCode { + if handle.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let client = unsafe { &mut (*handle).0 }; + let res = RUNTIME.block_on(async move { client.disable_memory_limit(pid).await }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} diff --git a/ffi/src/remote_server.rs b/ffi/src/remote_server.rs new file mode 100644 index 0000000..f39615c --- /dev/null +++ b/ffi/src/remote_server.rs @@ -0,0 +1,90 @@ +// Jackson Coxson + +use crate::core_device_proxy::AdapterHandle; +use crate::{IdeviceErrorCode, RUNTIME}; +use idevice::IdeviceError; +use idevice::dvt::remote_server::RemoteServerClient; +use idevice::tcp::adapter::Adapter; + +/// Opaque handle to a RemoteServerClient +pub struct RemoteServerAdapterHandle(pub RemoteServerClient); + +/// Creates a new RemoteServerClient from a ReadWrite connection +/// +/// # Arguments +/// * [`connection`] - The connection to use for communication +/// * [`handle`] - Pointer to store the newly created RemoteServerClient handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `connection` must be a valid pointer to a handle allocated by this library +/// `handle` must be a valid pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn remote_server_adapter_new( + adapter: *mut crate::core_device_proxy::AdapterHandle, + handle: *mut *mut RemoteServerAdapterHandle, +) -> IdeviceErrorCode { + if adapter.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let connection = unsafe { Box::from_raw(adapter) }; + + let res: Result, IdeviceError> = RUNTIME.block_on(async move { + let mut client = RemoteServerClient::new(connection.0); + client.read_message(0).await?; // Until Message has bindings, we'll do the first read + Ok(client) + }); + + match res { + Ok(client) => { + let boxed = Box::new(RemoteServerAdapterHandle(client)); + unsafe { *handle = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Frees a RemoteServerClient handle +/// +/// # Arguments +/// * [`handle`] - The handle to free +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library or NULL +#[unsafe(no_mangle)] +pub unsafe extern "C" fn remote_server_free(handle: *mut RemoteServerAdapterHandle) { + if !handle.is_null() { + let _ = unsafe { Box::from_raw(handle) }; + } +} + +/// Returns the underlying connection from a RemoteServerClient +/// +/// # Arguments +/// * [`handle`] - The handle to get the connection from +/// * [`connection`] - The newly allocated ConnectionHandle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library or NULL, and never used again +#[unsafe(no_mangle)] +pub unsafe extern "C" fn remote_server_adapter_into_inner( + handle: *mut RemoteServerAdapterHandle, + connection: *mut *mut AdapterHandle, +) -> IdeviceErrorCode { + if handle.is_null() || connection.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let server = unsafe { Box::from_raw(handle) }; + let connection_obj = server.0.into_inner(); + let boxed = Box::new(AdapterHandle(connection_obj)); + unsafe { *connection = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess +}