Stack Protection and Windows 2003 Server

Stack Protection and Windows 2003 Server

Stack protection is built into Windows 2003 Server and is provided by Microsoft's Visual C++ .NET. The /GS compiler flag, which is on by default, tells the compiler when generating code to use Security Cookies that are placed on the stack to guard the saved return address. For any readers who have looked at Crispin Cowan's StackGuard, a Security Cookie is the equivalent of a canary . The canary is a 4-byte value (or dword) placed on the stack after a procedure call and checked before procedure return to ensure that the value of the cookie is still the same. In this manner, the saved return address and the saved base pointer ( EBP ) are guarded . The logic behind this is as follows : If a local buffer is being overflowed, then on the way to overwriting the saved return address the cookie is also overwritten. A process can recognize then whether a stack-based buffer overflow has occurred and can take action to prevent the execution of arbitrary code. Normally, this action consists of shutting down the process. At first this may seem like an insurmountable obstacle that will prevent the exploitation of stack-based buffer overflows, but as we have already seen in the section on abusing frame-based exception handlers, this is not the case. Yes, these protections make stack-based overflows difficult, but not impossible .

Let's take a deeper look into this stack protection mechanism and explore other ways in which it can be bypassed. First, we need to know about the cookie itself. In what way is the cookie generated and how random is it? The answer to this is fairly randomat least a level of random that makes it too expensive to work out, especially when you cannot gain physical access to the machine. The following C source mimics the mechanism used to generate the cookie on process startup:

 #include <stdio.h> #include <windows.h>     int main() {             FILETIME ft;             unsigned int Cookie=0;             unsigned int tmp=0;             unsigned int *ptr=0;             LARGE_INTEGER perfcount;                 GetSystemTimeAsFileTime(&ft);             Cookie = ft.dwHighDateTime ^ ft.dwLowDateTime;             Cookie = Cookie ^ GetCurrentProcessId();             Cookie = Cookie ^ GetCurrentThreadId();             Cookie = Cookie ^ GetTickCount();             QueryPerformanceCounter(&perfcount);             ptr = (unsigned int)&perfcount;             tmp = *(ptr+1) ^ *ptr;             Cookie = Cookie ^ tmp;             printf("Cookie: %.8X\n",Cookie);             return 0; } 

First, a call to GetSystemTimeAsFileTime is made. This function populates a FILETIME structure with two elementsthe dwHighDateTime and the dwLowDateTime . These two values are XOR ed. The result of this is then XOR ed with the process ID, which in turn is XOR ed with the thread ID and then with the number of milliseconds since the system started up. This value is returned with a call to GetTickCount . Finally a call is made to Query_PerformanceCounter , which takes a pointer to a 64-bit integer. This 64-bit integer is split into two 32-bit values, which are then XOR ed; the result of this is XOR ed with the cookie. The end result is the cookie, which is stored within the .data section of the image file.

The / GS flag also reorders the placement of local variables. The placement of local variables used to appear as they were defined in the C source, but now any arrays are moved to the bottom of the variable list, placing them closest to the saved return address. The reason behind this change is so that if an overflow does occur, then other variables should not be affected. This idea has two benefits: It helps to prevent logic screw-ups, and it prevents arbitrary memory overwrites if the variable being overflowed is a pointer.

To illustrate the first benefit, imagine a program that requires authentication and that the procedure that actually performs this was vulnerable to an overflow. If the user is authenticated, a dword is set to 1; if authentication fails, the dword is set to . If this dword variable was located after the buffer and the buffer overflowed, then the attacker could set the variable to 1 , to look as though they've been authenticated even though they've not supplied a valid user ID or password.

When a procedure that has been protected with stack Security Cookies returns, the cookie is checked to determine whether its value is the same as it was at the beginning of the procedure. An authoritative copy of the cookie is stored in the .data section of the image file of the procedure in question. The cookie on the stack is moved into the ECX register and compared with the copy in the .data section. This is problem number onewe will explain why in a minute and under what circumstances.

If the cookie does not match, the code that implements the checking will call a security handler if one has been defined. A pointer to this handler is stored in the .data section of the image file of the vulnerable procedure; if this pointer is not NULL , it is moved into the EAX register and then EAX is called. This is problem number two. If no security handler has been defined then the pointer to the UnhandledExceptionFilter is set to 0x00000000 and the UnhandledExceptionFilter function is called. The UnhandledExceptionFilter function doesn't just terminate the processit performs all sorts of actions and calls all manner of functions.

For a detailed examination of what the UnhandledExceptionFilter function does, we recommend a session with IDA Pro. As a quick overview, however, this function loads the faultrep.dll library and then executes the ReportFault function this library exports. This function also does all kinds of things and is responsible for the Tell-Microsoft-about-this-bug popup. Have you ever seen the PCHHangRepExecPipe and PCHFaultRepExecPipe named pipes? These are used in ReportFault .

Let's now turn to the problems we mentioned and examine why they are in fact problems. The best way to do this is with some sample code. Consider the following (highly contrived) C source:

 #include <stdio.h>      #include <windows.h>          HANDLE hp=NULL;      int ReturnHostFromUrl(char **, char *);          int main()      {             char *ptr = NULL;             hp = HeapCreate(0,0x1000,0x10000); ReturnHostFromUrl(&ptr,"http://www.ngssoftware.com/index.html");             printf("Host is %s",ptr);             HeapFree(hp,0,ptr);             return 0;          }          int ReturnHostFromUrl(char **buf, char *url)      {             int count = 0;             char *p = NULL;             char buffer[40]="";                 // Get a pointer to the start of the host             p = strstr(url,"http://");             if(!p)                     return 0;             p = p + 7;             // do processing on a local copy             strcpy(buffer,p); // <------ NOTE 1             // find the first slash             while(buffer[count] !='/')                     count ++;             // set it to NULL             buffer[count] = 0;             // We now have in buffer the host name             // Make a copy of this on the heap             p = (char *)HeapAlloc(hp,0,strlen(buffer)+1);             if(!p)                     return 0;             strcpy(p,buffer);             *buf = p; // <-------------- NOTE 2             return 0;      } 

This program takes a URL and extracts the host name. The ReturnHostFromUrl function has a stack-based buffer overflow vulnerability marked at NOTE 1 . Leaving that for a moment, if we look at the function prototype we can see it takes two parametersone a pointer to a pointer ( char ** ) and the other a pointer to the URL to crack. Marked at NOTE 2 , we set the first parameter (the char ** ) to be the pointer to the host name stored on the dynamic heap. Let's look at the assembly behind this.

 004011BC   mov         ecx,dword ptr [ebp+8] 004011BF   mov         edx,dword ptr [ebp-8] 004011C2   mov         dword ptr [ecx],edx 

At 0x004011BC the address of the pointer passed as the first parameter is moved into ECX . Next , the pointer to the hostname on the heap is moved into EDX . This is then moved into the address pointed to by ECX . Here's where one of our problems creeps in. If we overflow the stack-based buffer, overwrite the cookie, overwrite the saved base pointer then the saved return address, we begin to overwrite the parameters that were passed to the function. Figure 8.3 shows how this looks visually.


Figure 8.3: Before and after snapshots of the buffer

After the buffer has been overflowed, the attacker is in control of the parameters that were passed to the function. Because of this, when the instructions at 0x004011BC perform the *buf = p; operation, we have the possibility of an arbitrary memory overwrite or the chance to cause an access violation. Looking at the latter of these two possibilities, if we overwrite the parameter at EBP + 8 with 0x41414141 , then the process will try to write a pointer to this address. Because 0x41414141 is (not normally) initialized memory, then we access violate . This allows us to abuse the Structured Exception Handling mechanisms to bypass stack protection discussed earlier. But what if we don't want to cause the access violation? Because we're currently exploring other mechanisms for bypassing the stack protection, let's look at the arbitrary memory overwrite option.

Returning to the problems mentioned in the description of the cookie checking process, the first problem occurs when an authoritative version of the cookie is stored in the .data section of the image file. For a given version of the image file, the cookie can be found at a fixed location (this may be true even across different versions). If the location of p , which is a pointer to our host name on the heap, is predictable; that is, every time we run the program the address is the same, then we can overwrite the authoritative version of the cookie in the .data section with this address and use this same value when we overwrite the cookie stored on the stack. This way, when the cookie is checked, they are the same . As we pass the check, we get to control the path of execution and return to an address of our choosing as in a normal stack-based buffer overflow.

This is not the best option in this case, however. Why not? Well, we get the chance to overwrite something with the address of a buffer whose contents we control. We can stuff this buffer with our exploit code and overwrite a function pointer with the address of our buffer. In this way, when the function is called, it is our code that is executed. However, we fail the cookie check, which brings us to problem number two. Recall that if a security handler has been defined, it will be called in the event of a cookie check failure, which is perfect for us in this case. The function pointer for the security handler is also stored in the .data section, so we know where it will be, and we can overwrite this with a pointer to our buffer. In this way, when the cookie check fails, our "security handler" is executed and we gain control.

Let's illustrate another method. Recall that if the cookie check fails and no security handler has been defined, then the UnhandledExceptionFilter is called after the actual handler is set to . So much code is executed in this function that we have a great playground in which to do anything we want. For example, GetSystemDirectoryW is called from within the UnhandledExceptionFilter function and then faultrep.dll is loaded from this directory. In the case of a Unicode overflow, we could overwrite the pointer to the system directory, which is stored in the .data section of kernel32.dll with a pointer to our own "system" directory. This way our own version of faultrep.dll is loaded instead of the real one. We simply export a ReportFault function, and it will be called.

Another interesting possibility (this is theoretical at the moment; we've not yet had enough time to prove it) is the idea of a nested secondary overflow. Most of the functions that UnhandledExceptionFilter calls are not protected with cookies. Now, let's say one of thesethe GetSystemDirectoryW function will dois vulnerable to a buffer overrun vulnerability: The system directory is never more than 260 bytes, and it's coming from a trusted source, so we don't need to worry about overruns in here. Let's use a fixed- sized buffer and copy data to it until we come across the null terminator. You get my drift . Now, under normal circumstances, this overflow could not be triggered, but if we overwrite the pointer to the system directory with a pointer to our buffer, then we could cause a secondary overflow in code that's not protected with a cookie. When we return, we do so to an address of our choosing, and we gain control. As it happens, GetSystemDirectory is not vulnerable in this way. However, there could be such a hidden vulnerability lurking within the code behind UnhandledExceptionFilter somewherewe just haven't found it yet. Feel free to look yourself.

You could ask if this kind of scenario (that is, the situation in which we have an arbitrary memory overwrite before the cookie checking code is called) is likely. The answer is yes; it will happen quite often. Indeed the DCOM vulnerability discovered by The Last Stages of Delirium suffered from this kind of problem. The vulnerable function took a type of wchar ** as one of its parameters. This happened just before the function returned the pointers that were set, allowing arbitrary memory to be overwritten. The only difficulty with using some of these techniques with this vulnerability is that to trigger the overflow, the input has to be a Unicode UNC path that starts with two backslashes. Assuming we overwrite the pointer to the security handler with a pointer to our buffer, the first thing that would execute when it is called would be:

 pop esp      add byte ptr[eax+eax+n],bl 

where n is the next byte. Since EAX+EAX+n is never writable, we access violate and lose the process. Because we're stuck with the \\ at the beginning of the buffer, the above was not a viable exploit method. Had it not been for the double forwardslash (\\), any of the methods discussed here would have sufficed.

In the end, we can see that many way exist to bypass the stack protection provided by Security Cookies and the .NET GS flag. We've looked at how Structured Exception Handling can be abused and also looked at how owning parameters pushed onto the stack and passed to the vulnerable function can be employed. As time goes on, Microsoft will make changes to their protection mechanisms, making it even harder to successfully exploit stack-based buffer overflows. Whether the loop ever will be fully closed remains to be seen.



The Shellcoder's Handbook. Discovering and Exploiting Security
Hacking Ubuntu: Serious Hacks Mods and Customizations (ExtremeTech)
ISBN: N/A
EAN: 2147483647
Year: 2003
Pages: 198
Authors: Neal Krawetz

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