Vault7: CIA Hacking Tools Revealed
Navigation: » Directory » Embedded Development Branch (EDB) » EDB Home » How-To Articles » EFI/UEFI Information
Owner: User #71491
ExitBootServices Hooking
So, You Want to Hook ExitBootServices?
If you're here, it's because you want to know more about ExitBootServices, and probably want to hook it so you can do things to the OS. Here's some basic information for you in case you need your memory refreshed about EFIExtensible Firmware Interface things.
On boot, the code located on the firmware has control of the system. It is what determines what happens during a boot. Typically, the UEFIUniversal Extendible Firmware Interface application run from the firmware is an OSOperating System Loader. The loader's main purpose is to initialize everything that the OSOperating System needs to run, load the kernel into memory, and pass control to the kernel itself. However, the loader is just another UEFIUniversal Extendible Firmware Interface Application: it can only use the memory it has been allocated by the firmware and can only access the UEFIUniversal Extendible Firmware Interface services and protocols that the firmware provides. All the way through to the end of the DXEDriver Execution Environment phase of a UEFIUniversal Extendible Firmware Interface boot, the firmware is in charge. However, eventually, all of the UEFIUniversal Extendible Firmware Interface boot services need to end, and the OSOperating System needs to take control. This is accomplished using the UEFIUniversal Extendible Firmware Interface boot service ExitBootServices.
When ExitBootServices is called by the OSOperating System loader, the firmware gives control of the system to the loader. All of the boot service memory is reclaimed, the boot services are all terminated, and the OSOperating System loader can hand over control of the system to the OS. At this point, only the runtime services provided by the firmware are still accessible.
Actually Hooking ExitBootServices
When ExitBootServices is called, the DXEDriver Execution Environment phase is about to end. The firmware has done all that it needs to do from UEFIUniversal Extendible Firmware Interface to set up the system for the OS, and the OSOperating System itself has already been loaded into memory. You can be creative with what you could do to the kernel that is just sitting there in memory, not protected by anything.
Because the ExitBootServices service can be found by getting its pointer from the global EFI_BOOT_SERVICES table, hooking the ExitBootServices call is trivial. From within a UEFIUniversal Extendible Firmware Interface driver, you store the original pointer and then replace the table's pointer with one to your hook function. From there, you let your driver run and wait for ExitBootServices to be called by the OSOperating System loader, and your hook code will run just before the OSOperating System loader gets control. When you're running in UEFI, that EFI_BOOT_SERVICES table isn't protected by anything, so you can just write directly to it.
At this point, you can do whatever you want. UEFIUniversal Extendible Firmware Interface boot services are still running (because they will be terminated when the real ExitBootServices is called), and the OSOperating System is sitting there. However, if you mess with memory (especially if you allocate new buffers), you will need to do some cleanup before you can return successfully to the original ExitBootServices.
The second parameter to ExitBootServices is a UINTN MapKey. This value identifies the current memory map of the system, and is changed every time something in the memory map changes. In order for ExitBootServices to do its job properly, it MUST have the current memory map. If it does not, it will do weird things, like cause the OSOperating System to not be able to identify the startup disk or not be able to load altogether.
In order to make sure your ExitBootServices call goes correctly, you will need to call GetMemoryMap first. Ironically, calling GetMemoryMap will require you to allocate memory for the map itself, which, in turn, will change the memory map.
You can deal with this issue by looping your calls – allocating space for the map, then calling GetMemoryMap again. Eventually, you will have allocated enough space for the (again updated) map before you make the GetMemoryMap call, and you'll get the up-to-date map.
Once you have the map, you can simply call the original ExitBootServices function and be on your merry way.
Example Code
Below is the code you'll need to do basic ExitBootServices hooking. Actually compiling this into an EFIExtensible Firmware Interface executable isn't covered in this tutorial, but you will need to run this from a UEFIUniversal Extendible Firmware Interface driver. In this example, your driver's entry point is HookDriverMain.
extern EFI_BOOT_SERVICES *gBS;
EFI_EXIT_BOOT_SERVICES gOrigExitBootServices;
EFI_STATUS
EFIAPI
ExitBootServicesHook(IN EFI_HANDLE ImageHandle, IN UINTN MapKey){
/* <hook related fun> */
/* Do fun hook-related stuff here */
/* </hook-related fun> */
/* Fix the pointer in the boot services table */
/* If you don't do this, sometimes your hook method will be called repeatedly, which you don't want */
gBS->ExitBootServices = gOrigExitBootServices;
/* Get the memory map */
UINTN MemoryMapSize;
EFI_MEMORY_DESCRIPTOR *MemoryMap;
UINTN LocalMapKey;
UINTN DescriptorSize;
UINT32 DescriptorVersion;
MemoryMap = NULL;
MemoryMapSize = 0;
do {
Status = gBS->GetMemoryMap(&MemoryMapSize, MemoryMap, &LocalMapKey, &DescriptorSize,&DescriptorVersion);
if (Status == EFI_BUFFER_TOO_SMALL){
MemoryMap = AllocatePool(MemoryMapSize + 1);
Status = gBS->GetMemoryMap(&MemoryMapSize, MemoryMap, &LocalMapKey, &DescriptorSize,&DescriptorVersion);
} else {
/* Status is likely success - let the while() statement check success */
}
DbgPrint(L"This time through the memory map loop, status = %r\n",Status);
} while (Status != EFI_SUCCESS);
return gOrigExitBootServices(ImageHandle,LocalMapKey);
}
EFI_STATUS
EFIAPI
HookDriverMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable){
/* Store off the original pointer and replace it with your own */
gOrigExitBootServices = gBS->ExitBootServices;
gBS->ExitBootServices = ExitBootServicesHook;
/* It's hooked! Return EFI_SUCCESS so your driver stays in memory */
return EFI_SUCCESS;
}
Previous versions:
| 1 |