Aanbevolen procedures voor het beperken van gedrag met hoge bevoegdheden in stuurprogramma's voor kernelmodus
In dit onderwerp worden de onveilige ontwikkelingspatronen samengevat die kunnen leiden tot uitbuiting van uw Windows-kernelstuurprogramma. In dit onderwerp vindt u aanbevelingen voor ontwikkeling en codevoorbeelden om het gedrag met bevoegdheden te beperken. Als u deze aanbevolen procedures volgt, verbetert u de veiligheid van het uitvoeren van bevoegd gedrag in de Windows-kernel.
Overzicht van onveilig rijgedrag
Hoewel het wordt verwacht dat Windows-stuurprogramma's hoog bevoegd gedrag uitvoeren in de kernelmodus, is het niet acceptabel om geen beveiligingscontroles uit te voeren en geen beperkingen op bevoegd gedrag op te leggen. Voor het Windows Hardware Compatibility Program (WHCP), voorheen WHQL, moeten nieuwe stuurprogramma-inzendingen voldoen aan deze vereiste.
Voorbeelden van onbeveiligd en gevaarlijk gedrag zijn onder andere:
- Biedt de mogelijkheid om te lezen en schrijven naar willekeurige computerspecifieke registers (MSR's)
- De mogelijkheid bieden om willekeurige processen te beëindigen
- De mogelijkheid bieden om poortinvoer en -uitvoer te lezen en schrijven
- Biedt de mogelijkheid om kernel, fysiek of apparaatgeheugen te lezen en te schrijven
Het bieden van de mogelijkheid om MSR's te lezen en schrijven
De beveiliging van het lezen van MSR's verbeteren
In dit voorbeeld van ReadMsr maakt het stuurprogramma onveilig gedrag mogelijk door toe te staan dat alle registers willekeurig kunnen worden gelezen met behulp van de modelspecifieke __readmsr registerinstructie. Dit kan leiden tot misbruik door schadelijke processen in de gebruikersmodus.
Func ReadMsr(int dwMsrIdx)
{
int value = __readmsr(dwMsrIdx); // Unsafe, can read from any MSR
return value;
}
Als uw scenario leesbewerkingen van MSR's vereist, moet het stuurprogramma altijd controleren of het register waaruit moet worden gelezen, beperkt is tot de verwachte index of het verwachte bereik. Twee voorbeelden van het implementeren van de veilige leesbewerking volgen.
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;
}
De beveiliging van schrijven naar MSR's verbeteren
In het eerste WriteMsr-voorbeeld staat het stuurprogramma onveilig gedrag toe door toe te staan dat alle registers willekeurig naar worden geschreven. Dit kan leiden tot misbruik door kwaadwillende processen om bevoegdheden in de gebruikersmodus te verhogen en naar alle MSR's te schrijven.
Func WriteMsr(int dwMsrIdx)
{
int value = __writemsr(dwMsrIdx); // Unsafe, can write to any MSR
return value;
}
Als voor uw scenario schrijven naar MSR's is vereist, moet het stuurprogramma altijd controleren of het register waarnaar moet worden geschreven, beperkt is tot de verwachte index of het verwachte bereik. Twee voorbeelden van het implementeren van de veilige schrijfbewerking volgen.
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;
}
De mogelijkheid bieden om processen te beëindigen
Extreme voorzichtigheid moet worden gebruikt bij het implementeren van functionaliteit in uw stuurprogramma, waardoor processen kunnen worden beëindigd. Beveiligde processen en PPL-processen (Protected Process Light), zoals processen die worden gebruikt door antimalware- en antivirusoplossingen, mogen niet worden beëindigd. Door deze functionaliteit beschikbaar te maken, kunnen aanvallers beveiligingsbeveiligingen op het systeem beëindigen.
Als voor uw scenario procesbeëindiging is vereist, moeten de volgende controles worden geïmplementeerd om te beveiligen tegen willekeurige procesafbreking, met behulp van PsLookupProcessByProcessId en 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;
}
De mogelijkheid bieden om invoer en uitvoer van de poort te lezen en te schrijven.
De beveiliging van het lezen van poort-IO verbeteren
Voorzichtigheid is geboden bij het bieden van de mogelijkheid om poortinvoer/uitvoer (I/O) te lezen. Dit codevoorbeeld dat gebruikmaakt van __indword is onveilig.
Func ArbitraryInputPort(int inPort)
{
dwResult = __indword(inPort); // Unsafe, allows for arbitrary reading from Input Port
return dwResult;
}
Om misbruik en uitbuiting van de driver te voorkomen, moet de verwachte invoerpoort worden beperkt tot de vereiste gebruiksgrens.
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;
}
De beveiliging van schrijven naar poort-IO verbeteren
Voorzichtigheid moet worden gebruikt bij het bieden van de mogelijkheid om naar poortinvoer/uitvoer (I/O) te schrijven. Dit codevoorbeeld waarin __outword wordt gebruikt, is onveilig.
Func ArbitraryOutputPort(int outPort, DWORD dwValue)
{
__outdword(OutPort, dwValue); // Unsafe, allows for arbitrary writing to Output Port
}
Om misbruik en uitbuiting van het stuurprogramma te voorkomen, moet de verwachte invoerpoort worden beperkt tot de vereiste toepassingsgrens.
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;
}
}
De mogelijkheid bieden om kernel, fysiek of apparaatgeheugen te lezen en schrijven
De beveiliging van Memcpy verbeteren
Deze voorbeeldcode toont onbeperkt en onveilig gebruik, evenals veilig gebruik van fysiek geheugen.
Func ArbitraryMemoryCopy(src, dst, length)
{
memcpy(dst, src, length); // Unsafe, can read and write anything from physical memory
}
Als uw scenario vereist dat u kernel, fysiek of apparaatgeheugen leest en schrijft, moet het stuurprogramma altijd controleren of de bron en de bestemmingen binnen de verwachte indexen of bereiken blijven.
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;
}
}
De beveiliging van ZwMapViewOfSection verbeteren
In het volgende voorbeeld ziet u de onveilige en onjuiste methode voor het lezen en schrijven van fysiek geheugen vanuit de gebruikersmodus met behulp van de ZwOpenSection- en ZwMapViewOfSection API's.
Func ArbitraryMap(PHYSICAL_ADDRESS Address)
{
ZwOpenSection(&hSection, ... ,"\Device\PhysicalMemory");
ZwMapViewOfSection(hSection, -1, 0, 0, 0, Address, ...);
}
Om misbruik en misbruik van het lees-/schrijfgedrag van het stuurprogramma door schadelijke gebruikersmodusprocessen te voorkomen, moet het stuurprogramma het invoeradres valideren en de geheugentoewijzing alleen beperken tot de vereiste gebruiksgrens voor het scenario.
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;
}
}
De beveiliging van MmMapLockedPagesSpecifyCache verbeteren
In het volgende voorbeeld ziet u de onveilige en onjuiste methode voor het lezen en schrijven van fysiek geheugen vanuit de gebruikersmodus met behulp van de MmMapIoSpace, IoAllocateMdl- en MmMapLockedPagesSpecifyCache API's.
Func ArbitraryMap(PHYSICAL_ADDRESS paAddress)
{
lpAddress = MmMapIoSpace(paAddress, qwSize, ...);
pMdl = IoAllocateMdl( lpAddress, ...);
MmMapLockedPagesSpecifyCache(pMdl, UserMode, ... );
}
Om misbruik en misbruik van het lees-/schrijfgedrag van het stuurprogramma door schadelijke gebruikersmodusprocessen te voorkomen, moet het stuurprogramma het invoeradres valideren en de geheugentoewijzing alleen beperken tot de vereiste gebruiksgrens voor het scenario.
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;
}
}