Getting System Privileges Using Errors in NTVDM



    Backward compatibility is a good thing, but it should be used within reasonable limits. After all, you can still find code developed in the last century in the Windows kernel. Talking about his high security would be foolish. And we will prove it by the example of three privilage escalation of vulnerabilities that have taken root in the subsystem of the DOS virtual machine

    In 1978, Intel released the first x86 processor, the 8086, which provided a fairly limited environment for executing 16-bit code, known as Real mode. Soon after this, active development of software solutions for a new hardware platform began, both for operating systems and for ordinary programs running in them. Microsoft's Disk Operating System (DOS) has quickly established itself as the leading desktop desktop environment, and applications for this OS have been created and entered the market for more than ten years. The most famous examples are Norton Commander, ChiWriter or Quattro Pro. When developing the NT architecture for the Windows operating system in 1992,

    WARNING

    All information is provided for informational purposes only. Neither the editors nor the author are responsible for any possible harm caused by the materials in this article.


    NTVDM


    The special compatibility mode created in Windows turned out to be very functional. Due to the fact that it was a rather complex component both from the technical side and the logic of work, its appearance opened up many new opportunities for local users to launch attacks aimed at increasing their rights in the operating system. NTVDM has repeatedly found and fixed security problems, but so far there are many interesting and unexplored features in the kernel. In the next section, we will examine in detail one of them - specific handling of exceptions that occur in the NTVDM.EXE host process. Here, however, it is worth noting that any potential bug that can be found in the DOS compatibility subsystem will affect only 32-bit Windows platforms, since the 64-bit versions of the system do not support VDM due to the peculiarities of the implementation of the Long Mode mode, in which the 64-bit code is executed on Intel processors. In the new Windows 8 and 8.1 operating systems, the impact of potential kernel vulnerabilities is leveled due to the fact that this subsystem is disabled by default there, and administrative rights are required to run it. Nevertheless, these vulnerabilities can be successfully exploited without user intervention on systems from Windows XP to Windows 7 32-bit (and at the moment such systems presumably account for about 50% of the total fleet of desktop OS). and to run it, you need administrative rights. Nevertheless, these vulnerabilities can be successfully exploited without user intervention on systems from Windows XP to Windows 7 32-bit (and at the moment such systems presumably account for about 50% of the total fleet of desktop OS). and to run it, you need administrative rights. Nevertheless, these vulnerabilities can be successfully exploited without user intervention on systems from Windows XP to Windows 7 32-bit (and at the moment such systems presumably account for about 50% of the total fleet of desktop OS).

    Real time


    Maintaining backward compatibility with 16-bit applications for a modern 32-bit environment is very difficult from a technical point of view due to fundamental differences in operation between real and protected mode. This includes processor modes, the width of words and addresses, and the encoding of instructions, and many other things. In other words, using the standard tools of modern operating systems to run obsolete programs from the 80s does not work. On the other hand, switching the processor to real mode every time you launch a 16-bit program is also not an option, since this makes the basic settings of protected mode, such as separation of rights, senseless. Transfer of control to potentially untrusted code that runs in a virtually unlimited runtime and has direct access to the entire periphery of the computer,

    Virtual 8086 mode


    Intel engineers, who understood these and many other backward compatibility issues very well, developed another completely new execution mode, calling it Virtual 8086 (V8086), which became an important component of protected mode. The main feature of the V8086 mode is that for the code running in it, it is completely indistinguishable from the real mode, but at the same time it is completely controlled by the main operating system. This allows you to run old applications in a 32-bit environment while maintaining security and without negative side effects. This technology can be considered as a virtualization mechanism for DOS software, in which the operating system acts as a virtual machine monitor (VMM). VMM is responsible for creating a working environment and handling critical and privileged instructions, which the guest application uses, while the 16-bit code is executed in a special mode and at native speed. A typical Intel-developed order of execution for an operating system using the V8086 is shown in fig. 1.


    Fig. 1. Transfer of control of the operating system when running obsolete 16-bit applications

    In the case of Microsoft Windows, the entity “operating system” further breaks down into two components: the kernel and the NTVDM.EXE process, which operates at the user level. Support for the described subsystem is available at both levels - some events occurring inside outdated software are processed directly by the kernel, while others require some help at the ring 3 level (see Fig. 2). Due to the fact that the execution of old software code is isolated in a special process, the kernel can easily determine whether a particular processor event needs separate processing, depending on whether it comes from a VDM host or not. As a result, most ring 0 level procedures provide additional functionality when called from special processes; As one of the striking examples, we can mention trap handlers for x86 (such as nt! KiTrap0c, nt! KiTrap0d, nt! KiTrap0e), NT system calls (for example, nt! NtVdmControl) and win32k.sys system calls ( e.g. nt! NtUserInitTask). It is important to note that although the NTVDM.EXE process is processed by the system in a special way, it still inherits the security token of the local user; this, in turn, allows an attacker to execute arbitrary code within the process using only two documented API functions - OpenProcess and CreateRemoteThread - in order to exploit a hypothetical vulnerability in those parts of the kernel that are responsible for VDM support. NtUserInitTask). It is important to note that although the NTVDM.EXE process is processed by the system in a special way, it still inherits the security token of the local user; this, in turn, allows an attacker to execute arbitrary code within the process using only two documented API functions - OpenProcess and CreateRemoteThread - in order to exploit a hypothetical vulnerability in those parts of the kernel that are responsible for VDM support. NtUserInitTask). It is important to note that although the NTVDM.EXE process is processed by the system in a special way, it still inherits the security token of the local user; this, in turn, allows an attacker to execute arbitrary code within the process using only two documented API functions - OpenProcess and CreateRemoteThread - in order to exploit a hypothetical vulnerability in those parts of the kernel that are responsible for VDM support.



    Fig. 2. An example of runtime execution execution runtime 16-bit applications in Microsoft Windows

    DPMI


    Finally, we must not forget that NTVDM supports most of the specifications of the DOS Protected Mode Interface (DPMI), that is, in addition to implementing Virtual 8086 mode, it can also provide a runtime environment (for example, create memory segments) and execute code protected mode. Therefore, code with support for processing 32-bit instructions in the kernel may well appear in addition to simple 16-bit emulation.

    CVE-2013-3196 (write-what-where in nt! PushInt)


    General protection fault


    One of the most important features of the Virtual 8086 mode, as well as the working environment created by NTVDM.EXE to execute obsolete 32-bit code with DPMI support, is that any attempt to execute a critical or elevated instruction (such as INT, OUT or STI) will immediately result in a General Protection Fault exception in the kernel. As noted above, after that, the operating system should emulate the operation of the instruction safely and return to the execution of the interrupted guest code, ensuring continued execution. As it turned out, the code for emulating instructions for 16- and 32-bit emulation modes is completely in the kernel space, which opens up interesting possibilities for attack: programmatically reproduce the behavior of special x86 instructions. In order to get into the emulator, the following conditions must be met:
    1. The exception  #GP occurs inside Virtual 8086 mode (the EFlags.VM flag is set) OR The #GP exception occurs in user mode (ring 3) and
    2. The cs: segment selector is not equal  KGDT_R3_CODE (0x1b) at the time of the exception and
    3. #GP exception occurs in the VDM host process.




    Fig. 3. Virtual 8086 mode instruction dispatch table used by the #GP handler.

    If any of the options is fully executed, the #GP handler transfers control to the internal procedure nt!VdmDispatchOpcode_try, which performs basic decoding of the failed instruction and calls one or more handler functions applicable to this instruction. Lists of handlers for emulation modes of 16 and 32 bits are shown in Fig. 3 and 4; as you can see, the kernel produces a very long list of instructions and their prefixes. In our opinion, until this year, this part of the code, most likely, has never before been tested for vulnerabilities, which made it a serious target for research. After this “discovery”, we decided to reverse engineer all processors in search of potential flaws and got the first results within the next few hours. The first vulnerability was in the emulation layer of the INT instruction for protected mode.



    Fig. 4. The protected mode instruction dispatch table used by the #GP handler


    Where is the dog buried


    The basic role of the nt! OpcodeINTnn handler function is that it retrieves the operand imm8 of the instruction that caused the failure, and then calls another nt! PushInt internal procedure. Its main task is to get the base address of the user ss: segment and put the address of the system interrupt handler on the stack (in the user's address space) using the full address of the ss: esp stack pointer. The C code obtained by reverse engineering, which is responsible for placing three 16-bit or 32-bit words on the stack (depending on the guest execution mode), is given below.

    if (reginfo->ViFlags & VDM_INT_32) { 
     *(DWORD *)(reginfo->RiSsBase + NewVdmEsp + 0) = reginfo->RiEip; 
     *(DWORD *)(reginfo->RiSsBase + NewVdmEsp + 4) = trap_frame->SegCs; 
     *(DWORD *)(reginfo->RiSsBase + NewVdmEsp + 8) = GetVirtualBits(reginfo->RiEFlags); 
    } else { 
     *(WORD *)(reginfo->RiSsBase + NewVdmEsp + 0) = reginfo->RiEip; 
     *(WORD *)(reginfo->RiSsBase + NewVdmEsp + 2) = trap_frame->SegCs; 
     *(WORD *)(reginfo->RiSsBase + NewVdmEsp + 4) = GetVirtualBits(reginfo->RiEFlags); 
    }
    


    The problem with the implementation was that the pointer to the user space stack (ring 3) was not checked for correctness at all, probably due to the assumption that it would always point to the address space of the NTVDM.EXE process. And this, of course, is not always the case, since the exploit can set the esp register to any arbitrary pointer, for example, to the address belonging to the kernel. Thus, to exploit the vulnerability, it was enough to follow only two simple instructions in the context of the DOS virtual machine:  mov esp, 0xdeadbeef and then  int 0. The only limitations were that and  cs:, and ss: must select code and data segments within the Local Descriptor Table (LDT), and the argument of the int instruction must be a kernel level interrupt (any value between 0–255, except for the sequence 0x2a – 0x2e). The result of running the two described instructions on an unpatched Windows 7 SP1 is shown below:

    TRAP_FRAME: a2ea4c24 -- (.trap 0xffffffffa2ea4c24)
    ErrCode = 00000002 
    eax=024ef568 ebx=00000000 ecx=00000000 edx=6710140f esi=a2ea4cb8 edi=deadbee3 
    eip=82ab21a7 esp=a2ea4c98 ebp=a2ea4d34 iopl=0 nv up ei pl nz na po nc 
    cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202 
    nt!PushInt+0xa5: 
    82ab21a7 89143b mov dword ptr [ebx+edi],edx ds:0023:deadbee3=???????? 
    Resetting default scope
    

    Due to the fact that one of the 32-bit variables is stored by the kernel in an arbitrarily selected eip exception, the situation turns into a simple write-what-where condition with minor restrictions that can be neglected (for example, the most significant bit cannot be set directly). Having at its disposal such a convenient basic ability, it becomes possible to “hijack” the control flow of the kernel by rewriting one of the function pointers, for example, the well-known nt! HalDispatchTable + 4 pointer, called from user space via the nt! NtQueryIntervalProfile system call.



    Fig. 5. Successful implementation of write-what-where in nt! PushInt

    Elimination of this vulnerability is quite simple and consists of three additional instructions added to nt! PushInt. They verify that the address ss:espthat should be from the user space does indeed point to the lower parts of the address space (see Figure 6).


    Figure 6. Binary differences between the initial implementation of nt! PushInt and after the patch.

    CVE-2013-3197 (write-what-where in nt! PushException)


    If you look closely at the Windows system interrupt handlers (besides nt!KiTrap0d), it becomes obvious that the specific functionality for VDM is provided not only by the #GP handler, but most of them. In this regard, a feature of General Protection Fault is that it has dedicated routines for handling specific types of exceptions (such as critical or privileged instructions); other handlers do not use such complex functionality, but instead simply call a function  nt!Ki386VdmReflectException if they encounter a VDM exception. By doing this they try to emulate an event in a virtual environment, in much the same way as emulating instructions in  nt!VdmDispatchOpcode_try. The control transfer graph illustrates that most handlers are dependent on this function (see Figure 7).


    Fig. 7. Function graph nt! Ki386VdmReflectException

    Headache cause


    Under typical circumstances (that is, for any ordinary process), execution usually ends in one of the nt! CommonDispatchException variants, which sends an event to the user space exception handler. In the case of VDM, the kernel first tries using nt! PushException to create a frame trap in the user space stack and redirect control to the cs: eip address, which is taken from the VfCsSelector and VfEip fields of the NtCurrentTeb () -> Vdm-> VdmIntDescriptor [trap_no] field (see . Fig. 8); and only if this procedure does not work, the exception is handled in the usual way. And, like the previous case, when creating an emulated trap frame, the kernel does not check that the stack pointer is actually within the user's address space.

    TRAP_FRAME: 8dd97c28 -- (.trap 0xffffffff8dd97c28)
    ErrCode = 00000002 
    eax=000007f7 ebx=00000000 ecx=00000000 edx=deadbebf esi=8dd97ce4 edi=00000634 
    eip=82a874b5 esp=8dd97c9c ebp=8dd97d1c iopl=0 nv up ei ng nz na po nc 
    cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010282 
    nt!PushException+0x150: 
    82a874b5 6689441a0e mov word ptr [edx+ebx+0Eh],ax ds:0023:deadbecd=???? 
    Resetting default scope
    




    Fig. 8. Internal user space structures that are used to resume execution of NTVDM, interrupted by exception.

    And this time due to the fact that one of the values ​​written to the monitored address is eip errors, the same method of using the function pointer (function pointer exploitation technique) can be used for full control of your computer. However, due to restrictions on the value of LDT, this vulnerability can only be used on systems starting with Windows Vista. In the Microsoft security update, they simply inserted two simple blocks that are responsible for checking the pointer in the stack of user space for the branches of creating the trap frame for both 16 and 32 bits.

    CVE-2013-3198 (write-what-where in nt! VdmCallStringIoHandlerPushException)


    In addition to processing privileged VDM instructions, the kernel also emulates the execution of critical instructions, that is, instructions that can only be executed if CPL <= IOPL, which provides control over interrupts and communications on I / O ports. In the end, all instructions for input / output of strings (INSB, INSW, OUTSB, OUTSW in Virtual 8086 mode and protected mode) are executed by the internal function nt! Ki386VdmDispatchStringIo, which acts as an entry point to a large and complex mechanism called “port emulation” (port emulation). Although its exact functionality is hardly known to anyone outside the development team, this mechanism was disassembled by reverse engineering and described in detail  French explorer Ivanlef0u in 2009. In short, any device driver running in the Windows kernel can register any I / O emulation handlers for specific processes, port ranges, and port access types using the undocumented function ZwSetInformationProcess (ProcessIoPortHandlers). Thus, the components of the kernel space can theoretically emulate physical devices for programs running as part of VDM. However, there is a more important question - are there any default handlers registered right after installing Windows?
    As far as we know, there is currently only one case of port emulation in Windows - when an outdated program runs in full-screen mode, the default graphics driver VIDEOPRT.SYS registers handlers for the VGA range (0x3b0-0x3df); The stack trace for this registration is presented below:

    ChildEBP RetAddr Args to Child 
    807b1738 82a55023 85886680 00000001 b06b1bf3 nt!Psp386InstallIoHandler 
    807b1994 828588a6 00000088 0000000d 807b1a40 nt!NtSetInformationProcess+0x7ad 
    807b1994 82857815 00000088 0000000d 807b1a40 nt!KiSystemServicePostCall 
    807b1a1c 91619f84 00000088 0000000d 807b1a40 nt!ZwSetInformationProcess+0x11 
    807b1a60 91616467 86a357f0 00000001 8597ae80 VIDEOPRT!pVideoPortEnableVDM+0x82 
    807b1ab4 82851c1e 86a357f0 86f32278 86f32278 VIDEOPRT!pVideoPortDispatch+0x360 
    807b1acc 9a5c45a2 fe915c48 fffffffe 00000000 nt!IofCallDriver+0x63 
    807b1af8 9a733564 86a35738 00230000 fe915c48 win32k!GreDeviceIoControlEx+0x97 
    807b1d18 828588a6 00000000 0130f294 00000004 win32k!NtGdiFullscreenControl+0x1100 
    807b1d18 77c77094 00000000 0130f294 00000004 nt!KiSystemServicePostCall 
    0130f25c 77ab6951 00670577 00000000 0130f294 ntdll!KiFastSystemCallRet 
    0130f260 00670577 00000000 0130f294 00000004 GDI32!NtGdiFullscreenControl+0xc 
    0130f28c 00672c78 00000088 0000003a 003bd0b0 conhost!ConnectToEmulator+0x6c 
    0130f3c0 0065f24d 00000001 003bd0b0 0130f4d4 conhost!DisplayModeTransition+0x40e 
    0130f458 7635c4e7 000e001c 0000003a 00000001 conhost!ConsoleWindowProc+0x419
    

    In other words, this technique works only if alternative drivers for the video card are not installed in the system, and standard Microsoft's is used. Switching between full-screen and windowed modes can be easily achieved using documented API calls SetConsoleDisplayMode (CONSOLE_FULLSCREEN_MODE) and SetConsoleDisplayMode (CONSOLE_WINDOWED_MODE).

    Source of trouble


    So, back to emulating instructions - the nt! Ki386VdmDispatchStringIo function defines a handler for the emulated operation using nt! Ps386GetVdmIoHandler, reads user data from memory at ds: si if it is a “read” operation, and calls the I / O handler and writes data to es: di if it is a write operation. As you probably already guessed, none of the two pointers (which seem to be taken from user space) pass validation before use. Not the best idea, especially considering that in protected mode segments can have 32-bit base addresses, so, as a result, this vulnerability will allow us to read and write randomly selected addresses in kernel memory.
    To summarize: to successfully exploit the vulnerability, we need to force the VIDEOPRT.SYS driver to register VGA I / O handlers by switching the VDM console to full-screen mode, create and load user segments in  cs: and  es: (and the base address of the last segment indicates the kernel memory for rewriting), initialize register di with the value 0x0 and dx with the value 0x3b0, and then call the insb instruction; Of course, all operations must be carried out inside the NTVDM.EXE process. If we install the base of the es segment: in 0xaaaaaaaaa and run the exploit on an unpatched system, the following should happen:

    TRAP_FRAME: 963889fc -- (.trap 0xffffffff963889fc)
    ErrCode = 00000002 
    eax=aaaaaa00 ebx=00000001 ecx=fffffffd edx=00000003 esi=8297d260 edi=aaaaaaaa 
    eip=82854fc6 esp=96388a70 ebp=96388a78 iopl=0 vif nv up ei ng nz ac po cy 
    cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00090293 
    nt!memcpy+0x166: 
    82854fc6 8807 mov byte ptr [edi],al ds:0023:aaaaaaaa=?? 
    Resetting default scope
    

    By default, port 0x3b0 writes a single byte to memory  0x00, so this vulnerability can be used to reset any pointer to the kernel space function; Having done this, we can redirect the execution of the ring 0 code to the NULL page, which is already located in the NTVDM address space. Thus, in this case too, we can increase the security token of the local process or compromise the security of the system in any other way convenient for us.
    To resolve this problem, Microsoft introduced an inline call to ProbeForRead before reading data from user space at ds: si, as well as a general call to ProbeForWrite before writing data back to es: di.

    Thinking out loud


    All three privilege escalation vulnerabilities discussed in this article were possible due to the write-what-where condition, which occurs because the data provided by the user does not pass any validation. In other situations, vulnerabilities of this type for the base NT kernel image (ntoskrnl.exe) are extremely rare. And although Microsoft in recent years has been able to track and eliminate most of these problems internally, they somehow missed the I / O emulation code, exceptions, and instructions in VDM; most likely due to the fact that the static analysis tools, which are very effective for high-level C and C ++ code, do not currently support assembler or interact poorly with it. It is worth noting, that the opportunity to exploit these vulnerabilities appeared only after a small unrelated change in the LDT input control code, which first appeared in Windows Vista. Due to this change, the nt! PspIsDescriptorValid internal function was deprived of checks related to the database and input restrictions. However, this is nothing more than a successful coincidence. The real reason underlying this vulnerability was not the differences in segment control, but the fact that the emulation code used the full addresses ss: esp, ds: si and es: di in memory operations, although it is one of the key security features for the Windows kernel says: privileged code should never trust any user segments of memory. PspIsDescriptorValid was deprived of checks related to the database and input restrictions. However, this is nothing more than a successful coincidence. The real reason underlying this vulnerability was not the differences in segment control, but the fact that the emulation code used the full addresses ss: esp, ds: si and es: di in memory operations, although it is one of the key security features for the Windows kernel says: privileged code should never trust any user segments of memory. PspIsDescriptorValid was deprived of checks related to the database and input restrictions. However, this is nothing more than a successful coincidence. The real reason underlying this vulnerability was not the differences in segment control, but the fact that the emulation code used the full addresses ss: esp, ds: si and es: di in memory operations, although it is one of the key security features for the Windows kernel says: privileged code should never trust any user segments of memory.

    Summarizing


    Using the example of these three discoveries, we once again clearly see that many kernel vulnerabilities are caused by the existence of code written almost at the beginning of the 90s. Then, security was not considered an important priority (unlike in our time), which led to the creation of bad code, and no one has reviewed it from the point of view of ensuring security since it was written twenty years ago. At the same time, large sections of code are used in the latest versions of Windows, including the latest Windows 8.1 and Server 2012. The modern kernel source code, which was written in 2013, must comply with the safe development guidelines and be thoroughly tested before release. Therefore, we believe that instead of delving into the new functional elements that were introduced in the latest versions of the system,
    Well, the last thing worth noting is that disabling the default backward compatibility with DOS applications in Windows 8 was a great Microsoft solution. Thanks to him, most of the errors that have not yet been discovered are either impossible to use, or it makes no sense to search, because the attacker will not receive sufficient dividends from their use. In addition, this solution made it possible to completely disable any NULL pages (the VDM host process required them before), and due to this, errors such as NULL pointer dereference, which are often found both in the kernel and in device drivers, completely disappear or are significantly reduced. In terms of its overall impact on future security features, this is one of the most significant improvements Microsoft has ever made. Well, now go ahead - find your own bug in the kernel!

    Posted by Mateusz "j00ru" Jurczyk.



    First published in the Hacker magazine from 01/2014.

    Subscribe to Hacker





    Also popular now: