NtEnumerateBootEntries
Liefert ein gepacktes Array von BOOT_ENTRY-Strukturen, das jede registrierte Firmware-Bootoption beschreibt.
Prototyp
NTSTATUS NtEnumerateBootEntries( PVOID Buffer, PULONG BufferLength );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| Buffer | PVOID | out | Vom Aufrufer allokierter Puffer, der eine gepackte Folge von BOOT_ENTRY_LIST-Datensätzen empfängt (jeweils mit NextEntryOffset und eingebettetem BOOT_ENTRY). Darf NULL sein, um die benötigte Größe abzufragen. |
| BufferLength | PULONG | in/out | Eingabe: Größe von Buffer in Byte. Ausgabe: geschriebene Byte oder benötigte Größe, wenn STATUS_BUFFER_TOO_SMALL zurückgegeben wird. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0xCF | win10-1507 |
| Win10 1607 | 0xD2 | win10-1607 |
| Win10 1703 | 0xD5 | win10-1703 |
| Win10 1709 | 0xD6 | win10-1709 |
| Win10 1803 | 0xD7 | win10-1803 |
| Win10 1809 | 0xD8 | win10-1809 |
| Win10 1903 | 0xD9 | win10-1903 |
| Win10 1909 | 0xD9 | win10-1909 |
| Win10 2004 | 0xDE | win10-2004 |
| Win10 20H2 | 0xDE | win10-20h2 |
| Win10 21H1 | 0xDE | win10-21h1 |
| Win10 21H2 | 0xDF | win10-21h2 |
| Win10 22H2 | 0xDF | win10-22h2 |
| Win11 21H2 | 0xE4 | win11-21h2 |
| Win11 22H2 | 0xE5 | win11-22h2 |
| Win11 23H2 | 0xE5 | win11-23h2 |
| Win11 24H2 | 0xE7 | win11-24h2 |
| Server 2016 | 0xD2 | winserver-2016 |
| Server 2019 | 0xD8 | winserver-2019 |
| Server 2022 | 0xE3 | winserver-2022 |
| Server 2025 | 0xE7 | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 E7 00 00 00 mov eax, 0xE7 F6 04 25 08 03 FE 7F 01 test byte ptr [0x7FFE0308], 1 75 03 jne short +3 0F 05 syscall C3 ret CD 2E int 2Eh C3 ret
Undokumentierte Hinweise
Klassisches Zwei-Aufruf-Schema: zuerst mit `Buffer = NULL` aufrufen, um die nötige Größe zu ermitteln, allokieren und erneut aufrufen. Die Ausgabe ist eine verkettete Liste — `BOOT_ENTRY_LIST.NextEntryOffset` ist jeweils ein Byte-Delta vom Anfang des aktuellen Datensatzes zum nächsten (Null beendet). Der Kernel läuft firmwareseitig: NVRAM-`BootOrder` + `Boot####`-Variablen auf UEFI, BCD-Hive-Enumeration auf Legacy-BIOS. SeSystemEnvironmentPrivilege ist sogar zum *Lesen* erforderlich.
Häufige Malware-Nutzung
Die Enumeration ist der Reconnaissance-Schritt jeder BCD-Persistenz-Killchain: Malware braucht die existierende `{bootmgr}`-ID, bevor sie NtModifyBootEntry aufrufen kann, und sie braucht die aktuelle Reihenfolge vor NtSetBootEntryOrder. Das UEFI-Modul von TrickBoot beginnt bekanntermaßen mit einer Inventur-Phase, die das Äquivalent von `BcdEnumerateObjects` + `NtEnumerateBootEntries` aufruft, um die Firmware zu fingerprinten (Hersteller, verwundbare bootmgr-Versionen, Vorhandensein von Recovery-Einträgen) und an C2 zu melden, bevor entschieden wird, ob die Bootkit-Stufe ausgeliefert wird.
Erkennungsmöglichkeiten
Reine Lese-Enumeration ist für sich genommen selten interessant — legitime Tools (`bcdedit`, Windows Update, BitLocker-Setup) rufen sie routinemäßig auf. Verdacht entsteht durch den **Kontext**: ein Nicht-Installer-/Nicht-System-Kontext-Prozess, der ein Handle mit SeSystemEnvironmentPrivilege anfordert *und* sofort BCD-Einträge enumeriert, ist anomal. Erkennung: Audit-Event 4673 nach SeSystemEnvironmentPrivilege-Nutzung abfragen und mit dem Image-Pfad des Prozesses korrelieren. EDRs, die NtSetInformationToken-/NtAdjustPrivilegesToken-Übergänge zu SE_PRIVILEGE_ENABLED für SeSystemEnvironment telemetrieren, fangen den Privilegerwerb ab, der jedem Schreibaufruf vorausgeht.
Direkte Syscall-Beispiele
asmx64 direct stub (Win11 24H2, SSN 0xE7)
NtEnumerateBootEntries PROC
mov r10, rcx ; PVOID Buffer
mov eax, 0E7h ; Win11 24H2
syscall
ret
NtEnumerateBootEntries ENDPcTwo-call size query + walk
// Enumerate every firmware boot entry, print FriendlyName.
extern NTSTATUS NTAPI NtEnumerateBootEntries(PVOID, PULONG);
void dump_entries(void) {
ULONG size = 0;
NtEnumerateBootEntries(NULL, &size); // STATUS_BUFFER_TOO_SMALL
if (size == 0) return;
PBYTE buf = (PBYTE)HeapAlloc(GetProcessHeap(), 0, size);
if (NT_SUCCESS(NtEnumerateBootEntries(buf, &size))) {
PBYTE p = buf;
while (p < buf + size) {
PBOOT_ENTRY_LIST list = (PBOOT_ENTRY_LIST)p;
PBOOT_ENTRY be = &list->BootEntry;
wprintf(L"id=%08X name=%s\n", be->Id,
(LPCWSTR)((PBYTE)be + be->FriendlyNameOffset));
if (!list->NextEntryOffset) break;
p += list->NextEntryOffset;
}
}
HeapFree(GetProcessHeap(), 0, buf);
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20