In this article we are going to analyze the Unified Extensible Firmware Interface, from both a technical and security perspective. We will also take a brief look at the new Windows 8 EFI Kernel, we’ll discover many new interesting feature Microsoft implemented in it.
In the beginning it was BIOS
Basic Input and Output system (BIOS) is a term used to indicate the technology introduced with the aim to successfully power on a Personal Computer in the early 1980s. Most CPUs at that time were implemented using 8 bit or 16 bit architecture. BIOS main task is to initialize all system peripherals as well as the CPU itself. Operating systems and software used to run in the so called “16 bit real mode” mode. Memory was limited to only 640 KB. After BIOS finished its initialization steps, it released control to the first sector of the bootable hard drive, called Master Boot Record.
BIOS technology still survives today, even though this technology is almost 30 years old. This kind of technology clearly showed its limits many times (especially with hard-drive sector addressing method). We don’t want to listen and analyze all the structural limits of the BIOS here, as it’s a well described topic online.
Before the EFI introduction, every x86 Operation System (and also x86-64 ones) used to start their loading stage still in 16 bit real mode. OS boot loader (intended here as MBR code + VBR code + boot loader) has to deal with old interrupts, used to proper initialize early stuff. Most part of the boot loader code has to be written indeed in assembly code. This makes the developing process of a boot loader an extremely hard and challenging job. The same applies to bootkit and boot viruses development.
UEFI technology has been introduced with the aim to bypass all these limits. UEFI’s job is to abstract all machine hardware-dependent code and provide a full framework capable of running EFI compatible boot applications. Thus, in a theoretical way, All EFI applications can run on any platform which is powered by UEFI technology.
UEFI Implementation – A Quick Glance
Entire UEFI code reside on the motherboard’s Firmware Volume (FV) that can be stored in a flash rom. UEFI Framework is composed by a list of handles’s database and a set of protocol interfaces.
The handle database is composed by objects called handles and protocols. Handles are a collection of one or more protocols, and protocols are data structures tagged by a GUID (global unique identifier). The data structure for a protocol may be empty, may contains data field, or may contain services (function pointer), or may contains both. During UEFI initialization, the system firmware, UEFI drivers (that run in DXE phase), and even UEFI applications create handles and attach one or more protocols to the handles. Information in the handle database is global and can be accessed by any executable UEFI images. The handle database is the central repository for the objects that are maintained by the UEFI-based firmware. It is a list of UEFI handles, and each UEFI handle is identified by a unique number. A UEFI handle may represent components such Executable Images like UEFI drivers and UEFI application (for example a boot loader), devices like network controllers and UEFI services.
UEFI Framework initialization process is composed by 4 phases:
- Security (SEC) Phase: Verifies some pieces of software present in Firmware volume, CPU, chipset, and Motherboard initialization code
- Pre EFI Initialization (PEI) Phase: Initialize CPU, temporary RAM and boot firmware volume (BFV), starts dispatching PEIMs (Pre EFI Initialization modules) found in BFV with the aim to basic initialize all found hardware in the system (only basic platform dependent initialization code). Finally it builds an Hand-Off Block List with all found resources interfaces (that serve as an abstraction to real peripherals platform dependent code) descriptors ready to be passed to the DXE phase.
- Driver Execution Environment (DXE) Phase: It consumes hand-off block list to initialize all system physical memory, I/O and MMIO resources, and to finally starts dispatching DXE Drivers found in all system Firmware Volumes (that are described still by HOBL). DXE Core is abstracted from the platform hardware through a set of DXE Architectural Protocols. The DXE Core consumes these protocols to produce the EFI Boot Services and EFI Runtime Services. DXE Drivers that are loaded from firmware volumes produce the DXE Architectural Protocols, and full initialize entire system. This design means that the DXE Core must have enough services to load and start DXE drivers before even a single DXE Driver is executed
- Boot Device Selection (BDS) Phase: Load all UEFI Drivers and one or more UEFI application found in a boot device. Is the phase responsible in presenting boot manager to the user and boot entire system. The boot sequence for UEFI consist of the following:
- Platform firmware reads the boot order list from globally defined NVRAM variable. The boot order list define a list of NVRAM variables that contain information about what is to be booted
- Each NVRAM boot variable contain a pointer to the hardware device and optionally to a file on that hardware device that contains the UEFI image to be loaded
Windows 8 EFI – Analysis of new features and architecture overview
At the next system restarts, when EFI Firmware ends execution, it releases control to the Windows EFI Boot Manager. One of the first thing Bootmgr does is to save EFI global data, like Boot and Runtime Services pointers. It then obtains Loaded Image protocol and Device Path protocol of its own loaded image handle, with the aim to gain Bootmgr image attributes (like base address, size, etc…). EfiInitpCreateApplicationEntry maps Bootmgr EFI Boot manager entry to Boot Configuration Data one. After EFI initialization has done, BmMain main startup function is called. Entire Bootmgr will use only EFI services to properly work.
The entire boot process is fully 64 bit (or 32 bit in case of 32 bits OSs) and that no Master Boot Record, Volume Boot Record or whatever 16 bit loader code exists.
Bootmgr uses EFI services to read BCD hive, initialize optional SecureBoot feature, locate Windows partition, and load and verify Windows EFI Loader file (winload.efi). Bootmgr uses ReadBlock service of EFI Block IO protocol to read and parse NTFS file system of partition that contains Windows Loader. At the end, Archpx64TransferTo64BitApplicationAsm Bootmgr function transfers control from Bootmgr to Windows loader.
Windows loader begins execution when it’s still in UEFI environment (with Boot Services still mapped and physical flat memory model). Loader does many things: it initializes the kernel debugger (with initial breakpoint), analyzes startup options, determines System root path, loads system hives, HAL module, all boot start drivers (especially FileSystem Driver where Nt Kernel resides), reads Nt Kernel & HAL files and their dependencies, setups hypervisor (if needed), start boot code integrity feature, protect secure boot variables and data structures (if needed), and much other. Finally Winload prepares the system for loading NT Kernel: OslFwKernelSetup loader procedure calls GetMemoryMap EFI boot service, and store returned structure pointer (current EFI virtual to physical memory map) in a global variable. This structure mainly describes the entire EFI Framework code (and obviously all of its loaded modules). Loader then prepares virtual address space to map NT kernel by calling OslBuildKernelMemoryMap procedure. Now if a kernel debugger is attached, Windows loader code patches Interrupt Descriptor Table (with the aim to render it compatible for Kernel debugging), reinitializes debugger module (for transition from Windows loader code to Kernel code), and load Windows Kernel image symbols.
Next, OslFwpKernelSetupPhase1 does an important thing: it calls ExitBootService EFI function. ExitBootService is used to signal that OS is able to use its own functions to control entire System. ExitBootService discard any EFI services needed to boot machine and destroy entire EFI handle database, releasing all employed memory. From now on, all boot services pointers become invalid. Immediately after ExitBootService has done its job, Winload calls SetMemoryMap EFI runtime function with the aim to remap EFI runtime services in the rights virtual addresses (Kernel only Virtual Addresses).
OslFwpKernelSetupPhase1 returns execution to OslpMain: Loader has now quite done its job: if needed it launches previously initialized Hypervisor (HvlpLaunchHypervisor procedure), it finds right Nt Kernel (previously loaded) loader data table entry and finally transfers execution to “ntoskrnl.exe” module (OslArchTransferToKernel procedure).
describe the physical memory on the system, a pointer to the in-memory copy of the HARDWARE and SYSTEM registry hives, as well as various other information related to the boot processing performed until this point. Nt Kernel begins first of its 2 phases initialization in KiSystemStartup function. The purpose of phase 0 is to build the rudimentary structures required to allow the services needed in phase 1. KiSystemStartup begins with interrupts disabled, starts initialization of Boot Structures (like IDT, System call dispatcher, Spin locks, HAL module, Multiprocessor support and so on), debugger module, and then calls KiInitializeKernel one time per each found processor. KiInitializeKernel, if running on the boot CPU, continues system and HAL initialization (HalInitSystem). After HAL is completely operative, InitBootProcessor deal with rest of system components: Multiprocessor dispatching, Language table, Executive (ExInitSystem), Memory Manager (MmInitSystem), Security (SeInitSystem), Processes and Threads Manager (PsInitSystem), Power Management (PpInitSystem) and Plug And Play Manager. When control returns to KiInitializeKernel, the last step is to allocate the DPC stack for the current processor, after which control proceeds to the Idle loop.
Indeed PsInitSystem, has previously initialized the Process and Thread object types, and created System process tied with the first System thread (Phase1Initialization start function). This System thread job is to start Phase 1 of Nt Kernel initialization. Process manager initialization procedure has also created the Idle process. Idle thread become the one that has executed KiSystemStartup routine.
Phase 1 of Windows 8 NT Kernel proceeds quite like old release of OS (Windows 7) until the creation of first user-mode process. The reader who would like to investigate this topic (that in the author opinion is very interesting) can refers to Windows Internals book.Near the end of its execution, Phase1InitializationDiscard procedure calls StartFirstUserProcess to create first Session Manager user mode process (smss.exe). Session Manager process doesn’t use any Win32 APIs because the Windows subsystem isn’t executing when Smss launches. In fact, one of Smss’s first tasks is to start the Windows subsystem. Session Manager in turn, after has done a lot of other things, end up in starting the default the Windows initialization process (Wininit, lauched from session 0) and interactive logon manager process (Winlogon, launched from another session). Here is the point where things change: Windows 8 EFI Boot manager has been moved indeed to a user mode process, called “BootIm”.
The Windows logon process (%SystemRoot%Winlogon.exe) is the system process responsible to handle interactive user logons and logoffs, manages the secure attention sequence (SAS) keystroke and implements user logons (with the aim of lsass.exe, local security authentication server). Winlogon manage furthermore the creation of interactive windows stations and desktops.
Winlogon builds System desktop and System windows station, then it determine if it has to display Boot manager or not. Windows Boot manager works in the following way: Windows Setup, as said before, maps every EFI NVRAM Boot entry to particular BCD objects (each represented by a GUID). Windows 8 EFI BCD store is located in /EFI/Microsoft/Boot/BCD file in EFI System partition. Boot manager reads the list of supported OSs from “BcdBootMgrObjectList_DisplayOrder“ Bootmgr BCD element (in the reader doesn’t know what are BCD objects and elements he has to just Google them) and draws relative buttons reading each right BCD object with the aim to obtain Description, path and all data needed (the BCD I/O is done with COM+).
But how Winlogon process knows whether or not to display Boot Manager? It checks 2 BCD elements in BCD store: the first Winload BCD element (the important thing to note here is that Winload elements are totally different from Bootmgr ones) “BcdOSLoaderInteger_BootMenuPolicy” (0x250000c2) globally enables or disables new Metro Boot Manager; the second and most important one is by the way “BcdBootMgrObjectList_BootSequence” Bootmgr element (0x24000002). Originally this element is not present in Bootmgr BCD store. When user clicks on a particular OS button, Boot manager add “Boot Sequence” element and immediately reboot system. At the next reboot, if Winlogon found this Bootmgr BCD element, and determines that GUID contained in it corresponds to current OS GUID, skips Boot Menù and proceeds with User Authentication. If instead GUID doesn’t correspond to a directly supported OS, Bootmgr at early stages proceeds to release control to other unsupported OS, with the aid of EFI Boot Services. In this last case Winlogon is not loaded.If Winlogon found that Boot manager has to be showed, WinLogonBootShell routine creates, launches “bootim.exe” process and attaches it to System desktop. It then wait indefinitely on new process handle. When bootim exits, Winlogon analyzes its exit code: if equals to 0, it switches to logon desktop and proceeds to normally load Windows User Interface; if it equals to 0xBC2 (or 0x281 whether a recovery tool has been chosen) it adjusts its token privileges (adding SeShutdownPrivilege) and reboot system with a call to NtShutdownSystem. No other actions are required because “bootim.exe” has already modified Bootmgr BCD object, adding the Boot Sequence element.
UEFI From a Security Point of View
We just took a quick glance at the kernel changes in Windows 8 UEFI. Now it’s time to discuss another important topic: Security. Starting from Windows Vista and 7, x64 NT Kernel is a real Secure System. Many new features have been introduced with the aim to render the life of an attacker very hard like ASLR, Patchguard, Driver Signing Enforcement (for further inspections the reader is can read the author’s X64 university thesis available here. Windows 8 keeps all this security features and introduces also some others (like Virtualization and Secure Boot)… We won’t analyze here these new characteristics, instead we would like to try to exploit its EFI boot mechanism.After successfully download EFI EDK2, it’s possible to start writing a new EFI Application with an environment like Visual Studio 2008. Every EFI Applications starts in the following entry point:
EFI_STATUS EfiMain (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE * SystemTable)
As already said in the Introduction, all the needed EFI interfaces are in the SystemTable parameter. What we would like to do in this example is to create an EFI Bootkit able to bypass Windows 8 Driver Signing Enforcement and Patchguard (just for example, we could easily do whatever modification we want). After having analyzed rights NT Kernel, Winload and Bootmgr parts of our interest, we conclude that our bootkit code must do the following:
- Obtain a Loaded Image and a Device Path Protocol from our ImageHandle
- Locate rights Device Path utility protocols needed to proper build up the device path of Bootmgfw.efi Windows boot loader
- Load Windows Boot loader EFI application image, and obtain its memory mapping address
- At this point we have two possible choices:
- Patch some procedures of interest in Windows Bootmgr EFI application (like Archpx64TransferTo64BitApplicationAsm). Used hook is very small, composed only of a “JMP” or a “CALL” opcode. In this way we can divert bootmgr execution in the same manner as seen in the classical BIOS Bootkit before it pass control to Winload.
- Hook only one or more EFI Runtime function, for example ExitBootServices is a perfect choice. We know in fact that these procedures are called only in late boot stages. We can indeed avoid to forge Bootmgr code and hit only Winload before it transfers control to Nt Kernel. New Hooked EFI runtime service has to patch Nt kernel.
- Start Windows Boot loader Image (with StartImage EFI BootService facility)
In the brief analysis we made we have seen that hitting an UEFI system is still a quite easy task. In our bootkit develop process, we have showed only a small subset of kernel patch modality. We can indeed hit Kernel in a lot of others way (hooking Block I/O protocol Read function for example). Furthermore, for an attacker, it would be easy with EFI to disable bootkit if a debugger is detected for example (task done by intercepting Load Options of Winload and Nt Kernel). Sky is the limit.We’ve also found that writing an EFI Bootkit is even a simpler task than writing a BIOS Bootkit. While a BIOS bootkit requires a very large knowledge of Assembly language and Intel x86 architecture, an EFI bootkit is much easier to be developed because with the UEFI framework everything is abstracted from machine.
How to protect Systems? Some words about the new Microsoft Secureboot technology are required. SecureBoot is a brand-new Microsoft Security feature that, in cooperation with Intel and OEM Firmware producers, digital signs even the main Boot EFI Loader. Firmware has a digitally signed catalog of recognized Boot loaders SHA hashes. If startup EFI Application is not digitally signed, or if it has been changed, EFI Firmware refuse to boot. This fact obviously will increase whole platform’s security, though the biggest drawback is that it will render entire architecture closer, decreasing user freedom’s of choice. Anyway, the discussion whether or not SecureBoot is the right technology is outside the scope of current analysis