Flashing coreboot on MacBooks without external programmer by using IFD hack

TLDR: use mmga.

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.

On Intel platforms, the firmware is stored on SPI chip. It consists of various regions: fd (Flash Descriptor), me (Intel ME), bios (BIOS/UEFI), and some other (such as gbe for Gigabit Ethernet). The most important region in context of this story is FD, the Intel Flash Descriptor.

The Intel Flash Descriptor is a data structure stored on the flash chip; it contains information such as space allocated for each region of the flash image (also called a layout), read-write permissions for each region and many more. The Flash Descriptor is located at the first 4K of the SPI chip (0x0000-0x0fff).

I have MacBook Air 5,2, so I will use it in this article as an example. What's described here should work on all SandyBridge and IvyBridge MacBooks Air and Pro, at least. I'm not sure about newer models because I don't have any at the moment (well I have a Coffee Lake MacBook Pro 15,1, but that's another story).

So let's take this thing and dump it's flash chip contents:

$ mkdir orig && cd $_
$ sudo flashrom -p internal -r dump.bin
flashrom v1.1-rc1-3-g4ca575d on Linux 4.9.0-9-amd64 (x86_64)
flashrom is free software, get the source code at https://flashrom.org

Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
No DMI table found.
Found chipset "Intel QS77".
Enabling flash write... SPI Configuration is locked down.
PR0: Warning: 0x00190000-0x0066ffff is read-only.
PR1: Warning: 0x00692000-0x01ffffff is read-only.
At least some flash regions are write protected. For write operations,
you should use a flash layout and include only writable regions. See
manpage for more details.
Found Micron/Numonyx/ST flash chip "N25Q064..3E" (8192 kB, SPI) mapped at physical address 0x00000000ff800000.
Reading flash... done.

Extract fd modules with ifdtool:

$ ifdtool -x dump.bin 
File dump.bin is 8388608 bytes
  Flash Region 0 (Flash Descriptor): 00000000 - 00000fff 
  Flash Region 1 (BIOS): 00190000 - 007fffff 
  Flash Region 2 (Intel ME): 00001000 - 0018ffff 
  Flash Region 3 (GbE): 00fff000 - 00000fff (unused)
  Flash Region 4 (Platform Data): 00fff000 - 00000fff (unused)

You can see that 3 new files were created:

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

Then dump flash chip regions into flashrom layout file:

$ ifdtool -f layout.txt dump.bin
$ cat layout.txt
00000000:00000fff fd
00190000:007fffff bios
00001000:0018ffff me

As you can see,

  • the first region (0x0000-0x0fff) is used for the Flash Descriptor (as it always is);
  • right next to it (0x1000-0x18ffff) the Intel ME firmware is stored;
  • and the last and largest region (0x190000-0x7fffff) is the BIOS.

As you probably noticed above in the flashrom output, only the bios region has write protections, and not even whole bios region, because 0x670000-0x681fff region is writable:

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

It's interesting that this behavior is reproducible only after cold boot. If you suspend to S3, resume and run flashrom again, fd will be read-only:

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

Looks like a bug and a security issue in Apple's firmware.

Anyway, that means that after cold boot fd and me regions are writable, and that gives us around 1.5M of writable space. Since we can rewrite FD, we can write a new FD with custom layout. So the idea is to repartition the flash chip and flash new bios to a writable space.

Let's write a new layout (I decided to use 0x00000-0xfffff region for convenience):

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

In this layout, we allocate 128K for me and 892K for bios. We also have to allocate the remaining 0x100000-0x7fffff region for something to be able to address and flash it in future, otherwise flashrom will give us a "Transaction error". So we just mark it as pd, which stands for "Platform Data".

Save the new layout to a file and update FD regions in the dump:

$ ifdtool -n new-layout.txt dump.bin
File dump.bin is 8388608 bytes
DANGER: Region BIOS is shrinking.
    The region will be truncated to fit.
    This may result in an unusable image.
DANGER: Region Intel ME is shrinking.
    The region will be truncated to fit.
    This may result in an unusable image.
The image has changed in size.
The old image is 8388608 bytes.
The new image is 1048576 bytes.
Copy Descriptor 0 (Flash Descriptor) (4096 bytes)
   from 00000000+00000000:00000fff (      4096)
     to 00000000+00000000:00000fff (      4096)
Copy Descriptor 1 (BIOS) (913408 bytes)
   from 00190000+00591000:007fffff (   6750208)
     to 00021000+00000000:000fffff (    913408)
Copy Descriptor 2 (Intel ME) (131072 bytes)
   from 00001000+0016f000:0018ffff (   1634304)
     to 00001000+00000000:00020fff (    131072)
Writing new image to dump.bin.new

The updated image has been saved to dump.bin.new, let's move it to a separate directory for convenience:

$ cd .. && mkdir fd-patched && cd $_
$ mv ../dump.bin.new .

And extract fd modules from the updated image:

$ ifdtool -x dump.bin.new

By now we have new flashregion_0_flashdescriptor.bin with our custom layout. We will use it later when building coreboot. The next piece is Intel ME.

To fit the original 1.6M ME image into the 128K region, it has to be "neutralized" with me_cleaner:

$ me_cleaner.py -t -r -O flashregion_2_intel_me.bin ../orig/flashregion_2_intel_me.bin
ME/TXE image detected
Found FPT header at 0x10
Found 15 partition(s)
Found FTPR header: FTPR partition spans from 0x93000 to 0x108000
ME/TXE firmware version
Public key match: Intel ME, firmware versions 7.x.x.x, 8.x.x.x
Reading partitions list...
 ???? (0x000003c0 - 0x000000400, 0x00000040 total bytes): removed
 FOVD (0x00000400 - 0x000001000, 0x00000c00 total bytes): removed
 MDES (0x00001000 - 0x000002000, 0x00001000 total bytes): removed
 FCRS (0x00002000 - 0x000003000, 0x00001000 total bytes): removed
 EFFS (0x00003000 - 0x00004b000, 0x00048000 total bytes): removed
 NVCL (NVRAM partition, no data, 0x00010511 total bytes): nothing to remove
 NVCP (NVRAM partition, no data, 0x0000a518 total bytes): nothing to remove
 NVJC (NVRAM partition, no data, 0x00005000 total bytes): nothing to remove
 NVKR (NVRAM partition, no data, 0x0001151a total bytes): nothing to remove
 NVSH (NVRAM partition, no data, 0x00007609 total bytes): nothing to remove
 NVTD (NVRAM partition, no data, 0x00001eac total bytes): nothing to remove
 GLUT (0x0004b000 - 0x00004d000, 0x00002000 total bytes): removed
 MDMV (0x0004d000 - 0x000093000, 0x00046000 total bytes): removed
 FTPR (0x00093000 - 0x000108000, 0x00075000 total bytes): NOT removed
 NFTP (0x00108000 - 0x00017d000, 0x00075000 total bytes): removed
Removing partition entries in FPT...
Removing EFFS presence flag...
Correcting checksum (0x4b)...
Reading FTPR modules list...
 UPDATE           (LZMA   , 0x0de8fa - 0x0de9fd       ): removed
 ROMP             (Huffman, fragmented data, ~2 KiB   ): NOT removed, essential
 BUP              (Huffman, fragmented data, ~54 KiB  ): NOT removed, essential
 KERNEL           (Huffman, fragmented data, ~135 KiB ): removed
 POLICY           (Huffman, fragmented data, ~89 KiB  ): removed
 HOSTCOMM         (LZMA   , 0x0de9fd - 0x0e56f4       ): removed
 RSA              (LZMA   , 0x0e56f4 - 0x0ea967       ): removed
 CLS              (LZMA   , 0x0ea967 - 0x0f009f       ): removed
 TDT              (LZMA   , 0x0f009f - 0x0f6783       ): removed
 FTCS             (Huffman, fragmented data, ~18 KiB  ): removed
 ClsPriv          (LZMA   , 0x0f6783 - 0x0f6b64       ): removed
 SESSMGR          (LZMA   , 0x0f6b64 - 0x105400       ): removed
Relocating FTPR from 0x93000 - 0x108000 to 0x4c0 - 0x754c0...
 Adjusting FPT entry...
 Adjusting LUT start offset...
 Adjusting Huffman start offset...
 Adjusting chunks offsets...
 Moving data...
The ME minimum size should be 94208 bytes (0x17000 bytes)
Truncating file at 0x17000...
Checking the FTPR RSA signature... VALID
Done! Good luck!

The truncated ME file size is around 92K:

$ stat --printf=%s flashregion_2_intel_me.bin

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

I usually use GRUB2 payload and here I assume that you have usable config in grub.cfg:

$ make menuconfig

Mainboard --->
    Mainboard vendor (Apple)
    Mainboard model (MacBookAir5,2)
    ROM chip size (1024 KB (1 MB)) <-- very important: set to 1024 KB because of new bios region
    (0xd0000) Size of CBFS filesystem in ROM

Chipset --->
    [*] Add Intel descriptor.bin file
    (/path/to/fd-patched/flashregion_0_flashdescriptor.bin) Path and filename of the descriptor.bin file
    [*]   Add Intel ME/TXE firmware 
    (/path/to/fd-patched/flashregion_2_intel_me.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)

Payload --->
    Add a payload (GRUB2)
    [*] Include GRUB2 runtime config file into ROM image
    (grub.cfg) Path of grub.cfg

Save the config and run make to build coreboot. When the building is complete you'll have 1024K coreboot ROM at build/coreboot.rom. Flashrom won't accept it for flashing, because it's size must match the chip, so we have to make it 8M by adding 7M of zeroes:

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

Now flash the new fd (0x0000-0x0fff), me (0x1000-0x20fff) and bios (0x21000-0xfffff) regions according to the new layout:

$ sudo flashrom -p internal -w coreboot8.rom -l new-layout.txt -i fd -N
$ sudo flashrom -p internal -w coreboot8.rom -l new-layout.txt -i me -N
$ sudo flashrom -p internal -w coreboot8.rom -l new-layout.txt -i bios -N

The first stage is completed. On the next cold boot, coreboot will be loaded from the 0x21000-0xfffff region, and Apple's firmware, which still resides in 0x190000-0x7fffff, will be ignored. Shutdown the laptop, wait a few seconds and power it up again.

(It's important: do not reboot, shut it down. If you just reboot, old FD will still be used and Apple's firmware will be loaded, because it was left untouched in the 0x190000-0x7fffff region. But you partly replaced old me region and that may lead to undefined behavior.)

After we boot Linux the next time with coreboot running, we're able to flash the whole 8M chip, because it's not write-protected anymore by Apple's firmware. We need to repartition it again, and here is the final layout:

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

It's almost the same, except that bios fills all the remaining space. Save it to a file and create new FD as you did before:

$ ifdtool -n final-layout.txt dump.bin
$ cd .. && mkdir fd-final && cd $_
$ mv ../dump.bin.new .
$ ifdtool -x dump.bin.new

Configure coreboot again. You need to change ROM chip size, CBFS size and Intel descriptor.bin file options:

$ make menuconfig

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

Chipset --->
    [*] Add Intel descriptor.bin file
    (/path/to/fd-final/flashregion_0_flashdescriptor.bin) Path and filename of the descriptor.bin file

Save the config and build coreboot again. Then flash fd and bios according to the final layout:

$ sudo flashrom -p internal -w build/coreboot.rom -l final-layout.txt -i fd -N
$ sudo flashrom -p internal -w build/coreboot.rom -l final-layout.txt -i bios -N

Congratulations, you have just exploited Apple's bug and replaced their EFI with open source coreboot without using external SPI programmer.

Shutdown the MacBook again, wait a few seconds, power it up and enjoy coreboot.

P.S. Be aware that flash regions are not locked in coreboot by default. To protect them, check out the "Protect flash regions" and "Flash locking during chipset lockdown" options under the Chipset menu.

If you have any comments, contact me by email.
powered by OpenBSD
© ch1p 2020