Compartilhar via


Práticas recomendadas de segurança de drivers do Windows para desenvolvedores de driver

Este tópico resume os padrões de desenvolvimento não seguros que podem levar à exploração e ao abuso do código do driver do Windows. Este tópico fornece recomendações de desenvolvimento e exemplos de código. Seguir essas práticas recomendadas ajudará a melhorar a segurança da execução de comportamento privilegiado no kernel do Windows.

Visão geral do comportamento do motorista inseguro

Embora se espere que os drivers do Windows executem um comportamento com altos privilégios no modo kernel, não executar verificações de segurança e adicionar restrições ao comportamento privilegiado é inaceitável com segurança. O Programa de Compatibilidade de Hardware do Windows (WHCP), anteriormente WHQL, exige que novos envios de driver cumpram esse requisito.

Exemplos de comportamento inseguro e perigoso incluem, mas não se limitam ao seguinte:

Fornecendo a capacidade de ler e gravar MSRs

Aumentando a segurança da leitura de MSRs

No primeiro exemplo de ReadMsr, o driver permite um comportamento inseguro, permitindo que todo e qualquer registro seja lido arbitrariamente. Isso pode resultar em abuso por processos mal-intencionados no modo de usuário.

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

Se o cenário exigir a leitura de MSRs, o driver sempre deverá verificar se o registro para leitura está restrito ao índice ou intervalo esperado. Seguem dois exemplos de como implementar a operação de leitura segura.

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

Aprimorando a segurança da gravação em MSRs

No primeiro exemplo de WriteMsr, o driver permite um comportamento inseguro, permitindo que todo e qualquer registro seja gravado arbitrariamente. Isso pode resultar em abuso por processos mal-intencionados para elevar o privilégio no modo de usuário e gravar em todos os MSRs.

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

Se o cenário exigir a gravação em MSRs, o driver sempre deverá verificar se o registro no qual gravar está restrito ao índice ou intervalo esperado. Seguem dois exemplos de como implementar a operação de gravação segura.

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

Fornecer a capacidade de encerrar processos

Deve-se ter muito cuidado ao implementar a funcionalidade em seu driver que permite que os processos sejam encerrados. Processos protegidos e processos PPL (processos leves protegidos), como aqueles usados por soluções antimalware e antivírus, não devem ser encerrados. Expor essa funcionalidade permite que os invasores encerrem as proteções de segurança no sistema.

Se o seu cenário exigir o encerramento do processo, as seguintes verificações deverão ser implementadas para proteger contra o encerramento arbitrário do processo:

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

Fornecendo a capacidade de ler e gravar na entrada e saída da porta

Aprimorando a segurança da leitura da porta IO

Deve-se ter cuidado ao fornecer a capacidade de ler a entrada/saída (E/S) da porta. Este exemplo de código não é seguro.

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

Para evitar o abuso e a exploração do driver, a porta de entrada esperada deve ser restrita ao limite de uso necessário.

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

Aprimorando a segurança da gravação na porta IO

Deve-se ter cuidado ao fornecer a capacidade de gravar na entrada/saída (E/S) da porta. Este exemplo de código não é seguro.

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

Para evitar o abuso e a exploração do driver, a porta de entrada esperada deve ser restrita ao limite de uso necessário.

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

Fornecer a capacidade de ler e gravar memória do kernel, física ou do dispositivo

Aumentando a segurança do Memcpy

Este código de exemplo mostra o uso irrestrito e inseguro do uso seguro da memória física.

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

Se o cenário exigir a leitura e a gravação de memória do kernel, física ou do dispositivo, o driver sempre deverá verificar se a origem e os destinos estão restritos aos índices ou intervalos esperados.

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

Aprimorando a segurança de ZwMapViewOfSection

O exemplo a seguir ilustra o método inseguro e impróprio para ler e gravar memória física do modo de usuário utilizando as APIs ZwOpenSection e ZwMapViewOfSection .

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

Para evitar o abuso e a exploração do comportamento de leitura/gravação do driver por processos de modo de usuário mal-intencionados, o driver deve validar o endereço de entrada e restringir o mapeamento de memória somente ao limite de uso necessário para o cenário.

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

Aprimorando a segurança de MmMapLockedPagesSpecifyCache

O exemplo a seguir ilustra o método inseguro e impróprio para ler e gravar memória física do modo de usuário utilizando as APIs MmMapIoSpace, IoAllocateMdl e MmMapLockedPagesSpecifyCache.

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

Para evitar o abuso e a exploração do comportamento de leitura/gravação do driver por processos de modo de usuário mal-intencionados, o driver deve validar o endereço de entrada e restringir o mapeamento de memória somente ao limite de uso necessário para o cenário.

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

Confira também

Lista de verificação de segurança do driver