漏洞描述

Windows内核异常处理函数KiTrap01、KiTrap0E 处理特殊值导致的异常时,没有检查之前的CPU模式(previous cpl)而引发的一个信息泄漏或拒绝服务安全漏洞。

漏洞分析

TrapFlag:在EFLAGS_TF标志被设置后执行的第一条指令将触发int 1(#DB), 通常被用来单步调试功能.

可以使用下面汇编指令序列单步进入系统调用处理函数:

pushf
or dword[esp], 0x100
popf                          ;设置eflags_tf标志
sysenter                      ;会触发 interrupt 1(#DB)

进入KiTrap01处理函数时:

KTRAP_FRAME.Eip   = KiFastCallEntry
KTRAP_FRAME.SegCs = 8

#DB异常处理函数KiTrap01:

_KiTrap01       proc

; Set up machine state frame for displaying

        push    0                       ; push dummy error code
        ENTER_TRAP      kit1_a, kit1_t

;
; Inspect old EIP in case this is a single stepped
; sysenter instruction.
;

        mov     ecx, [ebp]+TsEip
        cmp     ecx, _KiFastCallEntry
        je      Kt0100

                。。。
                
Kt0100: mov     [ebp].TsEip, _KiFastCallEntry2
        and     dword ptr [ebp].TsEflags, NOT EFLAGS_TF
        jmp     _KiExceptionExit        ; join common code

由以上可知: nt!KiTrap01异常处理函数处理EIP等于KiFastCallEntry时,会将 KTRAP_FRAME.Eip设置为nt!KiFastCallEntry2,并且清除EFlags_TF标志然后 返回,然而在KiFastCallEntry2中又会设置KTRAP_FRAME.EFlags_TF,所以在 Sysenter下一条指令又会产生一次单步异常。

由于 NT!KiTrap01异常处理函数并没有判断之前模式是否为kernel-mode, 所以下面指令序列与上面的代码效果相同(只是下面的代码没有进行权限切换):

pushf
or [esp], 0x100
popf
jmp 0x80403c86         ; 假设0x80403c86为KiFastCallEntry的地址

当执行jmp 0x80403c86指令时,如果0x80403c86为KiFastCallEntry的地址,则 在#DB异常处理函数nt!KiTrap01中会将KTRAP_FRAME.Eip修改为KiFastCallEntry2 ,由于产生#DB异常时cs.cpl为3(即在user-mode产生的异常),所以当返回到 nt!KiFastCallEntry2时,会产生一个#PF(STATUS_ACCESS_VIOLATION)异常。如果 jmp的目标地址与产生异常的地址不相同,则说明jmp的目标地址为KiFastCallEntry的地址。

利用以上方法遍历所有内核地址空间可用来查找Nt!KiFastCallEntry的地址。

当magic value为下面值时,KiTrap0E同样存在上面的问题,而且只需要调用 jmp 0x12345678 指令即可:

nt!KiSystemServiceCopyArguments
nt!KiSystemServiceAccessTeb
nt!ExpInterlockedPopEntrySListFault

Nt!KiTrap0E处理KiSystemServiceAccessTeb时会出现更严重的问题:

;
;   Did the fault occur in KiSystemService while copying arguments from
;   user stack to kernel stack?
;

Kt0e05a:mov     ecx, offset FLAT:KiSystemServiceCopyArguments
        cmp     [ebp].TsEip, ecx
        je      short Kt0e06

        mov     ecx, offset FLAT:KiSystemServiceAccessTeb
        cmp     [ebp].TsEip, ecx
        jne     short Kt0e07

        mov     ecx, [ebp].TsEbp        ; (eax)->TrapFrame of SysService
        test    [ecx].TsSegCs, MODE_MASK; 此处会使用 TrapFrame的ebp
        jz      short Kt0e07            ; caller of SysService is k mode, we
                                        ; will let it bugcheck.
        mov     [ebp].TsEip, offset FLAT:kss61
        mov     eax, STATUS_ACCESS_VIOLATION
        mov     [ebp].TsEax, eax
        jmp     _KiExceptionExit

由以上代码可知:当eip等于KiSystemServiceAccessTeb时,会将TrapFrame.Ebp作为新的TrapFrame的起始地址,且TrapFrame.Ebp是我们可以控制的。所以我们可以构造ebp,使KiTrapFrame引用 KiTrapFrame.Ebp时造成系统崩溃。

另一个问题, 利用自定义的LDT泄露内核栈指针部分内容

TPR

段描述符的 D/B位根据段描述符是一个可执行代码段、向下扩展数据段或者是堆栈段,有不同的含义。这个标志决定默认操作数大小,堆栈指针大小(ESP/SP),向下扩展的数据段的下限(4G/64K)。

在每个中断处理函数或系统调用返回时都会调用iret/iretd由kernel-mode返回到user-mode。

iretd指令的部分算法如下:

IF stack segment is big (Big=1)
THEN
        ESP ←tempESP
ELSE
        SP ←tempSP
FI;

可以构造 LDT,使用数据段描述符的B位为0,当调用iretd从ring0返回ring3时, esp的高16位没有被清除,泄露了内核栈指针的部分地址。

参考