first commit

This commit is contained in:
nab138
2025-08-06 22:01:36 -04:00
commit 9baf77f00e
51 changed files with 9878 additions and 0 deletions

View 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"

View 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)
}
}

View File

@@ -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
}
}

View 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(())
}
}

View 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(())
}
}

View 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(())
}
}

View 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(())
}
}

View 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(())
}
}

View File

@@ -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
}

View File

@@ -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)
}