Trao đổi với tôi

http://www.buidao.com

10/13/10

[System Info] Windows kernel2user transitions one more time

Hello,

Before I start talking (writing?) over the real subject of this short post, I would like to make some interesting announcements.

  1. My friend mawekl has recently fired up a project called Security Traps. The website consists of numerous IT-related challenges, ranging from typical JavaScript-hackmes, through Windows software Reverse Code Engineering tasks, up to C/C++ riddles and logical puzzles. If searching for non-trivial solutions of simple problems is what you like, taking a look at the system might appear to be both entertaining and informative - at least, it was for me ;)
    BTW. Try to beat the top2 players ;D.
  2. a_d_13, the http://kernelmode.info/ forums administrator and author of the RootRepeal anti-rootkit software has published an interesting tool called MemMAP v0.1.2, just a few minutes ago. The interesting thing about the program is that it was originally inspired by what I released in January this year: the tiny KernelMAP. AD's application greatly enhances my idea of memory visualization, by including additional kernel memory areas (kernel stacks and GDI objects), as well as makes it possible to observe the contents of user-mode memory. Feel encouraged to take a look at the production and possibly share interesting screens of the program output ;)

Now, to the point. In the most recent post, I tried to describe the ways of performing ring0-to-ring3 transitions on the Windows platform - in particular, the ways that were known to me at the time of writing the article. After publishing the entry, I had a few interesting conversations, and I was pointed out that there is at least one (possibly more) solution with the reliability level not lower than the nt!KeUserModeCallback method, claimed to be the best one. In this post, I am going to introduce yet another way of lowering the processor privilege level in a stable manner.

Please keep in mind that all of the clues and conclusions made in both publications should be thought of, in the context of Windows kernel-mode exploitation. Therefore, the presented techniques are only suitable for ring-0 payload code, and not for device drivers or other legitimate kernel modules. In case of a targeted Elevation of Privileges attack, one has to undertake the risk of putting the machine in a possibly inconsistent state - such assumptions cannot be made for regular executables, as system stability should be the first, and most important goal of any (especially kernel) developer.

The overall problem (or issue) with returning into user-mode can be divided into two major, separate groups:

  • implementing own exit code using iret/sysexit,
  • utilizing existing kernel routines, performing the above operation.

As there isn't much about the first option, that we can wonder about, let's leave it as it is. The latter solution group is in point of fact more interesting, as can be implemented using numerous techniques. The ones mentioned last time include:

  • Making use of an internal nt!KiServiceExit routine:
    1. by downloading the kernel symbols from Microsoft servers
    1. by performing a signature scan
  • Calling the exported nt!KeUserModeCallback symbol, and hooking user-mode callback handlers.

Interestingly enough, it turns out that the above functions are not the only ones, that can be successfully taken advantage of by an attacker on fire. Due to the fact that a majority of the user-to-kernel transitions (such as system calls, interrupts, exceptions) all result in a similar trap-frame being placed on the kernel stack, they are also managed in a similar way when returning to the less-privileged ring. This, in turn, makes it possible to employ one function, to return to user-mode from transitions caused by different events in the system. One, important requirement about the routine being used, is that it must end with an iret / sysexit instruction, responsible for switching between rings.

A complete list of Windows XP kernel functions, containing one of the aforementioned instructions, follows:

  1. KiSystemCallExit
  2. KiSystemCallExit2
  3. KiServiceExit
  4. KiServiceExit2
  5. KiGetTickCount
  6. Kei386EoiHelper
  7. KiTrap02, KiTrap06, KiTrap0D
  8. KiCallbackReturn
  9. two unnamed symbols

One could obviously try his luck in searching for the functions' addresses, just like with KiServiceExit described last time - as we all know, obtaining the location of an internal routine is never guaranteed to work on 100% of the attacked machine. Fortunately for us, one of the above symbols is publically exported by the ntoskrnl.exe executable image! Yes - this particular name is the key for the 5th solution.

A few words about the routine - if one takes a look at the Windows Research Kernel sources (a package that is only available to the legitimate MSDN subscribers), or more specifically at the \base\ntos\ke\i386\trap.asm file, he would find out that the procedure is described by two, separate symbols, each with its own description. These are:

  • KiExceptionExit
    ; ;   Routine Description: ; ;       This code is transferred to at the end of the processing for ;       an exception.  Its function is to restore machine state, and ;       continue thread execution.  If control is returning to user mode ;       and there is a user APC pending, then control is transferred to ;       the user APC delivery routine. ;
  • Kei386EoiHelper
    ; ;   Routine Description: ; ;       This code is transferred to at the end of an interrupt.  (via the ;       exit_interrupt macro).  It checks for user APC dispatching and ;       performs the exit_all for the interrupt. ;

Apparently, the piece of code is responsible for managing the transitions caused by both user-mode exceptions and interrupts (it can occasionally deal with system calls, as well :-) ). The question is - if Kei386EoiHelper and KiServiceExit are used to perform pretty much the same operation, why are they used separately? In order to answer the question, one should take a look at the implementation of these functions (both can be found inside the trap.asm file). As it turns out, the only difference is how the magic "EXIT_ALL" macro is invoked.

Seemingly, the parameters present in both implementations are used exclusively. Their exact meaning (or a short description) can be found in \base\ntos\ke\i386\kimacro.inc, where our EXIT_ALL macro is entirely implemented:

;   Arguments: ; ;       NoRestoreSegs - non-blank if DS, ES, GS are NOT to be restored ; ;       NoRestoreVolatiles - non-blank if Volatile regs are NOT to be restored ; ;       NoPreviousMode - if nb pop ThPreviousMode ;

And so, the service-exit routine avoids having the DS, ES, GS and volatile registers restored, while the interrupt-exit one resigns from copying the PreviousMode value stored on the trap-frame, to the Thread-Context structure (KTHREAD). Do the parameter differences cause any problem in the context of kernel->user transition after successful vulnerability exploitation? In fact, no.

When it comes to the first two arguments (NoRestoreSegs / NoRestoreVolatiles) - whether these are set or not, doesn't matter at all. They are indeed important for regular system execution, when the contents of particular registers must be preserved and/or restored upon certain events, while don't have to in case of others. In the currently considered scenario, however, we do not care about the processor context so much - the main goal is just to lower the privilege level, as the rest (i.e. registers' fix-up) can be manually performed "on the other side" - when already in ring-3.

One might also have doubts about the third parameter - NoPreviousMode. One should keep in mind that system service calls can practically come from both user- and kernel- mode. In case of regular application, the well known SYSENTER / SYSCALL instruction is used to perform the privileged jump. As for the kernel - multiple API functions exported by ntoskrnl.exe (or its equivalent), such as nt!ZwOpenFile make use of the syscall handlers, by manually crafting the trap frame on the stack, and then calling the nt!KiSystemService routine:

01..text:00405FCC ; NTSTATUS __stdcall ZwOpenFile
02..text:00405FCC public _ZwOpenFile@24
03..text:00405FCC _ZwOpenFile@24 proc near
04..text:00405FCC FileHandle = dword ptr 4
05..text:00405FCC DesiredAccess = dword ptr 8
06..text:00405FCC ObjectAttributes= dword ptr 0Ch
07..text:00405FCC IoStatusBlock = dword ptr 10h
08..text:00405FCC ShareAccess = dword ptr 14h
09..text:00405FCC OpenOptions = dword ptr 18h
10..text:00405FCC
11..text:00405FCC mov eax, 74h
12..text:00405FD1 lea edx, [esp+FileHandle]
13..text:00405FD5 pushf
14..text:00405FD6 push 8
15..text:00405FD8 call _KiSystemService
16..text:00405FDD retn 18h
17..text:00405FDD _ZwOpenFile@24 endp

This, in turn, makes it possible to invoke nested system calls - in such case, the preservation and restoration of the PreviousMode value should be a crucial part of the service-exit routine. The scenario presented in the latest post assumed, however, that the stack-based buffer overflow is triggered inside a syscall handler with no recursive calls, directly triggered by a user-mode application. This implies, that we don't have to worry about the PreviousMode value, as it is basically irrelevant for the situation we are in. Overall, nt!Kei386EoiHelper appears to meet our requirements regarding a correct "return routine", whatsoever.

One thing to keep in mind, however, is that the procedure requires both the EBP and ESP registers to point at the beginning of the trap frame, at the time of invoking nt!Kei386EoiHelper. Even though ESP is set to a valid address (it was used as a regular stack pointer up to the moment when we hijacked the execution), the EBP contents are junk; being a consequence of the fact, that the Stack Frame value is overwritten with attacker-controlled data, if the ret-addr is reached. Luckily for us, the valid EBP value can be restored based on ESP and basic additions / subtractions; for all system versions known to the author, just one:

1.MOV EBP, ESP

instruction fixes the problem ;) It is good to know about it, anyway ;)

To sum everything up - instead of messing up with the the win32k callback mechanism, one is able to carry on with the process execution, in a stable manner. All of the steps required to achieve the desired effect are:

  1. Find the nt kernel base (do-able from user-mode)
  2. Find the nt!Kei386EoiHelper address (do-able from user-mode)
  3. Trigger the vulnerability and execute payload (well, definitely doable :>)
  4. Fix the EBP value
  5. Jump to nt!Kei386EoiHelper, and continue regular process execution

So well.. that's it. To guys that informed me about the function existence (well, I am sure I had known about it before) and it being an exported symbol - thank you! Man learns all thorough his life :-)

Take care!

reflink: http://j00ru.vexillium.org/?p=641