SEH (Structured Exception Handling) Security Changes in XPSP2 and 2003 SP1

from eeye

If you're reading this, hopefully you already know why abusing Structured Exception Handling, or SEH, is extremely interesting for Windows exploit writers. As a quick summary, SEH can provide a useful and portable way to precisely locate stack-based shellcode, and can also be used to evade Microsoft's stack protection by having the OS dispatch control to a bogus exception handler, bypassing the stack cookie check. In fact, causing an exception before the function returns is probably the only way to avoid the stack cookie check, which makes the security of SEH an area of great importance.

So, this whole thing began when I started cataloguing all of the new exploitation-related security features that Microsoft have added to their recent operating systems. All those high level details, covering much more than just SEH, can be found in an eEye whitepaper(1).

After describing those new protection methods at a conference recently, a Microsoft employee told me privately that the new Structured Exception Handling (SEH) protection was not nearly as weak as I had described. That interested me, since my primary research for that part of the presentation was a paper by David Litchfield(2), written in 2003, nothing having been published since. It appeared that some changes had been made in XPSP2 and 2003 SP1 which had not been made public. Obviously, it was time to go digging!

The best overall reference work on Structured Exception Handling was written by Matt Pietrek, back in 1997(3). If you're not already familiar with low-level SEH operation, go read that paper now and come back, it will really help.

After an exception, the kernel returns control to userland at KiUserExceptionDispatcher, and the basic call structure from there is like this (this is from Pietrek's article):

   KiUserExceptionDispatcher()
      RtlDispatchException()
         RtlpExecuteHandlerForException()
            ExecuteHandler()

Assuming that Litchfield wrote his paper when Windows 2003 had been recently released, it seemed logical to compare that code with the code I was interested in, in Windows XPSP2.

KiUserExceptionDispatcher really just hands things off to RtlDispatchException, so there is nothing to see there. RtlDispatchException is where things start to get interesting. The first thing to note is that there is some code that wasn't there in Windows 2000 which prevents the OS from dispatching to an exception handler on the stack. That code is there in Windows 2003 SP0, and appeared in Windows XP sometime before Service Pack 2. The next thing to note as a difference from Windows 2000 is a whole new function – RtlIsValidHandler – that is called before RtlpExecuteHandlerForException. That function is present on 2003 SP0 and XPSP2, and it seems likely that a lot of the new protection is implemented in there.

A First Glance

Without even starting to reverse anything yet, let's take a quick look at the function graphs of RtlIsValidHandler from 2003 SP0 and XPSP2.



OK, it certainly looks like they changed a few things! For now, then, I'm going to assume that Litchfield's analysis of 2003 was correct at the time, and focus on the new code in XPSP2. By the way, in case you're wondering, RtlIsValidHandler() is identical in XPSP2 and 2003SP1.

After a few minutes of inspection, the pseudocode for RtlIsValidHandler on 2003 SP0 looks like this:

if (&handler is in a module with an SEH registration structure) {
if (&handler is registered) {
return TRUE;
else
return FALSE;
}
}
// otherwise...
unless (RtlLookupFunctionTable returned -1) {
//--- TODO, add some security here! For now just allow it
return TRUE;
}
return FALSE;


By the way, when RtlIsValidHandler returns FALSE that will cause RtlDispatchException to return DISPOSITION_DISMISS, which should in turn raise STATUS_NONCONTINUABLE_EXCEPTION, and bail out to the UEF (after doing whatever else it does).

Digging Deeper

Now let's take a closer look at the new code.

Things start off the same, with an attempt to retrieve the SEH information for the handler address. If there is an SEH registration structure, things proceed more or less the same as before. We reach the following assembly, which is reproduced below, as a small reverse engineering puzzle. What is going on here?

Register Start States:
EDI - SEHandlerCount
EDX - 0
EAX - Pointer to SEHandlerTable

.text:7C9378F3 CheckRegisteredHandlers:
.text:7C9378F3 lea ecx, [edi+edx]
.text:7C9378F6 sar ecx, 1
.text:7C9378F8 mov ebx, [eax+ecx*4]
.text:7C9378FB cmp esi, ebx
.text:7C9378FD jb loc_7C92AA34
.text:7C937903 ja loc_7C92E1D8

.text:7C937909 HandlerValid:
.text:7C937909
.text:7C937909 mov al, 1 ; return true
[...]
.text:7C937916 leave
.text:7C937917 retn 4

.text:7C92AA34 loc_7C92AA34:
.text:7C92AA34 lea edi, [ecx-1]
.text:7C92AA37
.text:7C92AA37 loc_7C92AA37:
.text:7C92AA37 cmp edi, edx
.text:7C92AA39 jl HandlerInvalid
.text:7C92AA3F jmp CheckRegisteredHandlers

.text:7C92E1D8 loc_7C92E1D8:
.text:7C92E1D8 lea edx, [ecx+1]
.text:7C92E1DB jmp loc_7C92AA37


The key to understanding this snippet is recognizing the "sar ecx, 1" instruction (Shift Arithmetic Right) as the division of a variable by two, which is then used as an index into the handler table "mov ebx, [eax+ecx*4]". This snippet is a binary search, and it's a much faster way to search an ordered table of values than simply iterating through them. Here's what that section looks like when converted into C:

i = 0;
j = SEHCount;

do {
k = (i + j) / 2;

if (HandlerAddress < SEHTable[k]) {
j = k - 1;
}
else if (HandlerAddress > SEHTable[k]) {
i = k + 1;
}
else { // found handler within SEH table
return TRUE;
}
}
while (i <= j);
//--- If not found...
RtlInvalidHandlerDetected(HandlerAddress, SEHTable, SEHCount);


Eventually we will be able to convert the whole of RtlIsValidHandler to C, to make it easier to follow.

So, that covers the cases where the handler is in a loaded module with an SEH registration structure – no more jumping to known DLL offsets to find a pop,pop,ret. At least not in DLLs that have registered exception handlers...

Now let's look at what happens when that's not the case. The first thing we see is a call to NtQueryVirtualMemory followed by a couple of interesting tests. Documented function calls are a big help when reverse engineering. Because we know the calling convention of NtQueryVirtualMemory, we can start to rename the internal function variables, which makes things much easier to understand.

When calling NtQueryVirtualMemory, one of the parameters is the address of a structure which will be filled out with the results of the call. In the example below, the code specifies that the results should be a MEMORY_BASIC_INFORMATION structure. This is a 28 byte structure, which is based at EBP-0x34, and so it finishes at EBP-0x18. Since we know the structure definition, we can go ahead and name the structure members in that range to match. Here is what it looks like when all that is done. Unfortunately, it's not always this easy.

.text:7C937B80           lea	eax, [ebp+ResultLength]
.text:7C937B83 push eax
.text:7C937B84 push 1Ch
; sizeof(MBI), 1C == 28 bytes
.text:7C937B86 lea eax, [ebp+MBI]
.text:7C937B89 push eax
; &struct (to hold the result)
.text:7C937B8A push ebx
; EBX = 0 - MEMORY_BASIC_INFORMATION
.text:7C937B8B push esi
; Handler Address
.text:7C937B8C push 0FFFFFFFFh
; -1 - CurrentProcessId
.text:7C937B8E call _NtQueryVirtualMemory@24
.text:7C937B93 test eax, eax
.text:7C937B95 jl HandlerValid
.text:7C937B9B test [ebp+MBI.Protect], 0F0h
.text:7C937B9F jz loc_7C94EA34
.text:7C937BA5 cmp [ebp+MBI.Type], 1000000h
.text:7C937BAC jnz HandlerValid


The first thing that jumps out is this:

.text:7C937B9B           test	[ebp+MBI.Protect], 0F0h 
.text:7C937B9F jz loc_7C94EA34


The test instruction performs a bitwise AND and sets the status flags accordingly. After looking up the Memory Protection Constants, we can see that 0F0 is a mask which will cause this test to be non-zero anytime the page is PAGE_EXECUTE. In other words, if the page containing our handler is NOT executable, we jump somewhere. Interesting, we'll check that out in a minute.

Next, we see this:

.text:7C937BA5           cmp	[ebp+MBI.Type], 1000000h 
.text:7C937BAC jnz HandlerValid


That type is defined as MEM_IMAGE – the page regions which contain loaded modules and the executable image itself will all be of this type. So, if the page containing our handler is executable but NOT within a loaded module then it's valid. This is presumably to cater for runtime code generation or some similar thing.

So, this is big news! None of these checks were present in Windows 2003 SP0. One of the attacks against stack protection in Windows 2003 SP0, described by David Litchfield in his paper, involved locating suitable trampoline code inside a memory-mapped file, or perhaps causing suitable code to fill the heap and dispatching to a heap location. In those circumstances, though, the page would not normally be executable, and so the check at loc_7C94EA34 will come into play.

Let's take a look at it.

.text:7C94EA34 loc_7C94EA34:             
.text:7C94EA34 push ebx
.text:7C94EA35 push 4
.text:7C94EA37 lea eax, [ebp+mystery]
.text:7C94EA3A push eax
.text:7C94EA3B push 22h
.text:7C94EA3D push 0FFFFFFFFh
.text:7C94EA3F mov [ebp+mystery], ebx
.text:7C94EA42 call _ZwQueryInformationProcess@20
.text:7C94EA47 test eax, eax
.text:7C94EA49 jl short loc_7C94EA55
.text:7C94EA4B test byte ptr [ebp+mystery], 10h
.text:7C94EA4F jnz HandlerValid
.text:7C94EA55 push 0FFFFFFFEh
.text:7C94EA57 push 0FFFFFFFEh
.text:7C94EA59 push esi
.text:7C94EA5A call _RtlInvalidHandlerDetected@12


The calling convention for ZwQueryInformationProcess (which is just a wrapper for NtQueryInformationProcess) is well known, but we still aren't able to identify our mystery variable. Why not? The second parameter is a PROCESS_INFORMATION_CLASS, which tells NtQueryInformationProcess what kind of information to return. Unfortunately, class 0x22 is not documented anywhere, so we really have no idea what this byte test against 0x10 is trying to determine. However, we can see that if that bit is set then the handler is considered valid, even though the page is not executable. Otherwise, we call RtlInvalidHandlerDetected(HandlerAddress, -2, -2).

First, a quick inspection of RtlInvalidHandlerDetected is in order, since we saw it called before with the SEH registration table and the registered SEH count as parameters instead of -2, -2. The results are a little unusual, and can be flippantly represented by the following pseudocode:

if(-2 == SEHTable && -2 == SEHCount) {
CallKernel32UnhandledExceptionFilter([...],c0000005,[...]);
// Shut down immediately, STATUS_ACCESS_VIOLATION
else
do_nothing()
// Fix later, free donuts in the kitchen...
}
return;


OK, so if this mystery check fails then the rest of the SEH machinery will be bypassed and the UEF will be called immediately. It's time to jump into the kernel and work out what this mystery PROCESS_INFORMATION_CLASS is.

Into Ring 0

On opening up NTOSKRNL.EXE in IDA, something horrible happened to me – the symbols didn't work. That's bad, since we don't get names or call-hints for non-exported functions. For now, we won't worry about it. NtQueryInformationProcess is easy enough, even without symbols. All the major functionality is based around a large switch statement, and tracking down case 0x22 is quickly done. Once there, there is a call to a subroutine that IDA calls sub_54D0A6, which is fairly simple:

PAGE:0054D0AB           mov	eax, large fs:124h
PAGE:0054D0B1 mov eax, [eax+44h]
PAGE:0054D0B4 mov cl, [eax+6Bh]
PAGE:0054D0B7 mov eax, [ebp+arg_0]
PAGE:0054D0BA and dword ptr [eax], 0
PAGE:0054D0BD xor edx, edx
PAGE:0054D0BF inc edx
PAGE:0054D0C0 test cl, dl ; case 1
PAGE:0054D0C2 jz short loc_54D0C6
PAGE:0054D0C4 mov [eax], edx
PAGE:0054D0C6
PAGE:0054D0C6 loc_54D0C6:
PAGE:0054D0C6 test cl, 2 ; case 2
PAGE:0054D0C9 jz short loc_54D0CE
PAGE:0054D0CB or dword ptr [eax], 2
[...] [...]
PAGE:0054D0DE test cl, 10h ; case 0x10
PAGE:0054D0E1 jz short loc_54D0E6
PAGE:0054D0E3 or dword ptr [eax], 10h
[...] [...]
PAGE:0054D0EE xor eax, eax
PAGE:0054D0F0 pop ebp
PAGE:0054D0F1 retn 4


So, we can see that the single parameter is a pointer to a bitfield, which is being modified according to the flags located in some kernel structure that has been loaded in CL (the bottom byte of ECX). Google quickly comes to the rescue. fs:124h is a shortcut to the kernel ETHREAD structure for the current thread. Probably the best way to get instant documentation on this kind of structure is to use the WinDbg kernel debugger. In this case it tells us this (output trimmed):

kd> .load kdex2x86 (for some kernel debugging extensions)
kd> !ethread 81893020
struct _ETHREAD (sizeof=584)
+000 struct _KTHREAD Tcb
+000 struct _DISPATCHER_HEADER Header
+000 byte Type = 03
+001 byte Absolute = 00
+002 byte Size = 1b
+003 byte Inserted = 00
+004 int32 SignalState = 00000000
+008 struct _LIST_ENTRY WaitListHead
+008 struct _LIST_ENTRY *Flink = 81893028
+00c struct _LIST_ENTRY *Blink = 81893028
[...]
+044 struct _KPROCESS *Process = 81893060
+048 byte KernelApcInProgress = 00
+049 byte KernelApcPending = 00
+04a byte UserApcPending = 00
[...]


So, offset 44h is a pointer to the KPROCESS structure for the current process. Next, offset 0x6b of that structure is moved into the bottom byte of ECX, so let’s see what WinDbg tells us about _KPROCESS (output trimmed).

kd> dt nt!_KPROCESS 81893060 -r
+0x000 Header : _DISPATCHER_HEADER
+0x000 Type : 0x3 ''
+0x001 Absolute : 0 ''
+0x002 Size : 0x1b ''
+0x003 Inserted : 0 ''
+0x004 SignalState : 0
[...]
+0x018 DirectoryTableBase : [2] 0xf8be000
+0x020 LdtDescriptor : _KGDTENTRY
+0x000 LimitLow : 0
+0x002 BaseLow : 0
+0x004 HighWord : __unnamed
+0x000 Bytes : __unnamed
+0x000 Bits : __unnamed
[...]
+0x05c Affinity : 1
+0x060 StackCount : 1
+0x062 BasePriority : 8 ''
+0x063 ThreadQuantum : 18 ''
[...]
+0x06b Flags : _KEXECUTE_OPTIONS
+0x000 ExecuteDisable : 0y0
+0x000 ExecuteEnable : 0y0
+0x000 DisableThunkEmulation : 0y0
+0x000 Permanent : 0y0
+0x000 ExecuteDispatchEnable : 0y0
+0x000 ImageDispatchEnable : 0y0
+0x000 Spare : 0y00
+0x06b ExecuteOptions : 0 ''


There we go! Flag 0x10 is ExecuteDispatchEnable. The XPSP2 system this was taken from uses the default DEP setting, which is OptIn (look in boot.ini for /NoExecute=OptIn). Just as a test, I changed it to AlwaysOff, and got the following:

+0x06b Flags            	: _KEXECUTE_OPTIONS
+0x000 ExecuteDisable : 0y0
+0x000 ExecuteEnable : 0y1
+0x000 DisableThunkEmulation : 0y0
+0x000 Permanent : 0y1
+0x000 ExecuteDispatchEnable : 0y1
+0x000 ImageDispatchEnable : 0y1
+0x000 Spare : 0y00
+0x06b ExecuteOptions : 0x3a ':'


Just to be doubly sure, this is a handy trick. By turning on "function offset addressing" in IDA, we can disassemble NtQueryVirtualMemory in WinDbg (using u <address> or uf <function>) and match up the offsets – we find that sub_54D0A6 is really called _MmGetExecutionOptions.

So, now things are becoming clearer. If DEP is enabled, RtlIsValidHandler will check the execution permissions on the page, and raise a STATUS_ACCESS_VIOLATION if an attempt is made to dispatch to a handler in NX memory – whether the CPU supports NX at a hardware level or not. That's an important change!

A Few Loose Ends

Looking back at the Windows 2003 pseudocode, we notice that the special check for a return value of -1 from RtlLookupFunctionTable is still present in the SP2 code. All that is left now is to find out what would cause this -1 return value, and we'll have the complete picture. The answer to this question lies not within RtlLookupFunctionTable itself, but in RtlCaptureImageExceptionValues. RtlCaptureImageExceptionValues is designed to return a pointer to the SEH Registration Table, if one exists, as well as the number of registered handlers. It is called by RtlLookupFunction table, which first has to find the base of the page region containing the handler.

Although we completely reversed RtlCaptureImageExceptionValues (looking for anomalies), here is a much faster way. Locate and name the code location that sets the -1 return values, then look for possible code paths leading to that location using the IDA function graph (F12). It turns out that there are only two ways to get there. This is the first, right at the start of the function:

.text:7C9379CE           push	 [ebp+ImageBase]
.text:7C9379D1 call _RtlImageNtHeader@4
.text:7C9379D6 test byte ptr [eax+5Fh], 4
.text:7C9379DA jnz returnminusone


So it looks like that's a check against the DllCharacteristics field in the IMAGE_OPTIONAL_HEADER:

0:000> dt _IMAGE_NT_HEADERS -r
+0x000 Signature : Uint4B
+0x004 FileHeader : _IMAGE_FILE_HEADER
[...]
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
[...]
+0x046 DllCharacteristics : Uint2B
+0x048 SizeOfStackReserve : Uint4B
+0x04c SizeOfStackCommit : Uint4B


So, if DllCharacteristics has 0x04 set, we return -1 immediately. Sadly, there seems to be no modern documentation on that field anywhere, so it will have to remain a minor mystery. One possible meaning for this check is that it lets you mark DLLs as "no handlers here, go away", but that's just a guess.

The other way to cause a return of -1 is this:

.text:7C937BD0 NoSEHTable:  ;There is no SEH Registration table
.text:7C937BD0
.text:7C937BD0 lea eax, [ebp+ImageBase]
.text:7C937BD3 push eax
.text:7C937BD4 push 0Eh ; COM_DESCRIPTOR
.text:7C937BD6 push 1
.text:7C937BD8 push [ebp+ImageBase]
.text:7C937BDB call _RtlImageDirectoryEntryToData@16
.text:7C937BE0 test eax, eax
.text:7C937BE2 jnz ManagedCodeCheck

.text:7C937BE8 returnzero:
.text:7C937BE8 mov eax, [ebp+HandlerTable]
.text:7C937BEB and dword ptr [eax], 0
.text:7C937BEE mov eax, [ebp+HandlerCount]
.text:7C937BF1 and dword ptr [eax], 0
.text:7C937BF4 jmp locret_7C937A37

.text:7C94F6C0 ManagedCodeCheck:
.text:7C94F6C0 test byte ptr [eax+10h], 1 ; flags
.text:7C94F6C4 jnz short returnminusone
.text:7C94F6C6 jmp returnzero


RtlImageDirectoryEntryToData returns information from the IMAGE_DIRECTORY entries linked to the PE Header. In this case, we’re looking at a flag that has something to do with the COM_DESCRIPTOR entry (0xe). A little sleuthing turns up the fact that IMAGE_DIRECTORY_COM_DESCRIPTOR uses the header structure COR20_HEADER. COR20 ... Common Object Runtime 2.0? Hey, COM+ 2.0 was the early name for the .NET framework, so it’s very likely that this check is something to do with .NET Managed Code. If the code is Managed then the checking can be much more strict about modules that have missing SEH Registration Tables. Probably this is why it has a special return value, to let RtlIsValidHandler know that it was more than just a generic failure to find the SEH table – that probably happens all the time with 3rd party applications that use custom .DLLs.

In fact, looking up the flag in corhdr.h and then some more detective work finally turns up the following definition. Bingo.

COMIMAGE_FLAGS_ILONLY (0x00000001) The Image contains IL Code Only, with no embedded native unmanaged code, except the startup stub. Because Common Language Runtime - aware Operating Systems (such as Windows XP) ignore the startup stub, for all practical purposes the file can be considered Pure-IL.[...]



_RtlIsValidHandler(PVOID handler)

So, with all that out of the way, we are finally ready to convert the new RtlIsValidHandler to pseudocode. This is done for clarity, actually based on an exact C conversion that was produced during the course of the investigation.

if (SEHTable != NULL && SEHCount != 0) {
if (SEHTable == -1 && SEHCount == -1) {
// Managed Code but no SEH Registration table
// or IMAGE_LOAD_CONFIG.DllCharacteristics == 4
return FALSE;
}
if (&handler is registered) {
return TRUE;
else
return FALSE;
}
}
// otherwise...
if (&handler is on an NX page) {
if (DEP is turned on) {
bail(STATUS_ACCESS_VIOLATION);
else
return TRUE;
}
}
if (&handler is on a page mapped MEM_IMAGE) {
// normally only true for executable modules
if (SEHTable == NULL && SEHCount == 0) {
return TRUE;
// probably an old or 3rd party DLL
// without SEH registrations
}
return FALSE // we should have caught this before
// so something is wrong.
}
// Handler is on a eXecutable page, but not in module space
// Allow it for compatibility.
return TRUE;


There are one or two little quirks, like this little snippet that happens just before the NX check:

.text:7C937B8E           call	_NtQueryVirtualMemory@24
.text:7C937B93 test eax, eax
.text:7C937B95 jl HandlerValid


Which is going to mark a handler as valid if NtQueryVirtualMemory fails for some reason – a more secure approach would be to mark the handler as invalid, which is “default deny”. Also, note that the default is to allow any handlers in non-module space which are on eXecutable pages – it would probably be more secure to deny this by default, and let applications which need to generate exception handlers at runtime override the default setting during compilation.

What about __except_handler3?

The next attack that springs to mind, if we can't just jump to our own code by using the handler pointer, is to attack the exception handling process itself. For Visual C++ compiled apps (which obviously covers all the Microsoft system components) most of the SEH work is performed by __except_handler3. In fact, the exception handler pointer in every EXCEPTION_REGISTRATION on the stack normally points to __except_handler3, which then sorts things out by using additional information that is specific to the Visual C++ implementation of SEH.

That extra information is stored in an array of scopetable entries, and the current trylevel is used as an index to the array. The scopetable array is, unfortunately, not stored on the stack, so we can't attack it directly. Here is the general structure of the scopetable array entries:

typedef struct _SCOPETABLE
{
DWORD previousTryLevel;
DWORD lpfnFilter
DWORD lpfnHandler
} SCOPETABLE, *PSCOPETABLE;


Again, I strongly recommend that you read this next part after understanding Pietrek's SEH article. During the handling process, __except_handler3 will ask each handler that has an EXCEPTION_REGISTRATION structure on the stack whether or not it is prepared to handle the exception. This is done by calling the handler's filter function which expected to return EXCEPTION_EXECUTE_HANDLER if it agrees to handle the exception or EXCEPTION_CONTINUE_SEARCH if it won't. In pseudocode that call to the filter function looks like this (again, taken from Pietrek's article):

[...]
search_for_handler:

if ( pRegistrationFrame->trylevel != TRYLEVEL_NONE )
{
if ( pRegistrationFrame->scopetable[trylevel].lpfnFilter )
{
// Save this frame EBP
PUSH EBP

// Switch to original EBP
EBP = &pRegistrationFrame->_ebp

// Call the filter function
// Maybe we can own this?
filterFuncRet = scopetable[trylevel].lpfnFilter();

// Restore handler frame EBP
POP EBP
[...]


Looking again at Litchfield's article on 2003 SP0, he mentions abusing an "existing registered handler" at 0x77F45A34. Although it is not mentioned, that handler is none other than __except_handler3 in NTDLL.DLL, and this is the relevant disassembly representing the code above.

[...]
.text:77F45A3F mov ebx,[ebp+arg_4] // &pRegistrationFrame
[...]
.text:77F45A61 mov esi,[ebx+0Ch] // trylevel
.text:77F45A64 mov edi,[ebx+8] // scopetable
[...]
.text:77F45A75 lea ecx,[esi+esi*2]
.text:77F45A78 mov eax,[edi+ecx*4+4]
// scopetable[trylevel].lpfnFilter() -> EAX
[...]
.text:77F45A81 push ebp // save EBP
.text:77F45A82 lea ebp,[ebx+10h] // original frame EBP
[...]
.text:77F45A8F call eax


So, given that we own everything on the stack, including &pRegistrationFrame, we can obviously use this to take control. The general method would be to supply bogus values for the scopetable pointer and trylevel, so that scopetable[trylevel].lpfnFilter() points to our payload, which __except_handler3 will call for us. Another idea would be to preserve the original frame EBP at EBX+16h (we control the stack at that location) and replace the filter function with the location of non-stack-based trampoline code like CALL EBP, PUSH EBP // RET, etc. This approach would have much the same effect, but might be useful for evading certain protection mechanisms.

Unfortunately for attackers, they fixed this area too.

The disassembly above was NTDLL.DLL from Windows 2003 SP0. In 2003 SP1 a new function has been added, called __ValidateEH3RN. This new function is pretty huge, there wasn't the time or the need to completely reverse engineer it. However here are some highlights that jumped out:

  1. A check to ensure that the scopetable array is not on the stack and that it is 4-byte aligned.
  2. A sanity check on the array, made by walking the array from scopetable[0] to scopetable[trylevel]. This would make it very difficult to use the real scopetable array with a bogus trylevel to reference an lpfnFilter address which is out of array bounds.
  3. Nested handlers are also sanity checked in step 2, above, which effectively means that any existing code to be used as a fake scopetable entry would need to have previousTryLevel set to -1, ie 0xFFFFFFFF preceding the payload address.
  4. An NtQueryVirtualMemory check on the scopetable against MEM_IMAGE and READONLY.
  5. A lot of other code, which is probably some kind of check against the lpfnFilter pointer itself.

This puts paid to the obvious attempts to abuse __except_handler3, although a complete analysis may yet turn up an exploitable scenario. For the time being, none of the "simple" attacks seem possible.

The Bottom Line

Overall, the new RtlIsValidHandler is a vast improvement over the old one, and closes the most egregious holes, so it seems very unlikely that control can be directly dispatched to a payload using "raw" exception handling. It may still be possible to find suitable trampoline code in a 3rd party DLL that does not have an SEH registration structure, but this would depend heavily on the individual application being attacked.

Attacking the Visual C++ SEH implementation via __except_handler3 is similarly unlikely, thanks to the new C++ runtime library function, __ValidateEH3RN. The SEH implementations of other compilers may still be vulnerable, however.

There will always be a few corner cases, 3rd party applications that opt-out of DEP and application specific errors, of course. The good news, though, is that in conjunction with the stack cookie implementation this new protection seems to eliminate every currently known, application-generic attack templates for Windows stack overflows - which is good news.

At the time of writing, virtually none of this new functionality had been publicized. Hopefully, bringing the full extent of this new protection to light will encourage organizations to accelerate their shift to Windows XP SP2 or Windows 2003 SP1, even on platforms without hardware NX support.

References

  1. B. Nagy, Generic Anti-Exploitation Technology for Windows, 2005, available online at http://www.eeye.com/html/research/whitepapers/index.html
  2. D. Litchfield, Defeating the Stack Based Buffer Overflow Prevention Mechanism of Microsoft Windows 2003 Server, 2003, http://www.nextgenss.com/papers/defeating-w2k3-stack-protection.pdf
  3. M. Pietrek, A Crash Course on the Depths of Win32™ Structured Exception Handling, in Microsoft Systems Journal, January 1997, online at http://www.microsoft.com/msj/0197/exception/exception.aspx


Acknowledgements
Derek Soeder and his Bit-Fu helped in countless ways, including some of the C conversion. I was spurred on by the efforts of a blogger in China called "nop" who reversed some of the same areas of code, but didn't get right to the bottom of it. Thank you to Robert Ross who gave me a great suggestion as well.

Source: Ben Nagy, Senior Security Engineer, eEye Digital Security


Ask Research

Q: Is silently fixing security issues made available during regular software updates common practice amongst software vendors?

A: Yes. Although recent speaking engagements by members of the eEye Research Team have focused on Microsoft patches, it has been observed that most software vendors will fix security issues during regular updates. The list of corrections published by the vendor will not mention the security fixes, so only by dissecting the actions of the patch can one tell that something was changed. Look for the slides from the eEye Research presentation "Skeletons in Microsoft's Closet: Silently Fixed Vulnerabilities" on the eEye Research Portal (research.eeye.com) in the near future.


Q: We noticed that in your D-Link security advisory that you listed all the patched models of D-Link routers, but D-Link support does not have all the patches available. What gives?

A: When eEye releases an advisory, we are dependent on the vendors informing us of their patches being released to the public. In this case, on the date we released the advisory, all of the listed models did in fact have a patch. If those patches have been removed for whatever reason, not all vendors will notify the author of the advisory. In this case, we suggest that you contact D-Link support for clarification on what patches are available and when patches for certain models will be re-released.

Have a question you would like answered? Send it to vice@eeye.com, and win an eEye t-shirt if we select your question for an upcoming newsletter.
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值