NtSetSystemEnvironmentValueEx
Schreibt oder löscht eine UEFI-Variable, identifiziert durch (Name, Hersteller-GUID) — die kanonische Firmware-Persistenz-Oberfläche.
Prototyp
NTSTATUS NtSetSystemEnvironmentValueEx( PUNICODE_STRING VariableName, LPGUID VendorGuid, PVOID Value, ULONG ValueLength, ULONG Attributes );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| VariableName | PUNICODE_STRING | in | Name der ziel-UEFI-Variable (z. B. L"BootOrder", L"dbx", ein eigener OEM-Schlüssel). |
| VendorGuid | LPGUID | in | Hersteller-Namespace-GUID. EFI_GLOBAL_VARIABLE für Boot-Konfiguration; EFI_IMAGE_SECURITY_DATABASE_GUID für Secure-Boot-Schlüssel-DBs. |
| Value | PVOID | in | Zeiger auf die neuen Wertebytes. NULL mit ValueLength=0 löscht die Variable (sofern die Attribute es erlauben). |
| ValueLength | ULONG | in | Größe des Wertepuffers in Bytes. Null (mit NULL Value) fordert Löschung an. |
| Attributes | ULONG | in | EFI-Variablen-Attribute: NON_VOLATILE, BOOTSERVICE_ACCESS, RUNTIME_ACCESS, AUTHENTICATED_WRITE_ACCESS, TIME_BASED_AUTH, APPEND_WRITE. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x18D | win10-1507 |
| Win10 1607 | 0x196 | win10-1607 |
| Win10 1703 | 0x19C | win10-1703 |
| Win10 1709 | 0x19F | win10-1709 |
| Win10 1803 | 0x1A1 | win10-1803 |
| Win10 1809 | 0x1A2 | win10-1809 |
| Win10 1903 | 0x1A3 | win10-1903 |
| Win10 1909 | 0x1A3 | win10-1909 |
| Win10 2004 | 0x1A9 | win10-2004 |
| Win10 20H2 | 0x1A9 | win10-20h2 |
| Win10 21H1 | 0x1A9 | win10-21h1 |
| Win10 21H2 | 0x1AB | win10-21h2 |
| Win10 22H2 | 0x1AB | win10-22h2 |
| Win11 21H2 | 0x1B4 | win11-21h2 |
| Win11 22H2 | 0x1B8 | win11-22h2 |
| Win11 23H2 | 0x1B8 | win11-23h2 |
| Win11 24H2 | 0x1BB | win11-24h2 |
| Server 2016 | 0x196 | winserver-2016 |
| Server 2019 | 0x1A2 | winserver-2019 |
| Server 2022 | 0x1B1 | winserver-2022 |
| Server 2025 | 0x1BB | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 BB 01 00 00 mov eax, 0x1BB 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
Das hochwertige Gegenstück zu NtQuerySystemEnvironmentValueEx. Dies ist der Kernelpfad, der `bcdedit /set` für viele plattformweite Schalter trägt (Test-Signing, Debug-Modus, Hypervisor-Launch-Typ), Secure-Boot-Policy-Variablen schreibt (PK/KEK/db/dbx — die meisten erfordern authentifizierte Schreibvorgänge, die das OS nicht selbst erzeugen kann), und die Oberfläche ist, zu der ein Angreifer greift, um **Firmware-Persistenz** zu setzen: eine EFI-Variable schreiben, die der OS-Loader oder ein verwundbarer Shim beim Boot liest. Erfordert SeSystemEnvironmentPrivilege. Die Firmware selbst, nicht nur der Kernel, erzwingt Signaturprüfungen für authentifizierte Schreibvorgänge — Modifikationen an PK/KEK/db ohne gültige Auth-Payload werden an der SMM-Grenze abgelehnt.
Häufige Malware-Nutzung
Schwerpunkt des UEFI-Persistenz-Tradecrafts. **LoJax** (Sednit / APT28, 2018) war das wegweisende Beispiel: eine Usermode-Komponente bewaffnete RWEverythings RwDrv, um direkt in den SPI-Flash zu schreiben, aber parallele Implants nutzen NtSetSystemEnvironmentValueEx, um Payload-Pointer oder Hilfszustände in NVRAM zu platzieren, die der Bootkit nach Umlenkung der Boot-Kette liest. **BlackLotus** (2022, CVE-2022-21894 „baton drop") schreibt angreifergewählte MOK/dbx-Einträge und nutzt NVRAM, um die Konfiguration seines Second-Stage-Bootkits abzulegen. **ESPecter**, **CosmicStrand** und **MoonBounce** (APT41) stützen sich alle auf die breitere UEFI-Variablenoberfläche für Konfigurationsspeicherung oder Staging. Defensive Notiz: dbx schreiben — d. h. Loader-Hashes *widerrufen* — wurde sowohl legitim (KB5025885-dbx-Push) als auch offensiv (DoS des eigenen Bootloaders) verwendet.
Erkennungsmöglichkeiten
ETW Microsoft-Windows-Kernel-General emittiert Firmware-Schreibereignisse mit Variablenname, GUID und Ergebnis. Verteidiger sollten drei Dinge verfolgen: (1) welche Prozesse das überhaupt aufrufen, (2) den Privilegien-Aktivierungsübergang bei SeSystemEnvironmentPrivilege und (3) jeden Schreibvorgang auf GUIDs, die EFI_GLOBAL_VARIABLE oder EFI_IMAGE_SECURITY_DATABASE_GUID entsprechen, aus Nicht-TrustedInstaller-/Nicht-OEM-Binaries. Microsoft Defender for Endpoint und CrowdStrike Falcon exponieren UEFI-Variablen-Schreibvorgänge in ihren Device-Control-/Boot-Integrity-Dashboards. Auf Windows 11 24H2 und Server 2025 vergleicht das OS zusätzlich mit dem System-Guard-Secure-Launch-Zustand; Abweichungen erscheinen in Microsoft-Windows-Kernel-Boot.
Direkte Syscall-Beispiele
asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtSetSystemEnvironmentValueEx (SSN 0x1BB, Win11 24H2)
NtSetSystemEnvironmentValueEx PROC
mov r10, rcx
mov eax, 1BBh
syscall
ret
NtSetSystemEnvironmentValueEx ENDPcLoJax-style NVRAM staging skeleton (NOT a working exploit — educational)
// Sketch only: a real bootkit ALSO needs a vulnerable / unsigned bootloader on the
// machine, a SecureBoot-disabled or signature-bypass state, and a SMM-friendly
// firmware. Modern attested boot, Secure Launch and KB5025885-class dbx pushes
// foreclose most of this on patched 2024+ systems.
#include <windows.h>
#define EFI_VARIABLE_NON_VOLATILE 0x00000001
#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002
#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004
static const wchar_t* kVendorGuid =
L"{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}"; // EFI_GLOBAL_VARIABLE
BOOL plant_persistence_marker(const wchar_t* name, const void* blob, DWORD len) {
BOOLEAN old;
// Privilege is the first gate. Without admin + SeSystemEnvironmentPrivilege
// the syscall returns STATUS_PRIVILEGE_NOT_HELD immediately.
if (RtlAdjustPrivilege(SE_SYSTEM_ENVIRONMENT_PRIVILEGE, TRUE, FALSE, &old) < 0)
return FALSE;
return SetFirmwareEnvironmentVariableExW(
name, kVendorGuid, (PVOID)blob, len,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS);
}rustDelete a custom NVRAM marker
// Cargo: windows = { version = "0.59", features = ["Win32_System_Environment"] }
use windows::core::*;
use windows::Win32::System::Environment::*;
fn delete_marker(name: &str, guid: &str) -> windows::core::Result<()> {
let hn = HSTRING::from(name);
let hg = HSTRING::from(guid);
// ValueLength = 0 + Value = NULL + attributes = 0 => delete (per UEFI 2.x spec).
unsafe {
SetFirmwareEnvironmentVariableExW(
PCWSTR(hn.as_ptr()),
PCWSTR(hg.as_ptr()),
None,
0,
0,
)
}?;
Ok(())
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20