About Us | Contact Us    

 


 

VUPEN Research

 
  VUPEN Research Team
  VUPEN Research Blog
  VUPEN Research Videos
 
   

VUPEN Vulnerability Research Team (VRT) Blog

 
Advanced Exploitation of Windows Kernel Intel 64-Bit Mode Sysret Vulnerability (MS12-042)
 
Published on 2012-08-06 16:48:29 UTC by Matthieu Bonetti, Security Researcher @ VUPEN

Twitter LinkedIn Delicious Digg   

Hi everyone,

In this new blog, we will share our advanced exploitation methods on
Windows 7 SP1 x64 and Windows Server 2008 R2 SP1 x64 to reliably take advantage of an awesome vulnerability discovered by Rafal Wojtczuk (Bromium) and Jan Beulich (SUSE).

This flaw
allows privilege escalation and arbitrary code execution with ring0 permissions, and is present in many 64-bit operating systems and virtualization software running on Intel CPU hardware. Microsoft has patched the flaw as part of the MS12-042 security bulletin.


1. Brief Analysis of the Vulnerability

On x64 systems, AMD has decided that only the least significant 48 bits of a virtual address would be used in address translation. Moreover bits 48 through 63 of any virtual address must be copies of bit 47. If not, the processor raises an exception: protection fault #GP.

This splits the virtual addresses in two blocks:

- Canonical "higher half": 0xFFFFFF`FFFFFFFF    
 -> 0xFFFF8000`00000000
- Canonical "lower half": 
 0x00007FFF`FFFFFFFF -> 0x00000000`00000000

All addresses in between are considered non-canonical.

The SYSRET instruction is used to return back to user-mode. In order to do this, it copies the value from the RCX register to the RIP register and changes the code segment selector to user-mode. However, the RCX register is a generic purpose register and may contain any value, including non-canonical addresses. As well, the SYSRET instruction is not responsible for switching the stack back to userland nor it is for the GS segment register. This means that the system developer needs to explicitly switch GS and the RSP, RBP registers before calling SYSRET.

A vulnerability exists in the Intel implementation of the SYSRET instruction. Indeed, if a non-canonical address is found in the RCX register, a #GP is issued before the privilege level is changed, thus still in ring0 since GS, RBP and RSP are pointing to userland.

2. Triggering the Vulnerability on Windows

In order to trigger this vulnerability on Microsoft Windows, one may find a way to set the return address in userland to a non-canonical address. There may be multiple ways to achieve this:

- One would be to map memory and execute a syscall at 0x7FFF`FFFFFFFF. The returned address would be non-canonical. However, as Windows limits the address space this address is not reachable, thus this method does not work.
- Find a system call that lets the possibility to manually change the return address.

The latter solution can be used with User-Mode Scheduling (UMS). According to Microsoft, User-Mode Scheduling is a lightweight mechanism that applications can use to schedule their own threads. An application can switch between UMS threads in user mode without involving the system scheduler.

In order to use UMS threads, one may create an UMS completion list by calling the "CreateUmsCompletionList()" function. Then ordinary threads (with a custom UMS thread context attribute) are linked to the completion list. Once done, the scheduler thread needs to be created. It is just an ordinary thread that has converted itself to UMS by calling the "EnterUmsSchedulingMode()" function. A scheduler function needs to be defined.

In the case of an exploit, the scheduler function remains simple. If the UMS thread is started or blocked, the "ExecuteUmsThread()" function needs to be called. If not, the scheduler needs to determine what thread will run next. This goes by de-queuing the completion list and by choosing the right thread.

The "EnterUmsSchedulingMode()" function is implemented in ntdll.dll. From the assembly code, we can see that it gets the current UMS thread, attaches the completion list, save the thread context (the registers, &) and calls the "RtlpUmsPrimaryContextWrap()" function.

 
 .text:0000000078F33A20 RtlEnterUmsSchedulingMode
 .text:0000000078F33A20
 .text:0000000078F33A20 arg_0 = qword ptr 8
 .text:0000000078F33A20 arg_8 = qword ptr 10h
 .text:0000000078F33A20 arg_10 = qword ptr 18h
 .text:0000000078F33A20
 .text:0000000078F33A20 mov [rsp+arg_8], rbx
 .text:0000000078F33A25 mov [rsp+arg_10], rsi
 .text:0000000078F33A2A push rdi
 .text:0000000078F33A2B sub rsp, 20h
 .text:0000000078F33A2F mov rsi, [rcx+8]
 .text:0000000078F33A33 mov rbx, [rcx+10h]
  [...]
 .text:0000000078F33A5D loc_78F33A5D:
 .text:0000000078F33A5D mov rdx, rsi
 .text:0000000078F33A60 xor ecx, ecx
 .text:0000000078F33A62 call RtlpAttachThreadToUmsCompletionList
 .text:0000000078F33A67 test eax, eax
 .text:0000000078F33A69 js short loc_78F33AA0
 .text:0000000078F33A6B lea rcx, [rsp+28h+arg_0]
 .text:0000000078F33A70 call RtlGetCurrentUmsThread
 .text:0000000078F33A75 test eax, eax
 .text:0000000078F33A77 js short loc_78F33A94
 .text:0000000078F33A79 mov rcx, [rsp+28h+arg_0]
 .text:0000000078F33A7E call RtlpSaveUmsDebugRegisterState
 .text:0000000078F33A83 test eax, eax
 .text:0000000078F33A85 js short loc_78F33A94
 .text:0000000078F33A87 mov rdx, rdi
 .text:0000000078F33A8A mov rcx, rbx
 .text:0000000078F33A8D call RtlpUmsPrimaryContextWrap
 .text:0000000078F33A92 jmp short $+2

 

The "RtlpUmsPrimaryContextWrap()" function is responsible of calling the scheduler function and saving, in an undocumented structure, information about the state of the thread: RSP, RBP, and the return address. The return address points to the middle of the "RtlpUmsPrimaryContextWrap()" function just before calling the scheduler function.

 
 .text:0000000078EA03F0 RtlpUmsPrimaryContextWrap
 .text:0000000078EA03F0
 .text:0000000078EA03F0 var_108 = xmmword ptr -108h
 .text:0000000078EA03F0 var_F8 = xmmword ptr -0F8h
 .text:0000000078EA03F0 var_E8 = xmmword ptr -0E8h
 .text:0000000078EA03F0 var_D8 = xmmword ptr -0D8h
 .text:0000000078EA03F0 var_C8 = xmmword ptr -0C8h
 .text:0000000078EA03F0 var_38 = byte ptr -38h
  [...]
 .text:0000000078EA0452 mov [rax+30h], r15
 .text:0000000078EA0456 mov r10, gs:14A0h
 .text:0000000078EA045F lea r10, [r10+10h]
 .text:0000000078EA0463
lea r11, loc_78EA0493
 .text:0000000078EA046A mov [r10+0D0h], rcx
 .text:0000000078EA0471 mov [r10+0A0h], rbp
 .text:0000000078EA0478 mov [r10+98h], rsp
 .text:0000000078EA047F mov [r10+0F8h], r11                 // saved return
 .text:0000000078EA0486 mov r12, 0
 .text:0000000078EA048D xor r13, r13
 .text:0000000078EA0490 mov r14, rdx
 .text:0000000078EA0493
 .text:0000000078EA0493
loc_78EA0493:
 .text:0000000078EA0493 mov r10, gs:14A0h
 .text:0000000078EA049C lea r10, [r10+10h]
 .text:0000000078EA04A0 mov r11, [r10+0D0h]               // scheduler func
 .text:0000000078EA04A7 mov rcx, r12
 .text:0000000078EA04AA mov rdx, r13
 .text:0000000078EA04AD mov r8, r14
 .text:0000000078EA04B0 call r11                                     // calls the scheduler function
 .text:0000000078EA04B3 lea rcx, [rsp+138h+var_38]
 .text:0000000078EA04BB movaps xmm6, [rsp+138h+var_108]
 .text:0000000078EA04C0 movaps xmm7, [rsp+138h+var_F8]
 

Indeed if we debug the kernel and break on the "KeBuildPrimaryThreadContext()" function from ntoskrnl.exe, we can see the 0x00000000`78EA0493 value being saved as the return to user land address.

 
 PAGE:FFFFF80002C5A070 KeBuildPrimaryThreadContext
 PAGE:FFFFF80002C5A070
 PAGE:FFFFF80002C5A070 arg_0 = qword ptr 8
 PAGE:FFFFF80002C5A070 arg_20 = qword ptr 28h
 PAGE:FFFFF80002C5A070 arg_28 = qword ptr 30h
 PAGE:FFFFF80002C5A070
 PAGE:FFFFF80002C5A070 mov [rsp+arg_0], rbx
 PAGE:FFFFF80002C5A075 movsxd rbx, r9d
 PAGE:FFFFF80002C5A078 mov r9, rdx
 PAGE:FFFFF80002C5A07B xor r10d, r10d
 PAGE:FFFFF80002C5A07E mov rax, [rcx+1B8h]
 PAGE:FFFFF80002C5A085 mov r11, [rax]
 PAGE:FFFFF80002C5A088 cmp r8, r10
 PAGE:FFFFF80002C5A08B jz loc_FFFFF80002C5A168
 [...]
 PAGE:FFFFF80002C5A168 loc_FFFFF80002C5A168:
 PAGE:FFFFF80002C5A168 mov rdx, [rdx+50h]
 PAGE:FFFFF80002C5A16C mov rcx, [r9+58h]
 PAGE:FFFFF80002C5A170 mov rax, [r11+108h]
 PAGE:FFFFF80002C5A177 mov [rdx+168h], rax               // saved RIP
 PAGE:FFFFF80002C5A17E mov rax, [r11+0A8h]
 PAGE:FFFFF80002C5A185 mov [rdx+180h], rax
 PAGE:FFFFF80002C5A18C mov rax, [r11+0B0h]
 PAGE:FFFFF80002C5A193 mov [rdx+158h], rax
 PAGE:FFFFF80002C5A19A mov eax, 33h ; '3'
 PAGE:FFFFF80002C5A19F mov [rdx+170h], ax
 PAGE:FFFFF80002C5A1A6 mov eax, 2Bh ; '+'
 PAGE:FFFFF80002C5A1AB mov [rdx+188h], ax
 

The idea here is to corrupt this address with a non-canonical one before the "ExecuteUmsThread()" function is called: from the user scheduler function. The address where the return address is saved can be found via: GS:[0x14a0] + 0x10 + 0x1F

Ultimately, when the kernel has done its job, the "KiUmsFastReturnToUser()" function from ntoskrnl.exe is called. Before calling the SYSRET instruction, the RSP, RBP, and GS registers are swapped to their saved value. The return to user land address is moved to the RCX register and SYSRET is called.

 
 .text:FFFFF800028DD440 KiUmsFastReturnToUser
 .text:FFFFF800028DD440
 .text:FFFFF800028DD440 var_5046 = dword ptr -5046h
 .text:FFFFF800028DD440 var_4FA6 = byte ptr -4FA6h
 .text:FFFFF800028DD440 arg_42 = byte ptr 52h
 .text:FFFFF800028DD440 arg_80 = byte ptr 88h
 .text:FFFFF800028DD440 arg_190 = dword ptr 198h
 .text:FFFFF800028DD440
 .text:FFFFF800028DD440 sub rsp, 28h
 .text:FFFFF800028DD444 mov rbx, gs:+188h
 .text:FFFFF800028DD44D mov rcx, [rbx+1D8h]
 .text:FFFFF800028DD454 lea rbp, [rcx+80h]
 .text:FFFFF800028DD45B mov rax, cr8
  [...]
 .text:FFFFF800028DD596 loc_FFFFF800028DD596:
 .text:FFFFF800028DD596 mov r8, [rbp+100h]                  // saved RSP
 .text:FFFFF800028DD59D mov r9, [rbp+0D8h]                 // saved RBP
 .text:FFFFF800028DD5A4 xor edx, edx
 .text:FFFFF800028DD5A6 pxor xmm0, xmm0
 .text:FFFFF800028DD5AA pxor xmm1, xmm1
 .text:FFFFF800028DD5AE pxor xmm2, xmm2
 .text:FFFFF800028DD5B2 pxor xmm3, xmm3
 .text:FFFFF800028DD5B6 pxor xmm4, xmm4
 .text:FFFFF800028DD5BA pxor xmm5, xmm5
 .text:FFFFF800028DD5BE mov rcx, [rbp+0E8h]               // saved ret address
 .text:FFFFF800028DD5C5 mov r11, [rbp+0F8h]
 .text:FFFFF800028DD5CC mov rbp, r9
 .text:FFFFF800028DD5CF mov rsp, r8
 .text:FFFFF800028DD5D2 swapgs                                    // switch GS to user
 .text:FFFFF800028DD5D5 sysret
 .text:FFFFF800028DD5D8 db 66h, 66h, 66h, 66h, 66h, 66h
 .text:FFFFF800028DD5D8 nop word ptr [rax+rax+00000000h]
 .text:FFFFF800028DD5E7 db 66h, 66h, 66h, 66h, 66h, 66h

 

However, if the value in RCX is not canonical, a #GP exception is thrown in a privileged mode. Depending on the values of GS and the stack, a kernel memory corruption occurs leading to a reboot of the system.

3. Advanced Exploitation on Windows 7 & Windows 2008 R2 x64

When the #GP exception occurs, RSP and RBP are controlled and the GS segment selector points to user land. The protection fault handler is triggered by the "KiGeneralProtectionFault()" function.

One way to exploit this vulnerability would be to take advantage of the controlled RBP and RSP registers. Indeed the "KiGeneralProtectionFault()" function writes values on the stack and the address of the stack is controlled. This can be used for a write-4 exploitation technique.


 
 .text:FFFFF800028DBAC0 KiGeneralProtectionFault
 .text:FFFFF800028DBAC0
 .text:FFFFF800028DBAC0 var_12D = byte ptr -12Dh
 .text:FFFFF800028DBAC0 var_12C = dword ptr -12Ch
 .text:FFFFF800028DBAC0 var_128 = qword ptr -128h
 .text:FFFFF800028DBAC0 var_120 = qword ptr -120h
 .text:FFFFF800028DBAC0 var_118 = qword ptr -118h
 .text:FFFFF800028DBAC0 var_110 = qword ptr -110h
 .text:FFFFF800028DBAC0 var_108 = qword ptr -108h
 .text:FFFFF800028DBAC0 var_100 = qword ptr -100h
 .text:FFFFF800028DBAC0 var_F8 = qword ptr -0F8h
 .text:FFFFF800028DBAC0 var_E8 = xmmword ptr -0E8h
 .text:FFFFF800028DBAC0 var_D8 = xmmword ptr -0D8h
 .text:FFFFF800028DBAC0 var_C8 = xmmword ptr -0C8h
 .text:FFFFF800028DBAC0 var_B8 = xmmword ptr -0B8h
 .text:FFFFF800028DBAC0 var_A8 = xmmword ptr -0A8h
 .text:FFFFF800028DBAC0 var_98 = xmmword ptr -98h
 .text:FFFFF800028DBAC0 var_58 = word ptr -58h
 .text:FFFFF800028DBAC0 arg_0 = qword ptr 10h
 .text:FFFFF800028DBAC0 arg_8 = byte ptr 18h
 .text:FFFFF800028DBAC0 arg_10 = qword ptr 20h
 .text:FFFFF800028DBAC0 arg_24 = dword ptr 34h
 .text:FFFFF800028DBAC0
 .text:FFFFF800028DB600 push rbp
 .text:FFFFF800028DB601 sub rsp, 158h
 .text:FFFFF800028DB608 lea rbp, [rsp+80h]
 .text:FFFFF800028DB610 mov [rbp+0D8h+var_12D], 1
 .text:FFFFF800028DB614 mov [rbp+0D8h+var_128], rax
 .text:FFFFF800028DB618 mov [rbp+0D8h+var_120], rcx
 .text:FFFFF800028DB61C mov [rbp+0D8h+var_118], rdx
 .text:FFFFF800028DB620 mov [rbp+0D8h+var_110], r8
 .text:FFFFF800028DB624 mov [rbp+0D8h+var_108], r9
 .text:FFFFF800028DB628 mov [rbp+0D8h+var_100], r10
 .text:FFFFF800028DB62C mov [rbp+0D8h+var_F8], r11
 .text:FFFFF800028DB630 test [rbp+0D8h+arg_8], 1
 .text:FFFFF800028DB637 jz short loc_FFFFF800028DB65A
 .text:FFFFF800028DB639 swapgs
 .text:FFFFF800028DB63C mov r10, gs:188h
 .text:FFFFF800028DB645 test byte ptr [r10+3], 3
 .text:FFFFF800028DB64A mov [rbp+0D8h+var_58], 0
 

However, the data around the targeted memory is corrupted, which makes this exploitation method unreliable.

A better way is to take advantage of the GS segment register pointing to user land. The idea here is to trick the kernel into calling a function that has been referenced from GS. Such a function can be reached from the Page Fault handler. All that needs to be done is to generate this exception.

This can be done following the code flow until the GS segment register is used. The "KiGeneralProtectionFault()" continues and the "KiBugCheckDispatch()" function is called.

 
 .text:FFFFF800028DB65A cld
 .text:FFFFF800028DB65B stmxcsr [rbp+0D8h+var_12C]
 .text:FFFFF800028DB65F ldmxcsr dword ptr gs:180h
 .text:FFFFF800028DB668 movaps [rbp+0D8h+var_E8], xmm0
 .text:FFFFF800028DB66C movaps [rbp+0D8h+var_D8], xmm1
 .text:FFFFF800028DB670 movaps [rbp+0D8h+var_C8], xmm2
 .text:FFFFF800028DB674 movaps [rbp+0D8h+var_B8], xmm3
 .text:FFFFF800028DB678 movaps [rbp+0D8h+var_A8], xmm4
 .text:FFFFF800028DB67C movaps [rbp+0D8h+var_98], xmm5
 .text:FFFFF800028DB680 mov eax, [rbp+0E0h]
 .text:FFFFF800028DB686 test [rbp+0D8h+arg_10], 200h
 .text:FFFFF800028DB691 jz short loc_FFFFF800028DB694
 .text:FFFFF800028DB693 sti
 .text:FFFFF800028DB694 loc_FFFFF800028DB694:
 .text:FFFFF800028DB694 mov r10, [rbp+0D8h+arg_0]
 .text:FFFFF800028DB69B mov r9, cr4
 .text:FFFFF800028DB69F mov r8, cr0
 .text:FFFFF800028DB6A3 mov edx, 8
 .text:FFFFF800028DB6A8 mov ecx, 7Fh
 .text:FFFFF800028DB6AD call KiBugCheckDispatch

 

This function calls "KeBugCheckEx()".

 
 .text:FFFFF800028DD180 KiBugCheckDispatch
 .text:FFFFF800028DD180
 .text:FFFFF800028DD180 var_118= qword ptr -118h
 .text:FFFFF800028DD180 var_108= xmmword ptr -108h
 .text:FFFFF800028DD180 var_F8= xmmword ptr -0F8h
 .text:FFFFF800028DD180 var_E8= xmmword ptr -0E8h
 .text:FFFFF800028DD180 var_D8= xmmword ptr -0D8h
 .text:FFFFF800028DD180 var_C8= xmmword ptr -0C8h
 .text:FFFFF800028DD180 var_38= byte ptr -38h
 .text:FFFFF800028DD180
 .text:FFFFF800028DD180 sub rsp, 138h
 .text:FFFFF800028DD187 lea rax, [rsp+138h+var_38]
 .text:FFFFF800028DD18F movaps [rsp+138h+var_108], xmm6
 .text:FFFFF800028DD194 movaps [rsp+138h+var_F8], xmm7
 .text:FFFFF800028DD199 movaps [rsp+138h+var_E8], xmm8
  [...]
 .text:FFFFF800028DD1D3 mov [rax+20h], r13
 .text:FFFFF800028DD1D7 mov [rax+28h], r14
 .text:FFFFF800028DD1DB mov [rax+30h], r15
 .text:FFFFF800028DD1DF mov [rsp+138h+var_118], r10
 .text:FFFFF800028DD1E4 call KeBugCheckEx
 .text:FFFFF800028DD1E4 KiBugCheckDispatch endp

 

The "KeBugCheckEx()" function needs to save the control state registers into a memory structure referenced from the GS register.

 
 .text:FFFFF800028DDC40 KeBugCheckEx
 .text:FFFFF800028DDC40
 .text:FFFFF800028DDC40 var_18= qword ptr -18h
 .text:FFFFF800028DDC40 var_10= qword ptr -10h
 .text:FFFFF800028DDC40 var_8= qword ptr -8
 .text:FFFFF800028DDC40 arg_0= qword ptr 8
 .text:FFFFF800028DDC40 arg_8= qword ptr 10h
 .text:FFFFF800028DDC40 arg_10= qword ptr 18h
 .text:FFFFF800028DDC40 arg_18= qword ptr 20h
 .text:FFFFF800028DDC40 arg_20= qword ptr 28h
 .text:FFFFF800028DDC40 arg_28= byte ptr 30h
 .text:FFFFF800028DDC40
 .text:FFFFF800028DDC40 mov [rsp+arg_0], rcx
 .text:FFFFF800028DDC45 mov [rsp+arg_8], rdx
 .text:FFFFF800028DDC4A mov [rsp+arg_10], r8
 .text:FFFFF800028DDC4F mov [rsp+arg_18], r9
 .text:FFFFF800028DDC54 pushfq
 .text:FFFFF800028DDC55 sub rsp, 30h
 .text:FFFFF800028DDC59 cli
 .text:FFFFF800028DDC5A mov rcx, gs:20h
 .text:FFFFF800028DDC63 mov rcx, [rcx+4BD8h]
 .text:FFFFF800028DDC6A call RtlCaptureContext
 .text:FFFFF800028DDC6F mov rcx, gs:20h
 .text:FFFFF800028DDC78 add rcx, 40h
 .text:FFFFF800028DDC7C call KiSaveProcessorControlState
 .text:FFFFF800028DDC81 mov r10, gs:20h
 .text:FFFFF800028DDC8A mov r10, [r10+4BD8h]
 .text:FFFFF800028DDC91 mov rax, [rsp+38h+arg_0]
 .text:FFFFF800028DDC96 mov [r10+80h], rax
 .text:FFFFF800028DDC9D mov rax, [rsp+38h+var_8]

 

Indeed, the "KeSaveProcessorControlState()" function tries to save the content of the cr0 register at GS:0x20.

 
 .text:FFFFF800028DDF70 KiSaveProcessorControlState
 .text:FFFFF800028DDF70 mov rax, cr0
 .text:FFFFF800028DDF73 mov [rcx], rax
 .text:FFFFF800028DDF76 mov rax, cr2
 .text:FFFFF800028DDF79 mov [rcx+8], rax
 .text:FFFFF800028DDF7D mov rax, cr3
 .text:FFFFF800028DDF80 mov [rcx+10h], rax
 .text:FFFFF800028DDF84 mov rax, cr4
 

This can be used to generate a page fault. Indeed, the GS register points to user land, to the address 0 precisely. On Windows 7 SP1/SP0 x64 and Windows Server 2008 R2 SP1/SP0 x64, this address can be mapped from user land and thus the content at this address can be controlled. If a bad value such as 0x54545454`54545454 is placed at GS:0x20, a page fault occurs.

From the page fault handler, the goal is to go through the errors and to reach a call to the "KiCheckForKernelApcDelivery()" function. It mainly consists into setting a valid pointer at GS:0x188 (set at 0, used later).

 
 .text:FFFFF800028DBC00 KiPageFault
 .text:FFFFF800028DBC00
 .text:FFFFF800028DBC00 var_158= dword ptr -158h
 .text:FFFFF800028DBC00 var_138= dword ptr -138h
 .text:FFFFF800028DBC00 var_12E= byte ptr -12Eh
 .text:FFFFF800028DBC00 var_12D= byte ptr -12Dh
 .text:FFFFF800028DBC00 var_12C= dword ptr -12Ch
 .text:FFFFF800028DBC00 var_128= qword ptr -128h
  [...]
 .text:FFFFF800028DBC00 push rbp
 .text:FFFFF800028DBC01 sub rsp, 158h
 .text:FFFFF800028DBC08 lea rbp, [rsp+80h]
 .text:FFFFF800028DBC10 mov [rbp+0D8h+var_12D], 1
 .text:FFFFF800028DBC14 mov [rbp+0D8h+var_128], rax
 .text:FFFFF800028DBC18 mov [rbp+0D8h+var_120], rcx
 .text:FFFFF800028DBC1C mov [rbp+0D8h+var_118], rdx
 .text:FFFFF800028DBC20 mov [rbp+0D8h+var_110], r8
 .text:FFFFF800028DBC24 mov [rbp+0D8h+var_108], r9
 .text:FFFFF800028DBC28 mov [rbp+0D8h+var_100], r10
 .text:FFFFF800028DBC2C mov [rbp+0D8h+var_F8], r11
 .text:FFFFF800028DBC30 test byte ptr [rbp+0D8h+arg_8], 1
 .text:FFFFF800028DBC37 jz short loc_FFFFF800028DBCAD
 .text:FFFFF800028DBC39 swapgs
 .text:FFFFF800028DBC3C mov r10, gs:188h
 .text:FFFFF800028DBC45 cmp [rbp+0D8h+arg_8], 33h ; '3'
  [...]
 .text:FFFFF800028DBCAD loc_FFFFF800028DBCAD:
 ; KiPageFault+A6 j
 .text:FFFFF800028DBCAD cld
 .text:FFFFF800028DBCAE stmxcsr [rbp+0D8h+var_12C]
 .text:FFFFF800028DBCB2 ldmxcsr dword ptr gs:180h
 .text:FFFFF800028DBCBB movaps [rbp+0D8h+var_E8], xmm0
 .text:FFFFF800028DBCBF movaps [rbp+0D8h+var_D8], xmm1
 .text:FFFFF800028DBCC3 movaps [rbp+0D8h+var_C8], xmm2
 .text:FFFFF800028DBCC7 movaps [rbp+0D8h+var_B8], xmm3
 .text:FFFFF800028DBCCB movaps [rbp+0D8h+var_A8], xmm4
 .text:FFFFF800028DBCCF movaps [rbp+0D8h+var_98], xmm5
 .text:FFFFF800028DBCD3 mov eax, cs:KiCodePatchCycle
 .text:FFFFF800028DBCD9 mov [rbp+0D8h+arg_24], eax
 .text:FFFFF800028DBCDF mov eax, [rbp+0E0h]
 .text:FFFFF800028DBCE5 mov rcx, cr2
 .text:FFFFF800028DBCE8 test [rbp+0D8h+arg_10], 200h
 .text:FFFFF800028DBCF3 jz short loc_FFFFF800028DBCF6
 .text:FFFFF800028DBCF5 sti
  [...]
 .text:FFFFF800028DBCF6 loc_FFFFF800028DBCF6:
 .text:FFFFF800028DBCF6 mov r9, gs:188h
 .text:FFFFF800028DBCFF bt dword ptr [r9+4Ch], 0Bh
 .text:FFFFF800028DBD05 jnb short loc_FFFFF800028DBD15
 .text:FFFFF800028DBD07 test byte ptr [rbp+0F0h], 1
  [...]
 .text:FFFFF800028EACF2 loc_FFFFF800028EACF2:
 .text:FFFFF800028EACF2 mov r12, gs:188h
 .text:FFFFF800028EACFB mov [rbp+0D0h+var_78], rdi
 .text:FFFFF800028EACFF mov rcx, [r12+70h]
 .text:FFFFF800028EAD04 mov [rbp+0D0h+var_98], rcx
 .text:FFFFF800028EAD08 cmp dword ptr [rcx+438h], 10h
 .text:FFFFF800028EAD0F lea r14, [rcx+398h]
 .text:FFFFF800028EAD16 ja loc_FFFFF800028EB0E2
 .text:FFFFF800028EAD1C mov eax, cs:MiDelayPageFaults
 .text:FFFFF800028EAD22 test eax, eax
  [...]
 .text:FFFFF800028EC79C loc_FFFFF800028EC79C:
 .text:FFFFF800028EC79C call KiCheckForKernelApcDelivery
 .text:FFFFF800028EC7A1 jmp loc_FFFFF800028EAF75

 

The "KiCheckForKernelApcDelivery()" function calls "KiDeliverApc()":

 
 .text:FFFFF8000288AF10 KiCheckForKernelApcDelivery
 .text:FFFFF8000288AF10 push rbx
 .text:FFFFF8000288AF12 sub rsp, 20h
 .text:FFFFF8000288AF16 mov rax, cr8
 .text:FFFFF8000288AF1A mov ecx, 1
 .text:FFFFF8000288AF1F test al, al
 .text:FFFFF8000288AF21 jnz short loc_FFFFF8000288AF3F
 .text:FFFFF8000288AF23 xor ebx, ebx
 .text:FFFFF8000288AF25 mov cr8, rcx
 .text:FFFFF8000288AF29 xor r8d, r8d
 .text:FFFFF8000288AF2C xor edx, edx
 .text:FFFFF8000288AF2E xor ecx, ecx
 .text:FFFFF8000288AF30 call KiDeliverApc
 .text:FFFFF8000288AF35 mov cr8, rbx

 

This function is the one used to achieve code execution. Indeed, it takes the pointer at GS:0x188 and after a few dereference operations it sets the R11 register. This register is used to call a function.

 
 .text:FFFFF800028D1130 KiDeliverApc
 .text:FFFFF800028D1130
 .text:FFFFF800028D1130 var_78= dword ptr -78h
 .text:FFFFF800028D1130 var_58= qword ptr -58h
 .text:FFFFF800028D1130 var_50= qword ptr -50h
 .text:FFFFF800028D1130 var_48= qword ptr -48h
 .text:FFFFF800028D1130 var_40= qword ptr -40h
 .text:FFFFF800028D1130 arg_0= qword ptr 8
  [...]
 .text:FFFFF800028D115B loc_FFFFF800028D115B:
 .text:FFFFF800028D115B mov rbx, gs:188h
 .text:FFFFF800028D1164 mov r15, [rbx+1D8h]
 .text:FFFFF800028D116B mov r14, [rbx+70h]
 .text:FFFFF800028D116F mov [rbx+79h], r9b
 .text:FFFFF800028D1173 mov [rbx+1D8h], r8
 .text:FFFFF800028D117A cmp [rbx+1C6h], r9w
 .text:FFFFF800028D1182 jnz short loc_FFFFF800028D11A8
 .text:FFFFF800028D1184 lock or [rsp+78h+var_78], r9d
 .text:FFFFF800028D1189 lfence
 .text:FFFFF800028D118C lea rsi, [rbx+50h]
  [...]
 .text:FFFFF800028D11EC loc_FFFFF800028D11EC:
 .text:FFFFF800028D11EC mov r8, [rsi]
 .text:FFFFF800028D11EF cmp r8, rsi
 .text:FFFFF800028D11F2 jz loc_FFFFF800029296C7
 .text:FFFFF800028D11F8 mov [rbx+79h], r9b
 .text:FFFFF800028D11FC lea r10, [r8-10h]
 .text:FFFFF800028D1200 prefetchw byte ptr [r10]
 .text:FFFFF800028D1204 mov rcx, [r10+30h]
 .text:FFFFF800028D1208 mov r11, [r10+20h]
 .text:FFFFF800028D120C mov [rsp+78h+arg_10], rcx
 .text:FFFFF800028D1214 mov rax, [r10+38h]
  [...]
 .text:FFFFF800028D12B0 loc_FFFFF800028D12B0:
 .text:FFFFF800028D12B0 mov rax, [r8+8]
 .text:FFFFF800028D12B4 mov rdx, [r8]
 .text:FFFFF800028D12B7 mov [rax], rdx
 .text:FFFFF800028D12BA mov [rdx+8], rax
 .text:FFFFF800028D12BE mov [r10+52h], r9b
 .text:FFFFF800028D12C2 lock and [rbx+88h], r9
 .text:FFFFF800028D12CA movzx eax, r12b
 .text:FFFFF800028D12CE mov cr8, rax
 .text:FFFFF800028D12D2 lea rax, [rsp+78h+arg_18]
 .text:FFFFF800028D12DA lea r9, [rsp+78h+var_48]
 .text:FFFFF800028D12DF lea r8, [rsp+78h+var_40]
 .text:FFFFF800028D12E4 lea rdx, [rsp+78h+arg_10]
 .text:FFFFF800028D12EC mov rcx, r10
 .text:FFFFF800028D12EF mov [rsp+78h+var_58], rax
 .text:FFFFF800028D12F4 call r11
 .text:FFFFF800028D12F7 xor r9d, r9d
 .text:FFFFF800028D12FA jmp loc_FFFFF800028D1190

 

The R11 register points to a kernel shellcode and the kernel calls it, leading to immediate, silent, and highly reliable code execution with ring0 privileges.

Copyright VUPEN Security



 

VUPEN Solutions  

 


 

 

 

 

 

 

 

 

 

2004-2014 VUPEN Security - Copyright - Privacy Policy