Partager via


Meilleures pratiques pour limiter le comportement à privilèges élevés dans les pilotes en mode noyau

Cette rubrique récapitule les modèles de développement non sécurisés qui peuvent entraîner l’exploitation et l’abus de votre code de pilote de noyau Windows. Cette rubrique fournit des recommandations de développement et des exemples de code pour aider à limiter le comportement privilégié. Le suivi de ces bonnes pratiques permet d’améliorer la sécurité de l’exécution de comportements privilégiés dans le noyau Windows.

Vue d’ensemble du comportement du pilote non sécurisé

Bien qu’il soit prévu que les pilotes Windows effectuent un comportement à privilèges élevés en mode noyau, il est inacceptable de ne pas effectuer de vérifications de sécurité et d’ajouter des contraintes sur le comportement privilégié. Le Programme de compatibilité matérielle Windows (WHCP), anciennement WHQL, exige que de nouvelles soumissions de pilotes soient conformes à cette exigence.

Les exemples de comportements non sécurisés et dangereux incluent, mais pas limités, les éléments suivants :

Fournir la possibilité de lire et d’écrire des MSR

Amélioration de la sécurité de la lecture à partir de MSR

Dans cet exemple ReadMsr, le pilote permet un comportement non sécurisé en autorisant la lecture arbitraire de tous les registres à l’aide de l’intrinsèque du registre spécifique au modèle __readmsr. Cela peut entraîner des abus par des processus malveillants en mode utilisateur.

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

Si votre scénario nécessite la lecture à partir de MSR, le pilote doit toujours vérifier que le registre à lire est limité à l’index ou à la plage attendu. Deux exemples d’implémentation de l’opération de lecture sécurisée suivent.

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;
}

Amélioration de la sécurité de l'écriture sur un MSR

Dans le premier exemple WriteMsr, le pilote permet un comportement non sécurisé en autorisant l’écriture arbitraire dans n’importe quel registre. Cela peut entraîner des abus par des processus malveillants afin d'accroître les privilèges en mode utilisateur et d'écrire sur tous les MSRs.

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

Si votre scénario nécessite d'écrire dans des registres MSR, le pilote doit toujours vérifier que le registre à écrire est limité à l'index ou à la plage attendus. Deux exemples d’implémentation de l’opération d’écriture sécurisée suivent.

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;
}

Fournir la possibilité d’arrêter les processus

La prudence extrême doit être utilisée lors de l’implémentation de fonctionnalités dans votre pilote, ce qui permet de mettre fin aux processus. Les processus protégés et les processus PPL (Protected Process Light), comme ceux utilisés par les solutions anti-programmes malveillants et antivirus, ne doivent pas être arrêtés. L’exposition de cette fonctionnalité permet aux attaquants de mettre fin aux protections de sécurité sur le système.

Si votre scénario nécessite un arrêt de processus, les vérifications suivantes doivent être implémentées pour vous protéger contre l’arrêt arbitraire du processus, à l’aide de PsLookupProcessByProcessId et de PsIsProtectedProcess:

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;
}

Fournir la possibilité de lire et d'écrire sur les ports d'entrée et de sortie

Renforcer la sécurité de la lecture depuis Port E/S

Il convient de faire preuve de prudence lorsqu’on donne la capacité de lire dans Port E/S. Cet exemple de code qui utilise __indword est dangereux.

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

Pour éviter l’abus et l’exploitation du pilote, le port d’entrée attendu doit être limité à la limite d’utilisation requise.

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; 
}

Renforcer la sécurité de l’écriture dans Port E/S

Il faut faire preuve de précaution lorsqu'on permet l’écriture sur l’entrée/sortie du port (E/S). Cet exemple de code qui utilise __outword est dangereux.

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

Pour éviter l’abus et l’exploitation du pilote, le port d’entrée attendu doit être limité à la limite d’utilisation requise.

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; 
	}
}

Possibilité de lire et d’écrire du noyau, de la mémoire physique ou de l’appareil

Amélioration de la sécurité de Memcpy

Cet exemple de code montre une utilisation non contrainte et non sécurisée de l’utilisation sécurisée de la mémoire physique.

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

Si votre scénario nécessite la lecture et l’écriture du noyau, de la mémoire physique ou de l’appareil, le pilote doit toujours vérifier que la source et les destinations sont limitées aux index ou plages attendus.

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;
	}
}

Amélioration de la sécurité de ZwMapViewOfSection

L’exemple suivant illustre la méthode non sécurisée et incorrecte pour lire et écrire la mémoire physique à partir du mode utilisateur à l’aide des API ZwOpenSection et ZwMapViewOfSection.

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

Pour éviter l’abus et l’exploitation du comportement de lecture/écriture du pilote par des processus malveillants en mode utilisateur, le pilote doit valider l’adresse d’entrée et limiter le mappage de mémoire uniquement à la limite d’utilisation requise pour le scénario.

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;
	}
}

Amélioration de la sécurité de MmMapLockedPagesSpecifyCache

L'exemple suivant illustre la méthode non sécurisée et incorrecte pour lire et écrire la mémoire physique en mode utilisateur en utilisant les API MmMapIoSpace, IoAllocateMdl et MmMapLockedPagesSpecifyCache.

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

Pour éviter l’abus et l’exploitation du comportement de lecture/écriture du pilote par des processus malveillants en mode utilisateur, le pilote doit valider l’adresse d’entrée et limiter le mappage de mémoire uniquement à la limite d’utilisation requise pour le scénario.

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;
	}
}

Voir aussi

liste de contrôle de sécurité du pilote