Basically we need to pull out the FILE_OBJECT, Header and Channel objects from the IRP’s IO Stack and verify if this is intended to read keyboard input. If it is not, forward this IRP to termdd.sys without any modification otherwise hook an IoCompletion routine for further filtering.
irpSP = IoGetCurrentIrpStackLocation (pIrp);
fileObject = irpSP->FileObject;
pHeader = (PHEADER)fileObject->FsContext;
if (pHeader->Type != Channel)
return FALSE;
pChannel = (PCHANNEL)fileObject->FsContext;
if (pChannel->ChannelClass != Channel_Keyboard)
return FALSE;
return TRUE;
As you can see the driver needs some private data structures to inspect that the IRP is indeed a keyboard channel input IRP. We will be documenting these data structures in this article.
The below code snippet provides a sample structure of the filter driver but it shouldn’t be treated as working sample in production environment. Development, testing and deployment of the filter driver will be customer’s responsibility.
#include <wdm.h>
#include <Ntddkbd.h>
typedef enum _TYPE {
Connection,
Stack,
Channel
} HEADER_TYPE;
typedef struct _HEADER {
HEADER_TYPE Type;
void *Reserved;
} HEADER, *PHEADER;
typedef enum _CHANNELCLASS {
Channel_Keyboard,
Channel_Mouse,
Channel_Video,
Channel_Beep,
Channel_Command,
Channel_Virtual
} CHANNELCLASS;
typedef struct _CHANNEL {
HEADER Header;
LONG Reserved1;
ERESOURCE Reserved2;
ERESOURCE Reserved3;
long Reserved4;
ULONG Reserved5;
LONG Reserved6;
void* Reserved7;
void* Reserved8;
CHANNELCLASS ChannelClass;
long Reserved9;
long Reserved10;
LIST_ENTRY Reserved11;
LIST_ENTRY Reserved12;
LIST_ENTRY Reserved13;
unsigned Reserved14;
unsigned Reserved15;
PVOID Reserved16;
ULONG Reserved17;
} CHANNEL, *PCHANNEL;
NTSTATUS SampleInstallHooks (
PDRIVER_OBJECT pDriverObject
);
void SampleUninstallHooks(void);
BOOLEAN
SampleIsKeyboardInputIrp(PIRP pIrp);
NTSTATUS
SampleCheckAndDisableSAS(
KEYBOARD_INPUT_DATA *pKbdData,
unsigned long NumData
);
PEPROCESS
IoGetRequestorProcess(
__in PIRP Irp
);
NTSTATUS
SampleForwardAndForget (
PDEVICE_OBJECT DeviceObject,
PIRP Irp
);
DRIVER_INITIALIZE DriverEntry;
DRIVER_ADD_DEVICE SampleAddDevice;
__drv_dispatchType(IRP_MJ_PNP)
DRIVER_DISPATCH SampleDispatchPnp;
__drv_dispatchType(IRP_MJ_POWER)
DRIVER_DISPATCH SampleDispatchPower;
DRIVER_DISPATCH SampleRead;
DRIVER_UNLOAD SampleUnload;
IO_COMPLETION_ROUTINE SampleKeyboardFilterCompletion;
PDEVICE_OBJECT g_pTermddFilter;
PDEVICE_OBJECT g_pTermddDeviceObject;
PFILE_OBJECT g_pTermddFileObject;
BOOLEAN g_bRightAltDown;
BOOLEAN g_bLeftAltDown;
BOOLEAN g_bRightCtrlDown;
BOOLEAN g_bLeftCtrlDown;
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (INIT, SampleInstallHooks)
#pragma alloc_text (PAGE, SampleAddDevice)
#pragma alloc_text (PAGE, SampleDispatchPnp)
#pragma alloc_text (PAGE, SampleUnload)
#pragma alloc_text (PAGE, SampleDispatchPower)
#pragma alloc_text (PAGE, SampleUninstallHooks)
#pragma alloc_text (PAGE, SampleRead)
#pragma alloc_text (PAGE, SampleIsKeyboardInputIrp)
#pragma alloc_text (PAGE, SampleCheckAndDisableSAS)
#pragma alloc_text (PAGE, SampleKeyboardFilterCompletion)
#endif
NTSTATUS
DriverEntry(
__in PDRIVER_OBJECT DriverObject,
__in PUNICODE_STRING RegistryPath
)
{
ULONG ulIndex;
PDRIVER_DISPATCH * dispatch;
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject->DriverExtension->AddDevice = SampleAddDevice;
DriverObject->DriverUnload = SampleUnload;
for (ulIndex = 0, dispatch = DriverObject->MajorFunction;
ulIndex <= IRP_MJ_MAXIMUM_FUNCTION;
ulIndex++, dispatch++) {
*dispatch = SampleForwardAndForget;
}
DriverObject->MajorFunction[IRP_MJ_PNP] = SampleDispatchPnp;
DriverObject->MajorFunction[IRP_MJ_POWER] = SampleDispatchPower;
DriverObject->MajorFunction[IRP_MJ_READ] = SampleRead;
//
// Create a control device object to install the keyboard hooks
//
return SampleInstallHooks(DriverObject);
}
NTSTATUS
SampleAddDevice(
__in PDRIVER_OBJECT DriverObject,
__in PDEVICE_OBJECT PhysicalDeviceObject
)
{
NTSTATUS status = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(PhysicalDeviceObject);
UNREFERENCED_PARAMETER(DriverObject);
PAGED_CODE();
/*
IoCreateDevice code for the PhysicalDeviceObject follows
*/
return status;
}
VOID
SampleUnload(
__in PDRIVER_OBJECT DriverObject
)
{
PAGED_CODE();
UNREFERENCED_PARAMETER(DriverObject);
SampleUninstallHooks();
return;
}
NTSTATUS
SampleDispatchPnp (
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
NTSTATUS status = STATUS_SUCCESS;
PAGED_CODE();
if (DeviceObject == g_pTermddFilter)
{
return SampleForwardAndForget(DeviceObject, Irp);
}
/*
Handling of PnP IRPs for the PhysicalDeviceObject follows
*/
return status;
}
NTSTATUS
SampleDispatchPower (
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
NTSTATUS status = STATUS_SUCCESS;
PAGED_CODE();
if (DeviceObject == g_pTermddFilter)
{
PoStartNextPowerIrp(Irp);
IoSkipCurrentIrpStackLocation(Irp);
return(PoCallDriver(g_pTermddDeviceObject, Irp));
}
/*
Handling of Power IRPs for the PhysicalDeviceObject follows
*/
return status;
}
NTSTATUS
SampleSystemControl (
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
NTSTATUS status;
PAGED_CODE();
if (DeviceObject == g_pTermddFilter)
{
return SampleForwardAndForget(DeviceObject, Irp);
}
/*
Handling of Power IRPs for the PhysicalDeviceObject follows
*/
return status;
}
NTSTATUS SampleInstallHooks (
PDRIVER_OBJECT pDriverObject
)
{
PDEVICE_OBJECT m_pNextDevice = NULL;
NTSTATUS status = STATUS_SUCCESS;
UNICODE_STRING termddObjectName;
g_pTermddDeviceObject = NULL;
g_pTermddFileObject = NULL;
g_pTermddFilter = NULL;
status = IoCreateDevice(pDriverObject,
0,
NULL,
FILE_DEVICE_TERMSRV,
0,
FALSE,
&g_pTermddFilter);
if (!NT_SUCCESS(status))
{
return status;
}
g_pTermddFilter->Flags &= ~DO_DEVICE_INITIALIZING;
RtlInitUnicodeString(&termddObjectName, L"\\Device\\Termdd");
status = IoGetDeviceObjectPointer(&termddObjectName, FILE_READ_DATA, &g_pTermddFileObject, &g_pTermddDeviceObject);
if (!NT_SUCCESS(status))
{
goto Error_Return;
}
m_pNextDevice = IoAttachDeviceToDeviceStack(g_pTermddFilter, g_pTermddDeviceObject);
if (m_pNextDevice != g_pTermddDeviceObject)
{
status = STATUS_OBJECT_TYPE_MISMATCH;
goto Error_Return;
}
return status;
Error_Return:
if(m_pNextDevice)
{
IoDetachDevice(m_pNextDevice);
g_pTermddDeviceObject = NULL;
}
if (g_pTermddFileObject)
{
ObDereferenceObject(g_pTermddFileObject);
g_pTermddFileObject = NULL;
}
if (g_pTermddFilter)
{
IoDeleteDevice(g_pTermddFilter);
g_pTermddFilter = NULL;
}
return status;
}
void SampleUninstallHooks(void)
{
if(g_pTermddDeviceObject)
{
IoDetachDevice(g_pTermddDeviceObject);
g_pTermddDeviceObject = NULL;
}
if (g_pTermddFileObject)
{
ObDereferenceObject(g_pTermddFileObject);
g_pTermddFileObject = NULL;
}
if (g_pTermddFilter)
{
IoDeleteDevice(g_pTermddFilter);
g_pTermddFilter = NULL;
}
}
NTSTATUS
SampleRead(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
//
// Read IRPs for the Physicaldeviceobject are ignored
//
if (g_pTermddFilter != DeviceObject)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
//
// hook the keyboard input irps directed towards termdd
//
if (SampleIsKeyboardInputIrp(Irp))
{
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,
SampleKeyboardFilterCompletion,
NULL,
TRUE,
TRUE,
TRUE
);
}
else
{
IoSkipCurrentIrpStackLocation(Irp);
}
return IoCallDriver(g_pTermddDeviceObject, Irp);
}
#define SCANCODE_CTRL 0x1D
#define SCANCODE_ALT 0x38
#define SCANCODE_END 0x4F
#define SCANCODE_DEL 0x53
#define SCANCODE_Z 0x2C
NTSTATUS SampleCheckAndDisableSAS(
KEYBOARD_INPUT_DATA *pKbdData,
unsigned long NumData)
{
unsigned long i;
NTSTATUS Status = STATUS_SUCCESS;
KEYBOARD_INPUT_DATA *pCurrentData = NULL;
BOOLEAN hasExt0;
BOOLEAN isRelease;
for (i = 0; i < NumData; i++)
{
pCurrentData = &pKbdData[i];
hasExt0 = (pCurrentData->Flags & KEY_E0);
isRelease = (pCurrentData->Flags & KEY_BREAK);
//
// Track the state of Ctrl and Alt
//
switch(pCurrentData->MakeCode)
{
case SCANCODE_ALT:
{
if(hasExt0)
{
g_bRightAltDown = !isRelease;
}
else
{
g_bLeftAltDown = !isRelease;
}
break;
}
case SCANCODE_CTRL:
{
if(hasExt0)
{
g_bRightCtrlDown = !isRelease;
}
else
{
g_bLeftCtrlDown = !isRelease;
}
break;
}
}
//
// Filter out either End or Delete keystrokes when Ctrl and Alt are set
//
if( (g_bLeftAltDown || g_bRightAltDown) &&
(g_bRightCtrlDown || g_bLeftCtrlDown) )
{
switch(pCurrentData->MakeCode)
{
case SCANCODE_DEL:
pCurrentData->MakeCode = SCANCODE_Z;
break;
}
}
}
return Status;
}
NTSTATUS
SampleKeyboardFilterCompletion (
PDEVICE_OBJECT pDeviceObject,
PIRP pIRP,
void* context
)
{
PKEYBOARD_INPUT_DATA keys;
unsigned long iKeysCount;
PVOID pUserBuffer = NULL;
PVOID pUserBuffer2 = NULL;
UNREFERENCED_PARAMETER(context);
UNREFERENCED_PARAMETER(pDeviceObject);
if (SampleIsKeyboardInputIrp(pIRP) &&
pIRP->IoStatus.Status == STATUS_SUCCESS &&
pIRP->IoStatus.Information != 0)
{
if ( (IoGetRequestorProcess( pIRP ) == IoGetCurrentProcess()))
{
try
{
pUserBuffer = ExAllocatePoolWithTag(NonPagedPool, pIRP->IoStatus.Information, 'abcd');
if (pUserBuffer == NULL)
{
goto RETURN;
}
RtlCopyMemory(pUserBuffer, pIRP->UserBuffer, pIRP->IoStatus.Information);
keys = pUserBuffer;
}
except( EXCEPTION_EXECUTE_HANDLER )
{
goto RETURN;
}
}
else if (pIRP->MdlAddress)
{
pUserBuffer = MmGetSystemAddressForMdlSafe( pIRP->MdlAddress, NormalPagePriority );
pUserBuffer2 = ExAllocatePoolWithTag(NonPagedPool, pIRP->IoStatus.Information, 'abcd');
if (pUserBuffer2 == NULL)
{
goto RETURN;
}
try
{
if (pUserBuffer != NULL)
{
RtlCopyMemory( pUserBuffer2, pUserBuffer, pIRP->IoStatus.Information);
keys = pUserBuffer2;
}
else
{
goto RETURN;
}
}
except( EXCEPTION_EXECUTE_HANDLER )
{
goto RETURN;
}
}
else
{
keys = (PKEYBOARD_INPUT_DATA)pIRP->AssociatedIrp.SystemBuffer;
}
iKeysCount = (ULONG)pIRP->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);
SampleCheckAndDisableSAS(keys, iKeysCount);
}
RETURN:
//
// Because the dispatch routine is returning the status of lower driver
// as is, you must do the following:
//
if (pUserBuffer2)
ExFreePoolWithTag(pUserBuffer2, 'abcd');
if (pUserBuffer)
ExFreePoolWithTag(pUserBuffer, 'abcd');
if (pIRP->PendingReturned) {
IoMarkIrpPending( pIRP );
}
return(pIRP->IoStatus.Status);
}
BOOLEAN SampleIsKeyboardInputIrp(PIRP pIrp)
{
PIO_STACK_LOCATION irpSP;
PFILE_OBJECT fileObject;
PHEADER pHeader;
PCHANNEL pChannel;
irpSP = IoGetCurrentIrpStackLocation (pIrp);
fileObject = irpSP->FileObject;
pHeader = (PHEADER)fileObject->FsContext;
if (pHeader->Type != Channel)
return FALSE;
pChannel = (PCHANNEL)fileObject->FsContext;
if (pChannel->ChannelClass != Channel_Keyboard)
return FALSE;
return TRUE;
}
NTSTATUS
SampleForwardAndForget (
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
NTSTATUS status = STATUS_SUCCESS;
if (DeviceObject != g_pTermddFilter) {
Irp->IoStatus.Status = status;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return status;
}
IoSkipCurrentIrpStackLocation (Irp);
status = IoCallDriver (g_pTermddDeviceObject, Irp);
return status;}
In DriverEntry, we call SampleInstallHooks that installs the filter deviceobject on top of termdd.sys deviceobject. Important routines are SampleInstallHooks, SampleUninstallHooks, SampleRead, SampleCheckAndDisableSAS, SampleKeyboardFilterCompletion, SampleIsKeyboardInputIrp. Explanation below:
In the sample code above, the SAS key state is currently maintained in global variables in the driver. These need to be maintained in a separate context that is unique for each RDP session.
There is one remote keyboard channel per session and win32k.sys sends IRP_MJ_READ on this keyboard channel handle to read the remote keyboard input. The FILE_OBJECT from the read IRP gives us a pointer to PCHANNEL object for the RDP session specific keyboard channel and that would be unique on the Server. The driver will need to maintain the SAS key state in per session context uniquely identified by the PCHANNEL obtained from the IRP_MJ_READ. This context can be passed in while installing the IoCompletionRoutine and it gets passed to the IoCompletion callback routine when the read IRP is completed.
Install the Win7 WDK and start with the toaster WDM sample and modifying the code using the above code snippets. Refer the WDK documentation for building, installation, deployment, testing and debugging.