retrage.github.io

Apple File Systemの下にはEFI driverが埋まっている

Apple File Systemの下にはEFI driverが埋まっている

Apple File System (APFS)はAppleが自社製品向けに開発したファイルシステムである.APFSの仕様は公開されており以下で参照できる.

その目次の中で特に興味を引いたのが"EFI Jumpstart"の章である.現代的なデバイスではEFIを含めブートローダはファイルシステムを参照してOSを起動する.このとき当然ながらブートローダはそのファイルシステムを扱える必要がある.特にEFIでは仕様上対応していなければならないのはEFI System Partition (ESP)で使われるFATのみでその他のファイルシステムが事前にサポートされていることは期待できない.このため例えばWindowsであればESPに配置されたWindows専用のブートローダがNTFSをサポートすることでカーネルの起動を行なっている.

これに対してmacOSではAPFS自身がAPFSのEFI driverをパーティションのブロックに直接埋め込む形で提供するという別のアプローチをとっている.ドキュメントによれば

This design intentionally simplifies the steps needed to boot, which means the code needed to boot a piece of hardware or virtualization software can likewise be simpler.

とのことで起動プロセスをシンプルにすることが目的のようである.

どのようにEFI driverが埋め込まれているのかをみていく. 最初の前提としてディスクのGPTエントリを読み,以下のUUIDのようなAPFSのパーティションがあることがわかったとする.

#define APFS_GPT_PARTITION_UUID ”7C3457EF-0000-11AA-AA11-00306543ECAC”

APFSパーティションは一つのAPFS containerオブジェクトとして存在し,その先頭にcontainerの情報を持ったcontainer superblockが配置されている.

container superblockを表すnx_superblock_tにはnx_efi_jumpstartというEFI Jumpstartの情報を持ったnx_efi_jumpstart_tへの物理アドレス(containerの先頭からのAPFSでのブロックサイズ単位でのオフセット)のフィールドが用意されている.

その参照先には以下のようなnx_efi_jumpstart_tがある.

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    nej_reserved[16];
  prange_t    nej_rec_extents[0];
} nx_efi_jumpstart_t;

このnej_rec_extentsにあるブロックを読むことでAPFSパーティションに直接埋め込まれたEFI driverを読み込むことができる.

ユーザ空間で以上のようなことを行いraw disk imageからEFI driverを抽出する簡単なアプリケーションを実装した.本来は色々と検証を行う必要があるが,ここでは省略した.

抽出したバイナリは以下のように確かにPE32+のEFI driverとして認識されている.

$ file apfs.efi
apfs.efi: PE32+ executable (EFI boot service driver) x86-64 (stripped to external PDB), for MS Windows

せっかくなので抜き出したEFI driverを覗いてみる.バイナリのハッシュは1e780147f2cee614ab7e9a63c4e86525f06c5a18d72a6b8ec572752ffd95dea0である..debugセクションの指す9E9FChをみると"MTOC"の文字列と"apfs.efi.macho"という文字列が見えるのでこのバイナリの名前は"apfs.efi"でMach-OバイナリをApple謹製のmtocでPE32+に変換して生成されたようである.

ちなみにClover EFI bootloaderでは先にたどっていったような手順でapfs.efiをロードするEFI driverがあり,これでAPFS対応を行なっているようである.

https://sourceforge.net/p/cloverefiboot/code/HEAD/tree/FileSystems/ApfsDriverLoader/

話を元に戻してバイナリをみていくと,.textセクションの先頭に"2021/08/30"や"06:36:21"のような日時があるのでこれがこのバイナリがビルドされた日時だと考えられる.対象としたmacOS Bit Sur 11.6は2021年9月13日にリリースされたので少なくとも2週間程度前にはビルドされたようである. 他にもみどころはたくさんありそうだがこれぐらいにしておく.

以上はIntel Mac上での話である.ではARM64ベースのm1 Macの場合はどうだろうか?

$ uname -a
Darwin 7261.local 20.5.0 Darwin Kernel Version 20.5.0: Sat May  8 05:10:31 PDT 2021; root:xnu-7195.121.3~9/RELEASE_ARM64_T8101 arm64
$ ./dumper ~/Desktop/recovery.img
superblock at 280000000
nx_block_size: 0x1000
nx_block_count: 0x13fff5
nx_efi_jumpstart: 0x0
APFS EFI Jumpstart not found

残念ながら答えは否のようである.すでに広く知られているように,m1 MacではEFIではなくiPhoneなどと同じiBootから起動するカスタマイズされたファームウェアであるため,サードパーティでの実行環境を考慮しなければEFI Jumpstartの機能は不要であるためだと考えられる.へその緒のようにあったら面白かったのだが.ということでm1 MacのAPFSではnx_efi_jumpstart_tnx_efi_jumpstartは使われないフィールドとなってしまったようである.

というわけでIntel MacのAPFSの下にはEFI driverが埋まっているのでIntel Macをお持ちの方は酒宴でも開きましょう.