Brief Information about Structured Exceptions

Being a legal mechanism of interaction with the operating system, SEH is well documented (at least, here only the documented features will be covered).

Carefully study the "Exception Handling: Frequently Asked Questions" section of MSDN ( http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore98/HTML/_core_exception_handling.3a_.frequently_asked_questions.asp ). With it, you'll find an interesting article, " A Crash Course on the Depths of Win32 Structured Exception Handling " by Matt Pietrek ( http://www.microsoft.com/msj/0197/exception/exception.aspx ). Lots of interesting information can be found in the excpt.h header file supplied as part of the SDK. Because SEH is a relatively new mechanism, it is necessary to provide a brief description of it here.

The address of the current SEH frame is contained in the double word at the zero offset from the FS selector. To retrieve it, it is possible to use the following Assembly construct: mov eax, FS:[00000000h]/mov my_var, eax . It points to the structure of the EXCEPTION_REGISTRATION type. The prototype of this structure is described in Listing 5.1.

Listing 5.1: Description of the EXCEPTION_REGISTRATION structure
image from book
 _EXCEPTION_REGISTRATION struc         prev        dd ?     ; Address of the previous SEH frame         handler     dd ?     ; Address of the structured exception handler _EXCEPTION_REGISTRATION ends 
image from book
 

When an exception is thrown, control is passed to the current structured exception handler. Having analyzed the situation, the structured exception handler ” which, by the way, is a normal cdecl function ” must either return ExceptionContinueExecution , which would inform the operating system that the exception has been handled successfully and program execution can be continued , or ExceptionContinueSearch if the handler doesn't know what to do with this exception. In the latter case, the operating system passes control to the next handler in the chain ( generally , it is not necessary to return control, and the structured exception handler may hold it as long as necessary). As a rule, the handlers installed by the shellcode proceed in this way.

The last handler is assigned by the operating system by default. Seeing that the situation didn't improve and no handler was able to handle this exception, it accesses the system registry and retrieves from there the HKLM\SOFTWARE\Microsoft\ windows NT\CurrentVersion\AeDebug key. Depending on its value, it then either kills the faulty application or passes control to the debugger (or, as a variant, to Dr. Watson).

When creating a new process, the operating system automatically adds to it the primary SEH frame with the default handler, which resides practically at the bottom of the stack memory allocated to the process. Reaching it by sequential overflow is unrealistic , because for this purpose it would be required to traverse the entire stack. Such catastrophic overflows have not been encountered for years !

Start-up code of the application added to the program by the linker also adds its own handler (although it is not obliged to do so). This handler is also located in the stack, residing slightly above the primary handler but still insufficiently close to the overflowing buffers, which are required to traverse the stack frames of all parent functions until they reach the local memory of the starting function of the application.

The developer also can assign custom handlers that are automatically created when the try and except keywords are encountered (such handlers will be referred to as user handlers henceforth). Despite all of Microsoft's efforts, most programmers remain indifferent to SEH (there are even some individuals that haven't heard the term ). Therefore, the probability of encountering a user SEH frame in a vulnerable program is low. Nevertheless, sometimes user SEH frames can be encountered. Otherwise , to replace the structured exception handler (and the primary handler is always at the hacker's disposal), it will be necessary to use index overflow or the poke pseudofunction, which was considered in Chapter 4 .

The operating system's course of action in case of an exception is shown in Fig. 5.1. At stage 1, an exception is generated, and the operating system is informed about it. At stage 2, the operating system analyzes the thread information block (TIB) and finds the first SEH frame in the chain. Having located it, the operating system passes control to the first structured exception handler in the chain (stage 3). However, the handler doesn't handle this exception; therefore, the first callback declines the request for processing this exception (stage 4). The operating system then moves to the next frame in the chain, requesting the next handler (stage 5). The operating system passes control to the next handler (stage 6); however, this handler also doesn't know how to handle this exception (stage 7). Thus, the operating system proceeds to the next frame (stage 8) and passes control to the next structured exception handler (stage 9). This handler also doesn't know how to handle the exception (stage 10); however, it must process the exception because it is the primary handler. Therefore, it simply kills the faulty application.

image from book
Figure 5.1: Global exception-handling chain (according to MSDN documentation)

To investigate the structured exception handlers, I have written a simple program that traces SEH frames and displays their contents. Its implementation appears as shown in Listing 5.2.

Listing 5.2: Simple tracer for SEH frames
image from book
 main(int argc, char **argv) {         int *a, xESP;         --try{                 __asm{                          MOV EAX, fs:[0];                          MOV a, EAX                          MOV xESP, ESP                 } printf("ESP :                         % 08Xh\n", xESP);                 while((int)a != -1)                 {                         printf("EXCEPTION_REGISTRATION.prev    :%08Xh\n"\                                "EXCEPTION_REGISTRATION.handler                                :%08Xh\n\n", a, *(a+1));                         a = (int*) *a;                 }         }         --except (1 /*EXCEPTION_EXECUTE_HANDLER */)                 {printf("exception\x7\n");         }         return 0; } 
image from book
 

Compile the program and start it for execution. The result that I have obtained is shown in Listing 5.3. Addresses of SEH frames and handler's addresses in your case will most likely be different.

Listing 5.3: Layout of SEH frames in the memory
image from book
 ESP                            : 0012FF54h ; Current pointer of the stack top EXCEPTION_REGISTRATION.prev    : 0012FF70h ; "User" SEH frame EXCEPTION_REGISTRATION.handler : 004011C0h ; "User" structured exception handler EXCEPTION_REGISTRATION.prev    : 0012FFB0h ; SEH frame of the start-up code EXCEPTION_REGISTRATION.handler : 004011C0h ; Structured exception handler                                            ; of the start-up code EXCEPTION_REGISTRATION.prev    : 0012FEE0h ; Primary SEH frame EXCEPTION_REGISTRATION.handler : 77EA1856h ; Primary Structured exception handler 
image from book
 

The "user" SEH frame formed by the try keyword lies near the top of the stack of the current function and is separated from it by only 1Ch bytes (the specific value depends on the amount of memory allocated for local variables , and on other circumstances).

The next frame in the chain is the frame formed by the start-up code. It is located considerably lower and is separated from the stack top by 5Ch bytes. Note that this is the demo program containing the smallest possible number of variables.

The primary frame assigned by the operating system is separated from the stack top by 8Ch bytes. In real-world applications, this distance would be even greater (the primary frame can be identified by an "abnormal" address of the structured exception handler located in the most significant addresses of the first half of the address space). Its linear address equal to 12FFE0h is the same for the first thread of all processes started under the current version of the operating system, which creates favorable conditions for its replacement. However, to capture control, the shellcode must capture the current handler, not the primary one, because the exception is not guaranteed to survive until the primary handler has been reached. To check this, run the following simple check: If the buffer is overflowing with a meaningless string like xxxxx... , the standard critical error message dialog pops up. Then it is possible to replace the primary handler; otherwise, its replacement won't produce any effect, and the shellcode will be killed before it manages to gain control.

The primary frame of all further threads is located dwStackSize bytes higher than the current frame, where dwStackSize is the size of the memory allocated to the thread. By default, 4 MB are allocated for the first thread, and all further threads receive 1 MB each. Now correct the test program by including a new line of code there (Listing 5.4).

Listing 5.4: Investigation of the SEH frame layout in a multithreaded environment
image from book
 CreateThread(0, 0, (void*) main, 0, 0, &xESP); gets(&xESP); 
image from book
 

The result of the test run will appear approximately as shown in Listing 5.5.

Listing 5.5: Layout of the SEH frame in memory
image from book
 ESP                            : 0012FF48h ; Current stack top of the first thread EXCEPTION_REGISTRATION.prev    : 0012FF70h ; "User" SEH frame of the first thread EXCEPTION_REGISTRATION.handler : 00401244h EXCEPTION_REGISTRATION.prev    : 0012FFB0h ; SEH frame of the start-up of all threads EXCEPTION_REGISTRATION.handler : 00401244h EXCEPTION_REGISTRATION.prev    : 0012FFE0h ; Primary SEH frame of the first thread EXCEPTION_REGISTRATION.handler : 77EA1856h ESP                            : 0051FF7Ch ; Current stack top of the second thread EXCEPTION_REGISTRATION.prev    : 0051FFA4h ; "User" SEH frame of the second thread EXCEPTION_REGISTRATION.handler : 00401244h EXCEPTION_REGISTRATION.prev    : 0051FFDCh ; Primary SEH frame of the second thread EXCEPTION_REGISTRATION.handler : 77EA1856h ESP                            : 0061FF7Ch ; Current stack top of the third thread EXCEPTION_REGISTRATION.prev    : 0061FFA4h ; "User" SEH frame of the third thread EXCEPTION_REGISTRATION.handler : 00401244h EXCEPTION_REGISTRATION.prev    : 0061FFDCh ; Primary SEH frame of the third thread EXCEPTION_REGISTRATION.handler : 77EA1856h ESP                            : 0071FF7Ch ; Current stack top of the fourth thread EXCEPTION_REGISTRATION.prev    : 0071FFA4h ; "User" SEH frame of the fourth thread EXCEPTION_REGISTRATION.handler : 00401244h EXCEPTION_REGISTRATION.prev    : 0071FFDCh ; Primary SEH frame of the fourth thread EXCEPTION_REGISTRATION.handler : 77EA1856h 
image from book
 

It is clear that the primary SEH frame of all threads is located at the same distance from the current top of the stack, which considerably simplifies the task of replacing it. Primary frames of the first and the second threads are divided by 4MB ( 51FFDCh - 12FFE0h == 0x3EFFFC ~4 MB ), and all the other ones are divided by 1MB ( 61FFDCh - 51FFDCh == 71FFDCh - 61FFDCh == 10.00.00 == 1 MB ). Well, a hacker will have no problems grasping the idea.

Because most serverside applications are designed according to the multi-threaded method, it is vitally important to learn how to navigate across the threads; otherwise, instead of capturing control the attacker will implement a denial-of-service (DoS) attack.



Shellcoder's Programming Uncovered
Shellcoders Programming Uncovered (Uncovered series)
ISBN: 193176946X
EAN: 2147483647
Year: 2003
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net