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


    内核参数验证的重要性

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

    以NtCreateProcessEx为例

    1. NTSTATUS
    2. NtCreateProcessEx(
    3. __out PHANDLE ProcessHandle,
    4. __in ACCESS_MASK DesiredAccess,
    5. __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    6. __in HANDLE ParentProcess,
    7. __in ULONG Flags,
    8. __in_opt HANDLE SectionHandle,
    9. __in_opt HANDLE DebugPort,
    10. __in_opt HANDLE ExceptionPort,
    11. __in ULONG JobMemberLevel
    12. )
    13. {
    14. NTSTATUS Status;
    15. PAGED_CODE(); /* 判断是否在可访问分页内存的IRQL */
    16. if (KeGetPreviousMode() != KernelMode) {
    17. //
    18. // Probe all arguments
    19. //
    20. try {
    21. ProbeForWriteHandle (ProcessHandle); /* 检查handle是否可写 */
    22. } except (EXCEPTION_EXECUTE_HANDLER) {
    23. return GetExceptionCode ();
    24. }
    25. }
    26. if (ARGUMENT_PRESENT (ParentProcess)) { /* 判断父进程handle是否存在 */
    27. Status = PspCreateProcess (ProcessHandle,
    28. DesiredAccess,
    29. ObjectAttributes,
    30. ParentProcess,
    31. Flags,
    32. SectionHandle,
    33. DebugPort,
    34. ExceptionPort,
    35. JobMemberLevel);
    36. } else {
    37. Status = STATUS_INVALID_PARAMETER;
    38. }
    39. return Status;
    40. }
    • NtCreateProcessEx默认是创建用户模式进程,必须有父进程。
    • PsCreateSystemProcess才可创建系统进程,默认父进程handle为PspInitialSystemProcessHandle.

    ProbeForWriteHandle

    1. FORCEINLINE
    2. VOID
    3. ProbeForWriteHandle (
    4. IN PHANDLE Address
    5. )
    6. {
    7. if (Address >= (HANDLE * const)MM_USER_PROBE_ADDRESS) {
    8. Address = (HANDLE * const)MM_USER_PROBE_ADDRESS;
    9. }
    10. *((volatile HANDLE *)Address) = *Address;
    11. return;
    12. }

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

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

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

                    

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

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

            

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

    ProbeForWrite

    1. VOID
    2. ProbeForWrite (
    3. __inout_bcount(Length) PVOID Address,
    4. __in SIZE_T Length,
    5. __in ULONG Alignment)
    6. {
    7. ULONG_PTR EndAddress;
    8. ULONG_PTR StartAddress;
    9. #define PageSize PAGE_SIZE
    10. if (Length != 0) { /* Length如果是0, 就不用检查了! */
    11. //
    12. // If the structure is not properly aligned, then raise a data
    13. // misalignment exception.
    14. //
    15. /* Alignment必须是1/2/4/8/16之一 */
    16. ASSERT((Alignment == 1) || (Alignment == 2) ||
    17. (Alignment == 4) || (Alignment == 8) ||
    18. (Alignment == 16));
    19. StartAddress = (ULONG_PTR)Address;
    20. if ((StartAddress & (Alignment - 1)) == 0) {
    21. //
    22. // Compute the ending address of the structure and probe for
    23. // write accessibility.
    24. //
    25. EndAddress = StartAddress + Length - 1;
    26. if ((StartAddress <= EndAddress) &&
    27. (EndAddress < MM_USER_PROBE_ADDRESS)) {
    28. EndAddress = (EndAddress & ~(PageSize - 1)) + PageSize;
    29. do {
    30. *(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress;
    31. StartAddress = (StartAddress & ~(PageSize - 1)) + PageSize;
    32. } while (StartAddress != EndAddress);
    33. return;
    34. } else { /* 地址范围不在用户空间可探测区域,抛出访问异常 */
    35. ExRaiseAccessViolation();
    36. }
    37. } else { /* 起始地址未对齐, 抛出未对齐异常 */
    38. ExRaiseDatatypeMisalignment();
    39. }
    40. }
    41. return;
    42. }
    •  官方介绍: 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

            

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

                    

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

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

    ARGUMENT_PRESENT

    1. //
    2. // Determine if an argument is present by testing the value of the pointer
    3. // to the argument value.
    4. //
    5. #define ARGUMENT_PRESENT(ArgumentPointer) (\
    6. (CHAR *)((ULONG_PTR)(ArgumentPointer)) != (CHAR *)(NULL) )

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

  • 相关阅读:
    「Java代码审计」Java代码审计基础知识
    C语言深度解剖——C语言关键字
    淘宝退货退款测试用例
    自动化设备制造行业常见管理难题及解决方案
    手机NFC录入门禁数据,实现手机开门
    Bit, byte, KB, GB, MG
    node+pm2安装部署
    探究Springboot自动装配原理
    17. 电话号码的字母组合
    从底层看 Redis 的五种数据类型
  • 原文地址:https://blog.csdn.net/cxsjabcabc/article/details/127981023