|
Interception of PsInvertedFunctionTable
Another variation on the theme of intercepting PatchGuard within the SEH
code path critical to the system integrity check routine involves taking
advantage of an optimization that exists in the x64 exception dispatcher.
Specifically, it is possible to utilize the fact that the exception dispatcher
on x64 uses a cache to improve the performance of exception handling. By
taking advantage of this cache, it may be possible to intercept control of
execution when the PatchGuard DPC routine deliberately creates an access
violation exception in order to trigger the system integrity check. This
proposed technique uses the nt!PsInvertedFunctionTable global
variable in the kernel, which represents a cache used to perform a fast
translation of RIP values to an associated image base and exception directory
pointer, without having to do a (slow) search through the linked list of
loaded kernel modules.
This technique is fairly similar to the one described in technique 4.2. Instead
of altering the actual exception directory entries corresponding to each
PatchGuard DPC routine in the kernel's image in-memory, this
technique alters the cached exception directory pointer stored within
PsInvertedFunctionTable. PsInvertedFunctionTable is consulted by
RtlLookupFunctionTableEntry, in order to translate a RIP value to an
associated image (and unwind metadata block). The logic within
RtlLookupFunctionTable is essentially to search through the cached entries
resident in PsInvertedFunctionTable for an image that corresponds to a given
RIP value. If a hit is found, then the exception directory pointer is loaded
directly from the PsInvertedFunctionTable cache, instead of through the
(slower) process of parsing the PE header of the given image. If no hit is
found, then the loaded module linked list is searched. Assuming a hit is made
in the loaded module list, then the PE header for the associated module is
processed in order to locate the exception directory for the module. From
there, the exception directory is searched to locate the unwind metadata block
corresponding to the function containing the specified RIP value.
The structure backing PsInvertedFunctionTable (RTL_INVERTED_FUNCTION_TABLE)
can be described as so in C:
typedef struct _RTL_INVERTED_FUNCTION_TABLE_ENTRY
{
PIMAGE_RUNTIME_FUNCTION_ENTRY ExceptionDirectory;
PVOID ImageBase;
ULONG ImageSize;
ULONG ExceptionDirectorySize;
} RTL_INVERTED_FUNCTION_TABLE_ENTRY, * PRTL_INVERTED_FUNCTION_TABLE_ENTRY;
typedef struct _RTL_INVERTED_FUNCTION_TABLE
{
ULONG Count;
ULONG MaxCount; // always 160 in Windows Server 2003
ULONG Pad[ 0x2 ];
RTL_INVERTED_FUNCTION_TABLE_ENTRY Entries[ ANYSIZE_ARRAY ];
} RTL_INVERTED_FUNCTION_TABLE, * PRTL_INVERTED_FUNCTION_TABLE;
In Windows Server 2003, there is space reserved for up to 160 loaded modules
in the array contained within PsInvertedFunctionTable. In Windows Vista, this
number has been expanded to 512 module entries. The array of loaded modules
is maintained by the system module loader such that when a module is loaded
or unloaded, a corresponding entry within PsInvertedFunctionTable is created
or deleted, respectively. It is not a fatal error for the module array
within PsInvertedFunctionTable to be exhausted; in this case, performance for
exception dispatching relating to additional modules will be slower, but the
system will still function.
Because the RIP-to-exception-directory cache described by
PsInvertedFunctionTable maintains a full 64-bit pointer to the exception
directory of the associated module, it is possible to disassociate the cached
exception directory pointer from its corresponding image. In other words, it
is possible to modify the ExceptionDirectory member of a particular cached
RTL_INVERTED_FUNCTION_TABLE_ENTRY to point to an arbitrary location instead of
the exception directory of that module. There are no security or integrity
checks that validate that the ExceptionDirectory member points to within the
given image. This could be exploited by a third-party driver in order to
take control of exception dispatching for any of the first 160 (or 512, in the
case of Windows Vista) kernel modules. This loaded module list includes
critical images such as the HAL (typically the first entry in the cache) and
the kernel itself (typically the second entry in the cache). With respect to
bypassing PatchGuard, this makes it possible for a third party driver
to copy the exception directory data of the kernel to dynamically allocated
memory and adjust it such that exception handlers for the PatchGuard DPC
routines point to a stub function that invokes a virtual unwind as
described in technique 4.2. After setting up its altered shadow copy
of the exception directory for the kernel, all that a third party driver would
need to do is swap the ExceptionDirectory pointer within the
PsInvertedFunctionTable cache entry for the kernel with the pointer to the
shadow copy. Following that, this approach is essentially the same as the
proposed approach described in 4.2. It has the added advantage of being
more difficult to detect from the perspective of validating the integrity of
the exception dispatching path, as the exception directory associated with the
kernel image in-memory is not actually altered; only a pointer to the
exception directory in a cache is changed.
This approach does require a reliable mechanism to detect
PsInvertedFunctionTable (which is not exported) at run-time, however. The
author feels that this is not a particularly difficult task, as the first few
members of PsInvertedFunctionTable (specifically, the maximum entry count and
the entries for the HAL and kernel) will have predictable values that can be
used in a classic egghunt style search of kernel global variable space.
Additional heuristics, such as requiring several data references to the
suspected PsInvertedFunctionTable location within kernel code could be applied
as well, in the interest of improving accuracy.
This proposed approach may be countered by many of the proposed counters to
techniques 4.1 and 4.2. Additionally, this technique could also be countered by
validating exception directory pointers within PsInvertedFunctionTable, such
as by ensuring that such exception directory pointers are within the confines
of the purported associated image. Although this validation is not perfect
since it might still be possible for one to reposition the exception directory
pointer to a different location within the image that could be safely modified
at runtime, such as overlapping a large global variable array or the like, it
would certainly increase the difficulty of subverting the exception
dispatcher's RIP translation cache. Additional validation techniques, such as
requiring that the exception directory point to read-only memory, could be
similarly adopted to reduce the chance that a third party driver could
meaningfully subvert the cache (with results leading to something other than a
system crash).
It should be noted that in the current implementation,
PsInvertedFunctionTable presents a relatively inviting target for
potentially malicious software to hijack parts of the kernel without
being detected. Indeed, through careful planned subversion of
PsInvertedFunctionTable, third party software could take control of
exception dispatchers throughout the kernel in order to gain control
of execution. Though this technique would be much more limited than
outright kernel patching, it has the advantage of being completely
undetected by current PatchGuard versions (which cannot validate
global variables that may change without notice at runtime, for
obvious reasons). It also has the advantage of being undetected by
current rootkit detection systems, which are presently (to the
author's knowledge) blissfully unaware of PsInvertedFunctionTable.
Although it would require administrative permissions (or an exploit
granting such permissions) for an attacker to modify
PsInvertedFunctionTable in the first place, Microsoft has at late
focused a great deal of effort on protecting the kernel even from
users with administrator permissions. For example, one could
conceive of a rootkit-style program that intercepts exception
dispatchers for system services, and passes invalid user mode
pointers to system services in order to surreptitiously execute
kernel mode code without detection when the standard pointer probe
throws an exception indicating that the given usermode pointer
parameter is invalid. Given this sort of threat (from the rootkit
perspective), the author feels that it would be in Microsoft's best
interests to put into place additional validation of
PsInvertedFunctionTable's cached exception directory pointers
(assuming that Microsoft wishes to continue down the path of
strengthening the kernel against malicious
administratively-privileged code).
|