Flashing coreboot on MacBooks without external programmer by using IFD hack

In this article, I will show how to flash coreboot on certain models of Apple MacBooks without disassembling and using external SPI programmer. All you need is Linux and root access.

This is a very delicate procedure. Be very careful, check everything twice, especially the numbers. A single mistake may brick your machine and you'll have to flash the backup externally. Given this fact, you should have an external SPI programmer, just in case.

It was tested and confirmed to work on following models:

  • MacBook Air 5,2
  • MacBook Pro 8,1
  • MacBook Pro 10,1

MacBook Air 4,2 and iMac 13,1 should work too, but were not tested.

Introduction

Apple's "Think Different" slogan fits perfectly with their approach to firmware security. Besides the fact that they do not use SMM_BWP to protect SPI flash from being writable from userspace, they do not even protect Flash Descriptor (fd) and Management Engine (me) regions.

The Intel Flash Descriptor is a data structure of fixed size (4KB) stored on the flash chip (resides in 0x0000-0x0fff), that contains various information such as space allocated for each region on the flash, access permissions, some chipset configuration and more. In particular, it contains access permissions for fd and me regions. Normally they should be read-only in production, but Apple, for whatever reasons, keeps them read-write.

Instead, they decided to use SPI Protected Range Registers (PR0-PR4) to set protection over fd, but here they failed again. Due to a bug in their firmware, 0x0000-0x0fff is not write-protected after cold boot and becomes read-only only after resuming from S3. You can dump PRx protections by running flashrom -p internal.

This is what you should see after a cold boot (if so, then you can use this guide):

PR0: Warning: 0x00190000-0x0066ffff is read-only.
PR1: Warning: 0x00692000-0x01ffffff is read-only.

And this is after resuming from S3:

PR0: Warning: 0x00000000-0x00000fff is read-only.
PR1: Warning: 0x00190000-0x0066ffff is read-only.
PR2: Warning: 0x00692000-0x01ffffff is read-only.

So, after cold boot flash descriptor is protected neither by PRx registers nor by access permission bits on the flash descriptor itself. Under certain circumstances, writable flash descriptor allows flashing whole SPI flash by using a couple of tricks.

The idea is that we can shrink ME firmware with me_cleaner, flash a small coreboot image on the freed space and move the reset vector there. Then power off, boot coreboot and flash full image, as there will be no more PRx set.

Stage 1. Flashing temporary BIOS

Preparations

Dump your ROM:

# flashrom -p internal -r orig.bin

Please save this backup to an external drive. You may need it in case of failure.

Extract flash layout:

$ ifdtool -f orig_layout.txt orig.bin
$ cat orig_layout.txt

You should see something like or exactly this:

00000000:00000fff fd
00190000:007fffff bios
00001000:0018ffff me

If you compare these regions with what's protected by PR0 and PR1, you'll notice that fd and me regions are fully writable and only bios is protected. Writable ME region gives us around 1.5 MB which we can use for our goals.

Extract flash regions from the dump into separate files:

$ ifdtool -x orig.bin

You can see that 3 new files were created:

$ ls flash*
flashregion_0_flashdescriptor.bin  flashregion_1_bios.bin  flashregion_2_intel_me.bin

The ME firmware is ~1.5 MB in size, but we can truncate it with me_cleaner:

$ me_cleaner.py -t -r -O flashregion_2_intel_me_truncated.bin flashregion_2_intel_me.bin

The truncated firmware should be around 90K:

$ stat --printf=%s flashregion_2_intel_me_truncated.bin
94208

Rename the original flashregion_2_intel_me.bin file to not mix them up in future:

$ mv flashregion_2_intel_me.bin flashregion_2_intel_me_orig.bin

Now we need to write a new flash layout. 128K is more than enough for the "neutered" ME firmware. We can use the rest for new bios region, but 892K is enough:

00000000:00000fff fd
00001000:00020fff me
00021000:000fffff bios
00100000:007fffff pd

Note that we must allocate the remaining 0x100000-0x7fffff for something to be able to address and flash it in future. Let's just mark it as pd (which stands for "Platform Data") for now.

Save the new layout to a file new_layout.txt and update regions in the dump:

$ ifdtool -n new_layout.txt orig.bin

The updated image will be saved to orig.bin.new. Move it to a separate directory for convenience:

$ mkdir patched && cd $_
$ mv ../orig.bin.new .

Extract flash regions again, now from the updated image:

$ ifdtool -x orig.bin.new

By now we have new flashregion_0_flashdescriptor.bin file with our custom layout. Let's also move the patched ME here:

$ rm flashregion_2_intel_me.bin
$ mv ../flashregion_2_intel_me_truncated.bin .

So far, so good. At this point our preparations for the first stage are finished and we're ready to configure coreboot.

Configuring and flashing coreboot

Run make menuconfig and configure as shown below. Note that you need to change ROM chip size, CBFS size and specify paths to modified flashregion_0_flashdescriptor.bin and flashregion_2_intel_me_truncated.bin.

Mainboard --->
    Mainboard vendor (Apple)
    Mainboard model () # Set according to your model
    ROM chip size (1024 KB (1 MB))
    (0xd0000) Size of CBFS filesystem in ROM

Chipset --->
    [*] Add Intel descriptor.bin file
    (/path/to/patched/flashregion_0_flashdescriptor.bin) Path and filename of the descriptor.bin file
    [*]   Add Intel ME/TXE firmware
    (/path/to/patched/flashregion_2_intel_me_truncated.bin) Path to management engine firmware
    [ ]     Verify the integrity of the supplied ME/TXE firmware
    [ ]     Strip down the Intel ME/TXE firmware
    Protect flash regions (Unlock flash regions)

Then you need to decide which payload to use. For now, it's recommended to use GRUB2. Be sure to include a good config for it that can load a variety of setups. SeaBIOS works too, but currently needs a patch for internal MacBook's keyboard to work.

When configuration is done, run make to build coreboot. In the end you should have 1024 KB coreboot ROM at build/coreboot.rom. Flashrom won't accept it, because it's size must match the chip, so we have to make it 8 MB. Just add 7 MB of zeroes:

$ dd if=/dev/zero of=7M.bin bs=1024 count=7168
$ cat build/coreboot.rom 7M.bin > coreboot8.rom

Now cross your fingers and flash the new fd (0x0000-0x0fff), me (0x1000-0x20fff) and bios (0x21000-0xfffff) regions using the layout file:

$ flashrom -p internal -w coreboot8.rom -l new_layout.txt -i fd -i me -i bios

The first stage is completed. Power off the machine now. Reboot won't work: new flash descriptor becomes active on cold boot.

On the next boot, if you're lucky and didn't do any mistake, coreboot will be loaded from the new bios region, and Apple's EFI, that still resides in 0x190000-0x7fffff, will be ignored.

Stage 2. Flashing full ROM

Now we can flash whole 8 MB chip, because no PRx are set anymore. Let's re-layout the chip again.

If you want to continue using truncated ME:

00000000:00000fff fd
00001000:00020fff me
00021000:007fffff bios

If you want to flash full ME firmware back:

00000000:00000fff fd
00001000:0018ffff me
00190000:007fffff bios

Save it to final_layout.txt and create new flash descriptor again.

$ mkdir patched2 && cd $_
$ cp ../orig.bin .
$ ifdtool -n final_layout.txt orig.bin
$ ifdtool -x orig.bin.new

Go back to coreboot directory and run make menuconfing.

In the Mainboard section change ROM chip size back to 8 MB and set Size of CBFS according to your needs: now you have plenty of space. 0x500000 or so should work just fine.

In the Chipset section, update paths to the flash descriptor and ME firmware files. If you decided to stick to truncated ME, use flashregion_2_intel_me_truncated.bin, otherwise use flashregion_2_intel_me_orig.bin.

Mainboard --->
    ROM chip size (8192 KB (8 MB))
    (0x500000) Size of CBFS filesystem in ROM

Chipset --->
    [*] Add Intel descriptor.bin file

    # Use the latest flashdescriptor from the patched2 directory
    (/path/to/patched2/flashregion_0_flashdescriptor.bin) Path and filename of the descriptor.bin file

    [*]   Add Intel ME/TXE firmware
    (/path/to/patched/flashregion_2_intel_me_truncated.bin) Path to management engine firmware
    [ ]     Verify the integrity of the supplied ME/TXE firmware
    [ ]     Strip down the Intel ME/TXE firmware

Save the config and make it again. Then flash fd and bios according to the final_layout.txt:

# flashrom -p internal -w build/coreboot.rom -l final_layout.txt -i fd -i bios

If you changed ME firmware back to original, flash it as well:

# flashrom -p internal -w build/coreboot.rom -l final_layout.txt -i me

Stage 2 is completed. Power off (reboot won't work again). On the next boot, you will have a completely corebooted MacBook. Congratulations!

Table of Contents
If you have any comments, contact me by email.