다음을 통해 공유


드라이버 개발자를 위한 Windows 드라이버 보안 모범 사례

이 항목에서는 Windows 드라이버 코드를 악용하고 남용할 수 있는 안전하지 않은 개발 패턴을 요약합니다. 이 항목에서는 개발 권장 사항 및 코드 샘플을 제공합니다. 이러한 모범 사례를 따르면 Windows 커널에서 권한 있는 동작을 수행하는 안전성을 개선하는 데 도움이 됩니다.

안전하지 않은 드라이버 동작 개요

Windows 드라이버는 커널 모드에서 높은 권한 있는 동작을 수행할 것으로 예상되지만 보안 검사를 수행하지 않고 권한 있는 동작에 대한 제약 조건을 안전하게 추가하는 것은 허용되지 않습니다. 이전의 WHQL(Windows 하드웨어 호환성 프로그램)에는 이 요구 사항을 준수하기 위해 새 드라이버 제출이 필요합니다.

안전하지 않고 위험한 동작의 예는 다음과 같습니다.

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;
}

포트 입력 및 출력을 읽고 쓰는 기능 제공

포트 IO에서 읽기 보안 강화

I/O(포트 입력/출력)로 읽을 수 있는 기능을 제공할 때는 주의해야 합니다. 이 코드 예제는 안전하지 않습니다.

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)에 쓸 수 있는 기능을 제공하는 경우 주의해야 합니다. 이 코드 예제는 안전하지 않습니다.

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의 보안 강화

다음 예제에서는 MmMapIoSpace, IoAllocateMdl 및 MmMapLockedPagesSpecifyCache API를 사용하여 사용자 모드에서 실제 메모리를 읽고 쓰는 안전하지 않고 부적절한 방법을 보여 줍니다.

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;
	}
}

참고 항목

드라이버 보안 검사 목록