Analyzing Windows Boot: bootmgfw.efi and the Early UEFI Boot Flow
About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.
This research is published for educational and defensive purposes. The target sample is anonymized throughout.
Introduction
This article walks through Windows’ early boot process as implemented by bootmgfw.efi.
Analysis
DebugService2
DebugService2 provides the earliest opportunity for WinDbg to attach to the operating system. From the call stack you can see it is invoked from DbgLoadImageSymbols:
kd> lm
start end module name
00000000`cf5f3000 00000000`cf7bd000 bootmgfw (pdb symbols)
kd> k
# Child-SP RetAddr Call Site
00 00000000`cf570590 00000000`cf6c5023 bootmgfw!DbgLoadImageSymbols+0x67
01 00000000`cf5705e0 00000000`cf6c551a bootmgfw!BlBdStart+0x193
02 00000000`cf5706d0 00000000`cf66524d bootmgfw!BlBdInitialize+0x342
03 00000000`cf5707b0 00000000`cf613ae7 bootmgfw!BlInitializeLibrary+0x95
04 00000000`cf5707e0 00000000`cf61344d bootmgfw!BmMain+0x4eb
05 00000000`cf570960 00000000`ce09283c bootmgfw!EfiEntry+0x1d
06 00000000`cf570990 00000000`d40b9218 0xce09283c
07 00000000`cf570998 00000000`d4da5918 0xd40b9218
08 00000000`cf5709a0 00000000`cf570a50 0xd4da5918
09 00000000`cf5709a8 00000000`00000000 0xcf570a50
From IDA’s static analysis plus the call stack, the call chain is easy to derive:
EfiEntry-->BmMain-->BlBdStart-->DebugService2
So how does Windows know it should call DebugService2? That’s BmMain’s job.
What BmMain does
It calls BmFwInitializeBootDirectoryPath to initialize the boot application path (\EFI\Microsoft\Boot). Let’s look at BmFwInitializeBootDirectoryPath:
void BmFwInitializeBootDirectoryPath()
{
// try to open the boot device
status = BlpDeviceOpen(g_SbControl+0x8e0, 1, NULL, &deviceHandle);
if (!NT_SUCCESS(status)) {
goto Cleanup;
}
// get the application directory path
status = BmpFwGetApplicationDirectoryPath(&appDirPath, &appDirPathLen);
if (!NT_SUCCESS(status) || !appDirPath) {
goto Cleanup;
}
// try to open the target file
status = BlFileOpen(deviceHandle, tempBuffer, FILE_READ_DATA, &fileHandle);
if (NT_SUCCESS(status)) {
BootDirectory = appDirPath;
appDirPath = NULL;
}
Cleanup:
// close the handle here
// on failure, fall back to \EFI\Boot\bootmgfw.efi
if (!BootDirectory) {
BootDirectory = (WCHAR*)DEFAULT_BOOT_DIR;
}
}
g_SbControl is a global; at offset 0x8e0 it holds a pointer to the EFI_SYSTEM_TABLE, which the HAL wraps.
After initializing the boot path, it calls BmOpenDataStore to mount and read the BCD database file (\EFI\Microsoft\Boot\BCD) through UEFI services (disk I/O):
NTSTATUS BmOpenDataStore(PVOID Context)
{
part1=NULL;
part2=NULL;
// get the data store path
status = BmGetDataStorePath(&part1, &part2, &freePart2);
if (!NT_SUCCESS(status)) {
goto Cleanup;
}
// compute the length of the part2 string (excluding null terminator)
for (part2Length = 0; part2[part2Length] != L'\0'; part2Length++);
// compute the total bytes for the combined path
ULONG totalBytes = (part2Length + 1) * sizeof(WCHAR); // includes null terminator
if (part1->LengthField > (ULONG_MAX - totalBytes)) {
status = STATUS_INTEGER_OVERFLOW;
goto Cleanup;
}
totalBytes += part1->LengthField;
// check against the 0xFFFFFFFF limit
if (totalBytes > 0xFFFFFFFF) {
status = STATUS_INTEGER_OVERFLOW;
goto Cleanup;
}
// allocate memory
combinedPath = BlMmAllocateHeap(totalBytes);
if (!combinedPath) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
// copy part1 data
memcpy(combinedPath, part1->Buffer, part1->LengthField);
// copy part2 data (including null terminator)
memcpy((PBYTE)combinedPath + part1->LengthField,
part2,
(part2Length + 1) * sizeof(WCHAR));
// set up the UNICODE_STRING, i.e. fullPath
// open the BCD store
status = BcdOpenStoreFromFile(&fullPath, Context);
Cleanup:
// free resources
if (combinedPath) {
BlMmFreeHeap(combinedPath);
}
if (part1) {
BlMmFreeHeap(part1);
}
if (part2 && freePart2) {
BlMmFreeHeap(part2);
}
return status;
}
During boot-manager startup, this function obtains the BCD (Boot Configuration Data) store path and opens the store to read boot configuration, preparing for the subsequent boot-option loading.
Here is the recovered original prologue of DebugService2:
cf70b9c4 488bc4 mov rax, rsp
cf70b9c7 4c894018 mov qword ptr [rax+18h], r8
cf70b9cb 53 push rbx
cf70b9cc 4883ec40 sub rsp, 40h
cf70b9d0 488348e0ff or qword ptr [rax-20h], 0FFFFFFFFFFFFFFFFh
cf70b9d5 4c8d4818 lea r9, [rax+18h]
cf70b9d9 4883601800 and qword ptr [rax+18h], 0
cf70b9de 4533c0 xor r8d, r8d
cf70b9e1 488bd9 mov rbx, rcx
cf70b9e4 488950d8 mov qword ptr [rax-28h], rdx
cf70b9e8 418d4801 lea ecx, [r8+1]
cf70b9ec e883f6f3ff call bootmgfw!RtlImageNtHeaderEx (cf64b074)
cf70b9f1 488b542460 mov rdx, qword ptr [rsp+60h]
cf70b9f6 4885d2 test rdx, rdx
cf70b9f9 7410 je bootmgfw!DbgLoadImageSymbols+0x47 (cf70ba0b)
cf70b9fb 8b4258 mov eax, dword ptr [rdx+58h]
cf70b9fe 89442430 mov dword ptr [rsp+30h], eax
cf70ba02 8b4250 mov eax, dword ptr [rdx+50h]
cf70ba05 89442434 mov dword ptr [rsp+34h], eax
cf70ba09 eb0d jmp bootmgfw!DbgLoadImageSymbols+0x54 (cf70ba18)
cf70ba0b 8364243000 and dword ptr [rsp+30h], 0
cf70ba10 c744243400001000 mov dword ptr [rsp+34h], 100000h
cf70ba18 41b803000000 mov r8d, 3
cf70ba1e 488d542420 lea rdx, [rsp+20h]
cf70ba23 488bcb mov rcx, rbx
cf70ba26 e8e5c80300 call bootmgfw!DebugService2 (cf748310)
cf70ba2b 4883c440 add rsp, 40h ; restored here
cf70ba2f 5b pop rbx
cf70ba30 c3 ret
BmpLaunchBootEntry
After DebugService2 is called there’s no convenient place to set a breakpoint, so break directly on BmpLaunchBootEntry. It loads and executes winload.efi. The control flow before BmpLaunchBootEntry selects the correct boot entry based on the BCD store values described earlier. If BitLocker is enabled, the boot manager decrypts the system partition before transferring control to the boot loader.

Let’s analyze BmpLaunchBootEntry:
bootmgfw!BmpLaunchBootEntry (00000000`cf6150f4)
bootmgfw!BmpLaunchBootEntry+0x49 (00000000`cf61513d):
call to bootmgfw!BcdUtilGetBootOption (00000000`cf6a2794)
bootmgfw!BmpLaunchBootEntry+0x5b (00000000`cf61514f):
call to bootmgfw!BmOpenDataStore (00000000`cf617dc0)
bootmgfw!BmpLaunchBootEntry+0x78 (00000000`cf61516c):
call to bootmgfw!BmPurgeOption (00000000`cf618334)
bootmgfw!BmpLaunchBootEntry+0x81 (00000000`cf615175):
call to bootmgfw!BmCloseDataStore (00000000`cf617ed8)
bootmgfw!BmpLaunchBootEntry+0x8c (00000000`cf615180):
call to bootmgfw!BcdUtilGetBootOption (00000000`cf6a2794)
bootmgfw!BmpLaunchBootEntry+0x9e (00000000`cf615192):
call to bootmgfw!BmOpenDataStore (00000000`cf617dc0)
bootmgfw!BmpLaunchBootEntry+0xbb (00000000`cf6151af):
call to bootmgfw!BmPurgeOption (00000000`cf618334)
bootmgfw!BmpLaunchBootEntry+0xc4 (00000000`cf6151b8):
call to bootmgfw!BmCloseDataStore (00000000`cf617ed8)
bootmgfw!BmpLaunchBootEntry+0x10c (00000000`cf615200):
call to bootmgfw!BlGetBootOptionDevice (00000000`cf6658c0)
bootmgfw!BmpLaunchBootEntry+0x135 (00000000`cf615229):
call to bootmgfw!BlGetBootOptionBoolean (00000000`cf6654d0)
bootmgfw!BmpLaunchBootEntry+0x15a (00000000`cf61524e):
call to bootmgfw!BmFindAssociatedOsEntry (00000000`cf618ad4)
bootmgfw!BmpLaunchBootEntry+0x17a (00000000`cf61526e):
call to bootmgfw!BlGetBootOptionDevice (00000000`cf6658c0)
bootmgfw!BmpLaunchBootEntry+0x19e (00000000`cf615292):
call to bootmgfw!BlMmFreeHeap (00000000`cf68bd48)
bootmgfw!BmpLaunchBootEntry+0x1ab (00000000`cf61529f):
call to bootmgfw!BlMmFreeHeap (00000000`cf68bd48)
bootmgfw!BmpLaunchBootEntry+0x1cf (00000000`cf6152c3):
call to bootmgfw!BlMmFreeHeap (00000000`cf68bd48)
bootmgfw!BmpLaunchBootEntry+0x1d7 (00000000`cf6152cb):
call to bootmgfw!BmpCreateDevices (00000000`cf615e6c)
bootmgfw!BmpLaunchBootEntry+0x1f7 (00000000`cf6152eb):
call to bootmgfw!McTemplateK0qq_McGenEventWriteBoot (00000000`cf61350c)
bootmgfw!BmpLaunchBootEntry+0x205 (00000000`cf6152f9):
call to bootmgfw!BlFveRegisterBootEntryForTrustedWimBoot (00000000`cf62b9e8)
bootmgfw!BmpLaunchBootEntry+0x243 (00000000`cf615337):
call to bootmgfw!FvebpUnlockSinglePartition (00000000`cf62be60)
bootmgfw!BmpLaunchBootEntry+0x255 (00000000`cf615349):
call to bootmgfw!BlFveRegisterDeviceForTrustedWimBoot (00000000`cf62b964)
bootmgfw!BmpLaunchBootEntry+0x274 (00000000`cf615368):
call to bootmgfw!FvebpLogError (00000000`cf62bb28)
bootmgfw!BmpLaunchBootEntry+0x2a1 (00000000`cf615395):
call to bootmgfw!BlGetBootOptionBoolean (00000000`cf6654d0)
bootmgfw!BmpLaunchBootEntry+0x2b8 (00000000`cf6153ac):
call to bootmgfw!BlAppendBootOptionBoolean (00000000`cf665e2c)
bootmgfw!BmpLaunchBootEntry+0x2c3 (00000000`cf6153b7):
call to bootmgfw!BlRemoveBootOption (00000000`cf666128)
bootmgfw!BmpLaunchBootEntry+0x2ce (00000000`cf6153c2):
call to bootmgfw!BlRemoveBootOption (00000000`cf666128)
bootmgfw!BmpLaunchBootEntry+0x2e2 (00000000`cf6153d6):
call to bootmgfw!BlGetBootOptionBoolean (00000000`cf6654d0)
bootmgfw!BmpLaunchBootEntry+0x2f9 (00000000`cf6153ed):
call to bootmgfw!BlAppendBootOptionBoolean (00000000`cf665e2c)
bootmgfw!BmpLaunchBootEntry+0x304 (00000000`cf6153f8):
call to bootmgfw!BlRemoveBootOption (00000000`cf666128)
bootmgfw!BmpLaunchBootEntry+0x30f (00000000`cf615403):
call to bootmgfw!BlRemoveBootOption (00000000`cf666128)
bootmgfw!BmpLaunchBootEntry+0x31f (00000000`cf615413):
call to bootmgfw!BmTransferExecution (00000000`cf61457c)
bootmgfw!BmpLaunchBootEntry+0x32f (00000000`cf615423):
call to bootmgfw!BlFveRegisterBootEntryForTrustedWimBoot (00000000`cf62b9e8)
bootmgfw!BmpLaunchBootEntry+0x383 (00000000`cf615477):
call to bootmgfw!BcdUtilGetBootOption (00000000`cf6a2794)
bootmgfw!BmpLaunchBootEntry+0x3be (00000000`cf6154b2):
call to bootmgfw!BlGetBootOptionBoolean (00000000`cf6654d0)
bootmgfw!BmpLaunchBootEntry+0x3ea (00000000`cf6154de):
call to bootmgfw!BlGetBootOptionBoolean (00000000`cf6654d0)
bootmgfw!BmpLaunchBootEntry+0x417 (00000000`cf61550b):
call to bootmgfw!BlFveRegisterBootEntryForTrustedWimBoot (00000000`cf62b9e8)
bootmgfw!BmpLaunchBootEntry+0x421 (00000000`cf615515):
call to bootmgfw!BmLaunchRecoverySequence (00000000`cf6143ac)
bootmgfw!BmpLaunchBootEntry+0x436 (00000000`cf61552a):
call to bootmgfw!BlFveRegisterBootEntryForTrustedWimBoot (00000000`cf62b9e8)
bootmgfw!BmpLaunchBootEntry+0x451 (00000000`cf615545):
call to bootmgfw!BlRemoveBootOption (00000000`cf666128)
bootmgfw!BmpLaunchBootEntry+0x474 (00000000`cf615568):
call to bootmgfw!BmFatalErrorEx (00000000`cf61a020)
bootmgfw!BmpLaunchBootEntry+0x479 (00000000`cf61556d):
call to bootmgfw!BmpPreserveBootmgrLogToBootstat (00000000`cf615998)
bootmgfw!BmpLaunchBootEntry+0x483 (00000000`cf615577):
call to bootmgfw!BmDisplayDumpError (00000000`cf616870)
bootmgfw!BmpLaunchBootEntry+0x48b (00000000`cf61557f):
call to bootmgfw!BmErrorPurge (00000000`cf61a5a4)
bootmgfw!BmpLaunchBootEntry+0x4e2 (00000000`cf6155d6):
call to bootmgfw!BlAppendBootOptionBoolean (00000000`cf665e2c)
bootmgfw!BmpLaunchBootEntry+0x514 (00000000`cf615608):
call to bootmgfw!BmOpenDataStore (00000000`cf617dc0)
bootmgfw!BmpLaunchBootEntry+0x526 (00000000`cf61561a):
call to bootmgfw!BmProcessCustomAction (00000000`cf614538)
bootmgfw!BmpLaunchBootEntry+0x535 (00000000`cf615629):
call to bootmgfw!BmCloseDataStore (00000000`cf617ed8)
FvebpUnlockSinglePartition
This is BitLocker’s decryption function. Let’s analyze it:
NTSTATUS FvebpUnlockSinglePartition(
PDEVICE_OBJECT Device, // RCX
PVOID BootOption, // RDX (possibly BOOT_OPTION)
DWORD Index, // R8
USHORT Flags, // R9
PVOID* OutDeviceData // [RBP+50h] (optional output)
)
{
HANDLE deviceHandle = INVALID_HANDLE_VALUE;
PDEVICE_OBJECT bootDevice = NULL;
PDEVICE_IDENTIFIER deviceId = NULL;
PVOID deviceData = NULL;
NTSTATUS status = STATUS_SUCCESS;
USHORT modifiedFlags = Flags;
// increment the global counter
InterlockedIncrement(&FvebGlobalData.Counter);
// check the device type; use the default device if necessary
if (*(DWORD*)Device == DEVICE_TYPE_5) {
Device = SbControlDevice;
}
// check whether a special flag should be added
if (!(Flags & FLAG_600H) &&
Index > 1 &&
BootOption != NULL)
{
// get the boot device
status = BlGetBootOptionDevice(
*(PVOID*)((PBYTE)BootOption + 0x18),
0x21000001,
NULL,
&bootDevice
);
if (NT_SUCCESS(status) &&
Device != NULL &&
bootDevice != NULL)
{
// use the default device if necessary
PDEVICE_OBJECT dev1 = (*(DWORD*)Device == DEVICE_TYPE_5)
? SbControlDevice : Device;
PDEVICE_OBJECT dev2 = (*(DWORD*)bootDevice == DEVICE_TYPE_5)
? SbControlDevice : bootDevice;
// compare whether the devices are the same
if (BlpDeviceCompare(dev1, dev2)) {
modifiedFlags |= FLAG_200H;
}
}
}
// check whether the device is already locked
if (*(BYTE*)((PBYTE)Device + 4) & 1) {
status = STATUS_UNSUCCESSFUL;
goto Cleanup;
}
// special handling on first call
if (FvebGlobalData.Counter == 1) {
if (FvebGlobalData.UnkD8 != 0) {
status = STATUS_UNSUCCESSFUL;
goto Cleanup;
}
FvebGlobalData.UnkD8 = Index;
}
// special handling on nested calls
else if (FvebGlobalData.Counter > 1) {
if (!(Flags & FLAG_600H) && Index > 1) {
modifiedFlags |= FLAG_400H;
}
}
// handle VHD devices (recursively unlock the host device)
DWORD devType = *(DWORD*)Device;
if ((devType == 2 || devType == 3) &&
*(DWORD*)((PBYTE)Device + 0x20) == DEVICE_TYPE_6)
{
status = FvebpGetVhdHostDevice(
(PVOID)((PBYTE)Device + 0x38),
&bootDevice
);
if (NT_SUCCESS(status)) {
// recursively unlock the host device
status = FvebpUnlockSinglePartition(
bootDevice,
NULL,
Index,
FLAG_400H,
NULL
);
if (!NT_SUCCESS(status)) goto Cleanup;
}
}
// open the target device
status = BlpDeviceOpen(
Device,
FILE_READ_DATA,
NULL,
&deviceHandle
);
if (!NT_SUCCESS(status)) goto Cleanup;
// get the device identifier
status = BlGetDeviceIdentifier(deviceHandle, &deviceId);
if (!NT_SUCCESS(status)) goto Cleanup;
// get device data
deviceData = FvebpGetDeviceData(deviceId);
if (!deviceData) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
// query the key (perform the unlock)
status = FvebpQueryKey(
deviceData,
Index,
modifiedFlags,
NULL
);
Cleanup:
// set the output parameter
if (OutDeviceData && NT_SUCCESS(status)) {
*OutDeviceData = deviceData;
} else if (deviceData) {
// free the unused device data
BlMmFreeHeap(deviceData);
}
// close the device handle
if (deviceHandle != INVALID_HANDLE_VALUE) {
BlDeviceClose(deviceHandle);
}
// free the boot device object
if (bootDevice) {
BlMmFreeHeap(bootDevice);
}
// update the global counter
if (InterlockedDecrement(&FvebGlobalData.Counter) == 0) {
FvebGlobalData.UnkD8 = 0;
}
return status;
}
Device handling: if the device type is 5, the system default control device is used, and when devices match, the FLAG_200H flag is added. For VHD virtual disks, the host physical device is unlocked first, with the special FLAG_400H flag on the recursive call.
The core decryption function:
FvebpQueryKey(deviceData, Index, modifiedFlags, NULL);
// perform the actual unlock using the combined flags
// the index likely indicates the key-slot position
BmFindAssociatedOsEntry, BmTransferExecution
These two functions only do basic argument conversion; the call flow is shown below:

BmpLaunchBootEntry then calls BmpTransferExecution to check the boot option, and ImgArchEfiStartBootApplication initializes winload.efi’s protected-memory mode. After that, Archpx64TransferTo64BitApplicationAsm runs, completing the preparation to start winload.efi.
After this critical point, all execution flow transfers to winload.efi, which is responsible for loading and initializing the Windows kernel. Before that, execution proceeds in the UEFI environment through boot services, operating under flat memory.
If Secure Boot is disabled, malicious code could make arbitrary modifications to memory at this stage of the boot process, because no PatchGuard exists for kernel-mode modules yet. PatchGuard is only initialized in later boot steps.
Conclusion
If I have time later, I’ll write up an analysis of winload.efi. Comments and corrections are welcome.
Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).
Get in touch