Initial implementation for house arrest

This commit is contained in:
Jackson Coxson
2025-05-09 14:57:39 -06:00
parent 6ede026b6f
commit 041899baf1
3 changed files with 136 additions and 0 deletions

View File

@@ -61,6 +61,7 @@ crashreportcopymobile = ["afc"]
debug_proxy = [] debug_proxy = []
dvt = ["dep:byteorder", "dep:ns-keyed-archive"] dvt = ["dep:byteorder", "dep:ns-keyed-archive"]
heartbeat = ["tokio/macros", "tokio/time"] heartbeat = ["tokio/macros", "tokio/time"]
house_arrest = ["afc"]
installation_proxy = [] installation_proxy = []
springboardservices = [] springboardservices = []
misagent = [] misagent = []

View File

@@ -0,0 +1,133 @@
//! iOS Device HouseArrest Service Abstraction
//!
//! The HouseArrest service allows access to the container and Documents directory of apps
//! installed on an iOS device. This is typically used for file transfer and inspection of
//! app-specific data during development or diagnostics.
use plist::{Dictionary, Value};
use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService};
use super::afc::AfcClient;
/// Client for interacting with the iOS HouseArrest service
///
/// HouseArrest is used to expose the container or Documents directory of an app to a host machine
/// over AFC (Apple File Conduit).
pub struct HouseArrestClient {
/// The underlying device connection with the HouseArrest service
pub idevice: Idevice,
}
impl IdeviceService for HouseArrestClient {
/// Returns the name of the HouseArrest service as registered with lockdownd
fn service_name() -> &'static str {
"com.apple.mobile.house_arrest"
}
/// Establishes a connection to the HouseArrest service
///
/// # Arguments
/// * `provider` - Device connection provider
///
/// # Returns
/// A connected `HouseArrestClient` instance
///
/// # Errors
/// Returns `IdeviceError` if any step of the connection process fails
///
/// # Process
/// 1. Connect to the lockdownd service
/// 2. Start a lockdown session
/// 3. Request the HouseArrest service
/// 4. Connect to the returned service port
/// 5. Start TLS if required by the 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 HouseArrestClient {
/// Creates a new HouseArrest client from an existing device connection
///
/// # Arguments
/// * `idevice` - A pre-established device connection with the HouseArrest service
pub fn new(idevice: Idevice) -> Self {
Self { idevice }
}
/// Requests access to the app's full container (Documents, Library, etc.) over AFC
///
/// # Arguments
/// * `bundle_id` - The bundle identifier of the target app (e.g., "com.example.MyApp")
///
/// # Returns
/// An `AfcClient` for accessing the container of the specified app
///
/// # Errors
/// Returns `IdeviceError` if the request or AFC setup fails
pub async fn vend_container(
self,
bundle_id: impl Into<String>,
) -> Result<AfcClient, IdeviceError> {
let bundle_id = bundle_id.into();
self.vend(bundle_id, "VendContainer".into()).await
}
/// Requests access to the app's Documents directory over AFC
///
/// # Arguments
/// * `bundle_id` - The bundle identifier of the target app (e.g., "com.example.MyApp")
///
/// # Returns
/// An `AfcClient` for accessing the Documents directory of the specified app
///
/// # Errors
/// Returns `IdeviceError` if the request or AFC setup fails
pub async fn vend_documents(
self,
bundle_id: impl Into<String>,
) -> Result<AfcClient, IdeviceError> {
let bundle_id = bundle_id.into();
self.vend(bundle_id, "VendDocuments".into()).await
}
/// Sends a HouseArrest command to expose a specific directory over AFC
///
/// This is an internal method used by `vend_container` and `vend_documents`.
///
/// # Arguments
/// * `bundle_id` - App bundle identifier
/// * `cmd` - Command to send ("VendContainer" or "VendDocuments")
///
/// # Returns
/// A connected `AfcClient` instance
///
/// # Errors
/// Returns `IdeviceError` if the request or AFC setup fails
async fn vend(mut self, bundle_id: String, cmd: String) -> Result<AfcClient, IdeviceError> {
let mut req = Dictionary::new();
req.insert("Command".into(), cmd.into());
req.insert("Identifier".into(), bundle_id.into());
self.idevice.send_plist(Value::Dictionary(req)).await?;
self.idevice.read_plist().await?;
Ok(AfcClient::new(self.idevice))
}
}

View File

@@ -12,6 +12,8 @@ pub mod debug_proxy;
pub mod dvt; pub mod dvt;
#[cfg(feature = "heartbeat")] #[cfg(feature = "heartbeat")]
pub mod heartbeat; pub mod heartbeat;
#[cfg(feature = "house_arrest")]
pub mod house_arrest;
#[cfg(feature = "installation_proxy")] #[cfg(feature = "installation_proxy")]
pub mod installation_proxy; pub mod installation_proxy;
pub mod lockdown; pub mod lockdown;