Freigeben über


Bewährte Verfahren zur Einschränkung von hochprivilegiertem Verhalten in Kernel-Modus-Treibern

In diesem Thema werden die unsicheren Entwicklungsmuster zusammengefasst, die zu Ausbeutung und Missbrauch Ihres Windows-Kerneltreibercodes führen können. Dieses Thema enthält Entwicklungsempfehlungen und Codebeispiele zum Einschränken des privilegierten Verhaltens. Das Befolgen dieser bewährten Methoden wird die Sicherheit bei der Ausführung privilegierter Vorgänge im Windows-Kernel verbessern.

Übersicht über unsicheres Treiberverhalten

Es wird zwar erwartet, dass Windows-Treiber ein hohes privilegiertes Verhalten im Kernelmodus ausführen, aber es ist inakzeptabel, wenn dabei keine Sicherheitsprüfungen durchgeführt und Einschränkungen für privilegiertes Verhalten nicht hinzugefügt werden. Das Windows Hardware Compatibility Program (WHCP), früher WHQL, erfordert neue Treiberübermittlungen, um diese Anforderung zu erfüllen.

Beispiele für unsicheres und gefährliches Verhalten umfassen, aber nicht beschränkt auf Folgendes:

Möglichkeit, MSRs zu lesen und zu schreiben

Verbessern der Sicherheit beim Lesen von MSRs

In diesem ReadMsr-Beispiel lässt der Treiber unsicheres Verhalten zu, indem er die Möglichkeit bietet, beliebige Register mit Hilfe der __readmsr modellspezifischen Registerintrinsik zu lesen. Dies kann zu Missbrauch durch böswillige Prozesse im Benutzermodus führen.

Func ReadMsr(int dwMsrIdx) 
{
	int value = __readmsr(dwMsrIdx); // Unsafe, can read from any MSR
	return value;
}

Wenn Ihr Szenario das Lesen von MSRs erfordert, muss der Treiber immer prüfen, ob das Register, aus dem gelesen werden soll, auf den erwarteten Index oder Bereich beschränkt ist. Es folgen zwei Beispiele für die Implementierung des sicheren Lesevorgangs.

Func ConstrainedReadMsr(int dwMsrIdx) 
{
    int value = 0;
    if (dwMsrIdx == expected_index) // Blocks from reading anything
    {
        value = __readmsr(dwMsrIdx); // Can only read the expected MSR
    }
    else
    {
        return error;
    }
    return value;
}

// OR

Func ConstrainedReadMsr(int dwMsrIdx) 
{
    int value = 0;
    if (min_range <= dwMsrIdx <= max_range) // Blocks from reading anything
    {
        value = __readmsr(dwMsrIdx); // Can only from the expected range of MSRs
    }
    else
    {
        return error;
    }
    return value;
}

Verbessern der Sicherheit beim Schreiben in MSRs

Im ersten WriteMsr-Beispiel ermöglicht der Treiber unsicheres Verhalten, indem alle Register beliebig geschrieben werden können. Dies kann dazu führen, dass schädliche Prozesse ihre Privilegien im Benutzermodus erhöhen und in alle MSRs schreiben.

Func WriteMsr(int dwMsrIdx) 
{
	int value = __writemsr(dwMsrIdx); // Unsafe, can write to any MSR
	return value;
}

Wenn Ihr Szenario das Schreiben in MSRs erfordert, muss der Treiber immer prüfen, ob das zu schreibende Register auf den erwarteten Index oder Bereich beschränkt ist. Zwei Beispiele für die Implementierung des sicheren Schreibvorgangs folgen.

Func ConstrainedWriteMsr(int dwMsrIdx) 
{
    int value = 0;
    if (dwMsrIdx == expected_index) // Blocks from reading anything
    {
        value = __writemsr(dwMsrIdx); // Can only write to the expected constrained MSR
    }
    else
    {
        return error;
    }
    return value;
}

// OR

Func ConstrainedWriteMSR(int dwMsrIdx) 
{
    int value = 0;
    if (min_range <= dwMsrIdx <= max_range) // Blocks from reading anything
    {
        value = __writemsr(dwMsrIdx); // Can only write to the expected constrained MSR
    }
    else
    {
        return error;
    }
    return value;
}

Bereitstellen der Möglichkeit zum Beenden von Prozessen

Extreme Vorsicht muss bei der Implementierung von Funktionen in Ihrem Treiber verwendet werden, wodurch Prozesse beendet werden können. Geschützte Prozesse und PPL-Prozesse (Protected Process Light), wie sie von Anti-Malware- und Anti-Virus-Lösungen verwendet werden, dürfen nicht beendet werden. Durch die Freigabe dieser Funktionalität können Angreifer die Sicherheitsvorkehrungen im System deaktivieren.

Wenn Ihr Szenario die Beendigung eines Prozesses erfordert, müssen die folgenden Prüfungen implementiert werden, um vor willkürlicher Beendigung von Prozessen zu schützen, dabei werden PsLookupProcessByProcessId und PsIsProtectedProcessverwendet:

Func ConstrainedProcessTermination(DWORD dwProcessId)
{
	// Function to check if a process is a Protected Process Light (PPL)
    NTSTATUS status;
    BOOLEAN isPPL = FALSE;
    PEPROCESS process;
    HANDLE hProcess;

    // Open the process
    status = PsLookupProcessByProcessId(processId, &process);
    if (!NT_SUCCESS(status)) {
        return FALSE;
    }

    // Check if the process is a PPL
    if (PsIsProtectedProcess(process)) {
        isPPL = TRUE;
    }

    // Dereference the process
    ObDereferenceObject(process);
    return isPPL;
}

Lesen und Schreiben auf die Port-Eingabe und -Ausgabe ermöglichen

Verbesserung der Sicherheit beim Lesen von Port-Ein-/Ausgaben

Vorsicht ist geboten, wenn Sie die Fähigkeit zum Lesen von Port-Ein-/Ausgaben (E/A) bereitstellen. Dieses Codebeispiel, das __indword verwendet, ist unsicher.

Func ArbitraryInputPort(int inPort) 
{
	dwResult = __indword(inPort); // Unsafe, allows for arbitrary reading from Input Port
	return dwResult; 
}

Um den Missbrauch und die Ausnutzung des Treibers zu verhindern, muss der erwartete Eingabe-Port auf die erforderliche Nutzungsgrenze beschränkt werden.

Func ConstrainedInputPort(int inPort) 
{
	// The expected input port must be constrained to the required usage boundary to prevent abuse
	if(inPort == expected_InPort)
	{
		dwResult = __indword(inPort);
	}
	else
	{
		return error; 
	}
	return dwResult; 
}

Erhöhung der Sicherheit beim Schreiben auf Port IO

Vorsicht ist geboten, wenn Sie die Fähigkeit zum Schreiben aufPort-Ein-/Ausgaben (E/A) bereitstellen. Dieses Codebeispiel, das __outword verwendet, ist unsicher.

Func ArbitraryOutputPort(int outPort, DWORD dwValue) 
{
	__outdword(OutPort, dwValue); // Unsafe, allows for arbitrary writing to Output Port
}

Um den Missbrauch und die Ausnutzung des Treibers zu verhindern, muss der erwartete Eingabe-Port auf die erforderliche Nutzungsgrenze beschränkt werden.

Func ConstrainedOutputPort(int outPort, DWORD dwValue) 
{
	// The expected output port must be constrained to the required usage boundary to prevent abuse
	if(outPort == expected_OutputPort)
	{
		__outdword(OutPort, dwValue); // checks on InputPort
	}
	else
	{
		return error; 
	}
}

Lesen und Schreiben von Kernel-, physischem oder Geräte-Speicher

Verbesserung der Sicherheit von Memcpy

Dieser Beispielcode zeigt die uneingeschränkte und unsichere Nutzung der sicheren Nutzung des physischen Speichers.

Func ArbitraryMemoryCopy(src, dst, length) 
{
	memcpy(dst, src, length); // Unsafe, can read and write anything from physical memory
}

Wenn Ihr Szenario das Lesen und Schreiben von Kernel-, physischem oder Geräte-Speicher erfordert, muss der Treiber immer überprüfen, ob die Quelle und die Ziele auf die erwarteten Indizes oder Bereiche beschränkt sind.

Func ConstrainedMemoryCopy(src, dst, length) 
{
	// valid_src and valid_dst must be constrained to required usage boundary to prevent abuse
	if(src == valid_Src && dst == valid_Dst)
	{
		memcpy(dst, src, length); 
	}
	else
	{
		return error;
	}
}

Verbesserung der Sicherheit von ZwMapViewOfSection

Das folgende Beispiel veranschaulicht die unsichere und unsachgemäße Methode zum Lesen und Schreiben des physischen Speichers aus dem Benutzermodus mithilfe der ZwOpenSection- und ZwMapViewOfSection-APIs.

Func ArbitraryMap(PHYSICAL_ADDRESS Address)
{
	ZwOpenSection(&hSection, ... ,"\Device\PhysicalMemory");
	ZwMapViewOfSection(hSection, -1, 0, 0, 0, Address, ...);
}

Um den Missbrauch und die Ausnutzung des Lese- und Schreibverhaltens des Treibers durch böswillige Benutzermodus-Prozesse zu verhindern, muss der Treiber die Eingabeadresse überprüfen und die Speicherzuordnung nur bis zur erforderlichen Nutzungsgrenze für das Szenario beschränken.

Func ConstrainedMap(PHYSICAL_ADDRESS paAddress)
{
	// expected_Address must be constrained to required usage boundary to prevent abuse
	if(paAddress == expected_Address)
	{
		ZwOpenSection(&hSection, ... ,"\Device\PhysicalMemory");
		ZwMapViewOfSection(hSection, -1, 0, 0, 0, paAddress, ...);
	}
	else
	{
		return error;
	}
}

Verbessern der Sicherheit von MmMapLockedPagesSpecifyCache

Das folgende Beispiel veranschaulicht die unsichere und unsachgemäße Methode zum Lesen und Schreiben von physischem Speicher aus dem Benutzermodus unter Verwendung der MmMapIoSpace, IoAllocateMdl und MmMapLockedPagesSpecifyCache APIs.

Func ArbitraryMap(PHYSICAL_ADDRESS paAddress)
{
	lpAddress = MmMapIoSpace(paAddress, qwSize, ...);
	pMdl = IoAllocateMdl( lpAddress, ...);
	MmMapLockedPagesSpecifyCache(pMdl, UserMode, ... );
}

Um den Missbrauch und die Ausnutzung des Lese-/Schreibverhaltens des Treibers durch böswillige Benutzermodusprozesse zu verhindern, muss der Treiber die Eingabeadresse überprüfen und die Speicherzuordnung nur auf die für das Szenario erforderliche Nutzungsbegrenzung beschränken.

Func ConstrainedMap(PHYSICAL_ADDRESS paAddress)
{
	// expected_Address must be constrained to required usage boundary to prevent abuse
	if(paAddress == expected_Address && qwSize == valid_Size) 
	{
		lpAddress = MmMapIoSpace(paAddress, qwSize, ...);
		pMdl = IoAllocateMdl( lpAddress, ...);
		MmMapLockedPagesSpecifyCache(pMdl, UserMode, ... );
	}
	else
	{
		return error;
	}
}

Weitere Informationen

Sicherheitscheckliste für Fahrer