Last active
September 18, 2021 08:27
-
-
Save retrage/9087102e28c70a383f96684ecdf09a83 to your computer and use it in GitHub Desktop.
Apple File System (APFS) EFI Jumpstart EFI Driver Extraction Application
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include <stdbool.h> | |
#include <fcntl.h> | |
#include <sys/stat.h> | |
#include <sys/mman.h> | |
#define BLOCK_SIZE (512) | |
typedef int64_t paddr_t; | |
typedef struct { | |
paddr_t pr_start_paddr; | |
uint64_t pr_block_count; | |
} prange_t; | |
typedef unsigned char uuid_t[16]; | |
#define GPT_SIGNATURE 0x5452415020494645 // 'EFI PART' | |
typedef struct { | |
uint64_t signature; | |
uint32_t revision; | |
uint32_t header_size; | |
uint32_t header_crc32; | |
uint8_t reserved1[4]; | |
uint64_t my_lba; | |
uint64_t alternate_lba; | |
uint64_t first_usable_lba; | |
uint64_t last_usable_lba; | |
uuid_t disk_guid; | |
uint64_t partition_entry_lba; | |
uint32_t number_of_partition_entries; | |
uint32_t size_of_partition_entry; | |
uint32_t partition_entry_array_crc32; | |
uint8_t reserved2[BLOCK_SIZE - 92]; | |
} __attribute__((packed)) gpt_header_t; | |
typedef struct { | |
uuid_t partition_type_guid; | |
uuid_t unique_partition_guid; | |
uint64_t starting_lba; | |
uint64_t ending_lba; | |
uint64_t attributes; | |
uint16_t partition_name[36]; | |
} __attribute__((packed)) gpt_partition_entry_t; | |
typedef uint64_t oid_t; | |
typedef uint64_t xid_t; | |
#define MAX_CKSUM_SIZE 8 | |
typedef struct { | |
uint8_t o_cksum[MAX_CKSUM_SIZE]; | |
oid_t o_oid; | |
xid_t o_xid; | |
uint32_t o_type; | |
uint32_t o_subtype; | |
} obj_phys_t; | |
#define APFS_GPT_PARTITION_UUID \ | |
{ \ | |
0xef, 0x57, 0x34, 0x7c, 0x00, 0x00, 0xaa, 0x11, 0xaa, 0x11, 0x00, 0x30, 0x65, 0x43, 0xec, 0xac \ | |
} | |
#define NX_MAGIC 0x4253584e // 'NXSB' | |
#define NX_MAX_FILE_SYSTEMS 100 | |
#define NX_EPH_INFO_COUNT 4 | |
#define NX_NUM_COUNTERS 32 | |
typedef struct { | |
obj_phys_t nx_o; | |
uint32_t nx_magic; | |
uint32_t nx_block_size; | |
uint64_t nx_block_count; | |
uint64_t nx_features; | |
uint64_t nx_readonly_compatible_features; | |
uint64_t nx_incompatible_features; | |
uuid_t nx_uuid; | |
oid_t nx_next_oid; | |
xid_t nx_next_xid; | |
uint32_t nx_xp_desc_blocks; | |
uint32_t nx_xp_data_blocks; | |
paddr_t nx_xp_desc_base; | |
paddr_t nx_xp_data_base; | |
uint32_t nx_xp_desc_next; | |
uint32_t nx_xp_data_next; | |
uint32_t nx_xp_desc_index; | |
uint32_t nx_xp_desc_len; | |
uint32_t nx_xp_data_index; | |
uint32_t nx_xp_data_len; | |
oid_t nx_spaceman_oid; | |
oid_t nx_omap_oid; | |
oid_t nx_reaper_oid; | |
uint32_t nx_test_type; | |
uint32_t nx_max_file_systems; | |
oid_t nx_fs_oid[NX_MAX_FILE_SYSTEMS]; | |
uint64_t nx_counters[NX_NUM_COUNTERS]; | |
prange_t nx_blocked_out_prange; | |
oid_t nx_evict_mapping_tree_oid; | |
uint64_t nx_flags; | |
paddr_t nx_efi_jumpstart; | |
uuid_t nx_fusion_uuid; | |
prange_t nx_keylocker; | |
uint64_t nx_ephemeral_info[NX_EPH_INFO_COUNT]; | |
oid_t nx_test_oid; | |
oid_t nx_fusion_mt_oid; | |
oid_t nx_fusion_wbc_oid; | |
prange_t nx_fusion_wbc; | |
uint64_t nx_newest_mounted_version; | |
prange_t nx_mkb_locker; | |
} nx_superblock_t; | |
#define NX_EFI_JUMPSTART_MAGIC 0x5244534a // 'JSDR' | |
#define NX_EFI_JUMPSTART_VERSION 1 | |
typedef struct { | |
obj_phys_t nej_o; | |
uint32_t nej_magic; | |
uint32_t nej_version; | |
uint32_t nej_efi_file_len; | |
uint32_t nej_num_extents; | |
uint64_t neh_reserved[16]; | |
prange_t nej_rec_extents[0]; | |
} nx_efi_jumpstart_t; | |
#define APFS_EFI_DRIVER_NAME "apfs.efi" | |
static bool cmp_uuid(uuid_t *lhs, uuid_t *rhs) | |
{ | |
unsigned char *l = (unsigned char *)lhs; | |
unsigned char *r = (unsigned char *)rhs; | |
for (int i = 0; i < sizeof(uuid_t) / sizeof(unsigned char); i++) { | |
if (l[i] != r[i]) { | |
return false; | |
} | |
} | |
return true; | |
} | |
static bool is_valid_uuid(uuid_t *uuid) | |
{ | |
uuid_t unused_entry; | |
memset(&unused_entry, 0, sizeof(uuid_t)); | |
return !cmp_uuid(uuid, &unused_entry); | |
} | |
static gpt_header_t *find_gpt(char *base) | |
{ | |
gpt_header_t *gpt = (gpt_header_t *)(base + BLOCK_SIZE * 1); | |
if (gpt->signature != GPT_SIGNATURE) { | |
return NULL; | |
} | |
return gpt; | |
} | |
static void dump_gpe(gpt_partition_entry_t *gpe) | |
{ | |
fprintf(stderr, "[*] GPT Partition Entry:\n"); | |
fprintf(stderr, " starting_lba: 0x%llx\n", gpe->starting_lba); | |
fprintf(stderr, " ending_lba: 0x%llx\n", gpe->ending_lba); | |
fprintf(stderr, " attributes: 0x%llx\n", gpe->attributes); | |
} | |
static gpt_partition_entry_t *find_gpe_by_uuid(char *disk_base, gpt_header_t *gpt, uuid_t *uuid) | |
{ | |
gpt_partition_entry_t *first_gpe = (gpt_partition_entry_t *)(disk_base + BLOCK_SIZE * gpt->partition_entry_lba); | |
size_t num_entries = gpt->number_of_partition_entries; | |
for (int i = 0; i < num_entries; i++) { | |
gpt_partition_entry_t *gpe = (gpt_partition_entry_t *)(first_gpe + i); | |
if (cmp_uuid(&gpe->partition_type_guid, uuid)) { | |
return gpe; | |
} | |
} | |
return NULL; | |
} | |
static nx_superblock_t *find_apfs_superblock(char *disk_base, gpt_partition_entry_t *apfs_gpe) | |
{ | |
nx_superblock_t *superblock = (nx_superblock_t *)(disk_base + BLOCK_SIZE * apfs_gpe->starting_lba); | |
if (superblock->nx_magic != NX_MAGIC) { | |
return NULL; | |
} | |
return superblock; | |
} | |
static void dump_apfs_superblock(nx_superblock_t *superblock) | |
{ | |
fprintf(stderr, "[*] APFS superblock\n"); | |
fprintf(stderr, " nx_block_size: 0x%x\n", superblock->nx_block_size); | |
fprintf(stderr, " nx_block_count: 0x%llx\n", superblock->nx_block_count); | |
fprintf(stderr, " nx_efi_jumpstart: 0x%llx\n", superblock->nx_efi_jumpstart); | |
} | |
static nx_efi_jumpstart_t *find_apfs_nej(nx_superblock_t *superblock) | |
{ | |
nx_efi_jumpstart_t *nej= (nx_efi_jumpstart_t *)((char *)superblock + superblock->nx_block_size * superblock->nx_efi_jumpstart); | |
if (nej->nej_magic != NX_EFI_JUMPSTART_MAGIC) { | |
return NULL; | |
} | |
return nej; | |
} | |
static void dump_nej(nx_efi_jumpstart_t *nej) | |
{ | |
fprintf(stderr, "[*] APFS EFI Jumpstart:\n"); | |
fprintf(stderr, " nej_magic: 0x%x\n", nej->nej_magic); | |
fprintf(stderr, " nej_version: 0x%x\n", nej->nej_version); | |
fprintf(stderr, " nej_efi_file_len: 0x%x\n", nej->nej_efi_file_len); | |
fprintf(stderr, " nej_num_extents: 0x%x\n", nej->nej_num_extents); | |
for (int i = 0; i < nej->nej_num_extents; i++) { | |
fprintf(stderr, " nej_rec_extents[%d]\n", i); | |
fprintf(stderr, " pr_start_paddr: 0x%llx\n", nej->nej_rec_extents[i].pr_start_paddr); | |
fprintf(stderr, " pr_block_count: 0x%llx\n", nej->nej_rec_extents[i].pr_block_count); | |
} | |
} | |
static int export_nej_rec_extents(nx_superblock_t *superblock, nx_efi_jumpstart_t *nej, const char *path) | |
{ | |
FILE *fp = fopen(path, "wb"); | |
if (!fp) { | |
return -1; | |
} | |
for (int i = 0; i < nej->nej_num_extents; i++) { | |
char *addr = (char *)superblock + superblock->nx_block_size * nej->nej_rec_extents[i].pr_start_paddr; | |
size_t size = superblock->nx_block_size * nej->nej_rec_extents[i].pr_block_count; | |
if (fwrite(addr, 1, size, fp) < size) { | |
fclose(fp); | |
return -1; | |
} | |
} | |
fclose(fp); | |
return 0; | |
} | |
int main(int argc, const char *argv[]) | |
{ | |
if (argc < 2) { | |
return -1; | |
} | |
const char *disk_path = argv[1]; | |
struct stat st; | |
if (stat(disk_path, &st) < 0) { | |
perror("stat"); | |
return -1; | |
} | |
size_t disk_size = (size_t)st.st_size; | |
int disk_fd = open(disk_path, O_RDONLY); | |
if (disk_fd < 0) { | |
perror("open"); | |
return -1; | |
} | |
void *disk_buf = mmap(NULL, disk_size, PROT_READ, MAP_SHARED, disk_fd, 0); | |
if (disk_buf == NULL) { | |
perror("mmap"); | |
return -1; | |
} | |
char *disk_base = (char *)disk_buf; | |
gpt_header_t *gpt = find_gpt(disk_base); | |
if (!gpt) { | |
fprintf(stderr, "GPT header not found\n"); | |
return -1; | |
} | |
uuid_t apfs_uuid = APFS_GPT_PARTITION_UUID; | |
gpt_partition_entry_t *apfs_gpe = find_gpe_by_uuid(disk_base, gpt, &apfs_uuid); | |
if (!apfs_gpe) { | |
fprintf(stderr, "APFS partition not found\n"); | |
return -1; | |
} | |
dump_gpe(apfs_gpe); | |
nx_superblock_t *superblock = find_apfs_superblock(disk_base, apfs_gpe); | |
if (!superblock) { | |
fprintf(stderr, "APFS superblock not found\n"); | |
return -1; | |
} | |
dump_apfs_superblock(superblock); | |
nx_efi_jumpstart_t *nej = find_apfs_nej(superblock); | |
if (!nej) { | |
fprintf(stderr, "APFS EFI Jumpstart not found\n"); | |
return -1; | |
} | |
dump_nej(nej); | |
if (export_nej_rec_extents(superblock, nej, APFS_EFI_DRIVER_NAME) < 0) { | |
fprintf(stderr, "Failed to export '%s'\n", APFS_EFI_DRIVER_NAME); | |
return -1; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment