Windows内核--系统调用参数验证(5.1)

内核参数验证的重要性

        内核模式之所以有别于用户模式,在于内核模式应该是安全、可信的。用户系统调用可以传入各式各样的参数,可能是代码无意写错或因不预期的内存覆盖"暗地修改"参数,也可能是Hack有意传入,内核都应当妥善处理,避免内核读写到不预期的地址,造成内核被破解或不稳定。

以NtCreateProcessEx为例

NTSTATUS
NtCreateProcessEx(
    __out PHANDLE ProcessHandle,
    __in ACCESS_MASK DesiredAccess,
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    __in HANDLE ParentProcess,
    __in ULONG Flags,
    __in_opt HANDLE SectionHandle,
    __in_opt HANDLE DebugPort,
    __in_opt HANDLE ExceptionPort,
    __in ULONG JobMemberLevel
    )
{
    NTSTATUS Status;

    PAGED_CODE(); /* 判断是否在可访问分页内存的IRQL */

    if (KeGetPreviousMode() != KernelMode) {

        //
        // Probe all arguments
        //

        try {
            ProbeForWriteHandle (ProcessHandle); /* 检查handle是否可写 */
        } except (EXCEPTION_EXECUTE_HANDLER) {
            return GetExceptionCode ();
        }
    }

    if (ARGUMENT_PRESENT (ParentProcess)) {  /* 判断父进程handle是否存在 */
        Status = PspCreateProcess (ProcessHandle,
                                   DesiredAccess,
                                   ObjectAttributes,
                                   ParentProcess,
                                   Flags,
                                   SectionHandle,
                                   DebugPort,
                                   ExceptionPort,
                                   JobMemberLevel);
    } else {
        Status = STATUS_INVALID_PARAMETER;
    }

    return Status;
}
  • NtCreateProcessEx默认是创建用户模式进程,必须有父进程。
  • PsCreateSystemProcess才可创建系统进程,默认父进程handle为PspInitialSystemProcessHandle.

ProbeForWriteHandle

FORCEINLINE
VOID
ProbeForWriteHandle (
    IN PHANDLE Address
    )
{

    if (Address >= (HANDLE * const)MM_USER_PROBE_ADDRESS) {
        Address = (HANDLE * const)MM_USER_PROBE_ADDRESS;
    }

    *((volatile HANDLE *)Address) = *Address;
    return;
}

        比较有意思的是,先判断地址是否越过默认的MM_USER_PROBE_ADDRESS,如果超过,说明在内核空间,不能向内核空间随便写数据测试权限,先把地址改成MM_USER_PROBE_ADDRESS做读写确认。

        这种情况,似乎检查了个寂寞。

        在PspCreateProcess结尾有如下这段code再次确认写ProcessHandle是否有异常:

                

                 try/except处理异常的方式是NOTHING! 这个应该是不恰当的处理方式。

        赋值部分用volatile为了确保写到内存,而非Cache.

        

nt!NtCreateProcessEx:
fffff800`00a1cc90 4883ec58        sub     rsp,58h
fffff800`00a1cc94 4d8bd1          mov     r10,r9
fffff800`00a1cc97 65488b042588010000 mov   rax,qword ptr gs:[188h]
fffff800`00a1cca0 80b85301000000  cmp     byte ptr [rax+153h],0
fffff800`00a1cca7 741b            je      nt!NtCreateProcessEx+0x34 (fffff800`00a1ccc4)
fffff800`00a1cca9 4c8bc9          mov     r9,rcx
fffff800`00a1ccac 488b05a5faeeff  mov     rax,qword ptr [nt!MmUserProbeAddress (fffff800`0090c758)]
fffff800`00a1ccb3 483bc8          cmp     rcx,rax
fffff800`00a1ccb6 4c0f43c8        cmovae  r9,rax
fffff800`00a1ccba 498b01          mov     rax,qword ptr [r9]  ; Probe地址先读出来
fffff800`00a1ccbd 498901          mov     qword ptr [r9],rax  ; 再把Probe地址的数据写回去

ProbeForWrite

VOID
ProbeForWrite (
    __inout_bcount(Length) PVOID Address,
    __in SIZE_T Length,
    __in ULONG Alignment)
{

    ULONG_PTR EndAddress;
    ULONG_PTR StartAddress;

#define PageSize PAGE_SIZE
    if (Length != 0) { /* Length如果是0, 就不用检查了! */

        //
        // If the structure is not properly aligned, then raise a data
        // misalignment exception.
        //
        /* Alignment必须是1/2/4/8/16之一 */
        ASSERT((Alignment == 1) || (Alignment == 2) ||
               (Alignment == 4) || (Alignment == 8) ||
               (Alignment == 16));

        StartAddress = (ULONG_PTR)Address;
        if ((StartAddress & (Alignment - 1)) == 0) {
            //
            // Compute the ending address of the structure and probe for
            // write accessibility.
            //
            EndAddress = StartAddress + Length - 1;
            if ((StartAddress <= EndAddress) &&
                (EndAddress < MM_USER_PROBE_ADDRESS)) {
                EndAddress = (EndAddress & ~(PageSize - 1)) + PageSize;
                do {
                    *(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress;
                    StartAddress = (StartAddress & ~(PageSize - 1)) + PageSize;
                } while (StartAddress != EndAddress);

                return;

            } else { /* 地址范围不在用户空间可探测区域,抛出访问异常 */
                ExRaiseAccessViolation();
            }

        } else { /* 起始地址未对齐, 抛出未对齐异常 */
            ExRaiseDatatypeMisalignment();
        }
    }
    return;
}
  •  官方介绍: ProbeForWrite

  • ProbeForWrite例程检查的是用户模式缓冲区权限是否正确:是否可写且正确对齐。

  • 内核和驱动对于用户模式参数必须用ProbeFor*系列函数确保权限正确,而且是任何需要访问用户模式参数的位置都需要检查。这是因为调用者可能之后用另一个线程修改参数,导致之前的检测不能挡住所有可能的非法访问,谨记!

Drivers must call ProbeForWrite inside a try/except block. If the routine raises an exception, the driver should complete the IRP with the appropriate error. Note that subsequent accesses by the driver to the user-mode buffer must also be encapsulated within a try/except block: a malicious application could have another thread deleting, substituting, or changing the protection of user address ranges at any time (even after or during a call to ProbeForRead or ProbeForWrite). For more information, see Handling Exceptions.

核心代码

if ((StartAddress & (Alignment - 1)) == 0) { /* 起始地址对齐 */
            //
            // Compute the ending address of the structure and probe for
            // write accessibility.
            //
            EndAddress = StartAddress + Length - 1;
            if ((StartAddress <= EndAddress) && /* 起始地址肯定要小于结束地址 */

                /* 地址不能超过用户空间可Probe范围*/
                (EndAddress < MM_USER_PROBE_ADDRESS)) {
                EndAddress = (EndAddress & ~(PageSize - 1)) + PageSize;
                do {

                    /* 每次只读写StartAddr第一个字节确认即可, 因为是分页为单位管理! */
                    *(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress;

                    /* 以PageSize为单位, 每个PageSize只读写首字节即可确认! */
                    StartAddress = (StartAddress & ~(PageSize - 1)) + PageSize;
                } while (StartAddress != EndAddress);

                return;

            } 

}

核心在于MM_USER_PROBE_ADDRESS.

MM_USER_PROBE_ADDRESS

        

         内存管理模块初始化会设置初始值。

                

/* Amd64 */
#define MI_HIGHEST_USER_ADDRESS (PVOID) (ULONG_PTR)((0x80000000000 - 0x10000 - 1)) // highest user address
#define MI_SYSTEM_RANGE_START (PVOID)(0xFFFF080000000000) // start of system space
#define MI_USER_PROBE_ADDRESS ((ULONG_PTR)(0x80000000000UI64 - 0x10000)) // starting address of guard page
/* x86 */
#define KSEG0_BASE 0x80000000
  •  64位系统,赋值为系统空间0xFFFF080000000000向下64KB.
  •  32位系统,赋值为系统空间0x8000 0000向下64KB.

        当然,用户空间也不能访问用于Probe的区域, 上图可以看到:MmHighestUserAddress被赋值为MI_USER_PROBE_ADDRESS - 1.

ARGUMENT_PRESENT

//
// Determine if an argument is present by testing the value of the pointer
// to the argument value.
//

#define ARGUMENT_PRESENT(ArgumentPointer)    (\
    (CHAR *)((ULONG_PTR)(ArgumentPointer)) != (CHAR *)(NULL) )

        32位和64位系统指针默认长度不一样,统一用可自定义长度的ULONG_PTR类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值