Рекомендации по обеспечению безопасности драйверов Windows для разработчиков драйверов
В этом разделе приведены небезопасные шаблоны разработки, которые могут привести к эксплуатации и злоупотреблению кодом драйвера Windows. В этом разделе приведены рекомендации по разработке и примеры кода. Следуя этим рекомендациям, вы сможете повысить безопасность привилегированного поведения в ядре Windows.
Общие сведения о небезопасном поведении драйвера
Хотя ожидается, что драйверы Windows выполняют высокий уровень привилегий в режиме ядра, не выполняя проверки безопасности и добавляя ограничения на привилегированное поведение, является неприемлемым. Программа совместимости оборудования Windows (WHCP), ранее WHQL, требует отправки новых драйверов для соблюдения этого требования.
Примеры небезопасного и опасного поведения включают в себя, но не ограничиваются следующими:
- Предоставление возможности чтения и записи в произвольные регистры компьютера (MSR)
- Предоставление возможности завершения произвольных процессов
- Предоставление возможности чтения и записи в порт входных и выходных данных
- Предоставление возможности чтения и записи ядра, физической или памяти устройства
Предоставление возможности чтения и записи 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;
}
}