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.
The MacBook's firmware is stored on SPI chip. It consists of various regions:
fd (Flash Descriptor),
me (Intel ME),
bios (the BIOS, or, in case of MacBooks, EFI), 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 (
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. OK. 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 Flash Descriptor (as it always is);
- right next to it (
0x1000-0x18ffff) Intel ME firmware is stored;
- and the last and largest region (
0x190000-0x7fffff) is the BIOS, or Apple's EFI in our case.
As you probably noticed above in the flashrom output, only the
bios region is read-only (and not even whole
bios region, because
0x670000-0x681fff is writable for some reason):
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 (a serious vulnerability, actually) in Apple's firmware.
Anyway, that means that after cold boot
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 126.96.36.1991 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 94208
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
$ 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
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
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 firmware hole and replaced 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.