Trough this analysis paper we’ll give a look at Windows 8 (and 8.1) UEFI startup mechanisms and we’ll try to understand their relationship with the underlying hardware platform.
Windows boot manager and loader
The Windows boot manager starts its execution in the EfiEntry procedure. EfiEntry is, as the name implies, the Bootmgr EFI entry point. It obtains the EFI startup disk device, creates the initial parameters needed for Bootmgr and then it calls the main Bootmgr startup function: BmMain.
Bootmgr PE file bootmgfw.efi is indeed loaded and executed by UEFI firmware (Boot Device Selection phase) with LoadImage and StartImage boot service functions. As we’ve already stated in our previous article, Windows Boot manager uses the UEFI firmware to read and write from the start-up disk. In particular, block I/O protocol is used. This protocol abstracts the physical disk mass storage devices and allows the code running in the UEFI context to access them without a specific knowledge of the type of device or controller that manages a particular disk device. The Block I/O protocol allows the OS doing some basic work: reading, writing, flushing and resetting.
From the Block I/O protocol derives another UEFI I/O protocol: Disk I/O. The latter is used to abstract the block accesses of the Block I/O protocol to a more general offset-length protocol (I.e. it eliminates LBA and sectors concepts, transforming them to byte offsets). Disk I/O protocol is not used by the Windows loader.
BmMain, first of all, initializes all the Boot manager data structures (BlInitializeLibrary), the UEFI firmware environment (BlpFwInitialize procedure obtains Boot services, Runtime services, ConnIn, ConnOut UEFI facilities), the Kernel mode debugger (BlBdInitialize initializes and establishes the earliest possible kernel Debugger connection) and the Secure Boot.
When the Initialization is done the control flow returns to BmMain. BmFwInitializeBootDirectoryPath initializes the Boot applications path (“\EFI\Microsoft\Boot”), it opens the boot disk partition, it probes the “BCD“ hive file, and subsequently it stores its boot directory. Bootmgr then opens and maps the Boot configuration Data. BmOpenDataStore indeed opens and reads the BCD hive file with UEFI services: a bunch of functions are used: BcdOpenStoreFromFile – BiLoadHive – BlpDeviceOpen and BlImgLoadImageWithProgressEx. We will demonstrate how the disk partitions I/O is done with EFI services. BlpDeviceOpen opens the disk partition device that contains the BCD file (EFI System partition with GUID equals to C12A7328-F81F-11D2-BA4B-00A0C93EC93B), BlImgLoadImageWithProgressEx instead opens and reads the BCD hive file. Then the BCD hive is mapped and analysed. If “bootmenupolicy” bcd element is set to “Legacy” , the boot menu is displayed and then winload is read and executed. The Boot flow is now transferred to Winload. We will investigate on Device and File open routines afterwards. For now it is enough to say that this kind of code is the same as the one located in Winload. Bootmgr gives control to Winload in its ImgArchEfiStartBootApplication (UEFI platform dependent, as its name implies).
Winload starts its execution in OslMain procedure. OslMain’s job is to initialize the Windows loader data structures, the kernel debugger, and the environment, exactly in the same way as Bootmgr does (the code indeed is the same), and finally it calls OslpMain. OslpMain first of all it analyses its BCD entry elements, various things are captured: OS load options string, OS physical disk device and partition GUID, System root path. Noteworthy it is the OS physical disk device and partition BCD element (BcdOSLoaderDevice_OSDevice – 0x21000001 hex code). This element contains the physical disk GUID and the partition start LBA and signature. These information are stored in winload’s global data structures.
Then the Boot status data (bootstat.dat) is read, and whether something went wrong in the previous boot, the recovery mode will be launched; if the boot flow proceeds normally, the System Hive is located and mapped (OslpLoadSystemHive), the boot bitmap and the bar are displayed, the code integrity and the secure boot are initialized. Now one of the tenets of our project is pinpointed: OslpLoadAllModules Winload procedure reads and maps Nt Kernel module, Hardware abstraction layer (Hal), Kernel debugger module (kdcom.dll) and each boot drivers. Each of this module has been loaded and mapped BUT not started yet. It will be a Nt kernel’s job to launch them (see Windows Internal 6th edition book, Part 2, chapter 13).
All boot modules are read and mapped by OslLoadImage. In this big function a call chain takes place. Finally the BlockIoRead is called to read the file data from a block device (LBA addressing). The BlockIoRead determines whether target device is physical or virtual (Vhd image), and, in the first case a call chain to BlockIopFirmwareOperation is made. This last procedure is defined in the following way:
NTSTATUS BlockIopFirmwareOperation(LPVOID winloadDataStruct, LPVOID lpDestBuff, QWORD qwLba, DWORD dwNumSectorToRead, int unknown)
BlockIopFirmwareOperation translates the target buffer address and the EFI Block I/O protocol interface for the UEFI context (indeed Winload has already setup an owner GDT, LDT), it switches the context and finally it emits an EFI Block I/O protocol interface ReadBlock call to physically read the data. Then context is switched again to Winload context and the control returns to the caller. If an error has occurred, the current EFI Block I/O protocol interface is closed, reopened with the OpenProtocol EFI Boot service (target device EFI Handle is stored somewhere in “winloadDataStruct”), then I/O is remitted. BlockIopFirmwareOperation is the same routine used previously in Bootmgr each time a disk I/O operation was needed.
Now we have demonstrated that both Windows Loader and its Boot manager use only the UEFI services to actually read and write to Physical disks. We still have to understand how Windows loader can identify the proper system disk partition device (where all Windows files are stored). To answer this question we have to return back in Bootmgr main start-up procedure. After BlInitializeLibrary has done its job, there is a call to BmFwInitializeBootDirectoryPath.
As stated before, the “BCD” file is opened and mapped. In order to map, the Bootmgr needs to initialize and open the EFI System partition. Bootmgr knows the boot partition GUIDs because it has retrieved it in EFI Entry point procedure, starting from its own EFI Loaded Image handle (Bootmgr Efi entry point procedure calls HandleProtocol EFI boot service to obtain Device Path protocol Interface. This interface contains Boot disk partition GUIDs).
An important concept to point out now is that, at this stage, the Windows boot manager can easily open only the boot Disk partitions, not the physical disks, because the physical EFI device handle of its Loaded Image interface is a partition handle, not a disk’s one (it contains only the partition GUID). We will now see how the Boot manager can identify the Physical disks.
BmFwInitializeBootDirectoryPath invokes BlpDeviceOpen (with first parameter set to a quite empty Winload boot disk structure, that contains the boot partition GUID) to open the target disk boot partition device. BlpDeviceOpen raises a large call chain that finally transfers the control to PartitionFirmwareOpen. This last procedure uses the LocateHandle EFI boot service to locate all handles that implement the Block I/O interface. For each of the identified handles, its Device path interface is obtained with EfiOpenProtocol. In the Device path interface resides partition GUID. The Partition GUID is compared with the start-up device GUID, if two GUIDs match, then a Bootmgr partition entry is created with the PartitionFwpCreateDeviceEntry function. Bootmgr indeed manages each of the system I/O devices, creating an associated internal structure.
Now Bootmgr has a proper boot partition connection (GUID, EFI handle pair) and the feeds read and write I/O to its File system code. We have just seen how read and write is implemented (keep in mind that, as stated before, all this code is shared between Winload and Bootmgr).
The difficult of this job is to understand how Windows identifies the EFI handle of the Physical boot disk. The algorithm used is quite complex. We provide here a summary description.
One of the first thing Winload does at its early stage is to initialize the physical disks, just after the Bootmgr has released the control. BlpEdriveInitializeContext calls SecCmdFirmwareOpen, that uses DiskOpen to proper identify the physical disk. The Physical disk GUID is now available because Bootmgr has already obtained it from the BCD hive (Winload BCD object). Here starts the Windows algorithm used to identify the proper EFI Handle. The Algorithm starts enumerating each of the EFI handles that implement the Block I/O protocol. For each handle found, Winload constructs its own management data structure with BlockIoEfiCreateDeviceEntry .
This procedure opens the Device path interface of the EFI handle, it allocates enough buffer and it compiles a structure with some device data (like block size, first and last addressable LBA and so on). Then it retrieves the device information with BlockIoEfiGetDeviceInformation. This is the key of algorithm. The last node of the device handle path is obtained (EfiGetLeafNode function) and analysed: whether it is a Messaging node type, Winload tries to get one of its direct child handles. If it succeeds, it will analyse the child device path leaf node. If the node results to be a media path type then Winload has found one physical hard disk device: it closes the child handle and it reads the GPT partition table from the original EFI Handle. The GPT partition table contains the Disk GUID in its header (LBA 1), as stated in this document: http://en.wikipedia.org/wiki/GUID_Partition_Table.
BlockIoGetGPTDiskSignature obtains the GUID and stores it in the device associate data structure. In the end, when control returns to DiskOpen, the Winload compares the new GUID with the searched disk device GUID and, if these two match, it returns STATUS_SUCCESS to its caller, otherwise it proceeds with the next entry. If no disk is found, it returns STATUS_NO_SUCH_DEVICE.
When this process is done at least one time for each EFI devices, the Winload doesn’t redo, because it stores each created device structure hash in a global Hash table (updated with BlHtStore function).
Through this analysis we have now understood how the Windows start-up code manages the disk I/O. We have pinpointed that only the UEFI services are used to read and write from Boot and System devices. This is one of the reasons that explain why Bootmgr and Winload files are platform dependent (there are different versions for different platforms).