Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions docs/NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Apple iDevice Restore/Upgrade/Revive Protocol

# DFU Mode

DFU is a USB standard protocol that Apple uses from `SecureROM` state to bring up the SEP and AP into a `recovery`
state. This requires a multi-stage process whereby the device is interrogated for it's version, chip, board etc.
(this is done by querying the SerialNumber using an alternate language string property of the USB device, which
is part of a USB device standard descriptors). This breaks the device into CPID (chip ID) and BDID (board ID).
Further the device is queried for the value of `APNonce` and `SEPNonce` which are transmitted to TSS (tatsu signing server)
at tss.apple.com to get a "personalized" manifest. This manifest is a ASN1 signed IMG4 (specifically an IM4M or IMG4 manifest)
that contains the SHA fingerprints of all the later component stages. Because the APNonce and SEPNonce are unique to each
boot, this means that the values must be submitted to tss on every recovery and cannot be reused (a mistake in NVRAM allowed
reuse of such things in the "FutureRestore" exploit). SecureROM is very low level assembly that has the responsibility
of interacting with USB, generation of the APNonce, interaction with the SEP for the SEP's SecureROM and associated SEPNonce
and the transfer, and validation of the payloads received over USB. This means SecureROM has a base USB stack, the SEP
mailbox protocol, SHA implementation, base bring-up of essential controllers such as Hydra/Tristar/ACE/PMP-PMGR,
a hardened ASN1/IMG4 implementation, and a base public/private algorithm implementation as well as compiled (or in the
case of silicon, masked-in) root-of-trust keys.

After personalizing the manifest it is transmitted to the device, which validates it against the nonce set, and then DFU
begins to receive the payloads specified in the BuildManifest.plist file. Each of these is copied to a volatile memory
region where it is hashed and compared to the manifest. (Because of the DFU specification this is handled using bulk
transfers to an output endpoint of the interface of 512 byes and completion is signaled by a final 0 byte transfer)
If everything lines up, the payload (iBoot recovery) is handed off too.

Modern versions of DFU / SecureROM include an arbitrary iBoot data payload which per never_released contains DDR4 training
data to bring online LPDDR4 memory early.

# Recovery Mode

Recovery mode is entered either by a failure of the iBoot process (in which case it is the version held in emulated NOR
which is truly NAND backed in any modern iDevice) or by a bring-up from DFU. In the case of a boot failure, the version
of iBoot / recovery could in fact be out of date, in the case of DFU (excepting AppleInternal access to the sign-anything TSS)
this should always be the most recent version of iBoot. iBoot is a slightly more sophisticated package as it can be patched
unlike the SecureROM variant, though they come from the exact same codebase. Because of it's patch-ability all other loading
behaviour happens here. This stage will bring up additional firmware devices, and bring the SEP out of spin-lock to SEPOS.
The iBoot protocol is also able to handle transports of larger size and to fully access NVRAM and SysCfg which is why
their serial number is available at this stage but not at DFU (ECID is available because its fused or masked in to the AP).
In the final stage, iBoot will receive the DeviceTree and XNU kernel, which should it passes IMG4 verification from personalization
will boot the XNU kernel as the next stage. (iBoot will set a few flags to alter the XNU behaviour for a recovery boot vs
a normal boot before the jump, for more info use siguza's `dt` to explore the DeviceTree and research OpenFirmware)

## M1 macOS Note Related to Local Security Policy

The M1 macOS restore process leverages the same recovery / restore process as the iDevice, but the iBoot system has
an additional payload as the mac, unlike a iPhone or iPad can boot code not signed by Apple. This is managed by the
modification of the "local security policy" using the `bptuil` command. This is a SEP entangled signed policy that
iBoot will verify. Because this policy must be reset to a secure state, the recovery process will provide a `lpol`
payload, which is the initial secure local security policy. It should NEVER be the case that this policy is different
than the version in the IPSW in the case of a restore as it could otherwise allow a SecureBoot policy violation
1 change: 0 additions & 1 deletion src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ struct idevicerestore_entry_t {
struct idevicerestore_client_t {
int flags;
plist_t tss;
plist_t tss_localpolicy;
plist_t tss_recoveryos_root_ticket;
char* tss_url;
plist_t version_data;
Expand Down
49 changes: 20 additions & 29 deletions src/dfu.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,40 +129,31 @@ int dfu_send_component(struct idevicerestore_client_t* client, plist_t build_ide

// Use a specific TSS ticket for the Ap,LocalPolicy component
plist_t tss = client->tss;
if (strcmp(component, "Ap,LocalPolicy") == 0) {
tss = client->tss_localpolicy;
}

unsigned char* component_data = NULL;
unsigned int component_size = 0;

if (strcmp(component, "Ap,LocalPolicy") == 0) {
// If Ap,LocalPolicy => Inject an empty policy
component_data = malloc(sizeof(lpol_file));
component_size = sizeof(lpol_file);
memcpy(component_data, lpol_file, component_size);
} else {
if (tss) {
if (tss_response_get_path_by_entry(tss, component, &path) < 0) {
debug("NOTE: No path for component %s in TSS, will fetch from build_identity\n", component);
}
}
if (!path) {
if (build_identity_get_component_path(build_identity, component, &path) < 0) {
error("ERROR: Unable to get path for component '%s'\n", component);
free(path);
return -1;
}
}
if (tss) {
if (tss_response_get_path_by_entry(tss, component, &path) < 0) {
debug("NOTE: No path for component %s in TSS, will fetch from build_identity\n", component);
}
}
if (!path) {
if (build_identity_get_component_path(build_identity, component, &path) < 0) {
error("ERROR: Unable to get path for component '%s'\n", component);
free(path);
return -1;
}
}

if (extract_component(client->ipsw, path, &component_data, &component_size) < 0) {
error("ERROR: Unable to extract component: %s\n", component);
free(path);
return -1;
}
free(path);
path = NULL;

if (extract_component(client->ipsw, path, &component_data, &component_size) < 0) {
error("ERROR: Unable to extract component: %s\n", component);
free(path);
return -1;
}
free(path);
path = NULL;
}

unsigned char* data = NULL;
uint32_t size = 0;
Expand Down
9 changes: 1 addition & 8 deletions src/idevicerestore.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,6 @@ static void usage(int argc, char* argv[], int err)
}
#endif

const uint8_t lpol_file[22] = {
0x30, 0x14, 0x16, 0x04, 0x49, 0x4d, 0x34, 0x50,
0x16, 0x04, 0x6c, 0x70, 0x6f, 0x6c, 0x16, 0x03,
0x31, 0x2e, 0x30, 0x04, 0x01, 0x00
};
const uint32_t lpol_file_length = 22;

static int idevicerestore_keep_pers = 0;

static int load_version_data(struct idevicerestore_client_t* client)
Expand Down Expand Up @@ -1096,7 +1089,7 @@ int idevicerestore_start(struct idevicerestore_client_t* client)
return -1;
}
if (client->build_major >= 20) {
if (get_local_policy_tss_response(client, build_identity, &client->tss_localpolicy) < 0) {
if (get_local_policy_tss_response(client, build_identity, &client->tss) < 0) {
error("ERROR: Unable to get SHSH blobs for this device (local policy)\n");
return -1;
}
Expand Down
5 changes: 0 additions & 5 deletions src/idevicerestore.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@ enum {
RESTORE_NUM_STEPS
};

// lpol_file has been extracted from the IMG4 dump of the ac2 usb protocol. It is not present in the .ipsw and
// represents and empty "local policy". See https://support.apple.com/guide/security/contents-a-localpolicy-file-mac-apple-silicon-secc745a0845/web.
extern const uint8_t lpol_file[22];
extern const uint32_t lpol_file_length;

typedef void (*idevicerestore_progress_cb_t)(int step, double step_progress, void* userdata);

struct idevicerestore_client_t* idevicerestore_client_new(void);
Expand Down
4 changes: 2 additions & 2 deletions src/restore.c
Original file line number Diff line number Diff line change
Expand Up @@ -3298,13 +3298,13 @@ int restore_send_restore_local_policy(restored_client_t restore, struct idevicer
// The Update mode does not have a specific build identity for the recovery os.
plist_t build_identity = restore_get_build_identity(client, client->flags & FLAG_ERASE ? 1 : 0);

int ret = get_recovery_os_local_policy_tss_response(client, build_identity, &client->tss_localpolicy, plist_dict_get_item(msg, "Arguments"));
int ret = get_recovery_os_local_policy_tss_response(client, build_identity, &client->tss, plist_dict_get_item(msg, "Arguments"));
if (ret < 0) {
error("ERROR: Unable to get recovery os local policy tss response\n");
return -1;
}

ret = personalize_component(component, component_data, component_size, client->tss_localpolicy, &data, &size);
ret = personalize_component(component, component_data, component_size, client->tss, &data, &size);
free(component_data);
component_data = NULL;
if (ret < 0) {
Expand Down