ios_core/services/amfi/
mod.rs1use 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
22pub 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
32pub 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}