You might be using unhandled exception filters without even knowing it.

In a previous posting, I discussed some of the pitfalls of unhandled exception filters (and how they can become a security problem for your application). I mentioned some guidelines you can use to help work around these problems and minimize the risk, but, as I alluded to earlier, the problem is actually worse than it might appear on the surface.

The real gotcha about unhandled exception filters is that you have probably used them before in programs or DLLs and not even known that you were using them, which makes it very hard to not use them in dangerous situations. How can this be, you might ask? Well, it turns out that the Microsoft C runtime library uses an unhandled exception filter to catch unhandled C++ exceptions and call the terminate handler registered by set_terminate.

This unhandled exception filter is setup by the internal CRT functions _cinit (via _initterm_e). If you have the CRT source handy, this lives in crt0dat.c. The call looks like:

/*
* do initializations
*/
initret = _initterm_e( __xi_a, __xi_z );

Here, “__xi_a” and “__xi_z” define the bounds of an array of function pointers to initializers called during the CRT’s initialization. There is a pointer to a function (_CxxSetUnhandledExceptionFilter) that sets up the unhandled exception filter for C++ exceptions in this array. Unfortunately, source code for the function used to setup _CxxUnhandledExceptionFilter is not present, but you can find it by looking at the CRT in a disassembler.

push    offset CxxUnhandledExceptionFilter
call    SetUnhandledExceptionFilter
mov     lpTopLevelExceptionFilter, eax
xor     eax, eax
retn

This is pretty standard; it is just saving away the old exception filter and registering its new exception filter. The unhandled exception filter itself checks for a C++ exception – if found, it calls terminate, otherwise it tries to verify that the previous exception filter points to executable code, and if so, it will call it.

push    esi
mov     esi, [esp+arg_0]
mov     eax, [esi]
cmp     dword ptr [eax], 0E06D7363h
jnz     short not_cpp_except
cmp     dword ptr [eax+10h], 3
jnz     short not_cpp_except
mov     eax, [eax+14h]
cmp     eax, 19930520h
jz      short is_cpp_except
cmp     eax, 19930521h
jnz     short not_cpp_except 

is_cpp_except:
call    terminate

not_cpp_except:
mov     eax, lpTopLevelExceptionFilter
test    eax, eax
jz      short old_filter_unloaded
push    eax             ; lpfn
call    _ValidateExecute
test    eax, eax
pop     ecx
jz      short old_filter_unloaded
push    esi
call    lpTopLevelExceptionFilter
jmp     short done

old_filter_unloaded:
xor     eax, eax

done:
pop     esi
retn    4

The problem with the latter validation is there is no way to tell if the code is part of a legitimate DLL, or part of the heap or some other allocation that has moved over where a DLL had previously been unloaded, which is where the security risk is introduced.

So, we have established that the CRT potentially does bad things by installing an unhandled exception filter – so what? Well, if you link to the DLL version of the CRT, you are probably fine. The CRT DLL is unlikely to be unloaded during the process lifetime and will only be initialized once.

The kicker is if you linked to the static (non-DLL) version of the CRT. This is where things start to get dicey. The dangerous combination here is that each image linked to the static version of the CRT will have its own copy of _cinit, and its own copy of _CxxSetUnhandledExceptionFilter, its own copy of _CxxUnhandledExceptionFilter, and soforth. What this boils down to is that every image linked to the static version of the Microsoft C runtime installs an unhandled exception filter. So, if you have a DLL (say one that hosts an ActiveX object) which links to the static CRT (which is pretty attractive, as for plugin type DLLs you don’t want to have to write a separate installer to ensure that end users have that cumbersome msvcr80.dll), then you’re in trouble. Since this is an especially common scenario (plugin DLL linking to the static CRT), you have probably ended up using an unhandled exception filter without knowing it (and probably without realizing the implications of doing so) – simply by making an ActiveX control usable by Internet Explorer, for example. This really turns into a worst case scenario when it comes to DLLs that host ActiveX objects. These are DLLs that are going to be frequently loaded and unloaded, are controllable by untrusted script, and are very likely to link to the static CRT to get out of the headache of having to manage installation of the DLL CRT version. If you put all of these things together and throw in any kind of crash bug, you’ve got a recipie for remote code execution. What is even worse is that this isn’t just quick-fixable with a patch to the CRT, as the vulnerable CRT version is compiled into your binaries and not in its own hotfixable standalone DLL.

So, in order to be truly safe from the dangers of unhandled exception filters, you also need to rid your programs of the static CRT. Yes, it does make setup more of a pain, but the DLL CRT is superior in many ways (not to mention that it doesn’t suffer from this security problem!).

4 Responses to “You might be using unhandled exception filters without even knowing it.”

  1. Hi Skywing,

    I’ve just been reading ‘Beware of custom unhandled exception filters in DLLs’ and this article. Thanks a lot for this very detailed explanation of this pitfall (I’m also using this api’s in an application with the big advantange of detecting the source of almost any unhandled Exception but as pointed out also the risk of introducing new crashes (and even security holes).
    I think it is almost the same with _set_se_translator apis.
    Are you aware of any >clean

  2. Skywing says:

    The way I typically do this is by hooking SetUnhandledExceptionFilter and disallowing further registrations after I register my SEH. Yes, it’s a hack, but it’s been unfortunately better than the documented API as far as working reliably and not breaking when used with unloading DLLs in my experience.

  3. […] [Skywing] You might be using UEFs without even knowing it. […]

  4. […] [Skywing] You might be using UEFs without even knowing it. […]