问题
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_FUNCTION
s.
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 ScopeRecord
s 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 ScopeRecord
s.
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