Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does UEFI work?

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?

like image 774
prog481 Avatar asked Aug 26 '15 09:08

prog481


3 Answers

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.

like image 54
Alex D Avatar answered Oct 16 '22 23:10

Alex D


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.

like image 38
Buddy Avatar answered Oct 17 '22 01:10

Buddy


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:

enter image description here

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.

enter image description here

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.

enter image description here

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.

enter image description here

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

enter image description here

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    
.
.
.
like image 24
Lewis Kelsey Avatar answered Oct 17 '22 01:10

Lewis Kelsey