Unsachgemäße Adressvalidierung in IOCTL mit METHOD_NEITHER I/O Control Code

Beschreibung

Unsachgemäße Adressvalidierung in IOCTL mit METHOD_NEITHER I/O Control Code ist eine Kernel-Modus-Schwachstelle spezifisch für Windows-Treiber, bei der ein IOCTL-Handler mit METHOD_NEITHER vom Benutzer bereitgestellte Speicheradressen nicht ordnungsgemäß validiert. Wenn METHOD_NEITHER in einem IOCTL-Control-Code angegeben ist, übergibt der I/O-Manager rohe User-Mode-Pointer direkt an den Treiber ohne jegliches Puffern oder Validierung. Der Treiber wird vollständig dafür verantwortlich, dass die Adressen gültige User-Mode-Adressen sind, bevor er darauf zugreift. Bei fehlender Validierung können Angreifer beliebige Speicheradressen angeben und möglicherweise Kernel-Speicher lesen oder schreiben.

Risiko

Diese Schwachstelle ermöglicht schwerwiegende Sicherheitsangriffe. Wenn ein Treiber beliebige Adressen ohne Validierung akzeptiert, können Angreifer sensiblen Kernel-Speicher lesen (einschließlich Anmeldedaten, Verschlüsselungsschlüssel oder Kernel-Datenstrukturen) oder in beliebige Kernel-Speicherorte schreiben. Das Schreiben in Kernel-Speicher kann vollständige Systemkompromittierung durch Code-Ausführung auf höchster Privilegstufe (SYSTEM/Kernel) ermöglichen. Selbst ohne Code-Ausführung können Angreifer das System zum Absturz bringen (BSOD), indem sie den Treiber veranlassen, auf ungültige Adressen zuzugreifen.

Lösung

Validieren Sie User-Space-Adressen immer bei Verwendung von METHOD_NEITHER. Verwenden Sie ProbeForRead() vor dem Lesen aus User-Puffern und ProbeForWrite() vor dem Schreiben in User-Puffer. Diese Routinen verifizieren, dass Adressen im gültigen User-Mode-Adressraum liegen. Umschließen Sie alle User-Puffer-Zugriffe mit try-except-Blöcken, um Zugriffsverletzungen elegant zu behandeln. Bevorzugen Sie METHOD_BUFFERED, METHOD_IN_DIRECT oder METHOD_OUT_DIRECT anstelle von METHOD_NEITHER wenn möglich, da diese Übertragungsmethoden den I/O-Manager automatisch Validierung durchführen lassen.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Speicher lesen - Angreifer können beliebigen Kernel-Speicher lesen einschließlich sensibler Daten und Anmeldedaten.
IntegritätBereich: Integrität

Speicher ändern - Angreifer können in beliebigen Speicher schreiben und Kernel-Datenstrukturen korrumpieren.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Absturz - Ungültiger Speicherzugriff verursacht Systemabsturz (BSOD).
ZugriffskontrolleBereich: Zugriffskontrolle

Privilegien erlangen - Beliebiges Kernel-Schreiben ermöglicht Code-Ausführung auf SYSTEM-Ebene.

Beispielcode

Verwundbarer Code

// Verwundbar: Keine Adressvalidierung mit METHOD_NEITHER
#include <ntddk.h>

#define IOCTL_VULNERABLE_READ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, \
    METHOD_NEITHER, FILE_ANY_ACCESS)

NTSTATUS VulnerableDeviceControl(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp)
{
    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    NTSTATUS status = STATUS_SUCCESS;

    if (IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_VULNERABLE_READ)
    {
        // Mit METHOD_NEITHER sind dies rohe User-Pointer
        PVOID userInputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
        PVOID userOutputBuffer = Irp->UserBuffer;

        // Verwundbar: Keine Validierung von User-Adressen
        // Angreifer könnte Kernel-Adresse angeben!
        ULONG dataToRead = *(PULONG)userInputBuffer;  // Beliebiges Lesen!

        // Verwundbar: Schreiben an angreifergesteuerte Adresse
        *(PULONG)userOutputBuffer = SensitiveKernelData;  // Beliebiges Schreiben!
    }

    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return status;
}

Gefixter Code

// Gefixt: Ordnungsgemäße Adressvalidierung mit METHOD_NEITHER
#include <ntddk.h>

#define IOCTL_FIXED_READ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, \
    METHOD_NEITHER, FILE_ANY_ACCESS)

NTSTATUS FixedDeviceControl(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp)
{
    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    NTSTATUS status = STATUS_SUCCESS;
    ULONG dataToRead = 0;

    if (IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_FIXED_READ)
    {
        PVOID userInputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
        PVOID userOutputBuffer = Irp->UserBuffer;
        ULONG inputLen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
        ULONG outputLen = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;

        // Gefixt: Puffergrößen validieren
        if (inputLen < sizeof(ULONG) || outputLen < sizeof(ULONG)) {
            status = STATUS_BUFFER_TOO_SMALL;
            goto Complete;
        }

        __try {
            // Gefixt: Prüfen dass Adressen im User-Space liegen
            ProbeForRead(userInputBuffer, sizeof(ULONG), sizeof(ULONG));
            ProbeForWrite(userOutputBuffer, sizeof(ULONG), sizeof(ULONG));

            // Gefixt: Zugriff innerhalb try-Block falls Seite ausgelagert
            dataToRead = *(PULONG)userInputBuffer;

            // Anfrage verarbeiten
            ULONG result = ProcessRequest(dataToRead);

            // Gefixt: Ergebnis innerhalb try-Block schreiben
            *(PULONG)userOutputBuffer = result;

            Irp->IoStatus.Information = sizeof(ULONG);
        }
        __except (EXCEPTION_EXECUTE_HANDLER) {
            // Gefixt: Zugriffsverletzung elegant behandeln
            status = GetExceptionCode();
            DbgPrint("Exception beim User-Puffer-Zugriff: 0x%X\n", status);
        }
    }

Complete:
    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return status;
}
// Gefixt: METHOD_BUFFERED stattdessen verwenden (bevorzugt)
#include <ntddk.h>

// METHOD_BUFFERED - I/O Manager behandelt Validierung
#define IOCTL_SAFE_BUFFERED CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, \
    METHOD_BUFFERED, FILE_ANY_ACCESS)

NTSTATUS SafeBufferedIoctl(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp)
{
    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    NTSTATUS status = STATUS_SUCCESS;

    if (IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_SAFE_BUFFERED)
    {
        // Mit METHOD_BUFFERED alloziert I/O Manager Kernel-Puffer
        // und kopiert Daten - keine User-Adressvalidierung nötig
        PVOID systemBuffer = Irp->AssociatedIrp.SystemBuffer;
        ULONG inputLen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
        ULONG outputLen = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;

        if (inputLen < sizeof(ULONG)) {
            status = STATUS_BUFFER_TOO_SMALL;
            goto Complete;
        }

        // Sicher: systemBuffer ist Kernel-Speicher, bereits validiert
        ULONG inputData = *(PULONG)systemBuffer;

        ULONG result = ProcessRequest(inputData);

        if (outputLen >= sizeof(ULONG)) {
            *(PULONG)systemBuffer = result;
            Irp->IoStatus.Information = sizeof(ULONG);
        }
    }

Complete:
    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return status;
}

CVE-Beispiele

  • CVE-2006-2373: Windows File-Sharing-Protokoll-Treiber erlaubte beliebige Code-Ausführung über METHOD_NEITHER IOCTL.
  • CVE-2009-0686: Antivirus-Produkt verwundbar für Privilegieneskalation durch unsachgemäße Adressvalidierung.
  • CVE-2008-5724: Personal Firewall erlaubte SYSTEM-Privilegieneskalation über unsachgemäße IOCTL-Behandlung.

Referenzen

  1. MITRE Corporation. "CWE-781: Improper Address Validation in IOCTL with METHOD_NEITHER I/O Control Code." https://cwe.mitre.org/data/definitions/781.html
  2. Microsoft. "Buffer Descriptions for I/O Control Codes." Windows Driver Documentation.
  3. Microsoft. "Using Neither Buffered Nor Direct I/O." Windows Driver Documentation.