Skip to main content

ios_core/services/amfi/
mod.rs

1//! AMFI (Apple Mobile File Integrity) – developer mode control.
2//!
3//! Service: `com.apple.amfi.lockdown`
4
5use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
6
7pub const SERVICE_NAME: &str = "com.apple.amfi.lockdown";
8
9const DEVELOPER_MODE_REVEAL: u64 = 0;
10const DEVELOPER_MODE_ENABLE: u64 = 1;
11
12#[derive(Debug, thiserror::Error)]
13pub enum AmfiError {
14    #[error("IO error: {0}")]
15    Io(#[from] std::io::Error),
16    #[error("plist error: {0}")]
17    Plist(#[from] plist::Error),
18    #[error("error: {0}")]
19    Device(String),
20}
21
22/// Enable developer mode on the device.
23///
24/// After calling this, the device needs to be rebooted.
25pub async fn enable_developer_mode<S>(stream: &mut S) -> Result<(), AmfiError>
26where
27    S: AsyncRead + AsyncWrite + Unpin,
28{
29    send_developer_mode_action(stream, DEVELOPER_MODE_ENABLE).await
30}
31
32/// Reveal the Developer Mode option in the device Settings UI.
33pub async fn reveal_developer_mode<S>(stream: &mut S) -> Result<(), AmfiError>
34where
35    S: AsyncRead + AsyncWrite + Unpin,
36{
37    send_developer_mode_action(stream, DEVELOPER_MODE_REVEAL).await
38}
39
40async fn send_developer_mode_action<S>(stream: &mut S, action: u64) -> Result<(), AmfiError>
41where
42    S: AsyncRead + AsyncWrite + Unpin,
43{
44    let req = plist::Value::Dictionary({
45        let mut d = plist::Dictionary::new();
46        d.insert("action".to_string(), plist::Value::Integer(action.into()));
47        d
48    });
49
50    let mut buf = Vec::new();
51    plist::to_writer_xml(&mut buf, &req)?;
52    stream.write_all(&(buf.len() as u32).to_be_bytes()).await?;
53    stream.write_all(&buf).await?;
54    stream.flush().await?;
55
56    let mut len_buf = [0u8; 4];
57    stream.read_exact(&mut len_buf).await?;
58    let len = u32::from_be_bytes(len_buf) as usize;
59    const MAX_PLIST_SIZE: usize = 4 * 1024 * 1024;
60    if len > MAX_PLIST_SIZE {
61        return Err(AmfiError::Io(std::io::Error::new(
62            std::io::ErrorKind::InvalidData,
63            format!("plist length {len} exceeds maximum of {MAX_PLIST_SIZE}"),
64        )));
65    }
66    let mut resp_buf = vec![0u8; len];
67    stream.read_exact(&mut resp_buf).await?;
68
69    let val: plist::Value = plist::from_bytes(&resp_buf)?;
70
71    if let Some(dict) = val.as_dictionary() {
72        if let Some(err) = dict.get("Error").and_then(|v| v.as_string()) {
73            return Err(AmfiError::Device(err.to_string()));
74        }
75        if dict
76            .get("success")
77            .and_then(|v| v.as_boolean())
78            .is_some_and(|success| !success)
79        {
80            return Err(AmfiError::Device(format!("{val:?}")));
81        }
82    }
83    Ok(())
84}