Intel Sysret --- CVE-2012-0217
Posted dreamoneonly
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Intel Sysret --- CVE-2012-0217相关的知识,希望对你有一定的参考价值。
CVE-2012-0217
漏洞成因
Intel CPU中的sysret指令在返回的三环地址是不规范地址时会产生#GP异常
canonical addresses:
? 0x0000000000000000-0x00007fffffffffff
? 0xffff800000000000-0xffffffffffffffff
在AMD64中只实现了48位有效的虚拟地址,因此处理器规定高16位必须根据第47位的符号位进行符号扩展,这样的地址才被称为canonical addresses
IF (CS.L =? 1 ) or (IA32_EFER.LMA =? 1) or (IA32_EFER.SCE =? 1)
(* Not in 64-Bit Mode or SYSCALL/SYSRET not enabled in IA32_EFER *)
THEN #UD; FI;
IF (CPL =? 0) THEN #GP(0); FI;
IF (operand size is 64-bit)
THEN (* Return to 64-Bit Mode *)
IF (RCX is not canonical) THEN #GP(0);
RIP ← RCX;
ELSE (* Return to Compatibility Mode *)
RIP ← ECX;
.......
但是由于sysret指令并不负责栈切换和GS切换,因此在产生#GP异常时栈已经被切换为用户态的栈并且GS已经指向了teb,由于执行#GP异常处理时仍然在0环,因此存在利用栈数据或teb数据有进行内核代码执行的可能
在Windows UMS中可以通过修改SchedulerProc为uncanonical地址,来导致sysret时产生#GP异常
UMS浅析
-
跟踪EnterUmsSchedulingMode函数
在EnterUmsSchedulingMode->RtlpAttachThreadToUmsCompletionList中发现一处比较重要的NtSetInformationThread调用
? 在NtSetInformationThread (class information = 31)->PspAttachThreadToUmsCompletionList->KeInitializeUmsThread中存在初始化_KThread->_UMS_CONTROL_BLOCK相关代码
? 填充_KThread->_UMS_CONTROL_BLOCK->_RTL_UMS_CONTEXT,并且_RTL_UMS_CONTEXT为三环地址,因此这里存在从三环修改_RTL_UMS_CONTEXT->_CONTEXT->Rip的可能
__int64 __fastcall KeInitializeUmsThread(char *Kthread, int a2, __int64 a3, __int64 a4, __int64 a5)
{
...
_UMS_CONTROL_BLOCK = (char *)ExAllocatePoolWithTag(NonPagedPool, 0x98ui64, ‘smUK‘);
...
_UMS_CONTROL_BLOCK->_RTL_UMS_CONTEXT = a5 //a5从三环NtSetInformationThread传递而来
...
_KThread->ucb = _UMS_CONTROL_BLOCK;
...
}
? 在EnterUmsSchedulingMode->RtlpUmsPrimaryContextWrap中填充了_KThread->_UMS_CONTROL_BLOCK->_RTL_UMS_CONTEXT中的Rip Rsp等成员,上面已经提到过_KThread->_UMS_CONTROL_BLOCK->_RTL_UMS_CONTEXT这是个三环地址,因此我们可以在这里做一个hook,替换Rip为一个uncanonical address
? 通过调试可以看到EnterUmsSchedulingMode->RtlpUmsPrimaryContextWrap中的Context和_KThread->_UMS_CONTROL_BLOCK->_RTL_UMS_CONTEXT是同一个物理页的不同映射,因此设置_RTL_UMS_CONTEXT->_CONTEXT->Rip并不是通过系统调用进入内核设置而是直接在环三直接进行设置
- 当EnterUmsSchedulingMode->RtlpUmsPrimaryContextWrap设置完Rip Rsp Rbp R11后,会通过call r11调用我们设置的UMS scheduler Proc,在我们设置的调度程序中通过调用ExecuteUmsThread执行当前被调度的Ums worker Thread,然后我们在Ums worker Thread中调用UmsThreadYield后会随即进入内核执行ZwUmsThreadYield,进行一系列分发后会通过sysret返回至_KThread->_UMS_CONTROL_BLOCK->_RTL_UMS_CONTEXT->_CONTEXT->Rip所指向的位置,即在EnterUmsSchedulingMode->RtlpUmsPrimaryContextWrap中设置的Rip(loc_77480493),因此我们可以通过hook这个位置改写Rip为Uncanonical address来产生一个#GP异常
- 如下MSDN所说:
- 当一个UMS工作线程调用UmsThreadYield或进入系统调用、缺页中断处理时会进入KiUmsCallEntry、KiUmsTrapEntry并最终通过sysret返回至UMS scheduler Proc
- 当一个非UMS线程通过调用ExecuteUmsThread转换为一个UMS调度线程从而调用UMS scheduler Proc,调用时机在上面已经提到过,是在EnterUmsSchedulingMode->RtlpUmsPrimaryContextWrap中进行调用的
https://docs.microsoft.com/en-us/windows/win32/procthread/user-mode-scheduling#ums-best-practices
An application‘s scheduler entry point function is implemented as a UmsSchedulerProc function. The system calls the application‘s scheduler entry point function at the following times:
- When a non-UMS thread is converted to a UMS scheduler thread by calling EnterUmsSchedulingMode.
- When a UMS worker thread calls UmsThreadYield.
- When a UMS worker thread blocks on a system service such as a system call or a page fault.
- 接下来我们来分析一下当UMS worker Thread调用UmsThreadYield后是如何返回至用户层UMS scheduler Proc的,通过调用UmsThreadYield其实是等价于在系统调用中进入KiUmsCallEntry,因为ZwUmsThreadYield也只是简单的调用了KiServiceInternal
void ZwUmsThreadYield()
{
unsigned __int64 v0; // rt0
_disable();
v0 = __readeflags();
KiServiceInternal();
}
- KiUmsCallEntry与KiUmsTrapEntry基本一致,我们这里以KiUmsCallEntry为例分析,在KiUmsCallEntry->KiSwapToUmsThread->KeBuildPrimaryThreadContext中通过读取之前在EnterUmsSchedulingMode->RtlpUmsPrimaryContextWrap设置的KThread->_UMS_CONTROL_BLOCK->_RTL_UMS_CONTEXT->_CONTEXT并将其写入栈上的TrapFrame
__int64 __fastcall KeBuildPrimaryThreadContext(char *a1, __int64 _rbp, __int64 a3, int a4, __int64 a5, __int64 a6)
{
__int64 v6; // rbx
__int64 v7; // r9
_RTL_UMS_CONTEXT *UmsContext; // r11
unsigned __int64 v9; // rcx
unsigned __int64 v10; // rcx
unsigned __int64 v11; // rcx
unsigned __int64 v12; // rcx
_KTRAP_FRAME *TrapFrame; // rdx
_QWORD *v14; // rcx
unsigned __int64 v15; // rcx
unsigned __int64 v16; // rcx
unsigned __int64 v17; // rcx
unsigned __int64 v18; // rcx
...
UmsContext = (_RTL_UMS_CONTEXT *)**((_QWORD **)a1 + 0x37); //_KThread->Ucb->UmsContext
...
else
{
TrapFrame = *(_KTRAP_FRAME **)(_rbp + 0x50);
v14 = *(_QWORD **)(v7 + 88);
TrapFrame->Rip = UmsContext->Context.Rip;
TrapFrame->Rsp = UmsContext->Context.Rsp;
TrapFrame->Rbp = UmsContext->Context.Rbp;
TrapFrame->SegCs = 51;
TrapFrame->SegSs = 43;
...
return 0i64;
}
然后通过KiUmsCallEntry->KiUmsFastReturnToUser返回
至此整个UMS的调度流程已经粗略的分析完了,如果我们之前在EnterUmsSchedulingMode->RtlpUmsPrimaryContextWrap改写了Rip,在这里就能成功的通过sysret产生一个#GP异常
利用浅析
我们注意到当sysret产生#GP异常时已经执行了swapgs并且栈也已经切换,gs即指向的是用户态的teb,我们来跟进一下#GP异常处理函数KiGeneralProtectionFault
在KiGeneralProtectionFault->KiExceptionDispatch->KiDispatchException->KeBugCheckEx->RtlCaptureContext中会取出gs:[0x20]中的值作为一个指针并向其写入内容,由于#GP异常是在内核中产生的,因此在KiGeneralProtectionFault中检查cs是来自内核态的调用后不会调用swapgs,所以现在的gs仍然是指向的teb,而teb[0x20]位置处明显不是一个指针,因此这里会产生一个#PF异常
在页面错误处理程序KiPageFault我们可以通过申请零地址页面来控制某些执行流程,通过布置零地址偏移0x4C处的数据来绕过KiUmsTrapFrame
并最终到达KiCheckForKernelApcDelivery,通过call r11调用内核APC,通过观察可以发现r11就是零地址偏移0x10的位置,因此我们只需要将shellcode的地址填入零地址偏移0x10的位置即可执行内核权限代码
实验
参考
https://www.52pojie.cn/forum.php?mod=viewthread&tid=174982
https://docs.microsoft.com/en-us/windows/win32/procthread/user-mode-scheduling#ums-best-practices
https://xenproject.org/2012/06/13/the-intel-sysret-privilege-escalation/
https://github.com/SecWiki/windows-kernel-exploits/tree/master/MS12-042
以上是关于Intel Sysret --- CVE-2012-0217的主要内容,如果未能解决你的问题,请参考以下文章
Mysql身份认证绕过漏洞(CVE-2012-2122)复现
Android内核sys_setresuid() Patch提权(CVE-2012-6422)