Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Vectored Exception Handling in Windows XP SP2

1.00/5 (1 vote)
1 Sep 2007CPOL2 min read 1   211  
This article updates Matt Pietrek's Vectored Exception Handling article from the MSDN Magazine.

Introduction

In September 2001, under The Hood column in MSDN Magazine, Matt Pietrek covered a new feature introduced in Windows XP - Vectored Exception Handling (VEH). Matt explained in detail the VEH and gave us a useful example. When I examined this issue in Windows XP SP2, the most popular OS currently, I found some problems.

First, the demo program crashes when I run it. As follows:

Screenshot - crash.jpg

According to the "Application Error" dialog, we know the error is a single step exception. I examined the source carefully, but couldn't find any problems. When I attached the Visual Studio .NET 2003 debugger to the crashed program, I found the error address is the second instruction of the LoadLibraryExW function. Why is the single step flag still here? Didn't we have it removed? Finally, I have found the reason. In the previous Windows Operation System, almost all system functions have a standard stack frame. The first instruction of these functions is "PUSH EBP" and its opcode is 55h, that is 1 byte. But in Windows XP SP2, the standard prolog code for the SEH stack frame of the system function has been replaced with the _SEH_prolog function. I have found that the first instruction of these functions push the count of memory that local variables of these functions take into stack. For LoadLibraryExW, it's "PUSH 34h" and its opcode is 6Ah 34h, that is 2 bytes. In the demo program, if the single step flag wants to be removed, it must satisfy two conditions: first, the exception code must be STATUS_SINGLE_STEP; second, the exception address must be the next address of LoadLibraryExW's address. But it's impossible in Windows XP SP2, because the first instruction of LoadLibraryExW is 2 bytes. If we replace 1 in the source with 2, we can solve this problem.

Second, in that article, Matt said that the Vectored Exception Handling linked list is a "Circular Linked List", but I have found it is a "Doubly Linked List". Moreover, the RtlpCalloutEntryList global variable is the head of this linked list. This global variable is a LIST_ENTRY structure (8 bytes), not a VECTORED_EXCEPTION_NODE pointer. How did I find this? When I used SoftICE to see its value, I found the following:

Screenshot - gv.jpg

Obviously, the variable takes 8 bytes. When you think of the style that Windows processes the linked list, you will get it.

As is well known, Windows XP SP2 enhances the security of the system. Many globally available pointers are encoded so that it is difficult to use these pointers to exploit. Exception handling is the first to be affected. Windows XP SP2 supplies a common way to do this. It is the EncodePointer API. In fact, this API is forwarded to RtlEncodePointer in NTDLL.DLL. Of course, there is DecodePointer, and it's forwarded to RtlDecodePointer in NTDLL.DLL.

Based on Matt's pseudocode, I have written the pseudocode for these API:

VEH.H file:
C++
typedef struct _VECTORED_EXCEPTION_NODE {
    LIST_ENTRY ListEntry;
    PVECTORED_EXCEPTION_HANDLER    pfnHandler;
    // for security, this pointer has been encoded
} VECTORED_EXCEPTION_NODE, *PVECTORED_EXCEPTION_NODE;

// The following two global variables are initialized by Windows loader
// (In fact, it's LdrpInitializeProcess routine)

// this variable is response to protect insert and delete operation from
// linked list, Windows loader call RtlInitializeCriticalSection to init it
RTL_CRITICAL_SECTION RtlpCalloutEntryLock;

// this variable is the head of the Vectored Exception Handling linked list
// Windows loader call InitializeListHead to init it
// please refer to the section "Singly- and Doubly-Linked Lists" in DDK documents
LIST_ENTRY RtlpCalloutEntryList;
VEH.C file:
C++
#include <ntddk.h>
#include "veh.h"

// the prototype comes from winbase.h in Windows Server 2003 R2 SDK
PVOID WINAPI RtlAddVectoredExceptionHandler(
            ULONG First,
            PVECTORED_EXCEPTION_HANDLER Handler)
{
    PVECTORED_EXCEPTION_NODE pCurrentNode = (PVECTORED_EXCEPTION_NODE)
        RtlAllocateHeap( GetProcessHeap(), 0, sizeof(VECTORED_EXCEPTION_NODE) );

    if (!pCurrentNode)
    {
        return 0;
    }

    pCurrentNode->pfnHandler = RtlEncodePointer(Handler);    // encode for security

    RtlEnterCriticalSection(&RtlpCalloutEntryLock);

    if (First)
    {
        InsertHeadList(&RtlpCalloutEntryList, &pCurrentNode->ListEntry);
    }
    else
    {
        InsertTailList(&RtlpCalloutEntryList, &pCurrentNode->ListEntry);
    }

    RtlLeaveCriticalSection(&RtlpCalloutEntryLock);

    return pCurrentNode;
}


// SDK document says Handler is PVOID, but according to the return value of
// AddVectoredExceptionHandler API, it is PVECTORED_EXCEPTION_NODE actually.
ULONG WINAPI RemoveVectoredExceptionHandler(PVOID Handler)
{
    PVECTORED_EXCEPTION_NODE pCurrentNode, pRemovedNode;
    BOOL bHandlerExist = FALSE;

    RtlEnterCriticalSection(&RtlpCalloutEntryLock);

    for ((PLIST_ENTRY)pCurrentNode  = RtlpCalloutEntryList.Flink;
         (PLIST_ENTRY)pCurrentNode != &RtlpCalloutEntryList;
         (PLIST_ENTRY)pCurrentNode  = pCurrentNode->ListEntry.Flink)
    {
        if (pCurrentNode == (PVECTORED_EXCEPTION_NODE)Handler)
        {
            pRemovedNode = pCurrentNode;
            RemoveEntryList(&pCurrentNode->ListEntry);

            bHandlerExist = TRUE;
            break;
        }
    }

    RtlLeaveCriticalSection(&RtlpCalloutEntryLock);

    if (bHandlerExist)
    {
        RtlFreeHeap(GetProcessHeap(), 0, (LPVOID)pRemovedNode);

        return 1;
    }

    return 0;
}


int WINAPI RtlCallVectoredExceptionHanlers(
                PEXCEPTION_RECORD pExcptRec,
                PCONTEXT pContext)
{
    EXCEPTION_POINTERS ExceptionPointers;

    // Is the list empty ?
    if (RtlpCalloutEntryList.Flink == &RtlpCalloutEntryList)
    {
        return 0;
    }
    else
    {
        PVECTORED_EXCEPTION_NODE pCurrentNode;
        BYTE retValue = 0;

        ExceptionPointers.ExceptionRecord = pExcptRec;
        ExceptionPointers.ContextRecord   = pContext;

        RtlEnterCriticalSection(&RtlpCalloutEntryLock);

        for ((PLIST_ENTRY)pCurrentNode  = RtlpCalloutEntryList.Flink;
             (PLIST_ENTRY)pCurrentNode != &RtlpCalloutEntryList;
             (PLIST_ENTRY)pCurrentNode  = pCurrentNode->ListEntry.Flink)
        {
            LONG disposition = ((PVECTORED_EXCEPTION_HANDLER)(
              RtlDecodePointer(pCurrentNode->pfnHandler)))(&ExceptionPointers);
            if (disposition == EXCEPTION_CONTINUE_EXECUTION)
            {
                retValue = 1;
                break;
            }
        }

        RtlLeaveCriticalSection(&RtlpCalloutEntryLock);

        return retValue;
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)