From a6d79f6446c2b9dc7e38db1cf4a67feb80b07bd1 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Wed, 26 Mar 2025 12:17:23 -0600 Subject: [PATCH] Debug proxy bindings --- ffi/examples/debug_proxy.c | 271 +++++++++++++++++++++ ffi/src/debug_proxy.rs | 467 +++++++++++++++++++++++++++++++++++++ ffi/src/lib.rs | 1 + 3 files changed, 739 insertions(+) create mode 100644 ffi/examples/debug_proxy.c create mode 100644 ffi/src/debug_proxy.rs diff --git a/ffi/examples/debug_proxy.c b/ffi/examples/debug_proxy.c new file mode 100644 index 0000000..ee645d7 --- /dev/null +++ b/ffi/examples/debug_proxy.c @@ -0,0 +1,271 @@ +// Jackson Coxson + +#include "idevice.h" +#include +#include +#include +#include +#include +#include + +#define MAX_COMMAND_LENGTH 1024 + +void print_usage(const char *program_name) { + printf("Usage: %s [pairing_file]\n", program_name); + printf("Example: %s 10.0.0.1 pairing.plist\n", program_name); +} + +int main(int argc, char **argv) { + // Initialize logger + idevice_init_logger(Debug, Disabled, NULL); + + if (argc < 2) { + print_usage(argv[0]); + return 1; + } + + const char *device_ip = argv[1]; + const char *pairing_file = argc > 2 ? argv[2] : "pairing_file.plist"; + + /***************************************************************** + * CoreDeviceProxy Setup + *****************************************************************/ + printf("=== Setting up CoreDeviceProxy ===\n"); + + // Create socket address + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCKDOWN_PORT); + if (inet_pton(AF_INET, device_ip, &addr.sin_addr) != 1) { + fprintf(stderr, "Invalid IP address\n"); + return 1; + } + + // 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; + } + + // Create TCP provider + TcpProviderHandle *tcp_provider = NULL; + err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing, + "DebugProxyShell", &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 Debug Proxy Service + *****************************************************************/ + printf("\n=== Getting Debug Proxy Service ===\n"); + + XPCServiceHandle *debug_service = NULL; + err = xpc_device_get_service( + xpc_device, "com.apple.internal.dt.remote.debugproxy", &debug_service); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to get debug proxy service: %d\n", err); + xpc_device_free(xpc_device); + adapter_free(adapter); + core_device_proxy_free(core_device); + idevice_pairing_file_free(pairing); + return 1; + } + printf("Debug Proxy Service Port: %d\n", debug_service->port); + + /***************************************************************** + * Debug Proxy Setup + *****************************************************************/ + printf("\n=== Setting up Debug Proxy ===\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(debug_service); + xpc_device_free(xpc_device); + core_device_proxy_free(core_device); + idevice_pairing_file_free(pairing); + return 1; + } + + // Connect to debug proxy port + err = adapter_connect(debug_adapter, debug_service->port); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to connect to debug proxy port: %d\n", err); + adapter_free(debug_adapter); + xpc_service_free(debug_service); + core_device_proxy_free(core_device); + idevice_pairing_file_free(pairing); + return 1; + } + printf("Successfully connected to debug proxy port\n"); + + // Create DebugProxyClient + DebugProxyAdapterHandle *debug_proxy = NULL; + err = debug_proxy_adapter_new(debug_adapter, &debug_proxy); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to create debug proxy client: %d\n", err); + adapter_free(debug_adapter); + xpc_service_free(debug_service); + core_device_proxy_free(core_device); + idevice_pairing_file_free(pairing); + return 1; + } + + /***************************************************************** + * Interactive Shell + *****************************************************************/ + printf("\n=== Starting Interactive Debug Shell ===\n"); + printf("Type GDB debugserver commands or 'quit' to exit\n\n"); + + char command[MAX_COMMAND_LENGTH]; + bool running = true; + + while (running) { + printf("debug> "); + fflush(stdout); + + if (fgets(command, sizeof(command), stdin) == NULL) { + break; + } + + // Remove newline + command[strcspn(command, "\n")] = '\0'; + + if (strcmp(command, "quit") == 0) { + running = false; + continue; + } + + // Split command into name and arguments + char *name = strtok(command, " "); + char *args = strtok(NULL, ""); + + // Create command + DebugserverCommandHandle *cmd = NULL; + if (args != NULL && args[0] != '\0') { + // Split arguments + char *argv[16] = {0}; + int argc = 0; + char *token = strtok(args, " "); + while (token != NULL && argc < 15) { + argv[argc++] = token; + token = strtok(NULL, " "); + } + + cmd = debugserver_command_new(name, (const char **)argv, argc); + } else { + cmd = debugserver_command_new(name, NULL, 0); + } + + if (cmd == NULL) { + fprintf(stderr, "Failed to create command\n"); + continue; + } + + // Send command + char *response = NULL; + err = debug_proxy_send_command(debug_proxy, cmd, &response); + debugserver_command_free(cmd); + + if (err != IdeviceSuccess) { + fprintf(stderr, "Command failed with error: %d\n", err); + continue; + } + + if (response != NULL) { + printf("%s\n", response); + idevice_string_free(response); + } else { + printf("(no response)\n"); + } + + // Read any additional responses + while (true) { + err = debug_proxy_read_response(debug_proxy, &response); + if (err != IdeviceSuccess || response == NULL) { + break; + } + printf("%s\n", response); + idevice_string_free(response); + } + } + + /***************************************************************** + * Cleanup + *****************************************************************/ + debug_proxy_free(debug_proxy); + xpc_service_free(debug_service); + + printf("\nDebug session ended\n"); + return 0; +} diff --git a/ffi/src/debug_proxy.rs b/ffi/src/debug_proxy.rs new file mode 100644 index 0000000..b3334ff --- /dev/null +++ b/ffi/src/debug_proxy.rs @@ -0,0 +1,467 @@ +// Jackson Coxson + +use std::ffi::{CStr, CString, c_char}; +use std::os::raw::c_int; +use std::ptr; + +use idevice::debug_proxy::{DebugProxyClient, DebugserverCommand}; +use idevice::tcp::adapter::Adapter; + +use crate::core_device_proxy::AdapterHandle; +use crate::{IdeviceErrorCode, RUNTIME}; + +/// Opaque handle to a DebugProxyClient +pub struct DebugProxyAdapterHandle(pub DebugProxyClient); + +/// Represents a debugserver command +#[repr(C)] +pub struct DebugserverCommandHandle { + pub name: *mut c_char, + pub argv: *mut *mut c_char, + pub argv_count: usize, +} + +/// Creates a new DebugserverCommand +/// +/// # Safety +/// Caller must free with debugserver_command_free +#[unsafe(no_mangle)] +pub unsafe extern "C" fn debugserver_command_new( + name: *const c_char, + argv: *const *const c_char, + argv_count: usize, +) -> *mut DebugserverCommandHandle { + if name.is_null() { + return ptr::null_mut(); + } + + let name_cstr = unsafe { CStr::from_ptr(name) }; + let name = match name_cstr.to_str() { + Ok(s) => s.to_string(), + Err(_) => return ptr::null_mut(), + }; + + let mut argv_vec = Vec::new(); + if !argv.is_null() && argv_count > 0 { + let argv_slice = unsafe { std::slice::from_raw_parts(argv, argv_count) }; + for &arg in argv_slice { + if !arg.is_null() { + let arg_cstr = unsafe { CStr::from_ptr(arg) }; + if let Ok(arg_str) = arg_cstr.to_str() { + argv_vec.push(arg_str.to_string()); + } + } + } + } + let argv_len = argv_vec.len(); + + let boxed = Box::new(DebugserverCommandHandle { + name: CString::new(name).unwrap().into_raw(), + argv: if argv_vec.is_empty() { + ptr::null_mut() + } else { + let mut argv_ptrs: Vec<*mut c_char> = argv_vec + .into_iter() + .map(|s| CString::new(s).unwrap().into_raw()) + .collect(); + argv_ptrs.shrink_to_fit(); + argv_ptrs.as_mut_ptr() + }, + argv_count: argv_len, + }); + + Box::into_raw(boxed) +} + +/// Frees a DebugserverCommand +/// +/// # Safety +/// `command` must be a valid pointer or NULL +#[unsafe(no_mangle)] +pub unsafe extern "C" fn debugserver_command_free(command: *mut DebugserverCommandHandle) { + if !command.is_null() { + let command = unsafe { Box::from_raw(command) }; + + // Free name + if !command.name.is_null() { + let _ = unsafe { CString::from_raw(command.name) }; + } + + // Free argv + if !command.argv.is_null() && command.argv_count > 0 { + let argv_slice = + unsafe { std::slice::from_raw_parts_mut(command.argv, command.argv_count) }; + for &mut arg in argv_slice { + if !arg.is_null() { + let _ = unsafe { CString::from_raw(arg) }; + } + } + let _ = unsafe { + Vec::from_raw_parts(command.argv, command.argv_count, command.argv_count) + }; + } + } +} + +/// Creates a new DebugProxyClient +/// +/// # Arguments +/// * [`socket`] - The socket to use for communication +/// * [`handle`] - Pointer to store the newly created DebugProxyClient handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `socket` 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 debug_proxy_adapter_new( + socket: *mut AdapterHandle, + handle: *mut *mut DebugProxyAdapterHandle, +) -> IdeviceErrorCode { + if socket.is_null() || handle.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let socket = unsafe { Box::from_raw(socket) }; + let client = DebugProxyClient::new(socket.0); + + let boxed = Box::new(DebugProxyAdapterHandle(client)); + unsafe { *handle = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess +} + +/// Frees a DebugProxyClient 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 debug_proxy_free(handle: *mut DebugProxyAdapterHandle) { + if !handle.is_null() { + let _ = unsafe { Box::from_raw(handle) }; + } +} + +/// Sends a command to the debug proxy +/// +/// # Arguments +/// * [`handle`] - The DebugProxyClient handle +/// * [`command`] - The command to send +/// * [`response`] - Pointer to store the response (caller must free) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` and `command` must be valid pointers +/// `response` must be a valid pointer to a location where the string will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn debug_proxy_send_command( + handle: *mut DebugProxyAdapterHandle, + command: *mut DebugserverCommandHandle, + response: *mut *mut c_char, +) -> IdeviceErrorCode { + if handle.is_null() || command.is_null() || response.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let client = unsafe { &mut (*handle).0 }; + let cmd = DebugserverCommand { + name: unsafe { + CStr::from_ptr((*command).name) + .to_string_lossy() + .into_owned() + }, + argv: if unsafe { &*command }.argv.is_null() { + Vec::new() + } else { + let argv_slice = + unsafe { std::slice::from_raw_parts((*command).argv, (*command).argv_count) }; + argv_slice + .iter() + .map(|&arg| unsafe { CStr::from_ptr(arg).to_string_lossy().into_owned() }) + .collect() + }, + }; + + let res = RUNTIME.block_on(async move { client.send_command(cmd).await }); + + match res { + Ok(Some(r)) => { + let cstr = CString::new(r).unwrap(); + unsafe { *response = cstr.into_raw() }; + IdeviceErrorCode::IdeviceSuccess + } + Ok(None) => { + unsafe { *response = ptr::null_mut() }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Reads a response from the debug proxy +/// +/// # Arguments +/// * [`handle`] - The DebugProxyClient handle +/// * [`response`] - Pointer to store the response (caller must free) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer +/// `response` must be a valid pointer to a location where the string will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn debug_proxy_read_response( + handle: *mut DebugProxyAdapterHandle, + response: *mut *mut c_char, +) -> IdeviceErrorCode { + if handle.is_null() || response.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let client = unsafe { &mut (*handle).0 }; + let res = RUNTIME.block_on(async move { client.read_response().await }); + + match res { + Ok(Some(r)) => { + let cstr = CString::new(r).unwrap(); + unsafe { *response = cstr.into_raw() }; + IdeviceErrorCode::IdeviceSuccess + } + Ok(None) => { + unsafe { *response = ptr::null_mut() }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Sends raw data to the debug proxy +/// +/// # Arguments +/// * [`handle`] - The DebugProxyClient handle +/// * [`data`] - The data to send +/// * [`len`] - Length of the data +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer +/// `data` must be a valid pointer to `len` bytes +#[unsafe(no_mangle)] +pub unsafe extern "C" fn debug_proxy_send_raw( + handle: *mut DebugProxyAdapterHandle, + data: *const u8, + len: usize, +) -> IdeviceErrorCode { + if handle.is_null() || data.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let client = unsafe { &mut (*handle).0 }; + let data_slice = unsafe { std::slice::from_raw_parts(data, len) }; + let res = RUNTIME.block_on(async move { client.send_raw(data_slice).await }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Reads data from the debug proxy +/// +/// # Arguments +/// * [`handle`] - The DebugProxyClient handle +/// * [`len`] - Maximum number of bytes to read +/// * [`response`] - Pointer to store the response (caller must free) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer +/// `response` must be a valid pointer to a location where the string will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn debug_proxy_read( + handle: *mut DebugProxyAdapterHandle, + len: usize, + response: *mut *mut c_char, +) -> IdeviceErrorCode { + if handle.is_null() || response.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let client = unsafe { &mut (*handle).0 }; + let res = RUNTIME.block_on(async move { client.read(len).await }); + + match res { + Ok(r) => { + let cstr = CString::new(r).unwrap(); + unsafe { *response = cstr.into_raw() }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Sets the argv for the debug proxy +/// +/// # Arguments +/// * [`handle`] - The DebugProxyClient handle +/// * [`argv`] - NULL-terminated array of arguments +/// * [`argv_count`] - Number of arguments +/// * [`response`] - Pointer to store the response (caller must free) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer +/// `argv` must be a valid pointer to `argv_count` C strings or NULL +/// `response` must be a valid pointer to a location where the string will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn debug_proxy_set_argv( + handle: *mut DebugProxyAdapterHandle, + argv: *const *const c_char, + argv_count: usize, + response: *mut *mut c_char, +) -> IdeviceErrorCode { + if handle.is_null() || response.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let client = unsafe { &mut (*handle).0 }; + let argv_vec = if argv.is_null() || argv_count == 0 { + Vec::new() + } else { + let argv_slice = unsafe { std::slice::from_raw_parts(argv, argv_count) }; + argv_slice + .iter() + .filter_map(|&arg| { + if arg.is_null() { + None + } else { + Some(unsafe { CStr::from_ptr(arg).to_string_lossy().into_owned() }) + } + }) + .collect() + }; + + let res = RUNTIME.block_on(async move { client.set_argv(argv_vec).await }); + + match res { + Ok(r) => { + let cstr = CString::new(r).unwrap(); + unsafe { *response = cstr.into_raw() }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Sends an ACK to the debug proxy +/// +/// # Arguments +/// * [`handle`] - The DebugProxyClient handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer +#[unsafe(no_mangle)] +pub unsafe extern "C" fn debug_proxy_send_ack( + handle: *mut DebugProxyAdapterHandle, +) -> IdeviceErrorCode { + if handle.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let client = unsafe { &mut (*handle).0 }; + let res = RUNTIME.block_on(async move { client.send_ack().await }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Sends a NACK to the debug proxy +/// +/// # Arguments +/// * [`handle`] - The DebugProxyClient handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer +#[unsafe(no_mangle)] +pub unsafe extern "C" fn debug_proxy_send_nack( + handle: *mut DebugProxyAdapterHandle, +) -> IdeviceErrorCode { + if handle.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let client = unsafe { &mut (*handle).0 }; + let res = RUNTIME.block_on(async move { client.send_noack().await }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Sets the ACK mode for the debug proxy +/// +/// # Arguments +/// * [`handle`] - The DebugProxyClient handle +/// * [`enabled`] - Whether ACK mode should be enabled +/// +/// # Safety +/// `handle` must be a valid pointer +#[unsafe(no_mangle)] +pub unsafe extern "C" fn debug_proxy_set_ack_mode( + handle: *mut DebugProxyAdapterHandle, + enabled: c_int, +) { + if !handle.is_null() { + let client = unsafe { &mut (*handle).0 }; + client.set_ack_mode(enabled != 0); + } +} + +/// Returns the underlying socket from a DebugProxyClient +/// +/// # Arguments +/// * [`handle`] - The handle to get the socket from +/// * [`adapter`] - 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 debug_proxy_adapter_into_inner( + handle: *mut DebugProxyAdapterHandle, + adapter: *mut *mut AdapterHandle, +) -> IdeviceErrorCode { + if handle.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let client = unsafe { Box::from_raw(handle) }; + let socket_obj = client.0.into_inner(); + let boxed = Box::new(AdapterHandle(socket_obj)); + unsafe { *adapter = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 4cba47b..8031933 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -2,6 +2,7 @@ pub mod adapter; pub mod core_device_proxy; +pub mod debug_proxy; mod errors; pub mod heartbeat; pub mod installation_proxy;