[RTL] Implement dynamic function tables for x64

This commit is contained in:
Timo Kreuzer 2022-06-10 18:44:51 +02:00
parent ef1311b7a4
commit 8521f6d7b5
6 changed files with 355 additions and 38 deletions

View file

@ -60,7 +60,7 @@ set_module_type(ntdll win32dll ENTRYPOINT 0)
set_subsystem(ntdll console)
################# END HACK #################
target_link_libraries(ntdll rtl ntdllsys libcntpr uuid ${PSEH_LIB})
target_link_libraries(ntdll rtl rtl_vista ntdllsys libcntpr uuid ${PSEH_LIB})
if (STACK_PROTECTOR)
target_sources(ntdll PRIVATE $<TARGET_OBJECTS:gcc_ssp_nt>)

View file

@ -881,7 +881,7 @@
@ stdcall RtlGetFrame()
@ stdcall RtlGetFullPathName_U(wstr long ptr ptr)
@ stdcall RtlGetFullPathName_UstrEx(ptr ptr ptr ptr ptr ptr ptr ptr)
@ stub -version=0x600+ -arch=x86_64 RtlGetFunctionTableListHead
@ stdcall -arch=x86_64 RtlGetFunctionTableListHead()
@ stdcall RtlGetGroupSecurityDescriptor(ptr ptr ptr)
@ stub -version=0x600+ RtlGetIntegerAtom
@ stdcall RtlGetLastNtStatus()

View file

@ -823,4 +823,21 @@ RtlCallVectoredContinueHandlers(_In_ PEXCEPTION_RECORD ExceptionRecord,
return;
}
#ifdef _M_AMD64
typedef PVOID PRUNTIME_FUNCTION, PUNWIND_HISTORY_TABLE;
PRUNTIME_FUNCTION
NTAPI
RtlpLookupDynamicFunctionEntry(
_In_ DWORD64 ControlPc,
_Out_ PDWORD64 ImageBase,
_In_ PUNWIND_HISTORY_TABLE HistoryTable)
{
/* No support for dynamic function tables in the kernel */
return NULL;
}
#endif
/* EOF */

View file

@ -94,6 +94,7 @@ elseif(ARCH STREQUAL "amd64")
list(APPEND SOURCE
bitmap64.c
byteswap.c
amd64/dynfntbl.c
amd64/except.c
amd64/unwind.c
amd64/stubs.c

View file

@ -0,0 +1,326 @@
/*
* PROJECT: ReactOS RTL
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Dynamic function table support routines
* COPYRIGHT: Copyright 2022 Timo Kreuzer (timo.kreuzer@reactos.org)
*/
#include <rtl.h>
#define NDEBUG
#include <debug.h>
#define TAG_RTLDYNFNTBL 'tfDP'
typedef
_Function_class_(GET_RUNTIME_FUNCTION_CALLBACK)
PRUNTIME_FUNCTION
GET_RUNTIME_FUNCTION_CALLBACK(
_In_ DWORD64 ControlPc,
_In_opt_ PVOID Context);
typedef GET_RUNTIME_FUNCTION_CALLBACK *PGET_RUNTIME_FUNCTION_CALLBACK;
typedef
_Function_class_(OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK)
DWORD
OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK(
_In_ HANDLE Process,
_In_ PVOID TableAddress,
_Out_ PDWORD Entries,
_Out_ PRUNTIME_FUNCTION* Functions);
typedef OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK *POUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK;
typedef enum _FUNCTION_TABLE_TYPE
{
RF_SORTED = 0x0,
RF_UNSORTED = 0x1,
RF_CALLBACK = 0x2,
RF_KERNEL_DYNAMIC = 0x3,
} FUNCTION_TABLE_TYPE;
typedef struct _DYNAMIC_FUNCTION_TABLE
{
LIST_ENTRY ListEntry;
PRUNTIME_FUNCTION FunctionTable;
LARGE_INTEGER TimeStamp;
ULONG64 MinimumAddress;
ULONG64 MaximumAddress;
ULONG64 BaseAddress;
PGET_RUNTIME_FUNCTION_CALLBACK Callback;
PVOID Context;
PWCHAR OutOfProcessCallbackDll;
FUNCTION_TABLE_TYPE Type;
ULONG EntryCount;
#if (NTDDI_VERSION <= NTDDI_WIN10)
// FIXME: RTL_BALANCED_NODE is defined in ntdef.h, it's impossible to get included here due to precompiled header
//RTL_BALANCED_NODE TreeNode;
#else
//RTL_BALANCED_NODE TreeNodeMin;
//RTL_BALANCED_NODE TreeNodeMax;
#endif
} DYNAMIC_FUNCTION_TABLE, *PDYNAMIC_FUNCTION_TABLE;
RTL_SRWLOCK RtlpDynamicFunctionTableLock = { 0 };
LIST_ENTRY RtlpDynamicFunctionTableList = { &RtlpDynamicFunctionTableList, &RtlpDynamicFunctionTableList };
static __inline
VOID
AcquireDynamicFunctionTableLockExclusive()
{
RtlAcquireSRWLockExclusive(&RtlpDynamicFunctionTableLock);
}
static __inline
VOID
ReleaseDynamicFunctionTableLockExclusive()
{
RtlReleaseSRWLockExclusive(&RtlpDynamicFunctionTableLock);
}
static __inline
VOID
AcquireDynamicFunctionTableLockShared()
{
RtlAcquireSRWLockShared(&RtlpDynamicFunctionTableLock);
}
static __inline
VOID
ReleaseDynamicFunctionTableLockShared()
{
RtlReleaseSRWLockShared(&RtlpDynamicFunctionTableLock);
}
/*
* https://docs.microsoft.com/en-us/windows/win32/devnotes/rtlgetfunctiontablelisthead
*/
PLIST_ENTRY
NTAPI
RtlGetFunctionTableListHead(void)
{
return &RtlpDynamicFunctionTableList;
}
static
VOID
RtlpInsertDynamicFunctionTable(PDYNAMIC_FUNCTION_TABLE DynamicTable)
{
//LARGE_INTEGER TimeStamp;
AcquireDynamicFunctionTableLockExclusive();
/* Insert it into the list */
InsertTailList(&RtlpDynamicFunctionTableList, &DynamicTable->ListEntry);
// TODO: insert into RB-trees
ReleaseDynamicFunctionTableLockExclusive();
}
BOOLEAN
NTAPI
RtlAddFunctionTable(
_In_ PRUNTIME_FUNCTION FunctionTable,
_In_ DWORD EntryCount,
_In_ DWORD64 BaseAddress)
{
PDYNAMIC_FUNCTION_TABLE dynamicTable;
ULONG i;
/* Allocate a dynamic function table */
dynamicTable = RtlpAllocateMemory(sizeof(*dynamicTable), TAG_RTLDYNFNTBL);
if (dynamicTable == NULL)
{
DPRINT1("Failed to allocate dynamic function table\n");
return FALSE;
}
/* Initialize fields */
dynamicTable->FunctionTable = FunctionTable;
dynamicTable->EntryCount = EntryCount;
dynamicTable->BaseAddress = BaseAddress;
dynamicTable->Callback = NULL;
dynamicTable->Context = NULL;
dynamicTable->Type = RF_UNSORTED;
/* Loop all entries to find the margins */
dynamicTable->MinimumAddress = ULONG64_MAX;
dynamicTable->MaximumAddress = 0;
for (i = 0; i < EntryCount; i++)
{
dynamicTable->MinimumAddress = min(dynamicTable->MinimumAddress,
FunctionTable[i].BeginAddress);
dynamicTable->MaximumAddress = max(dynamicTable->MaximumAddress,
FunctionTable[i].EndAddress);
}
/* Insert the table into the list */
RtlpInsertDynamicFunctionTable(dynamicTable);
return TRUE;
}
BOOLEAN
NTAPI
RtlInstallFunctionTableCallback(
_In_ DWORD64 TableIdentifier,
_In_ DWORD64 BaseAddress,
_In_ DWORD Length,
_In_ PGET_RUNTIME_FUNCTION_CALLBACK Callback,
_In_ PVOID Context,
_In_opt_z_ PCWSTR OutOfProcessCallbackDll)
{
PDYNAMIC_FUNCTION_TABLE dynamicTable;
SIZE_T stringLength, allocationSize;
/* Make sure the identifier is valid */
if ((TableIdentifier & 3) != 3)
{
return FALSE;
}
/* Check if we have a DLL name */
if (OutOfProcessCallbackDll != NULL)
{
stringLength = wcslen(OutOfProcessCallbackDll) + 1;
}
else
{
stringLength = 0;
}
/* Calculate required size */
allocationSize = sizeof(DYNAMIC_FUNCTION_TABLE) + stringLength * sizeof(WCHAR);
/* Allocate a dynamic function table */
dynamicTable = RtlpAllocateMemory(allocationSize, TAG_RTLDYNFNTBL);
if (dynamicTable == NULL)
{
DPRINT1("Failed to allocate dynamic function table\n");
return FALSE;
}
/* Initialize fields */
dynamicTable->FunctionTable = (PRUNTIME_FUNCTION)TableIdentifier;
dynamicTable->EntryCount = 0;
dynamicTable->BaseAddress = BaseAddress;
dynamicTable->Callback = Callback;
dynamicTable->Context = Context;
dynamicTable->Type = RF_CALLBACK;
dynamicTable->MinimumAddress = BaseAddress;
dynamicTable->MaximumAddress = BaseAddress + Length;
/* If we have a DLL name, copy that, too */
if (OutOfProcessCallbackDll != NULL)
{
dynamicTable->OutOfProcessCallbackDll = (PWCHAR)(dynamicTable + 1);
RtlCopyMemory(dynamicTable->OutOfProcessCallbackDll,
OutOfProcessCallbackDll,
stringLength * sizeof(WCHAR));
}
else
{
dynamicTable->OutOfProcessCallbackDll = NULL;
}
/* Insert the table into the list */
RtlpInsertDynamicFunctionTable(dynamicTable);
return TRUE;
}
BOOLEAN
NTAPI
RtlDeleteFunctionTable(
_In_ PRUNTIME_FUNCTION FunctionTable)
{
PLIST_ENTRY listLink;
PDYNAMIC_FUNCTION_TABLE dynamicTable;
BOOL removed = FALSE;
AcquireDynamicFunctionTableLockExclusive();
/* Loop all tables to find the one to delete */
for (listLink = RtlpDynamicFunctionTableList.Flink;
listLink != &RtlpDynamicFunctionTableList;
listLink = listLink->Flink)
{
dynamicTable = CONTAINING_RECORD(listLink, DYNAMIC_FUNCTION_TABLE, ListEntry);
if (dynamicTable->FunctionTable == FunctionTable)
{
RemoveEntryList(&dynamicTable->ListEntry);
removed = TRUE;
break;
}
}
ReleaseDynamicFunctionTableLockExclusive();
/* If we were successful, free the memory */
if (removed)
{
RtlpFreeMemory(dynamicTable, TAG_RTLDYNFNTBL);
}
return removed;
}
PRUNTIME_FUNCTION
NTAPI
RtlpLookupDynamicFunctionEntry(
_In_ DWORD64 ControlPc,
_Out_ PDWORD64 ImageBase,
_In_ PUNWIND_HISTORY_TABLE HistoryTable)
{
PLIST_ENTRY listLink;
PDYNAMIC_FUNCTION_TABLE dynamicTable;
PRUNTIME_FUNCTION functionTable, foundEntry = NULL;
PGET_RUNTIME_FUNCTION_CALLBACK callback;
ULONG i;
AcquireDynamicFunctionTableLockShared();
/* Loop all tables to find the one matching ControlPc */
for (listLink = RtlpDynamicFunctionTableList.Flink;
listLink != &RtlpDynamicFunctionTableList;
listLink = listLink->Flink)
{
dynamicTable = CONTAINING_RECORD(listLink, DYNAMIC_FUNCTION_TABLE, ListEntry);
if ((ControlPc >= dynamicTable->MinimumAddress) &&
(ControlPc < dynamicTable->MaximumAddress))
{
/* Check if there is a callback */
callback = dynamicTable->Callback;
if (callback != NULL)
{
PVOID context = dynamicTable->Context;
*ImageBase = dynamicTable->BaseAddress;
ReleaseDynamicFunctionTableLockShared();
return callback(ControlPc, context);
}
/* Loop all entries in the function table */
functionTable = dynamicTable->FunctionTable;
for (i = 0; i < dynamicTable->EntryCount; i++)
{
/* Check if this entry contains the address */
if ((ControlPc >= functionTable[i].BeginAddress) &&
(ControlPc < functionTable[i].EndAddress))
{
foundEntry = &functionTable[i];
*ImageBase = dynamicTable->BaseAddress;
goto Exit;
}
}
}
}
Exit:
ReleaseDynamicFunctionTableLockShared();
return foundEntry;
}

View file

@ -107,6 +107,13 @@ RtlLookupFunctionTable(
return Table;
}
PRUNTIME_FUNCTION
NTAPI
RtlpLookupDynamicFunctionEntry(
_In_ DWORD64 ControlPc,
_Out_ PDWORD64 ImageBase,
_In_ PUNWIND_HISTORY_TABLE HistoryTable);
/*! RtlLookupFunctionEntry
* \brief Locates the RUNTIME_FUNCTION entry corresponding to a code address.
* \ref http://msdn.microsoft.com/en-us/library/ms680597(VS.85).aspx
@ -126,10 +133,10 @@ RtlLookupFunctionEntry(
/* Find the corresponding table */
FunctionTable = RtlLookupFunctionTable(ControlPc, ImageBase, &TableLength);
/* Fail, if no table is found */
/* If no table is found, try dynamic function tables */
if (!FunctionTable)
{
return NULL;
return RtlpLookupDynamicFunctionEntry(ControlPc, ImageBase, HistoryTable);
}
/* Use relative virtual address */
@ -164,40 +171,6 @@ RtlLookupFunctionEntry(
return NULL;
}
BOOLEAN
NTAPI
RtlAddFunctionTable(
IN PRUNTIME_FUNCTION FunctionTable,
IN DWORD EntryCount,
IN DWORD64 BaseAddress)
{
UNIMPLEMENTED;
return FALSE;
}
BOOLEAN
NTAPI
RtlDeleteFunctionTable(
IN PRUNTIME_FUNCTION FunctionTable)
{
UNIMPLEMENTED;
return FALSE;
}
BOOLEAN
NTAPI
RtlInstallFunctionTableCallback(
IN DWORD64 TableIdentifier,
IN DWORD64 BaseAddress,
IN DWORD Length,
IN PGET_RUNTIME_FUNCTION_CALLBACK Callback,
IN PVOID Context,
IN PCWSTR OutOfProcessCallbackDll)
{
UNIMPLEMENTED;
return FALSE;
}
static
__inline
ULONG