mirror of
https://github.com/nab138/isideload.git
synced 2026-03-02 14:36:16 +01:00
first commit
This commit is contained in:
40
apple-private-apis/omnisette/Cargo.toml
Normal file
40
apple-private-apis/omnisette/Cargo.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "omnisette"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
remote-anisette = []
|
||||
async = ["dep:async-trait"]
|
||||
default = ["remote-anisette", "dep:remove-async-await"]
|
||||
remote-anisette-v3 = ["async", "dep:serde", "dep:serde_json", "dep:tokio-tungstenite", "dep:futures-util", "dep:chrono"]
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.21"
|
||||
hex = "0.4.3"
|
||||
plist = "1.4"
|
||||
reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls", "gzip"] }
|
||||
rand = "0.8"
|
||||
sha2 = "0.10.8"
|
||||
uuid = { version = "1.3", features = [ "v4", "fast-rng", "macro-diagnostics" ] }
|
||||
android-loader = { git = "https://github.com/Dadoum/android-loader", branch = "bigger_pages" }
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
async-trait = { version = "0.1", optional = true }
|
||||
remove-async-await = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1.0.142", optional = true }
|
||||
tokio-tungstenite = { version = "0.20.1", optional = true, features = ["rustls-tls-webpki-roots"] }
|
||||
futures-util = { version = "0.3.28", optional = true }
|
||||
chrono = { version = "0.4.37", optional = true }
|
||||
thiserror = "1.0.58"
|
||||
anyhow = "1.0.81"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
dlopen2 = "0.4"
|
||||
objc = "0.2"
|
||||
objc-foundation = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["rt", "macros"] }
|
||||
simplelog = "0.12"
|
||||
381
apple-private-apis/omnisette/src/adi_proxy.rs
Normal file
381
apple-private-apis/omnisette/src/adi_proxy.rs
Normal file
@@ -0,0 +1,381 @@
|
||||
use crate::adi_proxy::ProvisioningError::InvalidResponse;
|
||||
use crate::anisette_headers_provider::AnisetteHeadersProvider;
|
||||
use crate::AnisetteError;
|
||||
use base64::engine::general_purpose::STANDARD as base64_engine;
|
||||
use base64::Engine;
|
||||
use log::debug;
|
||||
use plist::{Dictionary, Value};
|
||||
use rand::RngCore;
|
||||
#[cfg(not(feature = "async"))]
|
||||
use reqwest::blocking::{Client, ClientBuilder, Response};
|
||||
use reqwest::header::{HeaderMap, HeaderValue, InvalidHeaderValue};
|
||||
#[cfg(feature = "async")]
|
||||
use reqwest::{Client, ClientBuilder, Response};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ServerError {
|
||||
pub code: i64,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProvisioningError {
|
||||
InvalidResponse,
|
||||
ServerError(ServerError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ProvisioningError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ProvisioningError {}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ADIError {
|
||||
Unknown(i32),
|
||||
ProvisioningError(#[from] ProvisioningError),
|
||||
PlistError(#[from] plist::Error),
|
||||
ReqwestError(#[from] reqwest::Error),
|
||||
Base64Error(#[from] base64::DecodeError),
|
||||
InvalidHeaderValue(#[from] InvalidHeaderValue),
|
||||
IOError(#[from] io::Error)
|
||||
}
|
||||
|
||||
impl ADIError {
|
||||
pub fn resolve(error_number: i32) -> ADIError {
|
||||
ADIError::Unknown(error_number)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "async", async_trait::async_trait)]
|
||||
trait ToPlist {
|
||||
#[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)]
|
||||
async fn plist(self) -> Result<Dictionary, ADIError>;
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "async", async_trait::async_trait)]
|
||||
impl ToPlist for Response {
|
||||
#[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)]
|
||||
async fn plist(self) -> Result<Dictionary, ADIError> {
|
||||
if let Ok(property_list) = Value::from_reader_xml(&*self.bytes().await?) {
|
||||
Ok(property_list.as_dictionary().unwrap().to_owned())
|
||||
} else {
|
||||
Err(ProvisioningError::InvalidResponse.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ADIError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SynchronizeData {
|
||||
pub mid: Vec<u8>,
|
||||
pub srm: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct StartProvisioningData {
|
||||
pub cpim: Vec<u8>,
|
||||
pub session: u32,
|
||||
}
|
||||
|
||||
pub struct RequestOTPData {
|
||||
pub otp: Vec<u8>,
|
||||
pub mid: Vec<u8>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "async", async_trait::async_trait(?Send))]
|
||||
pub trait ADIProxy: Send + Sync {
|
||||
fn erase_provisioning(&mut self, ds_id: i64) -> Result<(), ADIError>;
|
||||
fn synchronize(&mut self, ds_id: i64, sim: &[u8]) -> Result<SynchronizeData, ADIError>;
|
||||
fn destroy_provisioning_session(&mut self, session: u32) -> Result<(), ADIError>;
|
||||
fn end_provisioning(&mut self, session: u32, ptm: &[u8], tk: &[u8]) -> Result<(), ADIError>;
|
||||
fn start_provisioning(
|
||||
&mut self,
|
||||
ds_id: i64,
|
||||
spim: &[u8],
|
||||
) -> Result<StartProvisioningData, ADIError>;
|
||||
fn is_machine_provisioned(&self, ds_id: i64) -> bool;
|
||||
fn request_otp(&self, ds_id: i64) -> Result<RequestOTPData, ADIError>;
|
||||
|
||||
fn set_local_user_uuid(&mut self, local_user_uuid: String);
|
||||
fn set_device_identifier(&mut self, device_identifier: String) -> Result<(), ADIError>;
|
||||
|
||||
fn get_local_user_uuid(&self) -> String;
|
||||
fn get_device_identifier(&self) -> String;
|
||||
fn get_serial_number(&self) -> String;
|
||||
}
|
||||
|
||||
pub trait ConfigurableADIProxy: ADIProxy {
|
||||
fn set_identifier(&mut self, identifier: &str) -> Result<(), ADIError>;
|
||||
fn set_provisioning_path(&mut self, path: &str) -> Result<(), ADIError>;
|
||||
}
|
||||
|
||||
pub const AKD_USER_AGENT: &str = "akd/1.0 CFNetwork/808.1.4";
|
||||
pub const CLIENT_INFO_HEADER: &str =
|
||||
"<MacBookPro13,2> <macOS;13.1;22C65> <com.apple.AuthKit/1 (com.apple.dt.Xcode/3594.4.19)>";
|
||||
pub const DS_ID: i64 = -2;
|
||||
pub const IDENTIFIER_LENGTH: usize = 16;
|
||||
pub type Identifier = [u8; IDENTIFIER_LENGTH];
|
||||
|
||||
trait AppleRequestResult {
|
||||
fn check_status(&self) -> Result<(), ADIError>;
|
||||
fn get_response(&self) -> Result<&Dictionary, ADIError>;
|
||||
}
|
||||
|
||||
impl AppleRequestResult for Dictionary {
|
||||
fn check_status(&self) -> Result<(), ADIError> {
|
||||
let status = self
|
||||
.get("Status")
|
||||
.ok_or(InvalidResponse)?
|
||||
.as_dictionary()
|
||||
.unwrap();
|
||||
let code = status.get("ec").unwrap().as_signed_integer().unwrap();
|
||||
if code != 0 {
|
||||
let description = status.get("em").unwrap().as_string().unwrap().to_string();
|
||||
Err(ProvisioningError::ServerError(ServerError { code, description }).into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_response(&self) -> Result<&Dictionary, ADIError> {
|
||||
if let Some(response) = self.get("Response") {
|
||||
let response = response.as_dictionary().unwrap();
|
||||
response.check_status()?;
|
||||
Ok(response)
|
||||
} else {
|
||||
Err(InvalidResponse.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn ADIProxy {
|
||||
fn make_http_client(&mut self) -> Result<Client, ADIError> {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("Content-Type", HeaderValue::from_str("text/x-xml-plist")?);
|
||||
|
||||
headers.insert(
|
||||
"X-Mme-Client-Info",
|
||||
HeaderValue::from_str(CLIENT_INFO_HEADER)?,
|
||||
);
|
||||
headers.insert(
|
||||
"X-Mme-Device-Id",
|
||||
HeaderValue::from_str(self.get_device_identifier().as_str())?,
|
||||
);
|
||||
headers.insert(
|
||||
"X-Apple-I-MD-LU",
|
||||
HeaderValue::from_str(self.get_local_user_uuid().as_str())?,
|
||||
);
|
||||
headers.insert(
|
||||
"X-Apple-I-SRL-NO",
|
||||
HeaderValue::from_str(self.get_serial_number().as_str())?,
|
||||
);
|
||||
|
||||
debug!("Headers sent: {headers:?}");
|
||||
|
||||
let http_client = ClientBuilder::new()
|
||||
.http1_title_case_headers()
|
||||
.danger_accept_invalid_certs(true) // TODO: pin the apple certificate
|
||||
.user_agent(AKD_USER_AGENT)
|
||||
.default_headers(headers)
|
||||
.build()?;
|
||||
|
||||
Ok(http_client)
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)]
|
||||
async fn provision_device(&mut self) -> Result<(), ADIError> {
|
||||
let client = self.make_http_client()?;
|
||||
|
||||
let url_bag_res = client
|
||||
.get("https://gsa.apple.com/grandslam/GsService2/lookup")
|
||||
.send()
|
||||
.await?
|
||||
.plist()
|
||||
.await?;
|
||||
|
||||
let urls = url_bag_res.get("urls").unwrap().as_dictionary().unwrap();
|
||||
|
||||
let start_provisioning_url = urls
|
||||
.get("midStartProvisioning")
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.unwrap();
|
||||
let finish_provisioning_url = urls
|
||||
.get("midFinishProvisioning")
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.unwrap();
|
||||
|
||||
let mut body = plist::Dictionary::new();
|
||||
body.insert(
|
||||
"Header".to_string(),
|
||||
plist::Value::Dictionary(plist::Dictionary::new()),
|
||||
);
|
||||
body.insert(
|
||||
"Request".to_string(),
|
||||
plist::Value::Dictionary(plist::Dictionary::new()),
|
||||
);
|
||||
|
||||
let mut sp_request = Vec::new();
|
||||
plist::Value::Dictionary(body).to_writer_xml(&mut sp_request)?;
|
||||
|
||||
debug!("First provisioning request...");
|
||||
let response = client
|
||||
.post(start_provisioning_url)
|
||||
.body(sp_request)
|
||||
.send()
|
||||
.await?
|
||||
.plist()
|
||||
.await?;
|
||||
|
||||
let response = response.get_response()?;
|
||||
|
||||
let spim = response
|
||||
.get("spim")
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
|
||||
let spim = base64_engine.decode(spim)?;
|
||||
let first_step = self.start_provisioning(DS_ID, spim.as_slice())?;
|
||||
|
||||
let mut body = Dictionary::new();
|
||||
let mut request = Dictionary::new();
|
||||
request.insert(
|
||||
"cpim".to_owned(),
|
||||
Value::String(base64_engine.encode(first_step.cpim)),
|
||||
);
|
||||
body.insert("Header".to_owned(), Value::Dictionary(Dictionary::new()));
|
||||
body.insert("Request".to_owned(), Value::Dictionary(request));
|
||||
|
||||
let mut fp_request = Vec::new();
|
||||
Value::Dictionary(body).to_writer_xml(&mut fp_request)?;
|
||||
|
||||
debug!("Second provisioning request...");
|
||||
let response = client
|
||||
.post(finish_provisioning_url)
|
||||
.body(fp_request)
|
||||
.send()
|
||||
.await?
|
||||
.plist()
|
||||
.await?;
|
||||
|
||||
let response = response.get_response()?;
|
||||
|
||||
let ptm = base64_engine.decode(response.get("ptm").unwrap().as_string().unwrap())?;
|
||||
let tk = base64_engine.decode(response.get("tk").unwrap().as_string().unwrap())?;
|
||||
|
||||
self.end_provisioning(first_step.session, ptm.as_slice(), tk.as_slice())?;
|
||||
debug!("Done.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ADIProxyAnisetteProvider<ProxyType: ADIProxy + 'static> {
|
||||
adi_proxy: ProxyType,
|
||||
}
|
||||
|
||||
impl<ProxyType: ADIProxy + 'static> ADIProxyAnisetteProvider<ProxyType> {
|
||||
/// If you use this method, you are expected to set the identifier yourself.
|
||||
pub fn without_identifier(adi_proxy: ProxyType) -> Result<ADIProxyAnisetteProvider<ProxyType>, ADIError> {
|
||||
Ok(ADIProxyAnisetteProvider { adi_proxy })
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
mut adi_proxy: ProxyType,
|
||||
configuration_path: PathBuf,
|
||||
) -> Result<ADIProxyAnisetteProvider<ProxyType>, ADIError> {
|
||||
let identifier_file_path = configuration_path.join("identifier");
|
||||
let mut identifier_file = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(identifier_file_path)?;
|
||||
let mut identifier = [0u8; IDENTIFIER_LENGTH];
|
||||
if identifier_file.metadata()?.len() == IDENTIFIER_LENGTH as u64 {
|
||||
identifier_file.read_exact(&mut identifier)?;
|
||||
} else {
|
||||
rand::thread_rng().fill_bytes(&mut identifier);
|
||||
identifier_file.write_all(&identifier)?;
|
||||
}
|
||||
|
||||
let mut local_user_uuid_hasher = Sha256::new();
|
||||
local_user_uuid_hasher.update(identifier);
|
||||
|
||||
adi_proxy.set_device_identifier(
|
||||
uuid::Uuid::from_bytes(identifier)
|
||||
.to_string()
|
||||
.to_uppercase(),
|
||||
)?; // UUID, uppercase
|
||||
adi_proxy
|
||||
.set_local_user_uuid(hex::encode(local_user_uuid_hasher.finalize()).to_uppercase()); // 64 uppercase character hex
|
||||
|
||||
Ok(ADIProxyAnisetteProvider { adi_proxy })
|
||||
}
|
||||
|
||||
pub fn adi_proxy(&mut self) -> &mut ProxyType {
|
||||
&mut self.adi_proxy
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "async", async_trait::async_trait)]
|
||||
impl<ProxyType: ADIProxy + 'static> AnisetteHeadersProvider
|
||||
for ADIProxyAnisetteProvider<ProxyType>
|
||||
{
|
||||
#[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)]
|
||||
async fn get_anisette_headers(
|
||||
&mut self,
|
||||
skip_provisioning: bool,
|
||||
) -> Result<HashMap<String, String>, AnisetteError> {
|
||||
let adi_proxy = &mut self.adi_proxy as &mut dyn ADIProxy;
|
||||
|
||||
if !adi_proxy.is_machine_provisioned(DS_ID) && !skip_provisioning {
|
||||
adi_proxy.provision_device().await?;
|
||||
}
|
||||
|
||||
let machine_data = adi_proxy.request_otp(DS_ID)?;
|
||||
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert(
|
||||
"X-Apple-I-MD".to_string(),
|
||||
base64_engine.encode(machine_data.otp),
|
||||
);
|
||||
headers.insert(
|
||||
"X-Apple-I-MD-M".to_string(),
|
||||
base64_engine.encode(machine_data.mid),
|
||||
);
|
||||
headers.insert("X-Apple-I-MD-RINFO".to_string(), "17106176".to_string());
|
||||
headers.insert(
|
||||
"X-Apple-I-MD-LU".to_string(),
|
||||
adi_proxy.get_local_user_uuid(),
|
||||
);
|
||||
headers.insert(
|
||||
"X-Apple-I-SRL-NO".to_string(),
|
||||
adi_proxy.get_serial_number(),
|
||||
);
|
||||
headers.insert(
|
||||
"X-Mme-Client-Info".to_string(),
|
||||
CLIENT_INFO_HEADER.to_string(),
|
||||
);
|
||||
headers.insert(
|
||||
"X-Mme-Device-Id".to_string(),
|
||||
adi_proxy.get_device_identifier(),
|
||||
);
|
||||
|
||||
Ok(headers)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::AnisetteError;
|
||||
|
||||
#[cfg_attr(feature = "async", async_trait::async_trait)]
|
||||
pub trait AnisetteHeadersProvider: Send + Sync {
|
||||
#[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)]
|
||||
async fn get_anisette_headers(
|
||||
&mut self,
|
||||
skip_provisioning: bool,
|
||||
) -> Result<HashMap<String, String>, AnisetteError>;
|
||||
|
||||
#[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)]
|
||||
async fn get_authentication_headers(&mut self) -> Result<HashMap<String, String>, AnisetteError> {
|
||||
let headers = self.get_anisette_headers(false).await?;
|
||||
Ok(self.normalize_headers(headers))
|
||||
}
|
||||
|
||||
/// Normalizes headers to ensure that all the required headers are given.
|
||||
fn normalize_headers(
|
||||
&mut self,
|
||||
mut headers: HashMap<String, String>,
|
||||
) -> HashMap<String, String> {
|
||||
if let Some(client_info) = headers.remove("X-MMe-Client-Info") {
|
||||
headers.insert("X-Mme-Client-Info".to_string(), client_info);
|
||||
}
|
||||
|
||||
headers
|
||||
}
|
||||
}
|
||||
124
apple-private-apis/omnisette/src/aos_kit.rs
Normal file
124
apple-private-apis/omnisette/src/aos_kit.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use crate::anisette_headers_provider::AnisetteHeadersProvider;
|
||||
use anyhow::Result;
|
||||
|
||||
use dlopen2::symbor::Library;
|
||||
use objc::{msg_send, runtime::Class, sel, sel_impl};
|
||||
use objc_foundation::{INSString, NSObject, NSString};
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
pub struct AOSKitAnisetteProvider<'lt> {
|
||||
aos_utilities: &'lt Class,
|
||||
ak_device: &'lt Class,
|
||||
}
|
||||
|
||||
impl<'lt> AOSKitAnisetteProvider<'lt> {
|
||||
pub fn new() -> Result<AOSKitAnisetteProvider<'lt>> {
|
||||
Library::open("/System/Library/PrivateFrameworks/AOSKit.framework/AOSKit")?;
|
||||
Library::open("/System/Library/PrivateFrameworks/AuthKit.framework/AuthKit")?;
|
||||
Ok(AOSKitAnisetteProvider {
|
||||
aos_utilities: Class::get("AOSUtilities").ok_or(AOSKitError::ClassLoadFailed)?,
|
||||
ak_device: Class::get("AKDevice").ok_or(AOSKitError::ClassLoadFailed)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "async", async_trait::async_trait(?Send))]
|
||||
impl<'lt> AnisetteHeadersProvider for AOSKitAnisetteProvider<'lt> {
|
||||
#[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)]
|
||||
async fn get_anisette_headers(
|
||||
&mut self,
|
||||
_skip_provisioning: bool,
|
||||
) -> Result<HashMap<String, String>> {
|
||||
let mut headers_map = HashMap::new();
|
||||
|
||||
let headers: *const NSObject = unsafe {
|
||||
msg_send![self.aos_utilities, retrieveOTPHeadersForDSID: NSString::from_str("-2")]
|
||||
};
|
||||
|
||||
let otp: *const NSString =
|
||||
unsafe { msg_send![headers, valueForKey: NSString::from_str("X-Apple-MD")] };
|
||||
headers_map.insert(
|
||||
"X-Apple-I-MD".to_string(),
|
||||
unsafe { (*otp).as_str() }.to_string(),
|
||||
);
|
||||
|
||||
let mid: *const NSString =
|
||||
unsafe { msg_send![headers, valueForKey: NSString::from_str("X-Apple-MD-M")] };
|
||||
headers_map.insert(
|
||||
"X-Apple-I-MD-M".to_string(),
|
||||
unsafe { (*mid).as_str() }.to_string(),
|
||||
);
|
||||
|
||||
let machine_serial_number: *const NSString =
|
||||
unsafe { msg_send![self.aos_utilities, machineSerialNumber] };
|
||||
headers_map.insert(
|
||||
"X-Apple-SRL-NO".to_string(),
|
||||
unsafe { (*machine_serial_number).as_str() }.to_string(),
|
||||
);
|
||||
|
||||
let current_device: *const NSObject = unsafe { msg_send![self.ak_device, currentDevice] };
|
||||
|
||||
let local_user_uuid: *const NSString = unsafe { msg_send![current_device, localUserUUID] };
|
||||
headers_map.insert(
|
||||
"X-Apple-I-MD-LU".to_string(),
|
||||
unsafe { (*local_user_uuid).as_str() }.to_string(),
|
||||
);
|
||||
|
||||
let locale: *const NSObject = unsafe { msg_send![current_device, locale] };
|
||||
let locale: *const NSString = unsafe { msg_send![locale, localeIdentifier] };
|
||||
headers_map.insert(
|
||||
"X-Apple-Locale".to_string(),
|
||||
unsafe { (*locale).as_str() }.to_string(),
|
||||
); // FIXME maybe not the right header name
|
||||
|
||||
let server_friendly_description: *const NSString =
|
||||
unsafe { msg_send![current_device, serverFriendlyDescription] };
|
||||
headers_map.insert(
|
||||
"X-Mme-Client-Info".to_string(),
|
||||
unsafe { (*server_friendly_description).as_str() }.to_string(),
|
||||
);
|
||||
|
||||
let unique_device_identifier: *const NSString =
|
||||
unsafe { msg_send![current_device, uniqueDeviceIdentifier] };
|
||||
headers_map.insert(
|
||||
"X-Mme-Device-Id".to_string(),
|
||||
unsafe { (*unique_device_identifier).as_str() }.to_string(),
|
||||
);
|
||||
|
||||
Ok(headers_map)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AOSKitError {
|
||||
ClassLoadFailed,
|
||||
}
|
||||
|
||||
impl Display for AOSKitError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for AOSKitError {}
|
||||
|
||||
#[cfg(all(test, not(feature = "async")))]
|
||||
mod tests {
|
||||
use crate::anisette_headers_provider::AnisetteHeadersProvider;
|
||||
use crate::aos_kit::AOSKitAnisetteProvider;
|
||||
use anyhow::Result;
|
||||
use log::info;
|
||||
|
||||
#[test]
|
||||
fn fetch_anisette_aoskit() -> Result<()> {
|
||||
crate::tests::init_logger();
|
||||
|
||||
let mut provider = AOSKitAnisetteProvider::new()?;
|
||||
info!(
|
||||
"AOSKit headers: {:?}",
|
||||
(&mut provider as &mut dyn AnisetteHeadersProvider).get_authentication_headers()?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
231
apple-private-apis/omnisette/src/lib.rs
Normal file
231
apple-private-apis/omnisette/src/lib.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
//! A library to generate "anisette" data. Docs are coming soon.
|
||||
//!
|
||||
//! If you want an async API, enable the `async` feature.
|
||||
//!
|
||||
//! If you want remote anisette, make sure the `remote-anisette` feature is enabled. (it's currently on by default)
|
||||
|
||||
use crate::adi_proxy::{ADIProxyAnisetteProvider, ConfigurableADIProxy};
|
||||
use crate::anisette_headers_provider::AnisetteHeadersProvider;
|
||||
use adi_proxy::ADIError;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod adi_proxy;
|
||||
pub mod anisette_headers_provider;
|
||||
pub mod store_services_core;
|
||||
|
||||
#[cfg(feature = "remote-anisette-v3")]
|
||||
pub mod remote_anisette_v3;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod aos_kit;
|
||||
|
||||
#[cfg(feature = "remote-anisette")]
|
||||
pub mod remote_anisette;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct AnisetteHeaders;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AnisetteError {
|
||||
#[allow(dead_code)]
|
||||
#[error("Unsupported device")]
|
||||
UnsupportedDevice,
|
||||
#[error("Invalid argument {0}")]
|
||||
InvalidArgument(String),
|
||||
#[error("Anisette not provisioned!")]
|
||||
AnisetteNotProvisioned,
|
||||
#[error("Plist serialization error {0}")]
|
||||
PlistError(#[from] plist::Error),
|
||||
#[error("Request Error {0}")]
|
||||
ReqwestError(#[from] reqwest::Error),
|
||||
#[cfg(feature = "remote-anisette-v3")]
|
||||
#[error("Provisioning socket error {0}")]
|
||||
WsError(#[from] tokio_tungstenite::tungstenite::error::Error),
|
||||
#[cfg(feature = "remote-anisette-v3")]
|
||||
#[error("JSON error {0}")]
|
||||
SerdeError(#[from] serde_json::Error),
|
||||
#[error("IO error {0}")]
|
||||
IOError(#[from] io::Error),
|
||||
#[error("ADI error {0}")]
|
||||
ADIError(#[from] ADIError),
|
||||
#[error("Invalid library format")]
|
||||
InvalidLibraryFormat,
|
||||
#[error("Misc")]
|
||||
Misc,
|
||||
#[error("Missing Libraries")]
|
||||
MissingLibraries,
|
||||
#[error("{0}")]
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
pub const DEFAULT_ANISETTE_URL: &str = "https://ani.f1sh.me/";
|
||||
|
||||
pub const DEFAULT_ANISETTE_URL_V3: &str = "https://ani.sidestore.io";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnisetteConfiguration {
|
||||
anisette_url: String,
|
||||
anisette_url_v3: String,
|
||||
configuration_path: PathBuf,
|
||||
macos_serial: String,
|
||||
}
|
||||
|
||||
impl Default for AnisetteConfiguration {
|
||||
fn default() -> Self {
|
||||
AnisetteConfiguration::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AnisetteConfiguration {
|
||||
pub fn new() -> AnisetteConfiguration {
|
||||
AnisetteConfiguration {
|
||||
anisette_url: DEFAULT_ANISETTE_URL.to_string(),
|
||||
anisette_url_v3: DEFAULT_ANISETTE_URL_V3.to_string(),
|
||||
configuration_path: PathBuf::new(),
|
||||
macos_serial: "0".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn anisette_url(&self) -> &String {
|
||||
&self.anisette_url
|
||||
}
|
||||
|
||||
pub fn configuration_path(&self) -> &PathBuf {
|
||||
&self.configuration_path
|
||||
}
|
||||
|
||||
pub fn set_anisette_url(mut self, anisette_url: String) -> AnisetteConfiguration {
|
||||
self.anisette_url = anisette_url;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_macos_serial(mut self, macos_serial: String) -> AnisetteConfiguration {
|
||||
self.macos_serial = macos_serial;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_configuration_path(mut self, configuration_path: PathBuf) -> AnisetteConfiguration {
|
||||
self.configuration_path = configuration_path;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AnisetteHeadersProviderType {
|
||||
Local,
|
||||
Remote,
|
||||
}
|
||||
|
||||
pub struct AnisetteHeadersProviderRes {
|
||||
pub provider: Box<dyn AnisetteHeadersProvider>,
|
||||
pub provider_type: AnisetteHeadersProviderType,
|
||||
}
|
||||
|
||||
impl AnisetteHeadersProviderRes {
|
||||
pub fn local(provider: Box<dyn AnisetteHeadersProvider>) -> AnisetteHeadersProviderRes {
|
||||
AnisetteHeadersProviderRes {
|
||||
provider,
|
||||
provider_type: AnisetteHeadersProviderType::Local,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remote(provider: Box<dyn AnisetteHeadersProvider>) -> AnisetteHeadersProviderRes {
|
||||
AnisetteHeadersProviderRes {
|
||||
provider,
|
||||
provider_type: AnisetteHeadersProviderType::Remote,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnisetteHeaders {
|
||||
pub fn get_anisette_headers_provider(
|
||||
configuration: AnisetteConfiguration,
|
||||
) -> Result<AnisetteHeadersProviderRes, AnisetteError> {
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Ok(prov) = aos_kit::AOSKitAnisetteProvider::new() {
|
||||
return Ok(AnisetteHeadersProviderRes::local(Box::new(prov)));
|
||||
}
|
||||
|
||||
// TODO: handle Err because it will just go to remote anisette and not tell the user anything
|
||||
if let Ok(ssc_anisette_headers_provider) =
|
||||
AnisetteHeaders::get_ssc_anisette_headers_provider(configuration.clone())
|
||||
{
|
||||
return Ok(ssc_anisette_headers_provider);
|
||||
}
|
||||
|
||||
#[cfg(feature = "remote-anisette-v3")]
|
||||
return Ok(AnisetteHeadersProviderRes::remote(Box::new(
|
||||
remote_anisette_v3::RemoteAnisetteProviderV3::new(
|
||||
configuration.anisette_url_v3,
|
||||
configuration.configuration_path.clone(),
|
||||
configuration.macos_serial.clone(),
|
||||
),
|
||||
)));
|
||||
|
||||
#[cfg(feature = "remote-anisette")]
|
||||
#[allow(unreachable_code)]
|
||||
return Ok(AnisetteHeadersProviderRes::remote(Box::new(
|
||||
remote_anisette::RemoteAnisetteProvider::new(configuration.anisette_url),
|
||||
)));
|
||||
|
||||
#[cfg(not(feature = "remote-anisette"))]
|
||||
bail!(AnisetteMetaError::UnsupportedDevice)
|
||||
}
|
||||
|
||||
pub fn get_ssc_anisette_headers_provider(
|
||||
configuration: AnisetteConfiguration,
|
||||
) -> Result<AnisetteHeadersProviderRes, AnisetteError> {
|
||||
let mut ssc_adi_proxy = store_services_core::StoreServicesCoreADIProxy::new(
|
||||
configuration.configuration_path(),
|
||||
)?;
|
||||
let config_path = configuration.configuration_path();
|
||||
ssc_adi_proxy.set_provisioning_path(config_path.to_str().ok_or(
|
||||
AnisetteError::InvalidArgument("configuration.configuration_path".to_string()),
|
||||
)?)?;
|
||||
Ok(AnisetteHeadersProviderRes::local(Box::new(
|
||||
ADIProxyAnisetteProvider::new(ssc_adi_proxy, config_path.to_path_buf())?,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use log::LevelFilter;
|
||||
use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode};
|
||||
|
||||
pub fn init_logger() {
|
||||
if TermLogger::init(
|
||||
LevelFilter::Trace,
|
||||
ConfigBuilder::new()
|
||||
.set_target_level(LevelFilter::Error)
|
||||
.add_filter_allow_str("omnisette")
|
||||
.build(),
|
||||
TerminalMode::Mixed,
|
||||
ColorChoice::Auto,
|
||||
)
|
||||
.is_ok()
|
||||
{}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
#[test]
|
||||
fn fetch_anisette_auto() -> Result<()> {
|
||||
use crate::{AnisetteConfiguration, AnisetteHeaders};
|
||||
use log::info;
|
||||
use std::path::PathBuf;
|
||||
|
||||
crate::tests::init_logger();
|
||||
|
||||
let mut provider = AnisetteHeaders::get_anisette_headers_provider(
|
||||
AnisetteConfiguration::new()
|
||||
.set_configuration_path(PathBuf::new().join("anisette_test")),
|
||||
)?;
|
||||
info!(
|
||||
"Headers: {:?}",
|
||||
provider.provider.get_authentication_headers()?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
47
apple-private-apis/omnisette/src/remote_anisette.rs
Normal file
47
apple-private-apis/omnisette/src/remote_anisette.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use crate::{anisette_headers_provider::AnisetteHeadersProvider, AnisetteError};
|
||||
#[cfg(not(feature = "async"))]
|
||||
use reqwest::blocking::get;
|
||||
#[cfg(feature = "async")]
|
||||
use reqwest::get;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct RemoteAnisetteProvider {
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl RemoteAnisetteProvider {
|
||||
pub fn new(url: String) -> RemoteAnisetteProvider {
|
||||
RemoteAnisetteProvider { url }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "async", async_trait::async_trait)]
|
||||
impl AnisetteHeadersProvider for RemoteAnisetteProvider {
|
||||
#[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)]
|
||||
async fn get_anisette_headers(
|
||||
&mut self,
|
||||
_skip_provisioning: bool,
|
||||
) -> Result<HashMap<String, String>, AnisetteError> {
|
||||
Ok(get(&self.url).await?.json().await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(feature = "async")))]
|
||||
mod tests {
|
||||
use crate::anisette_headers_provider::AnisetteHeadersProvider;
|
||||
use crate::remote_anisette::RemoteAnisetteProvider;
|
||||
use crate::DEFAULT_ANISETTE_URL;
|
||||
use log::info;
|
||||
|
||||
#[test]
|
||||
fn fetch_anisette_remote() -> Result<(), AnisetteError> {
|
||||
crate::tests::init_logger();
|
||||
|
||||
let mut provider = RemoteAnisetteProvider::new(DEFAULT_ANISETTE_URL.to_string());
|
||||
info!(
|
||||
"Remote headers: {:?}",
|
||||
(&mut provider as &mut dyn AnisetteHeadersProvider).get_authentication_headers()?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
541
apple-private-apis/omnisette/src/remote_anisette_v3.rs
Normal file
541
apple-private-apis/omnisette/src/remote_anisette_v3.rs
Normal file
@@ -0,0 +1,541 @@
|
||||
// Implementing the SideStore Anisette v3 protocol
|
||||
|
||||
use std::{collections::HashMap, fs, io::Cursor, path::PathBuf};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use base64::engine::general_purpose;
|
||||
use base64::Engine;
|
||||
use chrono::{DateTime, SubsecRound, Utc};
|
||||
use futures_util::{stream::StreamExt, SinkExt};
|
||||
use log::debug;
|
||||
use plist::{Data, Dictionary};
|
||||
use rand::Rng;
|
||||
use reqwest::{Client, ClientBuilder, RequestBuilder};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fmt::Write;
|
||||
use tokio_tungstenite::{connect_async, tungstenite::Message};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{anisette_headers_provider::AnisetteHeadersProvider, AnisetteError};
|
||||
|
||||
fn plist_to_string<T: serde::Serialize>(value: &T) -> Result<String, plist::Error> {
|
||||
plist_to_buf(value).map(|val| String::from_utf8(val).unwrap())
|
||||
}
|
||||
|
||||
fn plist_to_buf<T: serde::Serialize>(value: &T) -> Result<Vec<u8>, plist::Error> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let writer = Cursor::new(&mut buf);
|
||||
plist::to_writer_xml(writer, &value)?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn bin_serialize<S>(x: &[u8], s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
s.serialize_bytes(x)
|
||||
}
|
||||
|
||||
fn bin_serialize_opt<S>(x: &Option<Vec<u8>>, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
x.clone().map(|i| Data::new(i)).serialize(s)
|
||||
}
|
||||
|
||||
fn bin_deserialize_opt<'de, D>(d: D) -> Result<Option<Vec<u8>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: Option<Data> = Deserialize::deserialize(d)?;
|
||||
Ok(s.map(|i| i.into()))
|
||||
}
|
||||
|
||||
fn bin_deserialize_16<'de, D>(d: D) -> Result<[u8; 16], D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: Data = Deserialize::deserialize(d)?;
|
||||
let s: Vec<u8> = s.into();
|
||||
Ok(s.try_into().unwrap())
|
||||
}
|
||||
|
||||
fn encode_hex(bytes: &[u8]) -> String {
|
||||
let mut s = String::with_capacity(bytes.len() * 2);
|
||||
for &b in bytes {
|
||||
write!(&mut s, "{:02x}", b).unwrap();
|
||||
}
|
||||
s
|
||||
}
|
||||
fn base64_encode(data: &[u8]) -> String {
|
||||
general_purpose::STANDARD.encode(data)
|
||||
}
|
||||
|
||||
fn base64_decode(data: &str) -> Vec<u8> {
|
||||
general_purpose::STANDARD.decode(data.trim()).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AnisetteClientInfo {
|
||||
client_info: String,
|
||||
user_agent: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AnisetteState {
|
||||
#[serde(
|
||||
serialize_with = "bin_serialize",
|
||||
deserialize_with = "bin_deserialize_16"
|
||||
)]
|
||||
keychain_identifier: [u8; 16],
|
||||
#[serde(
|
||||
serialize_with = "bin_serialize_opt",
|
||||
deserialize_with = "bin_deserialize_opt"
|
||||
)]
|
||||
adi_pb: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Default for AnisetteState {
|
||||
fn default() -> Self {
|
||||
AnisetteState {
|
||||
keychain_identifier: rand::thread_rng().gen::<[u8; 16]>(),
|
||||
adi_pb: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnisetteState {
|
||||
pub fn new() -> AnisetteState {
|
||||
AnisetteState::default()
|
||||
}
|
||||
|
||||
pub fn is_provisioned(&self) -> bool {
|
||||
self.adi_pb.is_some()
|
||||
}
|
||||
|
||||
fn md_lu(&self) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&self.keychain_identifier);
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
fn device_id(&self) -> String {
|
||||
Uuid::from_bytes(self.keychain_identifier).to_string()
|
||||
}
|
||||
}
|
||||
pub struct AnisetteClient {
|
||||
client_info: AnisetteClientInfo,
|
||||
url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
struct ProvisionBodyData {
|
||||
header: Dictionary,
|
||||
request: Dictionary,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnisetteData {
|
||||
machine_id: String,
|
||||
one_time_password: String,
|
||||
routing_info: String,
|
||||
device_description: String,
|
||||
local_user_id: String,
|
||||
device_unique_identifier: String,
|
||||
}
|
||||
|
||||
impl AnisetteData {
|
||||
pub fn get_headers(&self, serial: String) -> HashMap<String, String> {
|
||||
let dt: DateTime<Utc> = Utc::now().round_subsecs(0);
|
||||
|
||||
HashMap::from_iter(
|
||||
[
|
||||
(
|
||||
"X-Apple-I-Client-Time".to_string(),
|
||||
dt.format("%+").to_string().replace("+00:00", "Z"),
|
||||
),
|
||||
("X-Apple-I-SRL-NO".to_string(), serial),
|
||||
("X-Apple-I-TimeZone".to_string(), "UTC".to_string()),
|
||||
("X-Apple-Locale".to_string(), "en_US".to_string()),
|
||||
("X-Apple-I-MD-RINFO".to_string(), self.routing_info.clone()),
|
||||
("X-Apple-I-MD-LU".to_string(), self.local_user_id.clone()),
|
||||
(
|
||||
"X-Mme-Device-Id".to_string(),
|
||||
self.device_unique_identifier.clone(),
|
||||
),
|
||||
("X-Apple-I-MD".to_string(), self.one_time_password.clone()),
|
||||
("X-Apple-I-MD-M".to_string(), self.machine_id.clone()),
|
||||
(
|
||||
"X-Mme-Client-Info".to_string(),
|
||||
self.device_description.clone(),
|
||||
),
|
||||
]
|
||||
.into_iter(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_reqwest() -> Result<Client, AnisetteError> {
|
||||
Ok(ClientBuilder::new()
|
||||
.http1_title_case_headers()
|
||||
.danger_accept_invalid_certs(true) // TODO: pin the apple certificate
|
||||
.build()?)
|
||||
}
|
||||
|
||||
impl AnisetteClient {
|
||||
pub async fn new(url: String) -> Result<AnisetteClient, AnisetteError> {
|
||||
let path = format!("{}/v3/client_info", url);
|
||||
let http_client = make_reqwest()?;
|
||||
let client_info = http_client
|
||||
.get(path)
|
||||
.send()
|
||||
.await?
|
||||
.json::<AnisetteClientInfo>()
|
||||
.await?;
|
||||
Ok(AnisetteClient { client_info, url })
|
||||
}
|
||||
|
||||
fn build_apple_request(
|
||||
&self,
|
||||
state: &AnisetteState,
|
||||
builder: RequestBuilder,
|
||||
) -> RequestBuilder {
|
||||
let dt: DateTime<Utc> = Utc::now().round_subsecs(0);
|
||||
|
||||
builder
|
||||
.header("X-Mme-Client-Info", &self.client_info.client_info)
|
||||
.header("User-Agent", &self.client_info.user_agent)
|
||||
.header("Content-Type", "text/x-xml-plist")
|
||||
.header("X-Apple-I-MD-LU", encode_hex(&state.md_lu()))
|
||||
.header("X-Mme-Device-Id", state.device_id())
|
||||
.header("X-Apple-I-Client-Time", dt.format("%+").to_string())
|
||||
.header("X-Apple-I-TimeZone", "EDT")
|
||||
.header("X-Apple-Locale", "en_US")
|
||||
}
|
||||
|
||||
pub async fn get_headers(&self, state: &AnisetteState) -> Result<AnisetteData, AnisetteError> {
|
||||
let path = format!("{}/v3/get_headers", self.url);
|
||||
let http_client = make_reqwest()?;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GetHeadersBody {
|
||||
identifier: String,
|
||||
adi_pb: String,
|
||||
}
|
||||
let body = GetHeadersBody {
|
||||
identifier: base64_encode(&state.keychain_identifier),
|
||||
adi_pb: base64_encode(
|
||||
state
|
||||
.adi_pb
|
||||
.as_ref()
|
||||
.ok_or(AnisetteError::AnisetteNotProvisioned)?,
|
||||
),
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "result")]
|
||||
enum AnisetteHeaders {
|
||||
GetHeadersError {
|
||||
message: String,
|
||||
},
|
||||
Headers {
|
||||
#[serde(rename = "X-Apple-I-MD-M")]
|
||||
machine_id: String,
|
||||
#[serde(rename = "X-Apple-I-MD")]
|
||||
one_time_password: String,
|
||||
#[serde(rename = "X-Apple-I-MD-RINFO")]
|
||||
routing_info: String,
|
||||
},
|
||||
}
|
||||
|
||||
let headers = http_client
|
||||
.post(path)
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?
|
||||
.json::<AnisetteHeaders>()
|
||||
.await?;
|
||||
match headers {
|
||||
AnisetteHeaders::GetHeadersError { message } => {
|
||||
if message.contains("-45061") {
|
||||
Err(AnisetteError::AnisetteNotProvisioned)
|
||||
} else {
|
||||
panic!("Unknown error {}", message)
|
||||
}
|
||||
}
|
||||
AnisetteHeaders::Headers {
|
||||
machine_id,
|
||||
one_time_password,
|
||||
routing_info,
|
||||
} => Ok(AnisetteData {
|
||||
machine_id,
|
||||
one_time_password,
|
||||
routing_info,
|
||||
device_description: self.client_info.client_info.clone(),
|
||||
local_user_id: encode_hex(&state.md_lu()),
|
||||
device_unique_identifier: state.device_id(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn provision(&self, state: &mut AnisetteState) -> Result<(), AnisetteError> {
|
||||
debug!("Provisioning Anisette");
|
||||
let http_client = make_reqwest()?;
|
||||
let resp = self
|
||||
.build_apple_request(
|
||||
&state,
|
||||
http_client.get("https://gsa.apple.com/grandslam/GsService2/lookup"),
|
||||
)
|
||||
.send()
|
||||
.await?;
|
||||
let text = resp.text().await?;
|
||||
|
||||
let protocol_val = plist::Value::from_reader(Cursor::new(text.as_str()))?;
|
||||
let urls = protocol_val
|
||||
.as_dictionary()
|
||||
.unwrap()
|
||||
.get("urls")
|
||||
.unwrap()
|
||||
.as_dictionary()
|
||||
.unwrap();
|
||||
|
||||
let start_provisioning_url = urls
|
||||
.get("midStartProvisioning")
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.unwrap();
|
||||
let end_provisioning_url = urls
|
||||
.get("midFinishProvisioning")
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.unwrap();
|
||||
debug!(
|
||||
"Got provisioning urls: {} and {}",
|
||||
start_provisioning_url, end_provisioning_url
|
||||
);
|
||||
|
||||
let provision_ws_url =
|
||||
format!("{}/v3/provisioning_session", self.url).replace("https://", "wss://");
|
||||
let (mut connection, _) = connect_async(&provision_ws_url).await?;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "result")]
|
||||
enum ProvisionInput {
|
||||
GiveIdentifier,
|
||||
GiveStartProvisioningData,
|
||||
GiveEndProvisioningData {
|
||||
#[allow(dead_code)] // it's not even dead, rust just has problems
|
||||
cpim: String,
|
||||
},
|
||||
ProvisioningSuccess {
|
||||
#[allow(dead_code)] // it's not even dead, rust just has problems
|
||||
adi_pb: String,
|
||||
},
|
||||
}
|
||||
|
||||
loop {
|
||||
let Some(Ok(data)) = connection.next().await else {
|
||||
continue;
|
||||
};
|
||||
if data.is_text() {
|
||||
let txt = data.to_text().unwrap();
|
||||
let msg: ProvisionInput = serde_json::from_str(txt)?;
|
||||
match msg {
|
||||
ProvisionInput::GiveIdentifier => {
|
||||
#[derive(Serialize)]
|
||||
struct Identifier {
|
||||
identifier: String, // base64
|
||||
}
|
||||
let identifier = Identifier {
|
||||
identifier: base64_encode(&state.keychain_identifier),
|
||||
};
|
||||
connection
|
||||
.send(Message::Text(serde_json::to_string(&identifier)?))
|
||||
.await?;
|
||||
}
|
||||
ProvisionInput::GiveStartProvisioningData => {
|
||||
let http_client = make_reqwest()?;
|
||||
let body_data = ProvisionBodyData {
|
||||
header: Dictionary::new(),
|
||||
request: Dictionary::new(),
|
||||
};
|
||||
let resp = self
|
||||
.build_apple_request(state, http_client.post(start_provisioning_url))
|
||||
.body(plist_to_string(&body_data)?)
|
||||
.send()
|
||||
.await?;
|
||||
let text = resp.text().await?;
|
||||
|
||||
let protocol_val = plist::Value::from_reader(Cursor::new(text.as_str()))?;
|
||||
let spim = protocol_val
|
||||
.as_dictionary()
|
||||
.unwrap()
|
||||
.get("Response")
|
||||
.unwrap()
|
||||
.as_dictionary()
|
||||
.unwrap()
|
||||
.get("spim")
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.unwrap();
|
||||
|
||||
debug!("GiveStartProvisioningData");
|
||||
#[derive(Serialize)]
|
||||
struct Spim {
|
||||
spim: String, // base64
|
||||
}
|
||||
let spim = Spim {
|
||||
spim: spim.to_string(),
|
||||
};
|
||||
connection
|
||||
.send(Message::Text(serde_json::to_string(&spim)?))
|
||||
.await?;
|
||||
}
|
||||
ProvisionInput::GiveEndProvisioningData { cpim } => {
|
||||
let http_client = make_reqwest()?;
|
||||
let body_data = ProvisionBodyData {
|
||||
header: Dictionary::new(),
|
||||
request: Dictionary::from_iter([("cpim", cpim)].into_iter()),
|
||||
};
|
||||
let resp = self
|
||||
.build_apple_request(state, http_client.post(end_provisioning_url))
|
||||
.body(plist_to_string(&body_data)?)
|
||||
.send()
|
||||
.await?;
|
||||
let text = resp.text().await?;
|
||||
|
||||
let protocol_val = plist::Value::from_reader(Cursor::new(text.as_str()))?;
|
||||
let response = protocol_val
|
||||
.as_dictionary()
|
||||
.unwrap()
|
||||
.get("Response")
|
||||
.unwrap()
|
||||
.as_dictionary()
|
||||
.unwrap();
|
||||
|
||||
debug!("GiveEndProvisioningData");
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct EndProvisioning<'t> {
|
||||
ptm: &'t str,
|
||||
tk: &'t str,
|
||||
}
|
||||
let end_provisioning = EndProvisioning {
|
||||
ptm: response.get("ptm").unwrap().as_string().unwrap(),
|
||||
tk: response.get("tk").unwrap().as_string().unwrap(),
|
||||
};
|
||||
connection
|
||||
.send(Message::Text(serde_json::to_string(&end_provisioning)?))
|
||||
.await?;
|
||||
}
|
||||
ProvisionInput::ProvisioningSuccess { adi_pb } => {
|
||||
debug!("ProvisioningSuccess");
|
||||
state.adi_pb = Some(base64_decode(&adi_pb));
|
||||
connection.close(None).await?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if data.is_close() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RemoteAnisetteProviderV3 {
|
||||
client_url: String,
|
||||
client: Option<AnisetteClient>,
|
||||
pub state: Option<AnisetteState>,
|
||||
configuration_path: PathBuf,
|
||||
serial: String,
|
||||
}
|
||||
|
||||
impl RemoteAnisetteProviderV3 {
|
||||
pub fn new(
|
||||
url: String,
|
||||
configuration_path: PathBuf,
|
||||
serial: String,
|
||||
) -> RemoteAnisetteProviderV3 {
|
||||
RemoteAnisetteProviderV3 {
|
||||
client_url: url,
|
||||
client: None,
|
||||
state: None,
|
||||
configuration_path,
|
||||
serial,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AnisetteHeadersProvider for RemoteAnisetteProviderV3 {
|
||||
async fn get_anisette_headers(
|
||||
&mut self,
|
||||
_skip_provisioning: bool,
|
||||
) -> Result<HashMap<String, String>, AnisetteError> {
|
||||
if self.client.is_none() {
|
||||
self.client = Some(AnisetteClient::new(self.client_url.clone()).await?);
|
||||
}
|
||||
let client = self.client.as_ref().unwrap();
|
||||
|
||||
fs::create_dir_all(&self.configuration_path)?;
|
||||
|
||||
let config_path = self.configuration_path.join("state.plist");
|
||||
if self.state.is_none() {
|
||||
self.state = Some(if let Ok(text) = plist::from_file(&config_path) {
|
||||
text
|
||||
} else {
|
||||
AnisetteState::new()
|
||||
});
|
||||
}
|
||||
|
||||
let state = self.state.as_mut().unwrap();
|
||||
if !state.is_provisioned() {
|
||||
client.provision(state).await?;
|
||||
plist::to_file_xml(&config_path, state)?;
|
||||
}
|
||||
let data = match client.get_headers(&state).await {
|
||||
Ok(data) => data,
|
||||
Err(err) => {
|
||||
if matches!(err, AnisetteError::AnisetteNotProvisioned) {
|
||||
state.adi_pb = None;
|
||||
client.provision(state).await?;
|
||||
plist::to_file_xml(config_path, state)?;
|
||||
client.get_headers(&state).await?
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(data.get_headers(self.serial.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::anisette_headers_provider::AnisetteHeadersProvider;
|
||||
use crate::remote_anisette_v3::RemoteAnisetteProviderV3;
|
||||
use crate::{AnisetteError, DEFAULT_ANISETTE_URL_V3};
|
||||
use log::info;
|
||||
|
||||
#[tokio::test]
|
||||
async fn fetch_anisette_remote_v3() -> Result<(), AnisetteError> {
|
||||
crate::tests::init_logger();
|
||||
|
||||
let mut provider = RemoteAnisetteProviderV3::new(
|
||||
DEFAULT_ANISETTE_URL_V3.to_string(),
|
||||
"anisette_test".into(),
|
||||
"0".to_string(),
|
||||
);
|
||||
info!(
|
||||
"Remote headers: {:?}",
|
||||
(&mut provider as &mut dyn AnisetteHeadersProvider)
|
||||
.get_authentication_headers()
|
||||
.await?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
452
apple-private-apis/omnisette/src/store_services_core.rs
Normal file
452
apple-private-apis/omnisette/src/store_services_core.rs
Normal file
@@ -0,0 +1,452 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
mod posix_macos;
|
||||
#[cfg(target_family = "windows")]
|
||||
mod posix_windows;
|
||||
|
||||
use crate::adi_proxy::{
|
||||
ADIError, ADIProxy, ConfigurableADIProxy, RequestOTPData, StartProvisioningData,
|
||||
SynchronizeData,
|
||||
};
|
||||
use crate::AnisetteError;
|
||||
|
||||
use android_loader::android_library::AndroidLibrary;
|
||||
use android_loader::sysv64_type;
|
||||
use android_loader::{hook_manager, sysv64};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{c_char, CString};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct StoreServicesCoreADIProxy<'lt> {
|
||||
#[allow(dead_code)]
|
||||
store_services_core: AndroidLibrary<'lt>,
|
||||
|
||||
local_user_uuid: String,
|
||||
device_identifier: String,
|
||||
|
||||
adi_set_android_id: sysv64_type!(fn(id: *const u8, length: u32) -> i32),
|
||||
adi_set_provisioning_path: sysv64_type!(fn(path: *const u8) -> i32),
|
||||
|
||||
adi_provisioning_erase: sysv64_type!(fn(ds_id: i64) -> i32),
|
||||
adi_synchronize: sysv64_type!(
|
||||
fn(
|
||||
ds_id: i64,
|
||||
sim: *const u8,
|
||||
sim_length: u32,
|
||||
out_mid: *mut *const u8,
|
||||
out_mid_length: *mut u32,
|
||||
out_srm: *mut *const u8,
|
||||
out_srm_length: *mut u32,
|
||||
) -> i32
|
||||
),
|
||||
adi_provisioning_destroy: sysv64_type!(fn(session: u32) -> i32),
|
||||
adi_provisioning_end: sysv64_type!(
|
||||
fn(session: u32, ptm: *const u8, ptm_length: u32, tk: *const u8, tk_length: u32) -> i32
|
||||
),
|
||||
adi_provisioning_start: sysv64_type!(
|
||||
fn(
|
||||
ds_id: i64,
|
||||
spim: *const u8,
|
||||
spim_length: u32,
|
||||
out_cpim: *mut *const u8,
|
||||
out_cpim_length: *mut u32,
|
||||
out_session: *mut u32,
|
||||
) -> i32
|
||||
),
|
||||
adi_get_login_code: sysv64_type!(fn(ds_id: i64) -> i32),
|
||||
adi_dispose: sysv64_type!(fn(ptr: *const u8) -> i32),
|
||||
adi_otp_request: sysv64_type!(
|
||||
fn(
|
||||
ds_id: i64,
|
||||
out_mid: *mut *const u8,
|
||||
out_mid_size: *mut u32,
|
||||
out_otp: *mut *const u8,
|
||||
out_otp_size: *mut u32,
|
||||
) -> i32
|
||||
),
|
||||
}
|
||||
|
||||
impl StoreServicesCoreADIProxy<'_> {
|
||||
pub fn new<'lt>(library_path: &PathBuf) -> Result<StoreServicesCoreADIProxy<'lt>, AnisetteError> {
|
||||
Self::with_custom_provisioning_path(library_path, library_path)
|
||||
}
|
||||
|
||||
pub fn with_custom_provisioning_path<'lt>(library_path: &PathBuf, provisioning_path: &PathBuf) -> Result<StoreServicesCoreADIProxy<'lt>, AnisetteError> {
|
||||
// Should be safe if the library is correct.
|
||||
unsafe {
|
||||
LoaderHelpers::setup_hooks();
|
||||
|
||||
if !library_path.exists() {
|
||||
std::fs::create_dir(library_path)?;
|
||||
return Err(AnisetteError::MissingLibraries.into());
|
||||
}
|
||||
|
||||
let library_path = library_path.canonicalize()?;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const ARCH: &str = "x86_64";
|
||||
#[cfg(target_arch = "x86")]
|
||||
const ARCH: &str = "x86";
|
||||
#[cfg(target_arch = "arm")]
|
||||
const ARCH: &str = "armeabi-v7a";
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const ARCH: &str = "arm64-v8a";
|
||||
|
||||
let native_library_path = library_path.join("lib").join(ARCH);
|
||||
|
||||
let path = native_library_path.join("libstoreservicescore.so");
|
||||
let path = path.to_str().ok_or(AnisetteError::Misc)?;
|
||||
let store_services_core = AndroidLibrary::load(path)?;
|
||||
|
||||
let adi_load_library_with_path: sysv64_type!(fn(path: *const u8) -> i32) =
|
||||
std::mem::transmute(
|
||||
store_services_core
|
||||
.get_symbol("kq56gsgHG6")
|
||||
.ok_or(AnisetteError::InvalidLibraryFormat)?,
|
||||
);
|
||||
|
||||
let path = CString::new(
|
||||
native_library_path
|
||||
.to_str()
|
||||
.ok_or(AnisetteError::Misc)?,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!((adi_load_library_with_path)(path.as_ptr() as *const u8), 0);
|
||||
|
||||
let adi_set_android_id = store_services_core
|
||||
.get_symbol("Sph98paBcz")
|
||||
.ok_or(AnisetteError::InvalidLibraryFormat)?;
|
||||
let adi_set_provisioning_path = store_services_core
|
||||
.get_symbol("nf92ngaK92")
|
||||
.ok_or(AnisetteError::InvalidLibraryFormat)?;
|
||||
|
||||
let adi_provisioning_erase = store_services_core
|
||||
.get_symbol("p435tmhbla")
|
||||
.ok_or(AnisetteError::InvalidLibraryFormat)?;
|
||||
let adi_synchronize = store_services_core
|
||||
.get_symbol("tn46gtiuhw")
|
||||
.ok_or(AnisetteError::InvalidLibraryFormat)?;
|
||||
let adi_provisioning_destroy = store_services_core
|
||||
.get_symbol("fy34trz2st")
|
||||
.ok_or(AnisetteError::InvalidLibraryFormat)?;
|
||||
let adi_provisioning_end = store_services_core
|
||||
.get_symbol("uv5t6nhkui")
|
||||
.ok_or(AnisetteError::InvalidLibraryFormat)?;
|
||||
let adi_provisioning_start = store_services_core
|
||||
.get_symbol("rsegvyrt87")
|
||||
.ok_or(AnisetteError::InvalidLibraryFormat)?;
|
||||
let adi_get_login_code = store_services_core
|
||||
.get_symbol("aslgmuibau")
|
||||
.ok_or(AnisetteError::InvalidLibraryFormat)?;
|
||||
let adi_dispose = store_services_core
|
||||
.get_symbol("jk24uiwqrg")
|
||||
.ok_or(AnisetteError::InvalidLibraryFormat)?;
|
||||
let adi_otp_request = store_services_core
|
||||
.get_symbol("qi864985u0")
|
||||
.ok_or(AnisetteError::InvalidLibraryFormat)?;
|
||||
|
||||
let mut proxy = StoreServicesCoreADIProxy {
|
||||
store_services_core,
|
||||
|
||||
local_user_uuid: String::new(),
|
||||
device_identifier: String::new(),
|
||||
|
||||
adi_set_android_id: std::mem::transmute(adi_set_android_id),
|
||||
adi_set_provisioning_path: std::mem::transmute(adi_set_provisioning_path),
|
||||
|
||||
adi_provisioning_erase: std::mem::transmute(adi_provisioning_erase),
|
||||
adi_synchronize: std::mem::transmute(adi_synchronize),
|
||||
adi_provisioning_destroy: std::mem::transmute(adi_provisioning_destroy),
|
||||
adi_provisioning_end: std::mem::transmute(adi_provisioning_end),
|
||||
adi_provisioning_start: std::mem::transmute(adi_provisioning_start),
|
||||
adi_get_login_code: std::mem::transmute(adi_get_login_code),
|
||||
adi_dispose: std::mem::transmute(adi_dispose),
|
||||
adi_otp_request: std::mem::transmute(adi_otp_request),
|
||||
};
|
||||
|
||||
proxy.set_provisioning_path(
|
||||
provisioning_path.to_str().ok_or(AnisetteError::Misc)?,
|
||||
)?;
|
||||
|
||||
Ok(proxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ADIProxy for StoreServicesCoreADIProxy<'_> {
|
||||
fn erase_provisioning(&mut self, ds_id: i64) -> Result<(), ADIError> {
|
||||
match (self.adi_provisioning_erase)(ds_id) {
|
||||
0 => Ok(()),
|
||||
err => Err(ADIError::resolve(err)),
|
||||
}
|
||||
}
|
||||
|
||||
fn synchronize(&mut self, ds_id: i64, sim: &[u8]) -> Result<SynchronizeData, ADIError> {
|
||||
unsafe {
|
||||
let sim_size = sim.len() as u32;
|
||||
let sim_ptr = sim.as_ptr();
|
||||
|
||||
let mut mid_size: u32 = 0;
|
||||
let mut mid_ptr: *const u8 = std::ptr::null();
|
||||
let mut srm_size: u32 = 0;
|
||||
let mut srm_ptr: *const u8 = std::ptr::null();
|
||||
|
||||
match (self.adi_synchronize)(
|
||||
ds_id,
|
||||
sim_ptr,
|
||||
sim_size,
|
||||
&mut mid_ptr,
|
||||
&mut mid_size,
|
||||
&mut srm_ptr,
|
||||
&mut srm_size,
|
||||
) {
|
||||
0 => {
|
||||
let mut mid = vec![0; mid_size as usize];
|
||||
let mut srm = vec![0; srm_size as usize];
|
||||
|
||||
mid.copy_from_slice(std::slice::from_raw_parts(mid_ptr, mid_size as usize));
|
||||
srm.copy_from_slice(std::slice::from_raw_parts(srm_ptr, srm_size as usize));
|
||||
|
||||
(self.adi_dispose)(mid_ptr);
|
||||
(self.adi_dispose)(srm_ptr);
|
||||
|
||||
Ok(SynchronizeData { mid, srm })
|
||||
}
|
||||
err => Err(ADIError::resolve(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy_provisioning_session(&mut self, session: u32) -> Result<(), ADIError> {
|
||||
match (self.adi_provisioning_destroy)(session) {
|
||||
0 => Ok(()),
|
||||
err => Err(ADIError::resolve(err)),
|
||||
}
|
||||
}
|
||||
|
||||
fn end_provisioning(&mut self, session: u32, ptm: &[u8], tk: &[u8]) -> Result<(), ADIError> {
|
||||
let ptm_size = ptm.len() as u32;
|
||||
let ptm_ptr = ptm.as_ptr();
|
||||
|
||||
let tk_size = tk.len() as u32;
|
||||
let tk_ptr = tk.as_ptr();
|
||||
|
||||
match (self.adi_provisioning_end)(session, ptm_ptr, ptm_size, tk_ptr, tk_size) {
|
||||
0 => Ok(()),
|
||||
err => Err(ADIError::resolve(err)),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_provisioning(
|
||||
&mut self,
|
||||
ds_id: i64,
|
||||
spim: &[u8],
|
||||
) -> Result<StartProvisioningData, ADIError> {
|
||||
unsafe {
|
||||
let spim_size = spim.len() as u32;
|
||||
let spim_ptr = spim.as_ptr();
|
||||
|
||||
let mut cpim_size: u32 = 0;
|
||||
let mut cpim_ptr: *const u8 = std::ptr::null();
|
||||
|
||||
let mut session: u32 = 0;
|
||||
|
||||
match (self.adi_provisioning_start)(
|
||||
ds_id,
|
||||
spim_ptr,
|
||||
spim_size,
|
||||
&mut cpim_ptr,
|
||||
&mut cpim_size,
|
||||
&mut session,
|
||||
) {
|
||||
0 => {
|
||||
let mut cpim = vec![0; cpim_size as usize];
|
||||
|
||||
cpim.copy_from_slice(std::slice::from_raw_parts(cpim_ptr, cpim_size as usize));
|
||||
|
||||
(self.adi_dispose)(cpim_ptr);
|
||||
|
||||
Ok(StartProvisioningData { cpim, session })
|
||||
}
|
||||
err => Err(ADIError::resolve(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_machine_provisioned(&self, ds_id: i64) -> bool {
|
||||
(self.adi_get_login_code)(ds_id) == 0
|
||||
}
|
||||
|
||||
fn request_otp(&self, ds_id: i64) -> Result<RequestOTPData, ADIError> {
|
||||
unsafe {
|
||||
let mut mid_size: u32 = 0;
|
||||
let mut mid_ptr: *const u8 = std::ptr::null();
|
||||
let mut otp_size: u32 = 0;
|
||||
let mut otp_ptr: *const u8 = std::ptr::null();
|
||||
|
||||
match (self.adi_otp_request)(
|
||||
ds_id,
|
||||
&mut mid_ptr,
|
||||
&mut mid_size,
|
||||
&mut otp_ptr,
|
||||
&mut otp_size,
|
||||
) {
|
||||
0 => {
|
||||
let mut mid = vec![0; mid_size as usize];
|
||||
let mut otp = vec![0; otp_size as usize];
|
||||
|
||||
mid.copy_from_slice(std::slice::from_raw_parts(mid_ptr, mid_size as usize));
|
||||
otp.copy_from_slice(std::slice::from_raw_parts(otp_ptr, otp_size as usize));
|
||||
|
||||
(self.adi_dispose)(mid_ptr);
|
||||
(self.adi_dispose)(otp_ptr);
|
||||
|
||||
Ok(RequestOTPData { mid, otp })
|
||||
}
|
||||
err => Err(ADIError::resolve(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_local_user_uuid(&mut self, local_user_uuid: String) {
|
||||
self.local_user_uuid = local_user_uuid;
|
||||
}
|
||||
|
||||
fn set_device_identifier(&mut self, device_identifier: String) -> Result<(), ADIError> {
|
||||
self.set_identifier(&device_identifier[0..16])?;
|
||||
self.device_identifier = device_identifier;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_local_user_uuid(&self) -> String {
|
||||
self.local_user_uuid.clone()
|
||||
}
|
||||
|
||||
fn get_device_identifier(&self) -> String {
|
||||
self.device_identifier.clone()
|
||||
}
|
||||
|
||||
fn get_serial_number(&self) -> String {
|
||||
"0".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigurableADIProxy for StoreServicesCoreADIProxy<'_> {
|
||||
fn set_identifier(&mut self, identifier: &str) -> Result<(), ADIError> {
|
||||
match (self.adi_set_android_id)(identifier.as_ptr(), identifier.len() as u32) {
|
||||
0 => Ok(()),
|
||||
err => Err(ADIError::resolve(err)),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_provisioning_path(&mut self, path: &str) -> Result<(), ADIError> {
|
||||
let path = CString::new(path).unwrap();
|
||||
match (self.adi_set_provisioning_path)(path.as_ptr() as *const u8) {
|
||||
0 => Ok(()),
|
||||
err => Err(ADIError::resolve(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LoaderHelpers;
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
|
||||
use libc::{
|
||||
chmod, close, free, fstat, ftruncate, gettimeofday, lstat, malloc, mkdir, open, read, strncpy,
|
||||
umask, write,
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
use posix_macos::*;
|
||||
|
||||
static mut ERRNO: i32 = 0;
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
#[sysv64]
|
||||
unsafe fn __errno_location() -> *mut i32 {
|
||||
ERRNO = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
|
||||
&mut ERRNO
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
fn arc4random() -> u32 {
|
||||
rand::thread_rng().gen()
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
unsafe fn __system_property_get(_name: *const c_char, value: *mut c_char) -> i32 {
|
||||
*value = '0' as c_char;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
use posix_windows::*;
|
||||
|
||||
impl LoaderHelpers {
|
||||
pub fn setup_hooks() {
|
||||
let mut hooks = HashMap::new();
|
||||
hooks.insert("arc4random".to_owned(), arc4random as usize);
|
||||
hooks.insert("chmod".to_owned(), chmod as usize);
|
||||
hooks.insert(
|
||||
"__system_property_get".to_owned(),
|
||||
__system_property_get as usize,
|
||||
);
|
||||
hooks.insert("__errno".to_owned(), __errno_location as usize);
|
||||
hooks.insert("close".to_owned(), close as usize);
|
||||
hooks.insert("free".to_owned(), free as usize);
|
||||
hooks.insert("fstat".to_owned(), fstat as usize);
|
||||
hooks.insert("ftruncate".to_owned(), ftruncate as usize);
|
||||
hooks.insert("gettimeofday".to_owned(), gettimeofday as usize);
|
||||
hooks.insert("lstat".to_owned(), lstat as usize);
|
||||
hooks.insert("malloc".to_owned(), malloc as usize);
|
||||
hooks.insert("mkdir".to_owned(), mkdir as usize);
|
||||
hooks.insert("open".to_owned(), open as usize);
|
||||
hooks.insert("read".to_owned(), read as usize);
|
||||
hooks.insert("strncpy".to_owned(), strncpy as usize);
|
||||
hooks.insert("umask".to_owned(), umask as usize);
|
||||
hooks.insert("write".to_owned(), write as usize);
|
||||
|
||||
hook_manager::add_hooks(hooks);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{AnisetteConfiguration, AnisetteHeaders};
|
||||
use log::info;
|
||||
use std::path::PathBuf;
|
||||
use crate::AnisetteError;
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
#[test]
|
||||
fn fetch_anisette_ssc() -> Result<(), AnisetteError> {
|
||||
crate::tests::init_logger();
|
||||
|
||||
let mut provider = AnisetteHeaders::get_ssc_anisette_headers_provider(
|
||||
AnisetteConfiguration::new()
|
||||
.set_configuration_path(PathBuf::new().join("anisette_test")),
|
||||
)?;
|
||||
info!(
|
||||
"Headers: {:?}",
|
||||
provider.provider.get_authentication_headers()?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[tokio::test]
|
||||
async fn fetch_anisette_ssc_async() -> Result<(), AnisetteError> {
|
||||
|
||||
crate::tests::init_logger();
|
||||
|
||||
let mut provider = AnisetteHeaders::get_ssc_anisette_headers_provider(
|
||||
AnisetteConfiguration::new()
|
||||
.set_configuration_path(PathBuf::new().join("anisette_test")),
|
||||
)?;
|
||||
info!(
|
||||
"Headers: {:?}",
|
||||
provider.provider.get_authentication_headers().await?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
pub use libc::{chmod, close, free, ftruncate, gettimeofday, malloc, mkdir, read, strncpy, umask, write};
|
||||
|
||||
use libc::{lstat as lstat_macos, fstat as fstat_macos, stat as stat_macos, open as open_macos, O_CREAT, O_WRONLY, O_RDWR, O_RDONLY};
|
||||
|
||||
use android_loader::sysv64;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct StatLinux {
|
||||
pub st_dev: u64,
|
||||
pub st_ino: u64,
|
||||
pub st_nlink: u64,
|
||||
pub st_mode: u32,
|
||||
pub st_uid: u32,
|
||||
pub st_gid: u32,
|
||||
__pad0: libc::c_int,
|
||||
pub st_rdev: u64,
|
||||
pub st_size: i64,
|
||||
pub st_blksize: i64,
|
||||
pub st_blocks: i64,
|
||||
pub st_atime: i64,
|
||||
pub st_atime_nsec: i64,
|
||||
pub st_mtime: i64,
|
||||
pub st_mtime_nsec: i64,
|
||||
pub st_ctime: i64,
|
||||
pub st_ctime_nsec: i64,
|
||||
__unused: [i64; 3],
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn lstat(path: *const libc::c_char, buf: *mut StatLinux) -> libc::c_int {
|
||||
let mut st: stat_macos = std::mem::zeroed();
|
||||
lstat_macos(path, &mut st);
|
||||
*buf = StatLinux {
|
||||
st_dev: st.st_dev as _,
|
||||
st_ino: st.st_ino as _,
|
||||
st_nlink: st.st_nlink as _,
|
||||
st_mode: st.st_mode as _,
|
||||
st_uid: st.st_uid as _,
|
||||
st_gid: st.st_gid as _,
|
||||
__pad0: 0 as _,
|
||||
st_rdev: st.st_rdev as _,
|
||||
st_size: st.st_size as _,
|
||||
st_blksize: st.st_blksize as _,
|
||||
st_blocks: st.st_blocks as _,
|
||||
st_atime: st.st_atime as _,
|
||||
st_atime_nsec: st.st_atime_nsec as _,
|
||||
st_mtime: st.st_mtime as _,
|
||||
st_mtime_nsec: st.st_mtime_nsec as _,
|
||||
st_ctime: st.st_ctime as _,
|
||||
st_ctime_nsec: st.st_ctime_nsec as _,
|
||||
__unused: [0, 0, 0],
|
||||
};
|
||||
0
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn fstat(fildes: libc::c_int, buf: *mut StatLinux) -> libc::c_int {
|
||||
let mut st: stat_macos = std::mem::zeroed();
|
||||
fstat_macos(fildes, &mut st);
|
||||
*buf = StatLinux {
|
||||
st_dev: st.st_dev as _,
|
||||
st_ino: st.st_ino as _,
|
||||
st_nlink: st.st_nlink as _,
|
||||
st_mode: st.st_mode as _,
|
||||
st_uid: st.st_uid as _,
|
||||
st_gid: st.st_gid as _,
|
||||
__pad0: 0 as _,
|
||||
st_rdev: st.st_rdev as _,
|
||||
st_size: st.st_size as _,
|
||||
st_blksize: st.st_blksize as _,
|
||||
st_blocks: st.st_blocks as _,
|
||||
st_atime: st.st_atime as _,
|
||||
st_atime_nsec: st.st_atime_nsec as _,
|
||||
st_mtime: st.st_mtime as _,
|
||||
st_mtime_nsec: st.st_mtime_nsec as _,
|
||||
st_ctime: st.st_ctime as _,
|
||||
st_ctime_nsec: st.st_ctime_nsec as _,
|
||||
__unused: [0, 0, 0],
|
||||
};
|
||||
0
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn open(path: *const libc::c_char, oflag: libc::c_int) -> libc::c_int {
|
||||
let mut win_flag = 0; // binary mode
|
||||
|
||||
if oflag & 0o100 != 0 {
|
||||
win_flag |= O_CREAT;
|
||||
}
|
||||
|
||||
if oflag & 0o1 == 1 {
|
||||
win_flag |= O_WRONLY;
|
||||
} else if oflag & 0o2 != 0 {
|
||||
win_flag |= O_RDWR;
|
||||
} else {
|
||||
win_flag |= O_RDONLY;
|
||||
}
|
||||
|
||||
let val = open_macos(path, win_flag);
|
||||
|
||||
val
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
use android_loader::sysv64;
|
||||
use libc::{O_CREAT, O_RDONLY, O_RDWR, O_WRONLY};
|
||||
use log::debug;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
#[link(name = "ucrt")]
|
||||
extern "C" {
|
||||
fn _errno() -> *mut libc::c_int;
|
||||
fn _timespec64_get(__ts: *mut libc::timespec, __base: libc::c_int) -> libc::c_int;
|
||||
fn _chsize(handle: i64, length: u64) -> usize;
|
||||
}
|
||||
|
||||
// took from cosmopolitan libc
|
||||
#[sysv64]
|
||||
pub unsafe fn umask(mask: usize) -> usize {
|
||||
debug!("umask: Windows specific implementation called!");
|
||||
mask
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn ftruncate(handle: i64, length: u64) -> usize {
|
||||
debug!(
|
||||
"ftruncate: Windows translate-call. handle: {}, length: {}",
|
||||
handle, length
|
||||
);
|
||||
let ftr = _chsize(handle, length);
|
||||
|
||||
ftr
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PosixTimeval {
|
||||
tv_sec: u64,
|
||||
tv_usec: u64, /* microseconds */
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PosixTimespec {
|
||||
tv_sec: i64,
|
||||
tv_nsec: i64, /* microseconds */
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PosixTimezone {
|
||||
tz_minuteswest: u32,
|
||||
tz_dsttime: u32, /* microseconds */
|
||||
}
|
||||
|
||||
static HECTONANOSECONDS: u64 = 10000000;
|
||||
|
||||
impl PosixTimespec {
|
||||
pub fn from_windows_time(time: u64) -> PosixTimespec {
|
||||
PosixTimespec {
|
||||
tv_sec: (time / HECTONANOSECONDS) as i64,
|
||||
tv_nsec: (time % HECTONANOSECONDS) as i64 * 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn gettimeofday(timeval: *mut PosixTimeval, _tz: *mut PosixTimezone) -> isize {
|
||||
debug!("gettimeofday: Windows specific implementation called!");
|
||||
let mut ts = MaybeUninit::<libc::timespec>::zeroed();
|
||||
|
||||
let ret = _timespec64_get(ts.as_mut_ptr(), 1);
|
||||
let ts = ts.assume_init();
|
||||
|
||||
*timeval = PosixTimeval {
|
||||
tv_sec: ts.tv_sec as _,
|
||||
tv_usec: (ts.tv_nsec / 1000) as _,
|
||||
};
|
||||
|
||||
ret as _
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct StatLinux {
|
||||
pub st_dev: u64,
|
||||
pub st_ino: u64,
|
||||
pub st_nlink: u64,
|
||||
pub st_mode: u32,
|
||||
pub st_uid: u32,
|
||||
pub st_gid: u32,
|
||||
__pad0: libc::c_int,
|
||||
pub st_rdev: u64,
|
||||
pub st_size: i64,
|
||||
pub st_blksize: i64,
|
||||
pub st_blocks: i64,
|
||||
pub st_atime: i64,
|
||||
pub st_atime_nsec: i64,
|
||||
pub st_mtime: i64,
|
||||
pub st_mtime_nsec: i64,
|
||||
pub st_ctime: i64,
|
||||
pub st_ctime_nsec: i64,
|
||||
__unused: [i64; 3],
|
||||
}
|
||||
|
||||
trait ToWindows<T> {
|
||||
unsafe fn to_windows(&self) -> T;
|
||||
}
|
||||
|
||||
impl ToWindows<CString> for CStr {
|
||||
unsafe fn to_windows(&self) -> CString {
|
||||
let path = self
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.chars()
|
||||
.map(|x| match x {
|
||||
'/' => '\\',
|
||||
c => c,
|
||||
})
|
||||
.collect::<String>();
|
||||
|
||||
let path = path.trim_start_matches("\\\\?\\").to_string();
|
||||
|
||||
CString::new(path).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn lstat(path: *const libc::c_char, buf: *mut StatLinux) -> libc::c_int {
|
||||
debug!(
|
||||
"lstat: Windows translate-call, path: {:?}",
|
||||
CStr::from_ptr(path)
|
||||
);
|
||||
let mut stat_win = MaybeUninit::<libc::stat>::zeroed();
|
||||
let path = CStr::from_ptr(path).to_windows();
|
||||
|
||||
let ret = libc::stat(path.as_ptr(), stat_win.as_mut_ptr());
|
||||
let stat_win = stat_win.assume_init();
|
||||
|
||||
*buf = stat_win.to_windows();
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
impl ToWindows<StatLinux> for libc::stat {
|
||||
unsafe fn to_windows(&self) -> StatLinux {
|
||||
let atime = PosixTimespec::from_windows_time(self.st_atime as u64);
|
||||
let mtime = PosixTimespec::from_windows_time(self.st_mtime as u64);
|
||||
let ctime = PosixTimespec::from_windows_time(self.st_ctime as u64);
|
||||
|
||||
let mut mode = 0o555;
|
||||
let win_mode = self.st_mode;
|
||||
|
||||
if win_mode & 0b11 != 0 {
|
||||
mode |= 0o200;
|
||||
}
|
||||
|
||||
if win_mode & 0x4000 != 0 {
|
||||
mode |= 0o40000;
|
||||
}
|
||||
|
||||
StatLinux {
|
||||
st_dev: self.st_dev as _,
|
||||
st_ino: self.st_ino as _,
|
||||
st_nlink: self.st_nlink as _,
|
||||
st_mode: mode as _,
|
||||
st_uid: self.st_uid as _,
|
||||
st_gid: self.st_gid as _,
|
||||
__pad0: 0,
|
||||
st_rdev: self.st_rdev as _,
|
||||
st_size: self.st_size as _,
|
||||
st_blksize: 0,
|
||||
st_blocks: 0,
|
||||
st_atime: atime.tv_sec,
|
||||
st_atime_nsec: 0,
|
||||
st_mtime: mtime.tv_sec,
|
||||
st_mtime_nsec: 0,
|
||||
st_ctime: ctime.tv_sec,
|
||||
st_ctime_nsec: 0,
|
||||
__unused: [0, 0, 0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn fstat(fildes: libc::c_int, buf: *mut StatLinux) -> libc::c_int {
|
||||
debug!("fstat: Windows translate-call");
|
||||
let mut stat_win = MaybeUninit::<libc::stat>::zeroed();
|
||||
let ret = libc::fstat(fildes, stat_win.as_mut_ptr());
|
||||
let stat_win = stat_win.assume_init();
|
||||
|
||||
*buf = stat_win.to_windows();
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn malloc(size: libc::size_t) -> *mut libc::c_void {
|
||||
// debug!("malloc: Windows translate-call");
|
||||
libc::malloc(size)
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn free(p: *mut libc::c_void) {
|
||||
// debug!("free: Windows translate-call");
|
||||
libc::free(p)
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn strncpy(
|
||||
dst: *mut libc::c_char,
|
||||
src: *const libc::c_char,
|
||||
n: libc::size_t,
|
||||
) -> *mut libc::c_char {
|
||||
debug!("strncpy: Windows translate-call");
|
||||
libc::strncpy(dst, src, n)
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn chmod(path: *const libc::c_char, mode: libc::c_int) -> libc::c_int {
|
||||
debug!("chmod: Windows translate-call");
|
||||
libc::chmod(path, mode)
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn mkdir(path: *const libc::c_char) -> libc::c_int {
|
||||
debug!("mkdir: Windows translate-call");
|
||||
libc::mkdir(path)
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn open(path: *const libc::c_char, oflag: libc::c_int) -> libc::c_int {
|
||||
debug!("open: Windows translate-call oflag 0o{:o}", oflag);
|
||||
|
||||
let path = CStr::from_ptr(path).to_windows();
|
||||
|
||||
let mut win_flag = 0x8000; // binary mode
|
||||
|
||||
if oflag & 0o100 != 0 {
|
||||
win_flag |= O_CREAT;
|
||||
}
|
||||
|
||||
if oflag & 0o1 == 1 {
|
||||
win_flag |= O_WRONLY;
|
||||
} else if oflag & 0o2 != 0 {
|
||||
win_flag |= O_RDWR;
|
||||
} else {
|
||||
win_flag |= O_RDONLY;
|
||||
}
|
||||
|
||||
let val = libc::open(path.as_ptr(), win_flag);
|
||||
|
||||
val
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn close(fd: libc::c_int) -> libc::c_int {
|
||||
debug!("close: Windows translate-call");
|
||||
libc::close(fd)
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn read(fd: libc::c_int, buf: *mut libc::c_void, count: libc::c_uint) -> libc::c_int {
|
||||
debug!("read: Windows translate-call");
|
||||
|
||||
let r = libc::read(fd, buf, count);
|
||||
r
|
||||
}
|
||||
|
||||
#[sysv64]
|
||||
pub unsafe fn write(fd: libc::c_int, buf: *const libc::c_void, count: libc::c_uint) -> libc::c_int {
|
||||
debug!("write: Windows translate-call");
|
||||
libc::write(fd, buf, count)
|
||||
}
|
||||
Reference in New Issue
Block a user