Osvědčené postupy pro omezení vysokého privilegovaného chování v ovladačích režimu jádra
Toto téma shrnuje nebezpečné vývojové vzory, které můžou vést ke zneužití a zneužití kódu ovladače jádra Windows. Toto téma obsahuje doporučení pro vývoj a ukázky kódu, které pomáhají omezit privilegované chování. Následující osvědčené postupy vám pomůžou zlepšit bezpečnost výkonu privilegovaného chování v jádru Windows.
Přehled nebezpečného chování ovladačů
Přestože se očekává, že ovladače systému Windows budou provádět privilegované operace v režimu jádra, neprovádění kontrol zabezpečení a nepřidávání omezení na privilegované chování je nepřijatelné. Program WHCP (Windows Hardware Compatibility Program), dříve WHQL, vyžaduje, aby nové odeslání ovladačů splňovalo tento požadavek.
Mezi příklady nezabezpečeného a nebezpečného chování patří:
- poskytování možnosti čtení a zápisu do libovolných registrů specifických pro počítače (MSR)
- poskytování možnosti ukončit libovolné procesy
- poskytování možnosti čtení a zápisu do vstupního a výstupního portu
- poskytování možnosti čtení a zápisu jádra, fyzické paměti nebo paměti zařízení
Poskytování možnosti čtení a zápisu msrs
Zvýšení zabezpečení čtení z msrs
V tomto příkladu ReadMsr ovladač umožňuje nebezpečné chování tím, že umožňuje libovolný a všechny registry libovolně číst pomocí vnitřního registru specifického pro model __readmsr. To může vést ke zneužití škodlivými procesy v uživatelském režimu.
Func ReadMsr(int dwMsrIdx)
{
int value = __readmsr(dwMsrIdx); // Unsafe, can read from any MSR
return value;
}
Pokud váš scénář vyžaduje čtení z msRs, ovladač musí vždy zkontrolovat, jestli je registr, ze kterého se má číst, omezený na očekávaný index nebo rozsah. Dva příklady implementace bezpečné operace čtení následují.
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;
}
Zvýšení zabezpečení zápisu do msRS
V prvním příkladu WriteMsr umožňuje ovladač nebezpečné chování tím, že dovoluje libovolné a nekontrolované zapisování do všech registrů. To může vést ke zneužití škodlivých procesů ke zvýšení oprávnění v uživatelském režimu a zápisu do všech SRS.
Func WriteMsr(int dwMsrIdx)
{
int value = __writemsr(dwMsrIdx); // Unsafe, can write to any MSR
return value;
}
Pokud váš scénář vyžaduje zápis do MSR, musí ovladač vždy zkontrolovat, zda je registr pro zápis omezen na očekávaný index nebo rozsah. Dva příklady implementace bezpečné operace zápisu následují.
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;
}
Poskytování schopnosti ukončit procesy
Při implementaci funkcí v ovladači musí být použita extrémní opatrnost, která umožňuje ukončení procesů. Chráněné procesy a procesy PPL (Protected Process Light), jako jsou procesy používané antimalwarovým a antivirovým řešením, nesmí být ukončeny. Zveřejnění této funkce umožňuje útočníkům ukončit ochranu zabezpečení v systému.
Pokud váš scénář vyžaduje ukončení procesu, musí být implementovány následující kontroly pro ochranu před libovolným ukončením procesu pomocí PsLookupProcessByProcessId a 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;
}
Poskytnutí možnosti čtení a zápisu do vstupu a výstupu portu
Zvýšení zabezpečení čtení z Port IO
Při poskytování možnosti čtení vstupu a výstupu portu (vstupně-výstupní operace) je třeba opatrnosti. Tento příklad kódu, který používá __indword, je nebezpečný.
Func ArbitraryInputPort(int inPort)
{
dwResult = __indword(inPort); // Unsafe, allows for arbitrary reading from Input Port
return dwResult;
}
Aby se zabránilo zneužití a využití ovladače, musí být očekávaný vstupní port omezen na stanovenou hranici použití.
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;
}
Zvýšení zabezpečení zápisu do portu I/O
Při poskytování možnosti zápisu do vstupu a výstupu portu (V/V) je třeba opatrnosti. Tento příklad kódu, který používá __outword, je nebezpečný.
Func ArbitraryOutputPort(int outPort, DWORD dwValue)
{
__outdword(OutPort, dwValue); // Unsafe, allows for arbitrary writing to Output Port
}
Aby se zabránilo zneužití a využití ovladače, musí být vstupní port omezen na požadovanou hranici použití.
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;
}
}
Poskytnutí možnosti čtení a zápisu jádra, fyzické paměti nebo paměti zařízení
Zvýšení zabezpečení Memcpy
Tento ukázkový kód ukazuje nekontrénované a nebezpečné použití fyzické paměti.
Func ArbitraryMemoryCopy(src, dst, length)
{
memcpy(dst, src, length); // Unsafe, can read and write anything from physical memory
}
Pokud váš scénář vyžaduje čtení a zápis jádra, fyzické paměti nebo paměti zařízení, ovladač musí vždy zkontrolovat, že zdroj a cíle jsou omezené na očekávané indexy nebo rozsahy.
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;
}
}
Vylepšení zabezpečení ZwMapViewOfSection
Následující příklad znázorňuje nebezpečnou a nesprávnou metodu čtení a zápisu fyzické paměti z uživatelského režimu s využitím ZwOpenSection a ZwMapViewOfSection API.
Func ArbitraryMap(PHYSICAL_ADDRESS Address)
{
ZwOpenSection(&hSection, ... ,"\Device\PhysicalMemory");
ZwMapViewOfSection(hSection, -1, 0, 0, 0, Address, ...);
}
Aby se zabránilo zneužití chování ovladače při čtení a zápisu škodlivými procesy uživatelského režimu, musí ovladač ověřit vstupní adresu a omezit mapování paměti pouze na požadovanou hranici použití pro tento scénář.
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;
}
}
Zvýšení zabezpečení MmMapLockedPagesSpecifyCache
Následující příklad ukazuje nezabezpečenou a nesprávnou metodu čtení a zápisu fyzické paměti z uživatelského režimu s využitím API MmMapIoSpace, IoAllocateMdl a MmMapLockedPagesSpecifyCache.
Func ArbitraryMap(PHYSICAL_ADDRESS paAddress)
{
lpAddress = MmMapIoSpace(paAddress, qwSize, ...);
pMdl = IoAllocateMdl( lpAddress, ...);
MmMapLockedPagesSpecifyCache(pMdl, UserMode, ... );
}
Aby se zabránilo zneužití chování ovladače při čtení a zápisu škodlivými procesy uživatelského režimu, musí ovladač ověřit vstupní adresu a omezit mapování paměti pouze na požadovanou hranici použití pro tento scénář.
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;
}
}
Viz také
kontrolní seznam zabezpečení ovladače