fairplay #
Clean Rust port of https://github.com/EthanArbuckle/fairplay-decrypt.
A Rust library for the recovered FairPlay page-key algorithm and AES-128-CBC decryption of protected executable regions. It has no IPA, bundle, filesystem, Mach-O or device dependencies. The caller supplies the region and metadata.
use fairplay::{Decryptor, PageRecords, PAGE_SIZE};
fn decrypt_selected_region(
credential: &[u8; 16],
sinf: &[u8],
supf: &[u8],
original_file_offset: u64,
encrypted_region: &mut [u8],
) -> Result<usize, Box<dyn std::error::Error>> {
let decoder = Decryptor::from_sinf(credential, sinf)?;
let records = PageRecords::from_supf(supf, 0x0100000c, 0)?;
let first_page = u32::try_from(original_file_offset / PAGE_SIZE as u64)?;
Ok(decoder.decrypt_region_in_place(encrypted_region, first_page, records)?)
}
Cache a Decryptor for binaries sharing a SINF. from_sinf_fields accepts an
already parsed SinfFields { name, iviv, private }; from_work_key accepts the
recovered 16-byte masked working key directly. A working key differs from the
saved credential and from an individual page AES key.
PageRecords::from_bytes borrows flat 32-byte records from an existing SUPF
parser. from_supf provides optional metadata parsing and selects an exact CPU
type/subtype; it supports SUPF 3/5 and tags 507/510/612. SINF decoding handles
both 440-byte and 448-byte private records. No code parses the executable.
Caller responsibilities #
- Pass only the protected region, containing a nonzero whole number of 4096-byte pages. Decryption resets CBC with each record's IV and removes no padding. Extra SUPF records are allowed.
- Compute the first absolute page index from the original complete executable
offset. For a FAT binary this includes the architecture slice's file base:
(slice_base + cryptoff) / 4096. Do not reset it to zero for a section or slice. - Match the SUPF architecture and record range to the selected region. Records
start at the beginning of the protected region. For a later subregion, advance
both
first_page_indexand the record range viarecords.slice(start..end). - Clear the executable's encryption flag only after decryption succeeds, and handle bundle metadata, signing and output publication in the consuming code.
For page-at-a-time or parallel processing, PageRecords::get provides a
PageRecord for decrypt_page_in_place. derive_page_key is available when
another component owns CBC decryption. The decoder can be shared across threads.
Region decryption allocates no buffers. Size, record count and page-index validation happens before mutation. Those checks do not authenticate page ciphertext: the recovered scheme uses CBC without an authentication tag. The SINF private record's markers detect credentials that fail its decoding check. Secrets are omitted from Debug and temporary SINF keys/plaintext and AES key schedules use zeroization.