面向驱动程序开发人员的 Windows 驱动程序安全最佳做法

本主题总结了可能导致利用和滥用 Windows 驱动程序代码的不安全开发模式。 本主题提供开发建议和代码示例。 遵循这些最佳做法将有助于提高在 Windows 内核中执行特权行为的安全性。

不安全驱动程序行为概述

虽然 Windows 驱动程序在内核模式下执行高特权行为,但不会执行安全检查,并且对特权行为添加约束是不可接受的。 Windows 硬件兼容性计划(WHCP)以前是 WHQL,需要新的驱动程序提交才能符合此要求。

不安全和危险行为的示例包括但不限于:

提供读取和写入 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;
	}
}

另请参阅

驱动程序安全清单