カーネル モード ドライバーで高い特権の動作を制限するためのベスト プラクティス
このトピックでは、Windows カーネル ドライバー コードの悪用や悪用につながる可能性がある安全でない開発パターンの概要を示します。 このトピックでは、特権動作の制約に役立つ開発の推奨事項とコード サンプルを提供します。 これらのベスト プラクティスに従うと、Windows カーネルで特権動作を実行する安全性が向上します。
安全でないドライバーの動作の概要
Windows ドライバーはカーネル モードで高い特権の動作を実行することが期待されますが、セキュリティ チェックを実行せず、特権の動作に制約を追加することは許容できません。 Windows ハードウェア互換性プログラム (WHCP) (以前の WHQL) では、この要件に準拠するために新しいドライバーの申請が必要です。
セキュリティで保護されていない危険な動作の例には、次のものが含まれますが、これらに限定されません。
- 任意のコンピューター固有レジスタ (MSR) に対する読み取りと書き込みを行う機能を提供する
- 任意のプロセスを終了する機能を提供
- ポートの入出力 に対する読み取りと書き込みの機能を提供する
- カーネル、物理、またはデバイスのメモリ を読み書きする機能を提供する
MSR の読み取りと書き込みの機能の提供
MSR からの読み取りのセキュリティの強化
この ReadMsr の例では、ドライバーは、__readmsr モデル固有のレジスタの組み込みを使用して任意のすべてのレジスタを読み取ることができるようにすることで、安全でない動作を可能にします。 これにより、ユーザー モードの悪意のあるプロセスによる不正使用が発生する可能性があります。
Func ReadMsr(int dwMsrIdx)
{
int value = __readmsr(dwMsrIdx); // Unsafe, can read from any MSR
return value;
}
シナリオで MSR からの読み取りが必要な場合、ドライバーは常に、読み取るレジスタが予想されるインデックスまたは範囲に制限されていることを確認する必要があります。 安全な読み取り操作を実装する方法の 2 つの例を次に示します。
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 への書き込みが必要な場合、ドライバーは、書き込むレジスタが予想されるインデックスまたは範囲に制限されていることを常に確認する必要があります。 安全な書き込み操作を実装する方法の 2 つの例を次に示します。
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) プロセスを終了してはなりません。 この機能を公開すると、攻撃者はシステムのセキュリティ保護を終了できます。
シナリオでプロセスの終了が必要な場合は、PsLookupProcessByProcessId と 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;
}
ポートの入力と出力に対する読み取りと書き込みの機能を提供する
ポート IO からの読み取りのセキュリティの強化
ポート入出力 (I/O) への読み取り機能を提供する場合は、注意が必要です。 __indword を使用するこのコード例は安全ではありません。
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;
}
ポート IO への書き込みのセキュリティの強化
ポート入出力 (I/O) に書き込む機能を提供する場合は、注意が必要です。 __outword を使用するこのコード例は安全ではありません。
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 のセキュリティの強化
次の例は、ZwOpenSection と ZwMapViewOfSection API を使用して、ユーザー モードから物理メモリを読み書きする安全でない方法と不適切な方法を示しています。
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 のセキュリティの強化
次の例は、
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;
}
}