How to enable a third-party driver to intercept and disable the SAS keyboard sequence in Remote Desktop Protocol (RDP) sessions for Windows 7, Windows Server 2008 R2, Windows 8 and Windows Server 2012

Applies to: Windows 7 EnterpriseWindows 7 Home BasicWindows 7 Home Premium More

Summary


This article describes how to enable a third-party driver to intercept and disable the SAS keyboard sequence in Remote Desktop Protocol (RDP) sessions for Windows 7, Windows Server 2008 R2, Windows 8 and Windows Server 2012.

Differences between KM RDP and UM RDP keyboard stack


On Windows 7 and Windows Server 2008 R2, termdd.sys acts as a legacy driver that creates the named IcaDeviceObject "\\Device\termdd". All the IOCTL system calls by termsrv service and Read and Write I/O request packets (IRPs) by win32k.sys are sent to the IcaDeviceObject, and are completed by termdd.sys. These IRPs bypass any keyboard class filters like kbdclass.sys or a customer keyboard class filter since this device is not a keyboard class device. Therefore, a Windows Driver Model and Windows Driver Foundation (WDM/WDF) based keyboard class filter driver does not fit in this device stack. The solution is to create a legacy filter device object that directly opens a pointer to the IcaDeviceObject and attaches to the stack. In this situation, this resolution is able to intercept the keyboard input Read IRPs.

On Windows 8 and Windows Server 2012 UM Remote Desktop Protocol (RDP), terminput.sys acts as BUS driver for remote keyboard and mouse devices. Terminpt.sys enumerates keyboard as Keyboard class device TS_INPUT\TS_KBD. Because the remote keyboard input device installs as keyboard class device, keyboard class filters like kbdclass.sys or a WDM/WDF based custom keyboard class filter can be inserted into the device stack. This resolution is able to intercept the remote keyboard input and filter the required keystrokes as required. You do not need to directly open the remote keyboard device and attach a filter device object in this case.

Resolution for Windows 7 and Windows Server 2008 R2


Solution

  1. Write a legacy keyboard filter driver for termdd.sys to intercept the keystrokes received in a RDP session on Win 7 RTM/Win2k8 R2 RTM.
  2. This approach is based on the fact that termdd.sys names its DeviceObject as "\Device\termdd".
  3. Write a legacy filter driver that does the following:
    • Creates a deviceobject by calling IoCreateDevice with DeviceType as FILE_DEVICE_TERMSRV.
      status = IoCreateDevice(pDriverObject,
      0,
      NULL,
      FILE_DEVICE_TERMSRV,
      0,
      FALSE,
      &g_pTermddFilter);
    • Attaches this filter deviceobject on top of "\Device\termdd". First obtain a pointer to the deviceobject for termdd.sys by calling
      RtlInitUnicodeString(&termddObjectName, L"\\Device\\Termdd");
      status = IoGetDeviceObjectPointer(&termddObjectName, FILE_READ_DATA,
      &g_pTermddFileObject, &g_pTermddDeviceObject);
    • Then attach to the device stack by calling
      pLowerDevice = IoAttachDeviceToDeviceStack(pTermddFilter, pTermddDeviceObject);
      After this step the pTermddFilter deviceobject sits on top of the deviceObject for “Device\termdd” and receives every IRP that is sent to termdd.sys.
  4. Since we are only interested in monitoring keyboard input Read IRPs sent to termdd.sys from win32k.sys, forward the rest of the IRPs to termdd.sys as they come. These include all the PnP, Power, Write IRPs and IOCTLs etc. This is a mandatory requirement.
  5. Windows graphics driver Win32k.sys sends IRP_MJ_READ IRPs to "Device\termdd" to read the RDP keyboard input. In this case these will be first sent to our filter driver that sits on top of "Device\termdd".
  6. Monitor all the IRP_MJ_READ, verify if the IRP is for reading keyboard input by looking at its contents and install a IoCompletion routine for such IRPs. The filter driver shouldn’t filter any other IRPs except the ones verified to be intended for reading RDP keyboard input.
  7. When these IRP_MJ_READ are completed by termdd.sys, they are filled with the keyboard input. On the way back up, the IoCompletion routine of the filter driver would get called.
  8. In the IoCompletion routine, modify the scan codes for SAS sequence to filter them out. This technique can be used for filter any other customer keyboard sequence as well.


Monitoring IRP_MJ_READ for keyboard input

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.

Code snippet

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;


Publishing private data structures

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.

Code sample

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;}


Remarks

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:

SampleInstallHooksAttaches the filter device object to termdd.sys device object.
SampleUninstallHooksDetaches the filter during driver unload.
SampleReadFor Keyboard Channel read Irps, we install a Irp Completion Routine and send the Irp to termdd.

IsKeyboardInputIrpInspects if the IRP is keyboard channel read request
KeyboardFilterCompletionThe IRP completion routine that gets called after termdd has completion
CheckAndDisableSASLogic to check and disable SAS

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.

Few important points:
  • The driver needs to manage the lifetime of this per-session context.
  • It can be allocated as the driver sees a new PCHANNEL object in the IRP_MJ_READ indicating a new session. It can be maintained in a global list to be retrieved and used later.
  • There is no good way for the driver to determine when a particular session has ended so that it can delete a particular context. But it can implement an algorithm to clean up the context structure if it doesn’t have either of Ctrl or Alt key pressed.

Building, testing and installation

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.

Resolution for Windows 8 and Windows Server 2012


Solution

  1. Start with the WDK 8 sample keyboard class filter driver “kbfilter”. This driver installs as keyboard class filter and can be used to filter the RDP keyboard input as well. Please refer the Kbfiltr Driver Reference.
  2. The inbox keyboard class filter kbdclass.sys sends IOCTL_INTERNAL_KEYBOARD_CONNECT to the port driver before it opens the keyboard device.
  3. With our filter driver in place, it will be received by our driver first. While handling IOCTL_INTERNAL_KEYBOARD_CONNECT, do the following:
    1. Saves a copy of Kbdclass's CONNECT_DATA (Kbdclass) structure that is passed to the filter driver by Kbdclass.
    2. Substitutes its own KeyboardClassServiceCallback for the class driver connect information.
    3. Sends the IOCTL_INTERNAL_KEYBOARD_CONNECT request down the device stack.
  4. A function driver calls the KeyboardClassServiceCallback in its ISR dispatch completion routine whenever it receives input data from the keyboard device. The class service callback transfers input data from the input data buffer of a device to the class driver.
  5. With the kbfilter in place, its KbFilter_ServiceCallback will be called first by the ISR. This is the place where a custom filter driver can filter the SAS keystrokes and then pass the data to kbdclass’s Keyboard ClassService callback.


Code snippet

The key states must be maintained in the Device Extension.

typedef struct _DEVICE_EXTENSION
{
[…]
BOOLEAN bLeftCtrlDown;
BOOLEAN bRightCtrlDown;
BOOLEAN bLeftAltDown;
BOOLEAN bRightAltDown;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
#define SCANCODE_CTRL 0x1D
#define SCANCODE_ALT 0x38
#define SCANCODE_END 0x4F
#define SCANCODE_DEL 0x53
#define SCANCODE_Z 0x2C

VOID
KbFilter_ServiceCallback(
IN PDEVICE_OBJECT DeviceObject,
IN PKEYBOARD_INPUT_DATA InputDataStart,
IN PKEYBOARD_INPUT_DATA InputDataEnd,
IN OUT PULONG InputDataConsumed
)
{
PDEVICE_EXTENSION devExt;
WDFDEVICE hDevice;
PKEYBOARD_INPUT_DATA pCurrentData = NULL;

hDevice = WdfWdmDeviceGetWdfDeviceHandle(DeviceObject);

devExt = FilterGetData(hDevice);

pCurrentData = InputDataStart;

while(pCurrentData < InputDataEnd)
{
BOOLEAN hasExt0 = (pCurrentData->Flags & KEY_E0);
BOOLEAN isRelease = (pCurrentData->Flags & KEY_BREAK);
//
// Track the state of Ctrl and Alt
//
switch(pCurrentData->MakeCode)
{
case SCANCODE_ALT:
if(hasExt0)
{
devExt->bRightAltDown = !isRelease;
} else {
devExt->bLeftAltDown = !isRelease;
}
break;

case SCANCODE_CTRL:
if(hasExt0) {
devExt->bRightCtrlDown = !isRelease;
} else {
devExt->bLeftCtrlDown = !isRelease;
}
break;
}

//
// Filter out either End or Delete keystrokes when Ctrl and Alt are
// set
//
if( (devExt->bLeftAltDown || devExt->bRightAltDown) &&
(devExt->bLeftCtrlDown || devExt->bRightCtrlDown) )
{
switch(pCurrentData->MakeCode)
{
case SCANCODE_END:
case SCANCODE_DEL:
pCurrentData->MakeCode = SCANCODE_Z;
break;
}

}
++pCurrentData;
}

(*(PSERVICE_CALLBACK_ROUTINE)(ULONG_PTR) devExt>UpperConnectData.ClassService)(
devExt->UpperConnectData.ClassDeviceObject,
InputDataStart,
InputDataEnd,
InputDataConsumed);
}


Building, testing and installation

Install the Win8 WDK and start with the kbfilter sample and modifying the KbFilter_ServiceCallback using the above code snippets. Refer the WDK documentation for building, installation, deployment, testing and debugging.