I was studying about bootloaders when exactly came upon the term UEFI. I can understand some things about UEFI. But still, In what mode(Real,Protected,Long) does a system with UEFI start? If normal boot loaders cant work with UEFI, Then what is the alternate of boot loader while dealing with UEFI? And do I need any other programming to create one, than assembly?
UEFI firmware runs in 64 bit long mode for 64 bit platforms and flat mode for 32 bit platforms; Unlike BIOS, UEFI features its own architecture, independent of the CPU, and its own device drivers. UEFI can mount partitions and read certain file systems.
When an x86 computer equipped with UEFI, the interface searches the system storage for a partition labeled with a specific globally unique identifier (GUID) that marks it as the EFI System Partition (ESP). BTW Windows doesn't mount this partition and you cannot see it in the OS. But there is a trick, you simply change the partition type (using HexWorkshop) in VBR to regular FAT32 code and it will be mounted into the OS.
This partition contains applications compiled for the EFI architecture. In general you don't have to deal with assembler to write a UEFI application/loader, it's just a regular C code. By the default it is located at "EFI/BOOT/BOOTX64.EFI". When a bootloader is selected, manually or automatically, UEFI reads it into the memory and yields control of the boot process to it.
fdisk -l
. On my box it is /dev/nvme0n1p1
. And then mount it as usual sudo mount /dev/nvme0n1p1 ~/esp
. And then you can check the content within ~/esp
. –
Hedda In the following, some sentences are a copy, amalgamation and wording improvement of the best sources I've seen, and then I improve on and correct their unknowns / errors by using my own hardware case study.
Boot Guard
Intel Boot Guard is a technology introduced by Intel in the 4th Intel Core generation (Haswell) to verify the boot process. This is accomplished by flashing the public key of the BIOS signature into the field programmable fuses (FPFs), a one-time programmable memory inside Intel ME (in the PCH), during the manufacturing process; in this way it has the public key of the BIOS and it can verify the correct signature during every subsequent boot. Once enabled by the manufacturer, Intel Boot Guard can't be disabled anymore.
Boot Guard has two separate modes, according to Intel. Typical PC OEMs configure it to “Verified Boot” mode. The PC manufacturer fuses their public key into the hardware itself. If the UEFI firmware isn’t signed by the OEM—i.e., created by the OEM—the computer will halt and refuse to boot. That’s why you can’t modify the UEFI firmware. There’s also a second option: “Measured Boot” mode, where the hardware uses Intel TXT to secure stores information about the boot process (in a trusted platform module (TPM)) or Intel Platform Trust Technology (PTT) with the aid of SMX. The operating system could then examine this information, and—if there was a problem—present an error to the user.
Secure Boot
When enabled and fully configured, Secure Boot helps a computer resist attacks and infection from malware. Secure Boot detects tampering with boot loaders, key operating system files, and unauthorized option ROMs by validating their digital signatures. Detections are blocked from running before they can attack or infect the system. UEFI Secure Boot assumes the OEM platform firmware is a Trusted Computing Base (TCB) (i.e. that it's been initialised with BootGuard technologies and trusts it implicitly).
Verified Boot Process
Pre-UEFI stage
When the OEM receives the PCH, the ME is still in "Manufacturing Mode" and runs a special part of the firmware that will copy the "OEM Public Key Hash" and "Boot Guard Profile Configuration" policy values from its section of the flash ROM into the field programable fuses (FPFs) so that they are permanent and unchangable. It then sets a fuse to indicate that it has exited from manufacturing mode so that this portion of the firmware will not run again. These values can be adjusted in the flash image with the Intel Flash Image Tool (FITC), but unless you have a way to force the ME into manufacturing mode then the values in the flash image are ignored.
The Bootguard profile is as follows:
Protect BIOS Environment Enabled: if set, this possibly means that the ACM will copy the IBB segments into the CPU cache so that it runs in a cache-as-RAM (CAR) mode and has all DMA disabled to prevent devices from being able to modify it.
As soon as power is available, the ME CPU boots up from its on-die boot ROM, checks some straps and fuses to determine its configuration, and typically then copies the flash partition table (FPT) from the ME region of the SPI flash (which is typically below the BIOS region) to its on-die SRAM. It locates the ME region by using the flash descriptor at the lowest address of the SPI flash.
The boot ROM locates the FTPR partition using the FPT and copies it from the SPI flash into the on-die SRAM. It then checks that the SHA-1 hash of the key stored in the partition manifest matches the one in its on-die ROM and validates the RSA signature on the rest of the partition manifest. The partition table contains hashes of each of the modules in the partition, allowing the modules to be validated after they are copied into the on-die SRAM for execution.
When the ME boots the x86 CPU, possibly in bup.met
. The BIST and then the BSP MP Initialisation algorithm takes place. The legacy reset vector at CS:FFF0 (where CS is 0xF000 and the segment descriptor cache contains the base 0xFFFF0000) is no longer the first instruction the x86 CPU executes on reset. Instead, on-die microcode fetches the FIT pointer at 0xFFFFFFC0 (using 0xF000:FFC0 with the unreal mode segment descriptor hack – because this is the initial state of the registers), which points to the FIT table somewhere in the SPI flash BIOS region.
This image shows the 16 byte FIT entries with their modes 3 bytes from the end.
#pragma pack (1)
typedef struct {
UINT64 Address;
UINT8 Size[3];
UINT8 Rsvd;
UINT16 Version;
UINT8 Type:7;
UINT8 C_V:1;
UINT8 Checksum;
} FIRMWARE_INTERFACE_TABLE_ENTRY;
On my system, there is a FIT pointer at FFFFFFC0 to FFD90100
The FIT table points to microcode updates, ACMs, the BootGuard Boot Policy Manifest (which contains an IBBS) and BootGuard Key manifest etc.
My FIT does not contain any 0x7 entries. Record Types 7 is used by legacy Intel® TXT FIT boot only and is not needed, if latter is not used.
The BootGuard Boot Policy (IBBM) contains:
Intel BootGuard Boot Policy Manifest found at base FD3C00h
Tag: ACBP Version: 10h HeaderVersion: 01h
PMBPMVersion: 10h PBSVN: 00h ACMSVN: 02h NEMDataStack: 0010h
Initial Boot Block Element found at base FD3C28h
Tag: IBBS Version: 10h Unknown: 0Fh
Flags: 00000000h IbbMchBar: FED10000h VtdBar: 00000000h
PmrlBase: FED90000h PmrlLimit: 00000000h EntryPoint: 00100000h
Post IBB Hash:
0000000000000000000000000000000000000000000000000000000000000000
IBB Digest:
B9CCC06B77AEACC51768981D07CBE9E43D34DB6795752C4B998312241B26F874
IBB Segments:
Flags: 0000h Address: FFE10000h Size: 001C3C00h
Flags: 0000h Address: FFFD4C00h Size: 00000080h
Flags: 0000h Address: FFFD5C80h Size: 0000A380h
Flags: 0000h Address: FFFE8000h Size: 00018000h
Boot Policy Signature Element found at base FD3CDDh
Tag: PMSG Version: 10h
Boot Policy RSA Public Key (Exponent: 10001h):
....
Boot Policy RSA Public Key Hash:
....
Boot Policy RSA Signature:
....
The Key Manifest contains:
Intel BootGuard Key Manifest (KEYM) found at base FD4C80h
Tag: KEYM Version: 10h KmVersion: 10h KmSvn: 00h KmId: 0Fh
Key Manifest RSA Public Key Hash:
...
Boot Policy RSA Public Key Hash:
...
Key Manifest RSA Public Key (Exponent: 10001h):
...
Key Manifest RSA Signature:
... //it does actually contain a signature, I just removed these for space
Cache as RAM (CAR) (aka. AC-RAM, No fill mode and No evict mode) is then set up by the microcode. The FIT is then searched for microcode updates that match the CPU ID. The current microcode copies them linearly from flash into L3 cache and decrypts with an on-die symmetric AES key, then validates with (on-die?) RSA key. It's likely that these microcode updates also contain the key hashes for the ACM. The microcode update is then applied by writing to the UCODE MSR and the CPU reads it out I assume through normal memory accesses. The FIT absolutely has to contain a microcode patch and it patches the microcode SRAM to complement the already existing microcode ROM. [1]
Next, the microcode goes back to the FIT to find the Startup ACM (aka BIOS or Bootguard ACM) and does an odd copy of it into L3 (looks like multiple hyperthreads are copying 4KB chunks? -- according to that source). The ACM contains an RSA public key; the microcode compares it against either an on-die key or one stored in the microcode update and halts the CPU if it does not match. The microcode then checks the signature on the ACM and again halts if it does not match.
The Startup ACM runs entirely out of L3. The ACM receives the OEM public key hash (the Key Hash that verifies the Key Manifest) and Bootguard Profile from the ME via MSR.
The ACM reads the BootGuard Key Manifest from the SPI flash (pointed to by the FIT and identified by __KEYM__
) into L3 and hashes the RSA public key stored in it. If it doesn't match the OEM public key hash or if the OEM public key signature on the Key Manifest or if the stored KmSvn isn't right, the ACM takes action based on the Bootguard Profile bits. If it does match, it locates the Bootguard Policy in the FIT (and identified by __ACBP__
) and copies it into L3. The ACM then computes the hash of the RSA public key in the Policy and compares it to the SHA256 hash stored in the Key Manifest. If that fails to match, or if the RSA signature on the Policy doesn't match, then the ACM again takes action based on the Profile settings.
The ACM uses the now validated Bootguard Policy structure to read the Initial Boot Block (IBB) segments into L3, hashing them as they are copied. If this computed hash doesn't match the "IBB Digest" in the Policy, the ACM takes action based on the Profile settings. The 4th IBB segment contains the reset vector, so it prevents that from being modified. It's unclear whether it leaves the CPU in CAR mode or whether it just leaves them cached in the L3 but without eviction disabled i.e. without CAR mode left enabled. SEC however needs to (disable) and set up a new CAR mode.
Taking a dump of physical memory using RwEverything, or downloading CSME System Tools for your ME version and then performing fptw64 -d -me -bios dump.bin
will dump the flash (and often you will only be able to dump the BIOS region and not the ME region). The dump is now analysable in UEFITool NE Alpha 58, and the FIT shows the EntryPoint of the ACM in the body. So if you right click, extract it and open it up in IDA as 32 bit, the entry point will be at 3BB1h + 18h = 3BC9h
3BC9 mov ax, ds
3BCC mov ss, ax
3BCF mov es, ax
3BD2 mov fs, ax
3BD5 mov gs, ax
3BD8 mov esp, ebp
3BDA add esp, 1000h
3BE0 mov eax, ebp
3BE2 add eax, 4C8h
3BE7 lidt fword ptr [eax]
3BEA push ebp
3BEB call sub_392A
3BF0 mov ebx, eax
3BF2 mov edx, 0
3BF7 mov eax, 3
3BFC getsec
The startup ACM is always the final ACM and it GETSEC[EXITAC]
s to the IBBS base + entry point address (0xFEE90000
, which falls in the first IBB) in the bootguard policy manifest, which appears to be in the ME region and presumably contains code to switch the CPU back to unreal mode and jump to the legacy reset vector in the 4th IBB segment. GETSEC
can only be executed in protected mode, so clearly the CPU is in protected mode in the startup ACM, so must be enabled by the microcode before entry. The legacy reset vector is at 0xFFFFFFF0, which on my system is a relative jump to FFFFFFF5 - 3BD = FFFFFC38, which is the SEC core entry point.
SEC
The SEC core is a Raw section from FFFFCA14 - FFFFCA17, a PE32 image from FFFFCA18 - FFFFFFBB, a raw section from FFFFFFBC - FFFFFFBF, and a raw section from FFFFFFC0-FFFFFFFF (which contains the reset vector).
The entry point of the PE32 Image in the SEC Core that is jumped to by the reset vector contains:
0x00: DB E3 fninit
0x02: 0F 6E C0 movd mm0, eax //move BIST value to mm0
0x05: 0F 31 rdtsc
0x07: 0F 6E EA movd mm5, edx
0x0a: 0F 6E F0 movd mm6, eax //save tsc
0x0d: 66 33 C0 xor eax, eax //clear eax
0x10: 8E C0 mov es, ax
0x12: 8C C8 mov ax, cs
0x14: 8E D8 mov ds, ax
0x16: B8 00 F0 mov ax, 0xf000
0x19: 8E C0 mov es, ax
0x1b: 67 26 A0 F0 FF 00 00 mov al, byte ptr es:[0xfff0]
0x22: 3C EA cmp al, 0xea
0x24: 74 0E je 0x34 //if ea is at ffff0h then jump to the 0xf000e05b check
0x26: BA F9 0C mov dx, 0xcf9
0x29: EC in al, dx //read port 0xcf9
0x2a: 3C 04 cmp al, 4
0x2c: 75 25 jne 0x53
0x2e: BA F9 0C mov dx, 0xcf9 //perform warm reset since if CPU only reset is issued not all MSRs are restored to their defaults
0x31: B0 06 mov al, 6
0x33: EE out dx, al
0x34: 67 66 26 A1 F1 FF 00 00 mov eax, dword ptr es:[0xfff1]
0x3c: 66 3D 5B E0 00 F0 cmp eax, 0xf000e05b
0x42: 75 0F jne 0x53 //if it isn't, move to notwarmstart
0x44: B9 1B 00 mov cx, 0x1b //if it is equal, read bsp bit from apic_base msr
0x47: 0F 32 rdmsr
0x49: F6 C4 01 test ah, 1
0x4c: 74 41 je 0x8f //if the and operation with 00000001b produces a zero result i.e. it's an AP then jump to cli, hlt
0x4e: EA F0 FF 00 F0 ljmp 0xf000:0xfff0 //if it's the BSP, exit unreal mode by far jumping to 0xffff0 which reloads the segment descriptor cache with a 0 base
notwarmstart:
0x53: B0 01 mov al, 1
0x55: E6 80 out 0x80, al //send 1 as a debug POST code
0x57: 66 BE 68 FF FF FF mov esi, 0xffffff68
0x5d: 66 2E 0F 01 14 lgdt cs:[si] //loads 32&16 GDT pointer (not 16&6, due to 66 prefix) at 16bit address fff68 in si into GDTR (base:ffffff28 limit:003f); will be accessing alias and not shadow ROM
//enter 16 bit protected mode//
0x62: 0F 20 C0 mov eax, cr0
0x65: 66 83 C8 03 or eax, 3 //Set PE bit (bit #0) & MP bit (bit #1)
0x69: 0F 22 C0 mov cr0, eax //Activate protected mode
0x6c: 0F 20 E0 mov eax, cr4
0x6f: 66 0D 00 06 00 00 or eax, 0x600 //Set OSFXSR bit (bit #9) & OSXMMEXCPT bit (bit #10)
0x75: 0F 22 E0 mov cr4, eax
//set up selectors for 32 bit protected mode entry
0x78: B8 18 00 mov ax, 0x18 //segment descriptor at 0x18 in GDT is (raw): 00cf93000000ffff
0x7b: 8E D8 mov ds, ax
0x7d: 8E C0 mov es, ax
0x7f: 8E E0 mov fs, ax
0x81: 8E E8 mov gs, ax
0x83: 8E D0 mov ss, ax
0x85: 66 BE 6E FF FF FF mov esi, 0xffffff6e
0x8b: 66 2E FF 2C ljmp cs:[si] //transition to flat 32 bit protected mode and jump to address at 0x0:0xffffff6e aka. 0xffffff6e which is fffffcd8. CS contains 0 remember (it's the base that is 0xffff) so it will load the first entry. This address is also in the SEC Core PE32 Image
0x8f: FA cli
0x90: F4 hlt
.
.
.
Here's a good answer to this question:
Other modern 64-bit machines have new EFI firmwares. These don't load a bootstrap program from sector #0 of a disc at all. They bootstrap by the EFI Boot Manager loading and running an EFI boot loader application. Such programs are run in protected mode. This is the EFI bootstrap process.
EFI firmwares in general switch to protected mode within a few instructions of exiting processor reset. Switching to protected mode is done early on in the so-called "SEC Phase" of EFI firmware initialization. Technically, 32-bit and greater x86 processors don't even start in real mode proper, but in what is colloquially known as unreal mode. (The initial segment descriptor for the CS register does not describe the conventional real mode mapping and is what makes this "unreal".)
As such, it could be said that those EFI systems never enter real mode proper at all, when bootstrapping natively to an EFI bootloader (i.e. when they don't employ a compatibility support module), since they switch from unreal mode directly to protected mode and stay in protected mode from then on.
When you asked "In what mode(Real,Protected,Long) does a system with UEFI start?", what do you mean? The processor starts executing in a mode that is similar to the mode of the 80386 of the past. But do you really care. Don't you really care about the mode of the processor when your OS loader code is given control? And you care about what services are provided to your OS loader code.
The environment is defined in the UEFI spec. Latest Versions of the UEFI Specifications
As for which language you use, assembly is good to start with. It's probably easier to use C or some other high level language after a bit.
Other background:
There are a lot of terms here that we are not always careful about using correctly.
The code that executes when the processor comes out of reset is system firmware that does a lot of initialization of various hardware in the system.
In the old days on a x86 PC system before the UEFI Forum existed, the system firmware was called BIOS. The BIOS in those days executed all of its initialization code, then loaded some code off of a floppy diskette or the hard drive and jumped into the code. The BIOS also provided some interfaces between the hardware and the operating system to help isolate the OS from hardware differences. Nothing was standardized though. The only standards were the OS and applications level software that used the BIOS interfaces.
If the OS and apps functioned correctly then the BIOS was considered correct. But you could only prove correctness by lack of failure. Thus new OSs or applications would work on one correct system yet fail on a different correct system.
Today we've tried to provide some actual standardization of those interfaces. They are defined by the UEFI Forum. Today I can prove that my system is correct according to meeting the UEFI specifications.
When people say things like the UEFI, they are usually referring to the actual system firmware installed on the system that is executed before the OS starts executing. But a lot of us still toss around these terms like we've spilled alphabet soup.
The boot loader is actually OS owned code that is loaded in memory by the system firmware and the system firmware gives control of the hardware to the boot loader. One could say that the end of the system firmware is the UEFI Boot Loader. Or you could just say BDS uses the system policy to find the OS. And you could still find someone that disagreed with the words.
If you wish to know more about how UEFI works then the post UEFI boot: how does that actually work, then? by Adam Williamson is highly recommended.
He answers the questions you have and its a good read:
Now let’s look at how booting works on a UEFI system. Even if you don’t grasp the details of this post, grasp this: it is completely different. Completely and utterly different from how BIOS booting works. You cannot apply any of your understanding of BIOS booting to native UEFI booting. You cannot make a little tweak to a system designed for the world of BIOS booting and apply it to native UEFI booting. You need to understand that it is a completely different world.
The Wikipedia page Unified Extensible Firmware Interface is also a useful resource.
© 2022 - 2024 — McMap. All rights reserved.