From 80e502c94da63094979ab0d136a0ad1f828d8e87 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Wed, 26 Mar 2025 10:54:27 -0600 Subject: [PATCH] RemoteXPC bindings --- ffi/examples/remotexpc.c | 204 +++++++++++++++++++++++++++ ffi/src/lib.rs | 1 + ffi/src/remotexpc.rs | 288 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 493 insertions(+) create mode 100644 ffi/examples/remotexpc.c create mode 100644 ffi/src/remotexpc.rs diff --git a/ffi/examples/remotexpc.c b/ffi/examples/remotexpc.c new file mode 100644 index 0000000..2efdc5e --- /dev/null +++ b/ffi/examples/remotexpc.c @@ -0,0 +1,204 @@ +// Jackson Coxson + +#include "idevice.h" +#include +#include +#include +#include +#include +#include + +void print_service_details(XPCServiceHandle *service) { + printf(" Service Details:\n"); + printf(" Entitlement: %s\n", service->entitlement); + printf(" Port: %d\n", service->port); + printf(" Uses Remote XPC: %s\n", + service->uses_remote_xpc ? "true" : "false"); + printf(" Service Version: %lld\n", service->service_version); + + if (service->features_count > 0) { + printf(" Features:\n"); + for (size_t i = 0; i < service->features_count; i++) { + printf(" - %s\n", service->features[i]); + } + } +} + +int main() { + // Initialize logger + idevice_init_logger(Debug, Disabled, NULL); + + // Create the socket address (replace with your device's IP) + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCKDOWN_PORT); + inet_pton(AF_INET, "10.7.0.2", &addr.sin_addr); + + // Read pairing file (replace with your pairing file path) + IdevicePairingFile *pairing_file = NULL; + IdeviceErrorCode err = + idevice_pairing_file_read("pairing_file.plist", &pairing_file); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to read pairing file: %d\n", err); + return 1; + } + + /***************************************************************** + * TCP Provider and CoreDeviceProxy Test + *****************************************************************/ + printf("=== Testing TCP Provider and CoreDeviceProxy ===\n"); + + // Create TCP provider + TcpProviderHandle *tcp_provider = NULL; + err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, + "CoreDeviceProxyTest", &tcp_provider); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to create TCP provider: %d\n", err); + idevice_pairing_file_free(pairing_file); + 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); + return 1; + } + tcp_provider_free(tcp_provider); + + // Get client parameters + uint16_t mtu; + char *address = NULL; + char *netmask = NULL; + err = core_device_proxy_get_client_parameters(core_device, &mtu, &address, + &netmask); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to get client parameters: %d\n", err); + core_device_proxy_free(core_device); + return 1; + } + printf("Client Parameters:\n"); + printf(" MTU: %d\n", mtu); + printf(" Address: %s\n", address); + printf(" Netmask: %s\n", netmask); + idevice_string_free(address); + idevice_string_free(netmask); + + // Get server address + char *server_address = NULL; + err = core_device_proxy_get_server_address(core_device, &server_address); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to get server address: %d\n", err); + core_device_proxy_free(core_device); + return 1; + } + printf("Server Address: %s\n", server_address); + idevice_string_free(server_address); + + // 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); + return 1; + } + printf("Server RSD Port: %d\n", rsd_port); + + // Create TCP tunnel adapter + 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); + } else { + printf("Successfully created TCP tunnel adapter\n"); + } + err = adapter_connect(adapter, rsd_port); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to connect to RSD port: %d\n", err); + } else { + printf("Successfully connected to RSD port\n"); + } + + /***************************************************************** + * XPC Device Test + *****************************************************************/ + printf("\n=== Testing XPC Device ===\n"); + + // Create XPC device + XPCDeviceAdapterHandle *xpc_device = NULL; + err = xpc_device_new(adapter, &xpc_device); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to create XPC device: %d\n", err); + core_device_proxy_free(core_device); + return 1; + } + + // List all services + char **service_names = NULL; + size_t service_count = 0; + err = + xpc_device_get_service_names(xpc_device, &service_names, &service_count); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to get service names: %d\n", err); + xpc_device_free(xpc_device); + return 1; + } + + printf("Available Services (%zu):\n", service_count); + for (size_t i = 0; i < service_count; i++) { + printf("- %s\n", service_names[i]); + + // Get service details for each service + XPCServiceHandle *service = NULL; + err = xpc_device_get_service(xpc_device, service_names[i], &service); + if (err == IdeviceSuccess) { + print_service_details(service); + xpc_service_free(service); + } else { + printf(" Failed to get service details: %d\n", err); + } + } + xpc_device_free_service_names(service_names, service_count); + + // Test getting a specific service + const char *test_service_name = "com.apple.internal.dt.remote.debugproxy"; + XPCServiceHandle *test_service = NULL; + err = xpc_device_get_service(xpc_device, test_service_name, &test_service); + if (err == IdeviceSuccess) { + printf("\nSuccessfully retrieved service '%s':\n", test_service_name); + print_service_details(test_service); + xpc_service_free(test_service); + } else { + printf("\nFailed to get service '%s': %d\n", test_service_name, err); + } + + /***************************************************************** + * Adapter return + *****************************************************************/ + AdapterHandle *adapter_return = NULL; + err = xpc_device_adapter_into_inner(xpc_device, &adapter_return); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to extract adapter: %d\n", err); + } else { + printf("Successfully extracted adapter\n"); + } + + err = adapter_close(adapter_return); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to close adapter port: %d\n", err); + } else { + printf("Successfully closed adapter port\n"); + } + + /***************************************************************** + * Cleanup + *****************************************************************/ + adapter_free(adapter_return); + + printf("\nAll tests completed successfully!\n"); + return 0; +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 1e1686a..e7a012a 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -9,6 +9,7 @@ pub mod logging; pub mod mounter; mod pairing_file; pub mod provider; +pub mod remotexpc; pub mod usbmuxd; pub mod util; diff --git a/ffi/src/remotexpc.rs b/ffi/src/remotexpc.rs new file mode 100644 index 0000000..dcd7f8e --- /dev/null +++ b/ffi/src/remotexpc.rs @@ -0,0 +1,288 @@ +// Jackson Coxson + +use std::ffi::{CStr, CString, c_char}; + +use idevice::{tcp::adapter::Adapter, xpc::XPCDevice}; + +use crate::{IdeviceErrorCode, RUNTIME, core_device_proxy::AdapterHandle}; + +/// Opaque handle to an XPCDevice +pub struct XPCDeviceAdapterHandle(pub XPCDevice); + +/// Opaque handle to an XPCService +#[repr(C)] +pub struct XPCServiceHandle { + pub entitlement: *mut c_char, + pub port: u16, + pub uses_remote_xpc: bool, + pub features: *mut *mut c_char, + pub features_count: usize, + pub service_version: i64, +} + +impl XPCServiceHandle { + pub fn new( + entitlement: *mut c_char, + port: u16, + uses_remote_xpc: bool, + features: *mut *mut c_char, + features_count: usize, + service_version: i64, + ) -> Self { + Self { + entitlement, + port, + uses_remote_xpc, + features, + features_count, + service_version, + } + } +} + +/// Creates a new XPCDevice from an adapter +/// +/// # Arguments +/// * [`adapter`] - The adapter to use for communication +/// * [`device`] - Pointer to store the newly created XPCDevice handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `adapter` must be a valid pointer to a handle allocated by this library +/// `device` must be a valid pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn xpc_device_new( + adapter: *mut AdapterHandle, + device: *mut *mut XPCDeviceAdapterHandle, +) -> IdeviceErrorCode { + if adapter.is_null() || device.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let adapter = unsafe { Box::from_raw(adapter) }; + let res = RUNTIME.block_on(async move { XPCDevice::new(adapter.0).await }); + + match res { + // we have to unwrap res to avoid just getting a reference + Ok(_) => { + let boxed = Box::new(XPCDeviceAdapterHandle(res.unwrap())); + unsafe { *device = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Frees an XPCDevice 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 xpc_device_free(handle: *mut XPCDeviceAdapterHandle) { + if !handle.is_null() { + let _ = unsafe { Box::from_raw(handle) }; + } +} + +/// Gets a service by name from the XPCDevice +/// +/// # Arguments +/// * [`handle`] - The XPCDevice handle +/// * [`service_name`] - The name of the service to get +/// * [`service`] - Pointer to store the newly created XPCService handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library +/// `service_name` must be a valid null-terminated C string +/// `service` must be a valid pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn xpc_device_get_service( + handle: *mut XPCDeviceAdapterHandle, + service_name: *const c_char, + service: *mut *mut XPCServiceHandle, +) -> IdeviceErrorCode { + if handle.is_null() || service_name.is_null() || service.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let device = unsafe { &(*handle).0 }; + let service_name_cstr = unsafe { CStr::from_ptr(service_name) }; + let service_name = match service_name_cstr.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let xpc_service = match device.services.get(service_name) { + Some(s) => s, + None => return IdeviceErrorCode::ServiceNotFound, + }; + + // Convert features to C array if they exist + let (features_ptr, features_count) = if let Some(features) = &xpc_service.features { + let mut features_vec: Vec<*mut c_char> = features + .iter() + .map(|f| CString::new(f.as_str()).unwrap().into_raw()) + .collect(); + features_vec.shrink_to_fit(); + + let mut features_vec = Box::new(features_vec); + let features_ptr = features_vec.as_mut_ptr(); + let features_len = features_vec.len(); + + Box::leak(features_vec); + (features_ptr, features_len) + } else { + (std::ptr::null_mut(), 0) + }; + + let boxed = Box::new(XPCServiceHandle { + entitlement: CString::new(xpc_service.entitlement.as_str()) + .unwrap() + .into_raw(), + port: xpc_service.port, + uses_remote_xpc: xpc_service.uses_remote_xpc, + features: features_ptr, + features_count, + service_version: xpc_service.service_version.unwrap_or(-1), + }); + + unsafe { *service = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess +} + +/// Returns the adapter in the RemoteXPC Device +/// +/// # Arguments +/// * [`handle`] - The handle to get the adapter from +/// * [`adapter`] - The newly allocated AdapterHandle +/// +/// # 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 xpc_device_adapter_into_inner( + handle: *mut XPCDeviceAdapterHandle, + adapter: *mut *mut AdapterHandle, +) -> IdeviceErrorCode { + if handle.is_null() { + return IdeviceErrorCode::InvalidArg; + } + let device = unsafe { Box::from_raw(handle).0 }; + let adapter_obj = device.into_inner(); + let boxed = Box::new(AdapterHandle(adapter_obj)); + unsafe { *adapter = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess +} + +/// Frees an XPCService 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 xpc_service_free(handle: *mut XPCServiceHandle) { + if !handle.is_null() { + let handle = unsafe { Box::from_raw(handle) }; + + // Free the entitlement string + if !handle.entitlement.is_null() { + let _ = unsafe { CString::from_raw(handle.entitlement) }; + } + + // Free the features array + if !handle.features.is_null() { + for i in 0..handle.features_count { + let feature_ptr = unsafe { *handle.features.add(i) }; + if !feature_ptr.is_null() { + let _ = unsafe { CString::from_raw(feature_ptr) }; + } + } + let _ = unsafe { + Vec::from_raw_parts( + handle.features, + handle.features_count, + handle.features_count, + ) + }; + } + } +} + +/// Gets the list of available service names +/// +/// # Arguments +/// * [`handle`] - The XPCDevice handle +/// * [`names`] - Pointer to store the array of service names +/// * [`count`] - Pointer to store the number of services +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library +/// `names` must be a valid pointer to a location where the array will be stored +/// `count` must be a valid pointer to a location where the count will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn xpc_device_get_service_names( + handle: *mut XPCDeviceAdapterHandle, + names: *mut *mut *mut c_char, + count: *mut usize, +) -> IdeviceErrorCode { + if handle.is_null() || names.is_null() || count.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let device = unsafe { &(*handle).0 }; + let service_names: Vec = device + .services + .keys() + .map(|k| CString::new(k.as_str()).unwrap()) + .collect(); + + let mut name_ptrs: Vec<*mut c_char> = service_names.into_iter().map(|s| s.into_raw()).collect(); + + if name_ptrs.is_empty() { + unsafe { + *names = std::ptr::null_mut(); + *count = 0; + } + } else { + name_ptrs.shrink_to_fit(); + unsafe { + *names = name_ptrs.as_mut_ptr(); + *count = name_ptrs.len(); + } + std::mem::forget(name_ptrs); + } + + IdeviceErrorCode::IdeviceSuccess +} + +/// Frees a list of service names +/// +/// # Arguments +/// * [`names`] - The array of service names to free +/// * [`count`] - The number of services in the array +/// +/// # Safety +/// `names` must be a valid pointer to an array of `count` C strings +#[unsafe(no_mangle)] +pub unsafe extern "C" fn xpc_device_free_service_names(names: *mut *mut c_char, count: usize) { + if !names.is_null() && count > 0 { + let names_vec = unsafe { Vec::from_raw_parts(names, count, count) }; + for name in names_vec { + if !name.is_null() { + let _ = unsafe { CString::from_raw(name) }; + } + } + } +}