Struct RUNTIME_FUNCTION

瘦欲@ 提交于 2019-12-11 04:33:55

问题


I found a large array in .pdata segment of RUNTIME_FUNCTION structures by IDA. So, where I can find information: from what it's compiled, how I can create this and how to use it in C++. Give me please books, or links with good descriptions and tutorials for exception handling and unwinding with this structure.


回答1:


You can find more information on RUNTIME_FUNCTION and related structures at Microsoft's MSDN.

These structures are generated by the compiler and used to implement structured exception handling. During the execution of your code an exception may occur, and the runtime system needs to be able to walk up the call stack to find a handler for that exception. To do so, the runtime system needs to know the layout of the function prologs, which registers they save, in order to correctly unwind the individual function stack frames. More details are here.

The RUNTIME_FUNCTION is the structure which describes a single function, and it contains the data required to unwind it.

If you generate code at runtime and need to make that code available to the runtime system (because your code calls out to already compiled code which may raise an exception) then you create RUNTIME_FUNCTION instances for each of your generated functions, fill in the UNWIND_INFO for each, and then tell the runtime system by calling RtlAddFunctionTable.




回答2:


Windows x64 SEH

The compiler puts an exception directory in the .pdata section of an .exe image. The compiler fills the exception directory with _RUNTIME_FUNCTIONs.

typedef struct _RUNTIME_FUNCTION {
 ULONG BeginAddress;
 ULONG EndAddress;
 ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;

Each _RUNTIME_FUNCTION describes a function in the image that uses SEH. Each function in the program that has a try/except or try/finally block has one. BeginAddress points to the start of the function and EndAddress points to the end of the function.

UnwindData points to an _UNWIND_INFO table structure

#define UNW_FLAG_NHANDLER 0x0
#define UNW_FLAG_EHANDLER 0x1
#define UNW_FLAG_UHANDLER 0x2
#define UNW_FLAG_CHAININFO 0x4

typedef struct _UNWIND_INFO {
    UBYTE Version         : 3;
    UBYTE Flags           : 5;
    UBYTE SizeOfProlog;
    UBYTE CountOfCodes;
    UBYTE FrameRegister  : 4;
    UBYTE FrameOffset    : 4;
    UNWIND_CODE UnwindCode[1];
    union {
        //
        // If (Flags & UNW_FLAG_EHANDLER)
        //
        OPTIONAL ULONG ExceptionHandler;
        //
        // Else if (Flags & UNW_FLAG_CHAININFO)
        //
        OPTIONAL ULONG FunctionEntry;
    };
    //
    // If (Flags & UNW_FLAG_EHANDLER)
    //
    OPTIONAL ULONG ExceptionData[]; 
} UNWIND_INFO, *PUNWIND_INFO;

If UNW_FLAG_EHANDLER is set then ExceptionHandler points to a generic handler called _C_specific_handler whose purpose is to parse the ExceptionData which points to a SCOPE_TABLE structure. If UNW_FLAG_UHANDLER is set then it is a try/finally block and it will point to a termination handler also by the alias _C_specific_handler.

typedef struct _SCOPE_TABLE {
 ULONG Count;
 struct
 {
     ULONG BeginAddress;
     ULONG EndAddress;
     ULONG HandlerAddress;
     ULONG JumpTarget;
 } ScopeRecord[1];
} SCOPE_TABLE, *PSCOPE_TABLE;

The SCOPE_TABLE structure is a variable length structure with a ScopeRecord for each try block which contains the start and end address (probably RVA) of the try block. HandlerAddress is an offset to the exception filter function in the parenthesis of __except() (EXCEPTION_EXECUTE_HANDLER means always run the except, so it's analogous to except Exception) and JumpTarget is the offset to the first instruction in the __except block associated with the __try block.

Once the exception is raised by the processor, the standard exception handling mechanism in Windows will find the RUNTIME_FUNCTION for the offending instruction pointer and call the ExceptionHandler. This will always result in a call to _C_specific_handler for kernel-mode code running on current versions of Windows. _C_specific_handler will then begin walking all of the SCOPE_TABLE entries searching for a match on the faulting instruction, and will hopefully find an __except statement that covers the offending code. (Source)

To add to this, for nested exceptions I'd imagine it would always find the smallest range that covers the current faulting instruction and will unwind through the larger ranges of the exception is not handled.

It is also not made clear how the OS Exception handler knows which dll's exception directory to look in. I suppose it could use the RIP and consult the process VAD and then get the first address of the particular allocation and call RtlLookupFunctionEntry on it.

Exception Filters

An example function that uses SEH:

BOOL SafeDiv(INT32 dividend, INT32 divisor, INT32 *pResult)
{
    __try 
    { 
        *pResult = dividend / divisor; 
    } 
    __except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? 
         EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
    { 
        return FALSE;
    }
    return TRUE;
} 

Let's say catch (ArithmeticException a){//do something} were used in java. It is exactly equivalent to:

__except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? 
         EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {//do something}

The filter string in the parenthesis is pointed to by the ExceptionHandler value from earlier. The filter is always either equal to EXCEPTION_CONTINUE_SEARCH, EXCEPTION_EXECUTE_HANDLER or EXCEPTION_CONTINUE_EXECUTION. GetExceptionCode gets the ExceptionCode (windows specific error constant) from the _EXCEPTION_RECORD which was probably created by the specific exception handler in the IDT using the error code and exception no. (_EXCEPTION_RECORD is stored somewhere such that it is accessible through the call). It is compared against the specific error, EXCEPTION_INT_DIVIDE_BY_ZERO being what would be used by ArithmeticException. If the filter expression evaluates to EXCEPTION_EXECUTE_HANDLER then it will jump to JumpTarget otherwise I'd imagine it looks for a ScopeRecord with a wider scope. If it runs out of ScopeRecords that cover the RIP of the faulting instruction then it needs to call the exception of the try block defined over the thread creation itself which is depicted in the following:

VOID
WINAPI
BaseProcessStartup(PPROCESS_START_ROUTINE lpStartAddress)
{
 DPRINT("BaseProcessStartup(..) - setting up exception frame.\n");

 _SEH2_TRY
 {
     /* Set our Start Address */
     NtSetInformationThread(NtCurrentThread(),
                            ThreadQuerySetWin32StartAddress,
                            &lpStartAddress,
                            sizeof(PPROCESS_START_ROUTINE));

     /* Call the Start Routine */
     ExitThread(lpStartAddress());
 }
 _SEH2_EXCEPT(UnhandledExceptionFilter(_SEH2_GetExceptionInformation()))
 {
     /* Get the Exit code from the SEH Handler */
     if (!BaseRunningInServerProcess)
     {
         /* Kill the whole process, usually */
         ExitProcess(_SEH2_GetExceptionCode());
     }
     else
     {
         /* If running inside CSRSS, kill just this thread */
         ExitThread(_SEH2_GetExceptionCode());
     }
 }
 _SEH2_END;
 }

If the application is not currently being debugged then the unhandled filter that gets called will return EXCEPTION_EXECUTE_HANDLER which calls except and terminates the thread / process. It would make sense if there were a ScopeRecord for each thread that this OS exception dispatch code must use which points to the above try/except block. It would make sense if it were stored in the ETHREAD struct or something or perhaps if a _RUNTIME_FUNCTION were written to the image that describes the function that initialises and calls the thread (BaseProcessStartup), but remember, the RIP will be inside BaseProcessStartup which would have a kernel RIP, so looking up the module in the VAD space wouldn't work, so the OS Exception handler might as well have a function that checks if the RIP is a kernel mode address and then it knows the offset to the filter because the length and exact function is preknown as it is a kernel function.

UnhandledExceptionFilter will call the filter specified in SetUnhandledExceptionFilter which gets stored in process address space under the alias GlobalTopLevelExceptionFilter which gets initialised on the dynamic linking of kernel32.dll I think.

Prologue and Epilogue exceptions

Within a function described by a _RUNTIME_FUNCTION structure, an exception can occur in the prologue or the epilogue of the function as well as in the body of the function. The prologue is the part of the function call that negotiates parameter passing, calling convention and pushing parameters, CS:RIP, RBP to stack. The epilogue is the reversal of this process, i.e. returning from the function. The compiler stores each action that takes place in the prologue in an UnwindCodes array; each action is represented by a 2 byte UNWIND_CODE structure which contains a member for the offset in the prologue (1 byte), unwind operation code (4 bits) and operation info (4 bits).

After finding a _RUNTIME_FUNCTION for which the RIP is in range, before invoking _C_specific_handler, the OS exception handling code checks whether the RIP lies between BeginAddress and BeginAddress + SizeOfProlog defined in the _RUNTIME_FUNCTION and _UNWIND_INFO structures respectively. If it is then it looks at the UnwindCodes array for the first entry with an offset less than or equal to the offset of the RIP from the function start. It then undoes all of the actions described in the array in order. One of these actions might be UWOP_PUSH_MACHFRAME which signifies that a trap frame has been pushed. The restoring of this trap frame will cause the RIP to now be that before the function call. The process is restarted using the RIP before the function call once the actions have been undone; the OS exception handling will now use this RIP to find the _RUNTIME_FUNCTION which will be that of the calling function. This will now be in the body of the calling function so the _C_specific_handler of the parent _UNWIND_INFO can now be invoked to scan the ScopeRecords.

If the RIP is not in the range BeginAddress -- BeginAddress + SizeOfProlog then it examines the code stream after RIP and if it matches to the trailing portion of a legitimate epilogue then it's in an epilogue (Strange that it doesn't just define SizeOfEpilog and subtract from EndAddress but there we go) and the remaining portion of the epilogue is simulated with the _CONTEXT_RECORD structure it builds updated as each instruction is processed. The RIP will now be immediately after the function call in the calling function, hence the parent's _RUNTIME_FUNCTION will be picked up once again, like with the prologue and that will be used to handle the exception.

If it is neither in a prologue or epilogue then it invokes the _C_specific_handler of the _RUNTIME_FUNCTION as it was going to.

Another scenario that is worth mentioning is if the function is a leaf function it will not have a _RUNTIME_FUNCTION record because a leaf function does not call any other functions or allocate any local variables on the stack. Hence, RSP directly addresses the return pointer. The return pointer at [RSP] is stored in the updated context, the simulated RSP is incremented by 8 and then it looks for another _RUNTIME_FUNCTION.

Unwinding

When a filter returns EXCEPTION_CONTINUE_SEARCH rather than EXCEPTION_EXECUTE_HANDLER, it needs to return from the function, which is called unwinding. To do so, it just goes through the UnwindCode array like earlier and undoes all of the actions to restore the state of the CPU to before the function call -- it doesn't have to worry about locals because they'll be lost to the aether when it moves down a stack frame. It then looks for the _RUNTIME_FUNCTION of the parent function and it will call the __C_specific_handler. If the exception gets handled then it passes control to the except block at JumpTarget and execution continues as normal. If it is not handled (i.e. the filter expression does not evaluate to EXCEPTION_EXECUTE_HANDLER then it continues unwinding the stack until it reaches BaseProcessStartup and the RIP is in the bounds of that function which means the exception is unhandled. As I was saying earlier, it could recognise that it is a kernel address and the index to the exception filter expression which happens to be UnhandledExceptionFilter(_SEH2_GetExceptionInformation()) in which it will be passed to the debugger if the process is being debugged, or it will call the custom filter set with SetUnhandledExceptionFilter which will perform some actions but must return EXCEPTION_EXECUTE_HANDLER, if not it will just return EXCEPTION_EXECUTE_HANDLER.

Windows x86 SEH

x86 uses stack based exception handling rather than table based which x64 uses. This made it vulnerable to buffer overflow attacks //i'll continue later



来源:https://stackoverflow.com/questions/19808172/struct-runtime-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!