Skip to content

Instantly share code, notes, and snippets.

@retrage
Last active September 18, 2021 08:27
Show Gist options
  • Save retrage/9087102e28c70a383f96684ecdf09a83 to your computer and use it in GitHub Desktop.
Save retrage/9087102e28c70a383f96684ecdf09a83 to your computer and use it in GitHub Desktop.
Apple File System (APFS) EFI Jumpstart EFI Driver Extraction Application
#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