Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este tema se resumen los patrones de desarrollo no seguros que pueden provocar la explotación y el abuso del código del controlador del kernel de Windows. En este tema se proporcionan recomendaciones de desarrollo y ejemplos de código para ayudar a restringir el comportamiento con privilegios. Seguir estos procedimientos recomendados ayudará a mejorar la seguridad de realizar un comportamiento con privilegios en el kernel de Windows.
Información general sobre el comportamiento del conductor inseguro
Aunque se espera que los controladores de Windows realicen un comportamiento con privilegios elevados en modo kernel, no realizar comprobaciones de seguridad y agregar restricciones en el comportamiento con privilegios es inaceptable. El Programa de compatibilidad de hardware de Windows (WHCP), anteriormente WHQL, requiere nuevos envíos de controladores para cumplir este requisito.
Entre los ejemplos de comportamiento no seguro y peligroso se incluyen, entre otros, los siguientes:
- Proporcionar la capacidad de leer y escribir en registros arbitrarios específicos de la máquina (MSR)
- Proporcionar la capacidad de finalizar procesos arbitrarios
- Proporcionar la capacidad de leer y escribir en la entrada y salida del puerto
- Proporcionar la capacidad de leer y escribir en la memoria del kernel, física o del dispositivo
Proporcionar la capacidad de leer y escribir los MSR
Mejora DE la seguridad en la lectura desde MSR
En este ejemplo de ReadMsr, el controlador permite un comportamiento inseguro al permitir que cualquier registro se lea arbitrariamente utilizando la intrínseca de registro específica de modelo __readmsr. Esto puede dar lugar a abusos por procesos malintencionados en modo de usuario.
Func ReadMsr(int dwMsrIdx)
{
int value = __readmsr(dwMsrIdx); // Unsafe, can read from any MSR
return value;
}
Si el escenario requiere lectura desde MSR, el controlador siempre debe comprobar que el registro que se va a leer está restringido al índice o intervalo esperados. A continuación se muestran dos ejemplos de cómo implementar la operación de lectura 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;
}
Mejora de la seguridad al escribir en MSR
En el primer ejemplo de WriteMsr, el controlador facilita un comportamiento inseguro al permitir que todos los registros se escriban arbitrariamente. Esto puede dar lugar a abusos por parte de procesos malintencionados para elevar privilegios en modo de usuario y escribir en todos los MSR.
Func WriteMsr(int dwMsrIdx)
{
int value = __writemsr(dwMsrIdx); // Unsafe, can write to any MSR
return value;
}
Si el escenario requiere escribir en MSR, el controlador siempre debe comprobar que el registro en el que escribir está restringido al índice o intervalo esperados. A continuación se muestran dos ejemplos de cómo implementar la operación de escritura 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;
}
Proporcionar la capacidad de finalizar procesos
Debe usarse extrema precaución al implementar en su controlador la funcionalidad que permite terminar procesos. Los procesos protegidos y los procesos de protección ligera (PPL), como los que utilizan las soluciones antimalware y antivirus, no deben ser terminados. Exponer esta funcionalidad permite a los atacantes finalizar las protecciones de seguridad en el sistema.
Si el escenario requiere la terminación del proceso, se deben implementar las siguientes comprobaciones para protegerse frente a la terminación arbitraria del proceso, mediante PsLookupProcessByProcessId y 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;
}
Proporcionar la capacidad de leer y escribir en la entrada y salida del puerto
Mejora de la seguridad en la lectura desde la entrada/salida del puerto
Se debe tener precaución al proporcionar la capacidad de leer a la de entrada/salida (E/S) del puerto. Este ejemplo de código que usa __indword no es seguro.
Func ArbitraryInputPort(int inPort)
{
dwResult = __indword(inPort); // Unsafe, allows for arbitrary reading from Input Port
return dwResult;
}
Para evitar el abuso y la vulnerabilidad del controlador, el puerto de entrada esperado debe restringirse al límite de uso necesario.
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;
}
Mejora de la seguridad de escritura en la E/S del puerto
Se debe tener precaución al proporcionar la capacidad de escribir en la de entrada/salida (E/S) del puerto. Este ejemplo de código que usa __outword no es seguro.
Func ArbitraryOutputPort(int outPort, DWORD dwValue)
{
__outdword(OutPort, dwValue); // Unsafe, allows for arbitrary writing to Output Port
}
Para evitar el abuso y la vulnerabilidad del controlador, el puerto de entrada esperado debe restringirse al límite de uso necesario.
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;
}
}
Proporcionar la capacidad de leer y escribir en la memoria del kernel, física o del dispositivo
Mejora de la seguridad de Memcpy
Este código de ejemplo muestra un uso sin restricciones y no seguro de la memoria física.
Func ArbitraryMemoryCopy(src, dst, length)
{
memcpy(dst, src, length); // Unsafe, can read and write anything from physical memory
}
Si el escenario requiere lectura y escritura de kernel, memoria física o de dispositivo, el controlador siempre debe comprobar que el origen y los destinos están restringidos a los índices o 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;
}
}
Mejora de la seguridad de ZwMapViewOfSection
En el ejemplo siguiente se muestra el método no seguro e incorrecto para leer y escribir memoria física desde el modo de usuario mediante las API de
Func ArbitraryMap(PHYSICAL_ADDRESS Address)
{
ZwOpenSection(&hSection, ... ,"\Device\PhysicalMemory");
ZwMapViewOfSection(hSection, -1, 0, 0, 0, Address, ...);
}
Para evitar el abuso y la vulnerabilidad de seguridad del comportamiento de lectura y escritura del controlador por procesos de modo de usuario malintencionados, el controlador debe validar la dirección de entrada y restringir la asignación de memoria solo al límite de uso necesario para el escenario.
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;
}
}
Mejora de la seguridad de MmMapLockedPagesSpecifyCache
En el ejemplo siguiente se muestra el método no seguro e incorrecto para leer y escribir memoria física desde el modo de usuario mediante las API de MmMapIoSpace, IoAllocateMdl y MmMapLockedPagesSpecifyCache.
Func ArbitraryMap(PHYSICAL_ADDRESS paAddress)
{
lpAddress = MmMapIoSpace(paAddress, qwSize, ...);
pMdl = IoAllocateMdl( lpAddress, ...);
MmMapLockedPagesSpecifyCache(pMdl, UserMode, ... );
}
Para evitar el abuso y la vulnerabilidad de seguridad del comportamiento de lectura y escritura del controlador por procesos de modo de usuario malintencionados, el controlador debe validar la dirección de entrada y restringir la asignación de memoria solo al límite de uso necesario para el escenario.
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;
}
}