漏洞描述
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泄露内核栈指针部分内容:
段描述符的 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位没有被清除,泄露了内核栈指针的部分地址。