Compartilhar via


Práticas recomendadas para restringir o comportamento com privilégios elevados em drivers de modo kernel

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

Visão geral do comportamento inseguro do motorista

Embora seja esperado que os drivers do Windows executem um comportamento privilegiado alto no modo kernel, não executar verificações de segurança e adicionar restrições ao comportamento privilegiado é inaceitável. O WHCP (Programa de Compatibilidade de Hardware do Windows), anteriormente WHQL, requer novos envios de driver para atender a esse requisito.

Exemplos de comportamentos não seguros e perigosos incluem, mas não se limitam a:

Fornecer a capacidade de ler e gravar MSRs

Aprimorando a segurança da leitura de MSRs

Neste exemplo do ReadMsr, o driver permite um comportamento não seguro, permitindo que todos os registros sejam lidos arbitrariamente usando o registro específico do modelo __readmsr intrínseco. 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 seu cenário exigir a leitura de MSRs, o driver deverá sempre verificar se o registro a ser lido está restrito ao índice ou intervalo esperado. Dois exemplos de como implementar a operação de leitura segura seguem.

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

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

No primeiro exemplo do WriteMsr, o driver permite um comportamento não seguro, permitindo que todos os registros sejam gravados arbitrariamente. Isso pode resultar em abuso por parte de 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 seu cenário exigir a gravação em MSRs, o driver deverá sempre verificar se o registro a ser gravado está restrito ao índice ou intervalo esperado. Dois exemplos de como implementar a operação de gravação segura seguem.

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

Fornecendo a capacidade de encerrar processos

É necessário ter extrema cautela ao implementar a funcionalidade em seu driver, o que permite que os processos sejam encerrados. Processos protegidos e processos de PPL (Processo Leve Protegido), 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 cenário exigir o encerramento do processo, as seguintes verificações deverão ser implementadas para proteger contra o encerramento arbitrário do processo, usando PsLookupProcessByProcessId e 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;
}

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

Aumentar a segurança da leitura da porta E/S

É necessário ter cuidado ao permitir a capacidade de leitura da entrada/saída de porta (E/S). Este exemplo de código que usa __indword 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; 
}

Aumentar a segurança da gravação na porta E/S

Deve-se ter cuidado ao fornecer a capacidade de gravar na porta de entrada/saída (E/S). Este exemplo de código que usa __outword 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; 
	}
}

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

Aprimorando a segurança do Memcpy

Esse 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 leitura e gravação de kernel, memória física ou de 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 inadequado para ler e gravar memória física no 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 mal-intencionados do modo de usuário, o driver deve validar o endereço de entrada e restringir o mapeamento de memória apenas para o 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 não seguro e inadequado para ler e gravar memória física do modo de usuário utilizando as APIsMmMapIoSpace, IoAllocateMdl e MmMapLockedPagesSpecifyCache APIs.

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 mal-intencionados do modo de usuário, o driver deve validar o endereço de entrada e restringir o mapeamento de memória apenas para o 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;
	}
}

Consulte Também

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