Geoff Chappell, Software Analyst
The KSE_SHIM structure provides input to and receives some output from the KseRegisterShim and KseRegisterShimEx functions. It is the top-level description of a driver shim. A shim provider calls these functions to register the shim with the Kernel Shim Engine (KSE) for eventual application to one or more drivers.
The KSE_SHIM structure is not documented. Microsoft’s name for it is known from symbol files for a driver (NDIS.SYS) that registers a shim and uses C++ for instantiating its KSE_SHIM as statically allocated data: the C++ decoration names the type. Microsoft’s names and types are not known for members, there being no type information in the symbol file.
The KSE_SHIM is 0x1C and 0x38 bytes in 32-bit and 64-bit Windows 10, respectively.
Offset (x86) | Offset (x64) | Size | Description |
---|---|---|---|
0x00 | 0x00 | dword | ignored; observed to be size of structure |
0x04 | 0x08 | pointer | address of non-optional identifier for shim, as GUID structure |
0x08 | 0x10 | pointer | ignored; observed to be address of friendly name for shim as null-terminated Unicode string |
0x0C | 0x18 | pointer | after successful registration: address of callback-routines structure in kernel |
0x10 | 0x20 | pointer | address of optional routine for notification when shim is removed from a driver |
0x14 | 0x28 | pointer | address of optional routine for notification when shim is applied to driver |
0x18 | 0x30 | pointer | address of non-optional KSE_HOOK_COLLECTION array |
Each shim is assumed to have its own GUID. The KSE does not permit concurrent registration of two shims that have the same GUID. The first dword of the GUID is used to identify the shim in logs.
Each shim must have a KSE_HOOK_COLLECTION array. If instead the pointer is NULL, the shim cannot be registered.
When a shim is registered, the KSE edits the KSE_SHIM so that the registrant has access to helper routines. If only for now, the structure whose address is entered into the KSE_SHIM provides for two routines and both are specifically for hooks of I/O requests and related driver routines. Microsoft’s names for the structure and its members are not known. However, there’s just the one implementation and the members are pointers to kernel routines that are named in symbol files. These are the names given below:
Offset (x86) | Offset (x64) | Definition |
---|---|---|
0x00 | 0x00 |
VF_DRIVER_IO_CALLBACKS * (*KseGetIoCallbacks) ( PDRIVER_OBJECT); |
0x04 | 0x08 |
NTSTATUS (*KseSetCompletionHook) ( PDEVICE_OBJECT, PIRP, PIO_COMPLETION_ROUTINE, PVOID); |
A shim provider that hooks a driver’s handling of I/O requests will typically need to forward the dispatch phase of a request to wherever it would have gone in that driver if not hooked. These original addresses for the handling are collected into a structure that the KSE associates with the shimmed driver’s DRIVER_OBJECT. The linkage works through the KseCallbacks member of the DRIVER_EXTENSION but shim providers are not meant to know that, let alone depend on it. Instead, they use KseGetIoCallbacks.
The collection of addresses that a shim provider gets from KseGetIoCallbacks is named above as VF_DRIVER_IO_CALLBACKS. This is Microsoft’s name for an undocumented structure that is used by the Driver Verifier for so similar a purpose that its members have one-to-one correspondence with those of whatever structure the KSE uses. Perhaps this is the Driver Verifier’s structure taken as ready-made. Perhaps it’s renamed to something like KSE_DRIVER_IO_CALLBACKS. Who knows. Whatever it’s named, shim providers must know the layout:
Offset (x86) | Offset (x64) | Definition |
---|---|---|
0x00 | 0x00 |
PDRIVER_INITIALIZE DriverInit; |
0x04 | 0x08 |
PDRIVER_STARTIO DriverStartIo; |
0x08 | 0x10 |
PDRIVER_UNLOAD DriverUnload; |
0x0C | 0x18 |
PDRIVER_ADD_DEVICE AddDevice; |
0x10 | 0x20 |
PDRIVER_DISPATCH MajorFunction [IRP_MJ_MAXIMUM_FUNCTION + 1]; |
A shim provider that hooks a driver’s handling of I/O requests will often also want to learn of a request’s completion. While handling the request on its way to the shimmed driver, the shim provider may call KseSetCompletionHook to prepare the request’s current I/O stack location so that the shim provider will be notified of completion after any notification is handled by the shimmed driver. No matter whether the request completes as successful or failed, or is cancelled, the shim provider’s completion routine is called with the device object and context that were given to KseSetCompletionHook.
A KSE_SHIM may also provide the addresses of routines for the KSE to call whenever the shim that is described by the KSE_SHIM is applied to a driver or removed from a driver. Microsoft’s two built-in shim providers that supply these routines give them names that end in HookDriverTargeted and HookDriverUntargeted, respectively. Taking these as plausibly Microsoft’s names for the members, the following would be their definitions:
VOID (*HookDriverUntargeted) ( PVOID); VOID (*HookDriverTargeted) ( PUNICODE_STRING, PVOID, ULONG, ULONG, ULONG);
The UNICODE_STRING is for the driver’s so-called base name. The PVOID for each callback is the base address of the driver’s image in memory. The three ULONG arguments are respectively the size of the image, and the TimeDateStamp and CheckSum from the driver’s IMAGE_NT_HEADERS.