Oct 2019
Microsoft Windows offers the RtlAddFunctionTable function which "adds a dynamic function table to the dynamic function table list."
NTSYSAPI BOOLEAN RtlAddFunctionTable(
PRUNTIME_FUNCTION FunctionTable,
DWORD64 BaseAddress
);
A function table is used to associate unwind information to functions in order
to help 64-bit Windows unwind the call stack, but also to register an
exception handler for the function.
For an executable image (PE), the
compiler and linker normally put the function table into the .pdata
section.
In case code is generated at runtime (e.g. by a just-in-time (JIT) compiler) and stack unwinding or exception handling is required, a new function table can be added using the RtlAddFunctionTable API.
A process may be composed of several modules (one executable and zero or more DLLs), each module can provide a function table. In addition, there is a dynamic function table, managed by RtlAddFunctionTable and RtlDeleteFunctionTable. A function table is relative to an (image) base address, as it contains RVAs, relative virtual addresses.
Observations (Windows 10):
BaseAddress
coincides with an image with a static function table.
.pdata
) takes precedence over a dynamic function table for the entire image size.
The RtlLookupFunctionEntry function can be used to find the function table entry corresponding to a code address (i.e. program counter, PC, or instruction pointer, IP):
NTSYSAPI PRUNTIME_FUNCTION RtlLookupFunctionEntry(
DWORD64 ControlPc,
PDWORD64 ImageBase,
PUNWIND_HISTORY_TABLE HistoryTable
);
Observations (Windows 10):
ImageBase
is an output parameter; it is unmodified if the lookup fails.
HistoryTable
can be NULL; it can be used to speed up the lookup and should initially point to a zero-initialized HistoryTable
.
// sample code to demonstrate Windows RtlAddFunctionTable API // (c) 2019 Peter Meerwald-Stadler, pmeerw@pmeerw.net // 64-bit only, compile with: cl rtlft.c /link /fixed #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include "windows.h" typedef uint8_t UBYTE; typedef uint16_t USHORT; typedef union _UNWIND_CODE { struct { UBYTE CodeOffset; UBYTE UnwindOp : 4; UBYTE OpInfo : 4; }; USHORT FrameOffset; } UNWIND_CODE, *PUNWIND_CODE; typedef struct _UNWIND_INFO { UBYTE Version : 3; UBYTE Flags : 5; UBYTE SizeOfProlog; UBYTE CountOfCodes; UBYTE FrameRegister : 4; UBYTE FrameOffset : 4; UNWIND_CODE UnwindCode[1]; /* UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1]; * OPTIONAL ULONG ExceptionHandler; * OPTIONAL ULONG ExceptionData[]; */ } UNWIND_INFO, *PUNWIND_INFO; typedef struct { uint8_t code[0x1000]; RUNTIME_FUNCTION function_table[1]; UNWIND_INFO unwind_info[1]; } DYNSECTION; static EXCEPTION_DISPOSITION handler(PEXCEPTION_RECORD ExceptionRecord, ULONG64 EstablisherFrame, PCONTEXT ContextRecord, PDISPATCHER_CONTEXT DispatcherContext) { printf("handler!\n"); ContextRecord->Rip += 3; return ExceptionContinueExecution; } int main() { int ret; RUNTIME_FUNCTION *q; DYNSECTION *dynsection = VirtualAlloc(NULL, 0x2000, MEM_COMMIT, PAGE_EXECUTE_READWRITE); uint8_t *code = dynsection->code; size_t p = 0; code[p++] = 0xb8; // mov rax, 42 code[p++] = 0x2a; code[p++] = 0x00; code[p++] = 0x00; code[p++] = 0x00; code[p++] = 0xc6; // mov byte [rax], 0 -- raises exception! code[p++] = 0x00; code[p++] = 0x00; code[p++] = 0xc3; // ret size_t trampoline = p; code[p++] = 0x48; // mov rax,The output should show that the exception has been handled and look similar to:code[p++] = 0xb8; size_t patch_handler_address = p; code[p++] = 0x00; // address to handler patched here code[p++] = 0x00; code[p++] = 0x00; code[p++] = 0x00; code[p++] = 0x00; code[p++] = 0x00; code[p++] = 0x00; code[p++] = 0x00; code[p++] = 0xff; // jmp rax code[p++] = 0xe0; DWORD64 dyn_base = 0; q = RtlLookupFunctionEntry((DWORD64) code, &dyn_base, NULL); printf("lookup 'code' %p %llx\n", q, dyn_base); // no function table entry DWORD64 image_base = 0; q = RtlLookupFunctionEntry((DWORD64) main, &image_base, NULL); printf("lookup 'main' %p %llx\n", q, image_base); // there is a function table entry dyn_base = (DWORD64) dynsection; UNWIND_INFO *unwind_info = dynsection->unwind_info; unwind_info[0].Version = 1; unwind_info[0].Flags = UNW_FLAG_EHANDLER; unwind_info[0].SizeOfProlog = 0; unwind_info[0].CountOfCodes = 0; unwind_info[0].FrameRegister = 0; unwind_info[0].FrameOffset = 0; *(DWORD *) &unwind_info[0].UnwindCode = (DWORD64) &code[trampoline] - dyn_base; RUNTIME_FUNCTION *function_table = dynsection->function_table; function_table[0].BeginAddress = (DWORD64) &code[0] - dyn_base; // set RVA of dynamic code start function_table[0].EndAddress = (DWORD64) &code[trampoline] - dyn_base; // RVA of dynamic code end function_table[0].UnwindInfoAddress = (DWORD64) unwind_info - dyn_base; // RVA of unwind info *(DWORD64 *) &code[patch_handler_address] = (DWORD64) handler; // VA of handler printf("main VA %016llx\n", (DWORD64) main); printf("code VA %016llx\n", (DWORD64) code); printf("function table VA %016llx\n", (DWORD64) function_table); printf("unwind info VA %016llx\n", (DWORD64) unwind_info); printf("handler VA %016llx\n", (DWORD64) handler); printf("RUNTIME_FUNCTION begin RVA %08x, end RVA %08x, unwind RVA %08x\n", function_table[0].BeginAddress, function_table[0].EndAddress, function_table[0].UnwindInfoAddress); printf("UNWIND_INFO handler RVA %08x\n", *(DWORD *) &unwind_info[0].UnwindCode); if (!RtlAddFunctionTable(function_table, 1, dyn_base)) { printf("RtlAddFunctionTable() failed, exit.\n"); exit(EXIT_FAILURE); } q = RtlLookupFunctionEntry((DWORD64) code, &dyn_base, NULL); printf("lookup 'code' %p %llx\n", q, dyn_base); // should return address of function table entry uint64_t (*call)() = (uint64_t (*)()) code; uint64_t result = (*call)(); printf("result = %llx\n", result); if (!RtlDeleteFunctionTable(function_table)){ printf("RtlDeleteFunctionTable() failed, exit.\n"); exit(EXIT_FAILURE); } return EXIT_SUCCESS; }
C:\>cl rtlft.c /link /fixed ... C:\>rtlft.exe lookup 'code' 0000000000000000 0 lookup 'main' 000000014001F00C 140000000 main VA 0000000140001050 code VA 0000000000020000 function table VA 0000000000021000 unwind info VA 000000000002100c handler VA 0000000140001000 RUNTIME_FUNCTION begin RVA 00000000, end RVA 00000009, unwind RVA 0000100c UNWIND_INFO handler RVA 00000009 lookup 'code' 0000000000021000 20000 handler! result = 2a
The story here is related to how C++ exceptions work on Windows (video)...
posted at: 23:58 | path: /programming | permanent link