Intel sysret 在64位windows下引发的漏洞

漏洞描述

根据Intel手册描述,其64位 CPU 下的sysret指令不主动进行栈切换,需要由开发人员自己显式切换GS、RBP和 RSP,当以上受影响操作系统由kernel-mode返回user-mode时,首先将GS,RBP和RSP恢复为user-mode的值,然后调用sysret。但是当返回地址为无效地址(有效地址的48-63位必须和第47位一致)时,sysret将会触发#GP异常,此时会跳到KiGeneralProtectionFault执行,由于 此时CPU运行在ring0,但RSP、RBP、GS已经切到user-mode地址空间,若精心构造gs所指向的值,可导致ring0下执行任意代码。

漏洞分析

查看 wrk1.2, base\ntos\ke\amd64\trap.asm 如下代码:

       TRAP_ENTRY KiSystemCall64, KiSystemServiceHandler

        swapgs                          ; swap GS base to kernel PCR
        mov     gs:[PcUserRsp], rsp     ; save user stack pointer
        mov     rsp, gs:[PcRspBase]     ; set kernel stack pointer
        push    KGDT64_R3_DATA or RPL_MASK ; push dummy SS selector
        push    gs:[PcUserRsp]          ; push user stack pointer
        push    r11                     ; push previous EFLAGS
        push    KGDT64_R3_CODE or RPL_MASK ; push dummy 64-bit CS selector
        push    rcx                     ; push return address
        mov     rcx, r10                ; set first argument value

以上代码为user-mode进入kernel-mode时的栈切换情况。

查看 RESTORE_TRAP_STATE 宏, 返回 ring3时的代码如下:

        mov     r8, TrRsp[rbp]          ; get previous RSP value  , 获取用户模式rsp
        mov     r9, TrRbp[rbp]          ; get previous RBP value
        xor     edx, edx                ; scrub volatile integer registers
        xor     r10, r10                      ;
        pxor    xmm0, xmm0              ; scrub volatile floating registers
        pxor    xmm1, xmm1              ;
        pxor    xmm2, xmm2              ;
        pxor    xmm3, xmm3              ;
        pxor    xmm4, xmm4              ;
        pxor    xmm5, xmm5              ;
        mov     rcx, TrRip[rbp]         ; get return address
        mov     r11, TrEFlags[rbp]      ; get previous EFLAGS
        mov     rbp, r9                 ; restore RBP
        mov     rsp, r8                 ; restore RSP     , 恢复用户模式rsp
        swapgs                          ; swap GS base to user mode TEB
        sysretq                         ; return from system call to user mode  ,如果此时发生异常, 堆栈已切到用户模式空间,但CPU运行在ring0.

漏洞利用

利用User-Mode Scheduling (UMS)修改线程上下文环境。首先CreateUmsCompletionList()创建ums完成列表,然后调用EnterUmsSchedulingMode() (在此函数中会调用RtlpUmsPrimaryContextWrap ,其会将ring3的返回地址保存在 GS:[0x14a0]+0x10+0xF8 中)将自身线程由普通线程转换成UMS线程,我们可以在此时将GS:[0x14a0]+0x10+0xF8 中的值修改为一个无效地址,然后调用 ExecuteUmsThread来执行ums线程,最后,内核在完成一些操作后 会调用 KiUmsFastReturnToUser返回用户层,此函数中将rcx、rbp、rsp恢复为use-mode的值然后调用 sysret。由于sysret的返回地址无效,导致#GP异常, 执行流程:–>KiGeneralProtectionFault –> KiBugCheckDispatch –> KeBugCheckEx –> KiSaveProcessorControlState 在其调用过程中没有发现可利用的 代码执行指令,通过分析,发现可以通过构造 GS:0x20为一个非法值,可以在KiSaveProcessorControlState 函数中触发一个page fault异常,执行流程:KiPageFault –> KiCheckForKernelApcDelivery –> KiDeliverApc (在此函数中发现可利用的代码执行指令call r11)。

参考