Search code examples
exeintel-pin

Get location of exception handler after an exception has occurred(in a .exe file) via Intel PinTool


Sorry for the long POST :)

I would like to write a pintool for .exe files which on a exception does the following:

  1. Prints the excepting instruction address.
  2. Prints the address of the handler which will handle this exception.
  3. Prints the address of the instruction where the program returns.

I have read all about windows SEH mechanism and I have good familiarity with intel pin-tool itself. To get going, I wrote the following test program:

#include <stdio.h>
void bar(){
        throw 20;
}
void foo(char *s, int a){
    printf(s,a);
}
int main(){
    try{
        bar();
    }
    catch(int e){
        foo("%d\n",e );
    }
}

Then I printed all the routines (that are called after main has been called) with their return addresses for this program using pintool. Here is the list:

Then I read up the documentation of these routines. I thought that the arguments of these routines might contain the information I need. But all in vain. RtlRaiseException arguments did gave me access to the ExceptionRecord structure but its ExceptionAddress field contains the starting address of RaiseException instead of an address of bar.

And I was unable to find any way to get the Exception Handler location which will handle the throw.

Any help is appreciated; thanks :)


Solution

  • This is not an answer directly aimed at a PIN solution but rather a help on how to find the handler.

    Below is the interesting part of the code compiled without optimization (I added a printf below the bar function so the handler was not too close to bar; code compiled with VS 2015):

    CPU Disasm
    Address    Command                                Comments
    011D1071   CALL bar                               ; [bar
    011D1076   PUSH OFFSET 011D2110                   ; ASCII "you wont see me..."
    011D107B   CALL printf                            ; [printf
    011D1080   ADD ESP,4
    011D1083   JMP SHORT 011D109C
    011D1085   MOV EAX,DWORD PTR SS:[EBP-14]          ; Handler start
    011D1088   PUSH EAX
    011D1089   PUSH OFFSET 011D2124                   ; ASCII "%d"
    011D108E   CALL foo                               ; [foo
    

    Here's the code for the bar() function:

    CPU Disasm
    Address    Command                                Comments
    011D1000 b PUSH EBP                               ; TestCppException.bar(void)
    011D1001   MOV EBP,ESP
    011D1003   PUSH ECX
    011D1004   MOV DWORD PTR SS:[EBP-4],14
    011D100B   PUSH OFFSET _TI1H                      ; /Arg2 = TestCppException._TI1H
    011D1010   LEA EAX,[EBP-4]                        ; |
    011D1013   PUSH EAX                               ; |Arg1
    011D1014   CALL _CxxThrowException                ; \VCRUNTIME140._CxxThrowException
    011D1019   MOV ESP,EBP
    011D101B   POP EBP
    011D101C   RETN
    

    C++ exception are different beasts than other exceptions (access violation, divide by 0, etc.) as there are a lot of things going on under the hood before reaching the handler (the catch part).

    As you have seen, a C++ exception is raised using:

    When RtlRaiseException is called, the Exception record looks like this:

    CPU Stack
    Address   Value                Comments
    0018FAF8  |E06D7363    ; ExceptionCode=E06D7363
    0018FAFC  |00000001    ; Flags=EXCEPTION_NONCONTINUABLE
    0018FB00  |00000000    ; pExceptionRecord=NULL
    0018FB04  |7736DAA0    ; ExceptionAddress=KERNELBASE.RaiseException
    0018FB08  |00000003    ; Nparm=3
    0018FB0C  |19930520    ; exception version identifier
    0018FB10  |0018FBA4    ; pObject
    0018FB14  |011D2638    ; _ThrowInfo*
    
    • The exception code 0xE06D7363 identifies a C++ exception (same code for all C++ exception).

    • Flags is set to EXCEPTION_NONCONTINUABLE because C++ exceptions do not support continuation (the code can’t continue execution at the point where the exception occurred).

    • pExceptionRecord is NULL because there’s no exception chaining in CPP exception.

    • The ExceptionAddress is RaiseException because it is not “directly” your code that raises the exception, but the system does it for you (i.e: it’s not like if you had a divide by 0 which can be pinpointed to a very specific assembly instruction; here the system raises the exception for you, and it raises it at RaiseException).

    • There are 3 parameters to this exception record :

      • The first one is an exception identifier (this is undocumented, but probably means that this is a VC6 style exception).

      • pObject is a pointer to the thrown object (in our case this is a pointer to the integer 20)

      • Finally a _ThrowInfo structure (the same as the argument passed to _CxxThrowException) which is used by the system, more precisely the exception dispatcher, to see which handler can catch this exception.

    From this point, we have the “usual” SEH stuff where the system dispatches the exception and search for the right SEH handler. In our example, “which catch block can catch an int”.

    It happens that the last SEH in the chain can handle the thrown int.

    Here’s an assembly view of the SEH:

    CPU Disasm
    Address    Command                           Comments
    00CA1D91   MOV EAX,OFFSET 011D2580        ; pointer to __ehfuncinfo
    00CA1D96   JMP __CxxFrameHandler3            ; Jump to VCRUNTIME140.__CxxFrameHandler
    

    We only have a structure passed (through eax register) to a function named __CxxFrameHandler3.

    The structure, named __ehfuncinfo looks like this:

    struct ehfuncinfo1200 //_s_ESTypeList
    {
      /* 0x00 */  uint32_t        magic : 30;
      /* 0x04 */  ehstate_t       unwindtable_size;
      /* 0x08 */  unwindtable *   unwindtable;
      /* 0x0C */  size_t          tryblocktable_size;
      /* 0x10 */  tryblock *      tryblocktable;
      /* 0x14 */  size_t          _size;
      /* 0x18 */  void *          _;
    /* … snip … */
    };
    

    In our case we have:

    CPU 
    Address    Value       Comments
    011D2580   19930522    ; magic
    011D2584   00000002    ; unwind table size 
    011D2588   011D25A4    ; unwind table
    011D258C   00000001    ; try block table size
    011D2590   011D25B4    ; try block table
    011D2594   00000000
    011D2598   00000000
    

    An entry in the try block table looks like this:

    struct tryblock
    {
      ehstate_t   trylow;
      ehstate_t   tryhigh;
      ehstate_t   catchhigh;
      int         ncatches;
      ehandler *  catchsym;
    
      /* snip */
    };
    

    Below are the value from our example:

    CPU Stack
    Address   Value      Comments
    011D25B4   00000000  
    011D25B8   00000000
    011D25BC   00000001  
    011D25C0   00000001 ; ncatches
    011D25C4   011D25C8 ; catchsym
    

    The catchsym field is a ehandler structure:

    /// This type represents the catch clause
    struct ehandler
    {
    //  union { uint32_t  adjectives; void * ptr; };
      uint32_t isconst      : 1; /* + 00 */
      uint32_t isvolatile   : 1;
      uint32_t isunaligned  : 1;
      uint32_t isreference  : 1;
    
      const type_info *   typeinfo; /* + 04 */
      ptrdiff_t           eobject_bpoffset; // 0 = no object (catch by type)
      generic_function_t *  handler; /* + 0x0C */
    
      /* snip */
    };
    

    Note: the handler field is the address of the catch block!

    And the value:

    CPU Stack
    Address   Value        Comments
    011D25C8   00000000    ; 
    011D25CC   011D3030    ; Typeinfo (OFFSET TestCppException.int `RTTI Type Descriptor')
    011D25D0   FFFFFFEC    
    011D25D4   011D1085    ; Handler entry point
    

    And in our case the handler field points to the precise location of the catch block:

    CPU Disasm
    Address    Command                            Comments
    011D1085   MOV EAX,DWORD PTR SS:[EBP-14]      ; handler start
    011D1088   PUSH EAX
    011D1089   PUSH OFFSET 011D2124               ; ASCII "%d"
    011D108E   CALL foo                           ;
    

    Summary

    If an exception occurs (see Exception API in PIN Manual)

    • Check the exception ocde: if it's 0xE06D7363 then it's a C++ exception
    • Wait for __CxxFrameHandler3 to be called (technically it is a JMP to this function, not a CALL)
    • Check the eax register on __CxxFrameHandler3 entry.
    • The eaxregister is a pointer to a __ehfuncinfo structure.
    • Follow all structures up to the handler field of the ehandler structure.
      • the handler field is the address of the catch block

    Some good pointers on the subject:


    Pintool code

    /* ===================================================================== */
    /* This example demonstrates finding a function by name on Windows.      */
    /* ===================================================================== */
    
    #include "pin.H"
    #include <iostream>
    #include <fstream>
    
    /*
    * C++ exception structures
    */
    
    // This type represents the catch clause
    typedef struct _ehandler
    {
        //  union { uint32_t  adjectives; void * ptr; };
        uint32_t            adjectives;         /* + 0x00 */
        const type_info *   typeinfo;           /* + 0x04 */
        ptrdiff_t           eobject_bpoffset;   /* + 0x08 */
        void*               handler;            /* + 0x0C */ 
        /* snip */
    } ehandler;
    
    typedef struct _tryblock
    {
        uint32_t   trylow;
        uint32_t   tryhigh;
        uint32_t   catchhigh;
        int         ncatches;
        ehandler *  catchsym;
        /* snip */
    } tryblock;
    
    typedef struct ehfuncinfo1200 //_s_ESTypeList
    {
        /* 0x00 */  uint32_t        magic : 30;
        /* 0x04 */  uint32_t       unwindtable_size;
        /* 0x08 */  void *   unwindtable;
        /* 0x0C */  size_t          tryblocktable_size;
        /* 0x10 */  tryblock *      tryblocktable;
        /* 0x14 */  size_t          _size;
        /* 0x18 */  void *          _;
        /* snip */
    } ehfuncinfo;
    
    /* ===================================================================== */
    /* Global Variables */
    /* ===================================================================== */
    
    std::ofstream TraceFile;
    
    /* ===================================================================== */
    /* Commandline Switches */
    /* ===================================================================== */
    
    KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool",
        "o", "check_handler.out", "specify trace file name");
    
    /* ===================================================================== */
    /* Print Help Message                                                    */
    /* ===================================================================== */
    
    INT32 Usage()
    {
        cerr << "This tool produces a trace of calls to RtlAllocateHeap.";
        cerr << endl << endl;
        cerr << KNOB_BASE::StringKnobSummary();
        cerr << endl;
        return -1;
    }
    
    /* ===================================================================== */
    /* Analysis routines                                                     */
    /* ===================================================================== */
    
    VOID Before(CHAR * name, ADDRINT RegValue)
    {
        TraceFile << "At function entry (" << name << "); EAX: " << hex << RegValue << endl;
    
        // eax is a pointer to ehfuncinfo struct
        ehfuncinfo* funcinfo = reinterpret_cast<ehfuncinfo*>(RegValue);
    
        // get the tryblock table from ehfuncinfo
        tryblock* tryb = funcinfo->tryblocktable;
    
        // get ehandler struct from try block
        ehandler* ehand = tryb->catchsym;
    
        // from ehandler structure, get handler address
        void* handler = ehand->handler;
    
        // save it to file
        TraceFile << "Handler Address: " << hex << handler << endl;
    }
    
    /* ===================================================================== */
    /* Instrumentation routines                                              */
    /* ===================================================================== */
    
    VOID Image(IMG img, VOID *v)
    {
        // Walk through the symbols in the symbol table.
        //
        for (SYM sym = IMG_RegsymHead(img); SYM_Valid(sym); sym = SYM_Next(sym))
        {
            string undFuncName = PIN_UndecorateSymbolName(SYM_Name(sym), UNDECORATION_NAME_ONLY);
    
            //  Find function.
            if (undFuncName == "__CxxFrameHandler3")
            {
                std::cout << "OK! found __CxxFrameHandler3" << std::endl;
    
                RTN allocRtn = RTN_FindByAddress(IMG_LowAddress(img) + SYM_Value(sym));
    
                if (RTN_Valid(allocRtn))
                {
                    // Instrument to print the input argument value and the return value.
                    RTN_Open(allocRtn);
    
                    RTN_InsertCall(allocRtn, IPOINT_BEFORE, (AFUNPTR)Before,
                        IARG_ADDRINT, "__CxxFrameHandler3",
                        IARG_REG_VALUE, REG::REG_EAX,
                        IARG_END);
    
                    RTN_Close(allocRtn);
                }
            }
        }
    }
    
    /* ===================================================================== */
    
    VOID Fini(INT32 code, VOID *v)
    {
        TraceFile.close();
    }
    
    /* ===================================================================== */
    /* Main                                                                  */
    /* ===================================================================== */
    
    int main(int argc, char *argv[])
    {
        // Initialize pin & symbol manager
        PIN_InitSymbols();
        if (PIN_Init(argc, argv))
        {
            return Usage();
        }
    
        // Write to a file since cout and cerr maybe closed by the application
        TraceFile.open(KnobOutputFile.Value().c_str());
        TraceFile << hex;
        TraceFile.setf(ios::showbase);
    
        // Register Image to be called to instrument functions.
        IMG_AddInstrumentFunction(Image, 0);
        PIN_AddFiniFunction(Fini, 0);
    
        // Never returns
        PIN_StartProgram();
    
        return 0;
    }
    
    /* ===================================================================== */
    /* eof */
    /* ===================================================================== */
    

    Output

    At function entry (__CxxFrameHandler3); EAX: 0xcc2580
    Handler Address: 0x00cc1085
    

    Both match the given code (minus offsets due to ASLR):

     MOV EAX,OFFSET 011D2580        ; pointer to __ehfuncinfo
    
     ...
    
    011D1085   MOV EAX,DWORD PTR SS:[EBP-14]  ; Handler start