Поделиться через


Рекомендации по обеспечению безопасности драйверов Windows для разработчиков драйверов

В этом разделе приведены небезопасные шаблоны разработки, которые могут привести к эксплуатации и злоупотреблению кодом драйвера Windows. В этом разделе приведены рекомендации по разработке и примеры кода. Следуя этим рекомендациям, вы сможете повысить безопасность привилегированного поведения в ядре Windows.

Общие сведения о небезопасном поведении драйвера

Хотя ожидается, что драйверы Windows выполняют высокий уровень привилегий в режиме ядра, не выполняя проверки безопасности и добавляя ограничения на привилегированное поведение, является неприемлемым. Программа совместимости оборудования Windows (WHCP), ранее WHQL, требует отправки новых драйверов для соблюдения этого требования.

Примеры небезопасного и опасного поведения включают в себя, но не ограничиваются следующими:

Предоставление возможности чтения и записи MSR

Повышение безопасности чтения из MSR

В первом примере ReadMsr драйвер позволяет небезопасному поведению, позволяя любому и всем регистрам быть произвольным считываемым. Это может привести к злоупотреблению вредоносными процессами в пользовательском режиме.

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

Если для сценария требуется чтение из MSR, драйвер должен всегда проверять, что регистр для чтения ограничен ожидаемым индексом или диапазоном. Два примера реализации безопасной операции чтения.

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

Повышение безопасности записи в MSR

В первом примере WriteMsr драйвер позволяет небезопасному поведению, позволяя любому и всем регистрам быть произвольно записаны. Это может привести к злоупотреблению вредоносными процессами для повышения привилегий в пользовательском режиме и записи во все MSR.

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

Если для сценария требуется запись в MSR, драйвер должен всегда проверять, что регистр для записи ограничен ожидаемым индексом или диапазоном. Два примера реализации безопасной операции записи.

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

Предоставление возможности завершения процессов

При реализации функциональных возможностей в драйвере необходимо использовать крайнюю осторожность, которая позволяет завершить процессы. Защищенные процессы и защищенные процессы освещения (PPL), такие как те, которые используются решениями для защиты от вредоносных программ и антивирусной защиты, не должны быть прекращены. Предоставление этой функции позволяет злоумышленникам прекратить защиту безопасности в системе.

Если для сценария требуется завершение процесса, для защиты от произвольного завершения процесса необходимо выполнить следующие проверки:

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

Предоставление возможности чтения и записи в порт входных и выходных данных

Повышение безопасности чтения из порта ввода-вывода

Необходимо использовать осторожность при предоставлении возможности чтения в порт ввода-вывода (ввода-вывода). Этот пример кода небезопасн.

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

Чтобы предотвратить злоупотребление и эксплойт драйвера, ожидаемый порт ввода должен быть ограничен требуемой границей использования.

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

Повышение безопасности записи в портовый ввод-вывод

Необходимо использовать осторожность при предоставлении возможности записи в порт ввода-вывода (ввода-вывода). Этот пример кода небезопасн.

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

Чтобы предотвратить злоупотребление и эксплойт драйвера, ожидаемый порт ввода должен быть ограничен требуемой границей использования.

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

Предоставление возможности чтения и записи ядра, физической или памяти устройства

Повышение безопасности Memcpy

В этом примере кода показано небезопасное использование безопасного использования физической памяти.

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

Если в вашем сценарии требуется чтение и запись ядра, физической или памяти устройства, драйвер должен всегда проверять, что исходный и целевой объекты ограничены ожидаемыми индексами или диапазонами.

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

Повышение безопасности ZwMapViewOfSection

В следующем примере показан небезопасный и неправильный метод для чтения и записи физической памяти из пользовательского режима, использующего API ZwOpenSection и ZwMapViewOfSection.

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

Чтобы предотвратить злоупотребление и использование поведения драйвера на чтение и запись в процессе вредоносных процессов пользовательского режима, драйвер должен проверить входной адрес и ограничить сопоставление памяти только требуемой границей использования для сценария.

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

Повышение безопасности MmMapLockedPagesSpecifyCache

В следующем примере показан небезопасный и неправильный метод для чтения и записи физической памяти из пользовательского режима с помощью API MmMapIoSpace, IoAllocateMdl и MmMapLockedPagesSpecifyCache.

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

Чтобы предотвратить злоупотребление и использование поведения драйвера на чтение и запись в процессе вредоносных процессов пользовательского режима, драйвер должен проверить входной адрес и ограничить сопоставление памяти только требуемой границей использования для сценария.

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

См. также

Контрольный список безопасности драйвера