📜 ⬆️ ⬇️

We get system privileges using errors in NTVDM



Backward compatibility is a good thing, but it must be used reasonably. After all, you can still find code developed in the last century in the Windows kernel. Talking about his high security would be stupid. And we will prove it by the example of the three privilage escalation of vulnerabilities, rooted 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, active development of software solutions began for the new hardware platform, both operating systems and the usual programs working in them. Microsoft's Disk Operating System (DOS) has quickly established itself as the leading desktop environment for desktop PCs, 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 in 1992 the NT architecture for the Windows operating system, which took advantage of the already more powerful and secure protected mode (Protected Mode), one of the key decisions was to maintain backward compatibility with DOS, that is, to ensure the possibility of safe launch of old programs in the new graphical environment .

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 of 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 from the logic of work, its appearance opened up many new opportunities for local users to launch attacks aimed at enhancing their rights in the operating system. NTVDM has already found and corrected security problems more than once, but there are still many interesting and unexplored features in the kernel. In the next section, we will look at one of them in detail - the specific handling of exceptions that occur in the NTVDM.EXE host process. Here, however, it is worth noting that any potential bug that may be found in the DOS compatibility subsystem will affect only 32-bit Windows platforms, since 64-bit versions of the system do not support VDM due to the implementation features of the Long Mode mode, in which 64-bit code on Intel processors. In the new Windows 8 and 8.1 operating systems, the impact of potential kernel vulnerabilities is leveled by the fact that this subsystem is disabled by default and administrative rights are required to run it. Nevertheless, these vulnerabilities can be successfully exploited without user involvement on systems from Windows XP to Windows 7 32-bit (and at the moment such systems presumably account for about 50% of the park of all desktop operating systems).

Real time


From the technical point of view, it is very difficult to maintain backward compatibility with 16-bit applications for modern 32-bit environments because of the fundamental differences in the operation between real and protected mode. This includes the modes of the processor, the width of words and addresses, and the coding of instructions, and many other points. In other words, standard tools of modern operating systems can not run outdated programs from the 80s. On the other hand, switching the processor to real mode each time you start a 16-bit program is also not an option, since this deprives the basic settings of the protected mode, such as rights sharing. Transferring control to a potentially untrusted code that runs in a virtually unlimited runtime environment and has direct access to all computer peripherals not only poses a potential security threat to the system, but also deprives the operating system of control over the computer, since the decision to return to the previous working context will be made it is this old application.

Virtual 8086 mode


Intel engineers, who perfectly understood these and many other problems related to backward compatibility, developed another completely new execution mode, calling it Virtual 8086 (V8086), which has become an important part of the 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 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 plays the role of a virtual machine monitor (Virtual Machine Monitor - VMM). VMM is responsible for creating the working environment and handling the critical and privileged instructions that 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 Figure. one.


Fig. 1. Transferring control of the operating system when running legacy 16-bit applications

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



Fig. 2. Example of execution execution execution time for outdated 16-bit applications in Microsoft Windows

DPMI


Finally, we must not forget that NTVDM supports most DOS Protected Mode Interface (DPMI) interface specifications, that is, in addition to implementing Virtual 8086, it can also provide a runtime environment (for example, create memory segments) and execute code protected mode. Consequently, there may well be code with support for processing 32-bit instructions in the kernel 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 Virtual 8086 mode, as well as the working environment created by NTVDM.EXE for executing obsolete 32-bit code with DPMI support, is that any attempt to perform a critical or elevated instruction (such as INT, OUT or STI) will immediately lead to the exclusion of the General Protection Fault in the kernel. As noted above, after this the operating system must safely emulate the operation of the instruction and return to the execution of the interrupted guest code, ensuring that the execution continues. As it turned out, the instruction emulation code for the 16-bit and 32-bit emulation modes is completely in the kernel space, which opens up interesting possibilities for an attack: programmatically reproduce the behavior of special x86 instructions. In order to get into the emulator, the following conditions must be met:
  1. The #GP exception 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 to KGDT_R3_CODE (0x1b) at the time of the exception and
  3. The #GP exception occurs in the VDM host process.




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

If any of the options is fully implemented, the #GP handler passes 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. Handler lists for emulation modes of 16 and 32 bits are shown in Fig. 3 and 4; as you can see, the kernel issues a very long list of instructions and their prefixes. In our opinion, until this year, this part of the code, most likely, had never before been checked for the presence of vulnerabilities, which made it a serious target for research. After this “discovery” we decided to carry out reverse engineering of all the handlers in search of potential flaws and got the first results in the next few hours. The first vulnerability was in the INT instruction emulation layer for protected mode.



Fig. 4. The table of dispatching protected-mode instructions used by the #GP processor


Where is the dog buried


The basic role of the nt! OpcodeINTnn handler function is that it extracts the imm8 operand of the instruction that caused the crash, and then calls another internal procedure nt! PushInt. 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 address space) using the full address of the ss: esp stack pointer. The C-code obtained by reverse engineering, responsible for placing three 16-bit or 32-bit words on the stack (depending on the guest execution mode), is shown 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) is not checked for correctness in any way, probably due to the assumption that it will 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 an address relative to the kernel. Thus, to enable the vulnerability, it was enough to execute only two simple instructions in the context of a DOS virtual machine: mov esp, 0xdeadbeef and then int 0 . The only limitations were that both cs: and ss: should select code and data segments within the local descriptor table (LDT), and the argument of the int instruction should 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 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 a randomly selected eip exception, the situation turns into a simple write-what-where condition with minor restrictions that can be ignored (for example, that the immediate value cannot have the leading bit set). Having such a convenient basic feature at its disposal, it becomes possible to “hijack” the control flow of the kernel by rewriting one of the function pointers, for example, the well-known pointer nt! HalDispatchTable + 4, called from user space via the nt! NtQueryIntervalProfile system call.



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

Fixing this vulnerability is fairly straightforward and consists of three additional instructions added to nt! PushInt. They verify that the ss:esp address, which must be from user space, does 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 most of them provide not only the #GP handler, but also the specific functionality for VDM. In this regard, the General Protection Fault feature 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 the function nt!Ki386VdmReflectException if they encounter a VDM exception. With this they are trying to emulate an event in a virtual environment, approximately along the same lines as the emulation of instructions in nt!VdmDispatchOpcode_try . The control transfer graph illustrates that most handlers depend on this function (see Figure 7).


Fig. 7. Graph function nt! Ki386VdmReflectException

Headache cause


Under typical circumstances (that is, for any normal process), execution usually completes in one of the variants nt! CommonDispatchException, 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 cs: eip, 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 processed in the usual way. And, like in the previous case, when creating an emulated frame-trap, the kernel does not check that the stack pointer is indeed within the user's address space. This again leads to the possibility to use the write-what-where condition, only 16 or 32 bytes instead of 6 or 12. It is also easy to exploit the vulnerability, for this it is enough to set esp to an arbitrary address in kernel space and raise an exception (for example, #DE through The div edx instruction with normal details of a fully initialized VDM environment and user segments cs: and ss: at the time of the error.

 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 the execution of NTVDM interrupted by an exception

And this time due to the fact that one of the values ​​written to the monitored address is an eip error, the same method of using the function pointer (function pointer exploitation technique) can be used to gain complete control over the computer. However, due to restrictions on the value of LDT, this vulnerability can be exploited only 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 user-space stack for the trap frame creation branches 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, such instructions that can be executed only if CPL <= IOPL, which provides control over interrupts and communications over I / O ports. In the end, all instructions for input / output strings (INSB, INSW, OUTSB, OUTSW in Virtual 8086 mode and protected mode) are executed by the nt! Ki386VdmDispatchStringIo internal function, 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 someone outside the development team, this mechanism was disassembled by reverse engineering and described in detail by French researcher 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 ZwSetInformationProcess function (ProcessIoPortHandlers). Thus, the components of the kernel space can in theory emulate physical devices for programs running within the VDM. However, there is a more important question - are there any default handlers registered right after installing Windows?
As far as we know, currently there is only one case of port emulation in Windows — when an outdated program is running in full screen mode, the default VIDEOPRT.SYS graphics driver registers handlers for the VGA range (0x3b0–0x3df); The stack trace for this registration is shown 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 is used. Switching between fullscreen and windowed mode can be easily achieved using the documented SetConsoleDisplayMode API (CONSOLE_FULLSCREEN_MODE) and SetConsoleDisplayMode (CONSOLE_WINDOWED_MODE) calls.

Source of troubles


So, going back to instructions emulation, the nt! Ki386VdmDispatchStringIo function defines a handler for an emulated operation using nt! Ps386GetVdmIoHandler, reads user data from the memory at ds: si, if it is a “read” operation, and calls the I / O handler and writes data to es: di if this is a “write” operation. As you probably already guessed, none of the two pointers (which seem to come from user space) are not validated before use. Not the best idea, especially considering that in the protected mode, segments can have 32-bit base addresses, so, as a result, this vulnerability will allow us to read and write arbitrarily selected addresses in the 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 to create and load custom segments in cs: and es: (the base address of the last segment indicates the kernel memory for rewriting), initialize the di register with the value 0x0 and dx with the value 0x3b0, and then call the insb instruction; Of course, all operations must be carried out within the NTVDM.EXE process. If we install the base of the es segment: at 0xaaaaaaaa and launch the exploit on the 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 a kernel space function; having done this, we can redirect the execution of the ring 0 code to a NULL page, which is already located in the NTVDM address space. Thus, in this case, we can increase the security token of the local process or compromise the security of the system in any other way that is convenient for us.
To fix 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 elevation vulnerabilities discussed in this article were possible due to the write-what-where condition, which arises because the user-provided data does not undergo any validation. In other situations, this type of vulnerability to the base NT kernel image (ntoskrnl.exe) is extremely rare. And although Microsoft has been able to track down and fix most of these problems in recent years, they somehow missed the I / O emulation code, exceptions, and instructions in the VDM; Most likely, due to the fact that static analysis tools that are very effective for high-level C and C ++ code do not currently support assembler or interact poorly with it. It should be noted that the ability to use these vulnerabilities appeared only after a small unrelated change in the LDT input control code, which first appeared in Windows Vista. Because of this change, the internal function nt! PspIsDescriptorValid was deprived of checks related to the base and input restrictions. However, this is nothing more than a successful coincidence. The real reason behind this vulnerability was not the differences in segment control, but the fact that the emulation code used the full ss: esp, ds: si and es: di addresses in memory operations, although one of the key security features for The Windows kernel says: privileged code should never trust any memory segments from the user.

Summarizing


Using the example of these three discoveries, we once again clearly see that many kernel vulnerabilities are due to the existence of code written almost in the early 1990s. Then security was not considered as an important priority (unlike our time), which led to the creation of bad code, and no one has revised it from a security point of view since it was written twenty years ago. At the same time, large portions of the code are used in the most recent versions of Windows, including the latest Windows 8.1 and Server 2012. Modern kernel source code, which is written in 2013, must comply with the guidelines for safe development and be thoroughly tested before release. Therefore, we believe that instead of digging into new functional elements that were introduced in the latest versions of the system, it is much more effective to look for errors in those key components that were created a long time ago.
And 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 not yet detected errors are either impossible to use or there is no point in searching, because the attacker will not receive sufficient dividends from their use. In addition, this solution allowed to completely disable any NULL pages (before their presence required the VDM host process), and due to this, errors like NULL pointer dereference, which are often found in the kernel and device drivers, either completely disappear. According to the overall impact on future defenses, this is one of the most dramatic improvements by Microsoft in all time. But now go ahead - find your own bug in the core!

Posted by: Mateusz "j00ru" Jurczyk.



First published in the journal "Hacker" from 01/2014.

Subscribe to "Hacker"




Source: https://habr.com/ru/post/232207/


All Articles