SetUnhandledExceptionFilter和VS2005

来源:互联网 发布:canny边缘检测算法原理 编辑:程序博客网 时间:2024/05/16 09:10

 

Visual C++ 2005 CRT library tends to call Dr.Watson directly in some emergency cases, ignoring user-defined unhandled exception filter. We need an ability to configure this behavior.

Rationale

When any of CRT invariants are violated (invalid arguments passed, buffer overrun detected, etc.) CRT needs to report somehow about that violation. VC++ 2005 uses the following strategy:
1. Display (optionally) a message box with error description.
2. Invoke Dr.Watson to create crash dump.

(All this code is located in abort.c, gs_report.c, and invarg.c files).

The concern and question is about the second step - an invocation of Dr.Watson.

There's no "good" way to replace Dr.Watson's reporting with user-defined error reporting procedure. Some applications need to perform some additional actions, such as writing their own crash dumps, logging error information, etc.

But VC++ 2005 CRT forces us to use Dr.Watson - all mentioned above files use the following trick to invoke Dr.Watson:

         /* Make sure any filter already in place is deleted. */
         SetUnhandledExceptionFilter(NULL);

         UnhandledExceptionFilter(&ExceptionPointers);

CRT removes any currently installed exception filter with SetUnhandledExceptionFilter(NULL) and produce an unhandled exception to invoke Dr.Watson.

QUESTION: Why there's no way to change this behavior?

We need an ability to replace Dr.Watson invocation with our own procedure OR to register a callback that will be fired before Dr.Watson invocation.

Workarounds

1. _set_abort_behavior, signal(SIGABRT, ...), and _set_invalid_parameter_handler(...) functions allow to intercept just *several* cases. But there're a lot of checks in CRT code that pass around and invoke Dr.Watson directly (just search for "_invoke_watson").

2. API hooking technique to intercept SetUnhandledExceptionFilter function to prevent user-defined filter to be replaced. It couldn’t be considered as a good way in many applications.
建议

There are several separate situations here

One is when we have detected a buffer overrun (/GS failure). For security reasons in this case, we directly call the UnhandledExceptionFilter. We removed the mechanism that VC7 had to intercept these because it represented a real vulnerability. This is not going to change.

Another is when we detect an invalid parameter before a bad situation occurs. You can use _set_invalid_parameter_handler to influence these errors and intercept what happens.

A third are other abnormal terminations (purecall, unexpected, etc). These generally have their own handlers that you can set (_set_purecall, _set_terminate, _set_unexpected). These exist only in cases where we consider it safe to do so.

If there's a specific case that I've not covered above where you'd like to be able to intercept but cannot, please open a specific suggestion item for that one. But we did review all the places we added _invoke_watson.

Aside from the /GS case (where we do have no interception point, deliberately, because the process is already unsafe), we do have intervention points. Since the watson code automatically collects a crash dump and writes to the event log, we think this design provides the appropriate balance of security and configurability.

In a last resort (not recommended) you can link to the static library where you can replace any function.

api hook:

void DisableSetUnhandledExceptionFilter()

{

    void *addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")),

                                                           "SetUnhandledExceptionFilter");

    if (addr)

    {

              unsigned char code[16];

              int size = 0;

              //xor eax,eax;

              code[size++] = 0x33;

              code[size++] = 0xC0;

              //ret 4

              code[size++] = 0xC2;

              code[size++] = 0x04;

              code[size++] = 0x00;

           DWORD dwOldFlag, dwTempFlag;

              VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);

              WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);

              VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);

       }

}