Window XP驱动开发(二十一) 过滤驱动程序

转载请标明是引用于 http://blog.csdn.net/chenyujing1234 

欢迎大家拍砖

 

参考书籍<<Windows驱动开发技术详解>>

 

过滤驱动程序的开发十分灵活,可以修改已有驱动程序的功能,也可以对数据进行过滤加密,另外,利用过滤驱动程序还能编写出很多具有相当神奇功能的程序来。

1、文件过滤驱动程序

文件过滤驱动是过滤驱动中典型的一种,它将挂载在磁盘驱动上,它将所有发往磁盘驱动的IRP全部拦截,并有选择地过滤这些IRP。

1、1 过滤驱动程序的概念

对于WDM框架的过滤程序来说共有两种:一种是高层过滤驱动程序;一种是低层过滤程序。

如果将过滤程序附在功能驱动(FDO)的下面,这样介于FDO和PDO之间的过滤驱动称为低层过滤驱动程序,一般记为Low FiDO。

如果被除在功能驱动(FDO)的上面,则称为上层过滤驱动程序,一般记为High FiDO。

在WMD框架中的过滤驱动可以相互嵌套,层层叠加,即上层过滤驱动程序之上可以再附加更高层的过滤驱动程序,同理低层过滤驱动程序可以被更低层的过滤驱动所过滤。

1、2 过滤驱动程序的入口函数

作为一个过滤驱动程序,一般都是将上层驱动程序传过来的IRP进行拦截以后并不做任何处理,并将此IRP再转发给底层驱动程序,这种方式被称为Pass-Through;而对于

个别IRP,过滤驱动程序需要对其处理。

过滤驱动的作用是过滤IRP,然后将IRP做进一步处理。不同过滤驱动会有不同的需求,这种处理也不会不同。一个最简单的过滤驱动程序,就是让FDO到PDO之间的请求完全通过,而不加任何干涉。

在下面的代码中,驱动程序的入口函数用一个for 循环将所有的IRP派遣函数都置成DispatchAny函数,而对于IRP_MJ_POWER、IRP_MJ_PNP及IRP_MJ_SCSI则进行单独

处理。当然,对于不同的过滤驱动,可以选择不同的IRP,而让其他的IRP只是穿过本层驱动

#pragma INITCODE 
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
	IN PUNICODE_STRING RegistryPath)
{							// DriverEntry
	KdPrint((DRIVERNAME " - Entering DriverEntry: DriverObject %8.8lX\n", DriverObject));
	// Initialize function pointers
	DriverObject->DriverUnload = DriverUnload;
	DriverObject->DriverExtension->AddDevice = AddDevice;
	for (int i = 0; i < arraysize(DriverObject->MajorFunction); ++i)
		DriverObject->MajorFunction[i] = DispatchAny;
	DriverObject->MajorFunction[IRP_MJ_POWER] = DispatchPower;
	DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp;
	DriverObject->MajorFunction[IRP_MJ_SCSI] = DispatchForSCSI;
	return STATUS_SUCCESS;
}


对于不做处理的IRP只需要将其直接穿过本层驱动即可,主要是采用IoSkipCurrentStackLocation函数将IRP达到,再调用内核函数IoCallDriver 调用更底层的驱动程序

运行下面的函数就可以达到此目的,在这个函数中加入一些同步处理操作,并将IRP的相关信息打印出来:

#pragma LOCKEDCODE				// make no assumptions about pageability of dispatch fcns
NTSTATUS DispatchAny(IN PDEVICE_OBJECT fido, IN PIRP Irp)
{							// DispatchAny
	// 获得设备扩展
	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fido->DeviceExtension;
	// 获得I/O堆栈
	PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
#if DBG
	static char* irpname[] = 
	{
		"IRP_MJ_CREATE",
		"IRP_MJ_CREATE_NAMED_PIPE",
		"IRP_MJ_CLOSE",
		"IRP_MJ_READ",
		"IRP_MJ_WRITE",
		"IRP_MJ_QUERY_INFORMATION",
		"IRP_MJ_SET_INFORMATION",
		"IRP_MJ_QUERY_EA",
		"IRP_MJ_SET_EA",
		"IRP_MJ_FLUSH_BUFFERS",
		"IRP_MJ_QUERY_VOLUME_INFORMATION",
		"IRP_MJ_SET_VOLUME_INFORMATION",
		"IRP_MJ_DIRECTORY_CONTROL",
		"IRP_MJ_FILE_SYSTEM_CONTROL",
		"IRP_MJ_DEVICE_CONTROL",
		"IRP_MJ_INTERNAL_DEVICE_CONTROL",
		"IRP_MJ_SHUTDOWN",
		"IRP_MJ_LOCK_CONTROL",
		"IRP_MJ_CLEANUP",
		"IRP_MJ_CREATE_MAILSLOT",
		"IRP_MJ_QUERY_SECURITY",
		"IRP_MJ_SET_SECURITY",
		"IRP_MJ_POWER",
		"IRP_MJ_SYSTEM_CONTROL",
		"IRP_MJ_DEVICE_CHANGE",
		"IRP_MJ_QUERY_QUOTA",
		"IRP_MJ_SET_QUOTA",
		"IRP_MJ_PNP",
	};

	UCHAR type = stack->MajorFunction;
	// 获得IRP的主代码
// 	if (type >= arraysize(irpname))
// 		KdPrint((DRIVERNAME " - Unknown IRP, major type %X\n", type));
// 	else
// 		KdPrint((DRIVERNAME " - %s\n", irpname[type]));

#endif
	
	// Pass request down without additional processing
	NTSTATUS status;
	// 获取自旋锁
	status = IoAcquireRemoveLock(&pdx->RemoveLock, Irp);
	// 判断是否成功获取自旋锁
	if (!NT_SUCCESS(status))
		// 结束IRP请求
		return CompleteRequest(Irp, status, 0);
	// 略过当前I/O堆栈
	IoSkipCurrentIrpStackLocation(Irp);
	// 调用底层驱动程序
	status = IoCallDriver(pdx->LowerDeviceObject, Irp);
	// 释放自旋锁
	IoReleaseRemoveLock(&pdx->RemoveLock, Irp);
	return status;
}							// DispatchAny


1、3 U盘过滤驱动程序

本节的例子是将一个U盘的驱动程序进行过滤,让U盘成为一个只读存储器。当U盘插入计算机时,系统会枚举出一个USB的PDO,将将一个叫做USBSTOR的驱动程序作为FDO加载到FDO之上。USBSTOR会另外创建一个物理设备,上面挂载磁盘驱动,其上再挂载PartMgr驱动(分区驱动)。如下图:

为了让编写的过滤驱动能让U盘变为只读状态,可以在此DISK.sys和USBSTOR.sys驱动之间建立一个过滤驱动。

我们将这个驱动称为MyFilter驱动,和前面介绍所有WDM驱动一样,该驱动需要在注册表的

HEKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Sevices\MyFilter  建立一系列列表项。这里可以用INF文件来实现,和以往文件安装不同的是,

只需要鼠标右键单击INF文件,点“安装”即可。

[Version]
Signature=$CHICAGO$
Provider=%MFGNAME%

[DestinationDirs]
DefaultDestDir=10,system32\drivers
FiltJectCopyFiles=11

[SourceDisksFiles]
MyFilter.sys=1

[SourceDisksNames]
1=%INSTDISK%,,,MyFilter_Check

;------------------------------------------------------------------------------
;  Windows 2000 Sections
;------------------------------------------------------------------------------

[DefaultInstall.ntx86]
CopyFiles=DriverCopyFiles,FiltJectCopyFiles

[DriverCopyFiles]
MyFilter.sys,,,0x60		; replace newer, suppress dialog

[DefaultInstall.ntx86.services]
AddService=MyFilter,,FilterService

[FilterService]
ServiceType=1
StartType=3
ErrorControl=1
ServiceBinary=%10%\system32\drivers\MyFilter.sys

;------------------------------------------------------------------------------
;  String Definitions
;------------------------------------------------------------------------------

[Strings]
MFGNAME="Zhangfan Software"
INSTDISK="Zhangfan Disc"
DESCRIPTION="Sample File Filter Driver"

1、4 过滤驱动程序加载方法一

为了加载过滤驱动程序,需要手动修改注册表,操作系统对于每插入一种U盘,都会在HKEY_LOCAL_MACHIN\SYSTEM\CurrentControlSet\Enum\USBSTOR 位置

上有相应的体现,我的电脑插入过两种U盘,如下图示:

默认情况下,管理员不能修改这个位置的注册表项,但可以修改其权限,方法是:

单击右键,选择“权限”,将管理员(Administrator)加入安全列表,并选中“完全权限”复选框,如下:

修改注册表权限后,就可以对该项目中的注册表进行修改了,根据不同U盘,在不同的子目录下建立子健LowerFilters。

如下图:

1、5 过滤驱动程序加载方法二

将特定U盘被插入时,操作系统会加载过滤驱动程序。一旦换了别的品牌U盘,还需要重新修改注册表。即使使用同一个U盘,但如果换了别的插槽,还是

需要重新修改注册表。也就是说上述方法只适用于特定的设备加载特定的过滤驱动程序

还有另外一种方法,就是对同一类别驱动程序过滤驱动

例如,对于U盘这一类设备,其ClassGUID为{4D36E967-E325-11CE-BFC1-08002BE10318},可以在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E967-E325-11CE-BFC1-08002BE10318}位置上进行设置,

同样是创建子健LowerFilters,子键的过滤驱动的Class名称,这里为MyFilter,如下:

这样的好处是对于一类的设备进行了过滤,但是同时会带来一个问题,就是ClassGUID为{4D36E967-E325-11CE-BFC1-08002BE10318}代表所有的磁盘

设备,也包括IDE硬盘,对于本过滤驱动一样会将其过滤,使IDE硬盘也变得只读,这不是希望的,因为这样可能造成系统启动时机器蓝屏。这个问题我们在本文章的后机解决。

网上有人说:“ 采用了应用层监控usb设备的动作实施动态加载”,应用程序监控USB设备再动态加载,怎么个加载法?USB设备如果已经加载了,才能被应用程序识别到,这个时候应用程序再去加载过滤驱动??
可能方法:
(1)通过WM_DEVICEHCNAGE 检测到设备,然后把.sys当成服务启动来。
WM_DEVICECHANGE 消息

通知应用程序硬件设备配置的改变,应用程序通过WindowProc接收

LRESULT CALLBACK WindowProc(HWND   hwnd,     // handle to window
                            UINT   uMsg,     // WM_DEVICECHANGE
                            WPARAM wParam,   // device-change event
                            LPARAM lParam ); // event-specific data


wParam

The event that has occurred. This parameter can be one of the following values from the Dbt.h header file.

ValueMeaning
DBT_CONFIGCHANGECANCELED 0x0019

A request to change the current configuration (dock or undock) has been canceled.

DBT_CONFIGCHANGED 0x0018

The current configuration has changed, due to a dock or undock.

DBT_CUSTOMEVENT 0x8006

A custom event has occurred.

DBT_DEVICEARRIVAL 0x8000

一个设备已经被插入,且可用.

DBT_DEVICEQUERYREMOVE 0x8001

Permission is requested to remove a device or piece of media. Any application can deny this request and cancel the removal.

DBT_DEVICEQUERYREMOVEFAILED 0x8002

A request to remove a device or piece of media has been canceled.

DBT_DEVICEREMOVECOMPLETE 0x8004

A device or piece of media has been removed.

DBT_DEVICEREMOVEPENDING 0x8003

A device or piece of media is about to be removed. Cannot be denied.

DBT_DEVICETYPESPECIFIC 0x8005

A device-specific event has occurred.

DBT_DEVNODES_CHANGED 0x0007

A device has been added to or removed from the system.

DBT_QUERYCHANGECONFIG 0x0017

Permission is requested to change the current configuration (dock or undock).

DBT_USERDEFINED 0xFFFF

(2)

我遇到这样一个需求:
银行的客户里装有两个键盘驱动
一个是农行的,一个是建行的,
一天建行的工程师想设计一款安全的键盘,因为农行的也装了,所以他们被要求如果先加载了农行的驱动,那么自动把它卸了然后加载自己的驱动。

(3)http://blog.chinaunix.net/uid-21323988-id-1827852.html

usb禁用驱动:

我完全是用DDK的例子修改的,就是src\general\toaster\filter做的修改,在

IRP_MJ_INTERNAL_DEVICE_CONTROL的分发例程中,比较驱动的名称是否为usbstor,如果是的话即为

存储设备,将IRP返回即可

   if (_wcsnicmp(DeviceObject->AttachedDevice->DriverObject->DriverName.Buffer,L"\Driver\USBSTOR",15)==0)
  ...{
          DbgPrint("Find USB StorIRP_MJ_DEVICE_INTERL_CONTROL::Cmp Result is %d ",
        _wcsnicmp(DeviceObject->AttachedDevice->DriverObject->DriverName.Buffer,L"\Driver\USBSTOR",15));
        Irp->IoStatus.Status = STATUS_ACCESS_DENIED;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest( Irp, IO_NO_INCREMENT );
        return STATUS_ACCESS_DENIED;
    }
     else
      ...{
                 Irp->IoStatus.Status = STATUS_SUCCESS;
           return FilterPass(DeviceObject, Irp); 
      }
 

usb只读过滤驱动也只是简单的在禁用的的代码上做了修改,做的还很幼稚,需要进一步完善,也列出关键的地方

吧:

同样处理还是在IRP_MJ_INTERNAL_DEVICE_CONTROL的分发例程中,不过只读的处理代码我在完成例程中处

理的,处理的关键代码如下:

 

NTSTATUS USBSCSICompletion(IN PDEVICE_OBJECT DeviceObject,
                           IN PIRP Irp,
                           IN PVOID Context)
{
    PDEVICE_EXTENSION  deviceExtension;
 NTSTATUS    status;
 PIO_STACK_LOCATION  irpStack;

 PSCSI_REQUEST_BLOCK  CurSrb;
 PMODE_PARAMETER_HEADER modeData;
 PDEVICE_OBJECT   pDeviceObject;

 PCDB cdb ;
 UCHAR opCode;

    PDEVICE_EXTENSION StorExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
 
    irpStack = IoGetCurrentIrpStackLocation(Irp);
   
 if (irpStack->MajorFunction==IRP_MJ_INTERNAL_DEVICE_CONTROL)
 {                                                         
      CurSrb=irpStack->Parameters.Scsi.Srb;
      if(CurSrb == NULL)
                return STATUS_SUCCESS;
      cdb = (PCDB)CurSrb->Cdb;
      if(cdb == NULL)
                return STATUS_SUCCESS;
      opCode=cdb->CDB6GENERIC.OperationCode;
      if (opCode==SCSIOP_WRITE)
      {
       DbgPrint("opCode==SCSIOP_WRITE\n");
      }
         if (opCode==SCSIOP_MODE_SENSE
            && CurSrb->DataBuffer
   && CurSrb->DataTransferLength >= sizeof(MODE_PARAMETER_HEADER)
   )
      {
       modeData = (PMODE_PARAMETER_HEADER)CurSrb->DataBuffer;
                modeData->DeviceSpecificParameter |= MODE_DSP_WRITE_PROTECT;
      }
 }
  if ( Irp->PendingReturned )
  {
    IoMarkIrpPending( Irp );
  }

  return Irp->IoStatus.Status ;
}

这个只读代码缺陷有如下几个:

1:windows2000系统下如果USB格式为NTFS,这样设置之后,NTFS盘符会不可读写,显示盘符正在被写保护

     具体的原因请参阅深入windows系统内幕的第三章关于文件系统的介绍,根本原因是windows2000的NTFS

  版本是3.0还不支持USB只读属性

2:目前还没有找到一个统一的地方挂载过滤驱动,让只读驱动对所有类型的usb设备生效,目前我采用了一个非常

  土的办法,监视系统的usb设备插入情况,如果有usb设备插入,且当前要求只读的话,检查当前的盘是否已经

设置了只读属性,如果没有设置的话,加载只读属性

3:目前看了USSE的usb安全存储方案发现它在2007版里面usb只读过滤驱动的实现里面,只有在class键值下设

设置了过滤驱动,而且只读是立即生效的,目前正在拿着它的驱动进行反汇编以及调试,看看能不能找到更好的办

进行改进

 

过滤驱动的安装:

如果要使用禁用功能的话,过滤驱动很好安装,使用createService创建驱动,然后在注册表的键值

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{36FC9E60-C465-11CF-8056-444553540000}

下面增加子项:

LowerFilters =  "你的过滤驱动服务名称"

过滤驱动的安装,参考DDK提供的例子:addfilter

关于只读的过滤驱动的安装位置:

只读驱动不能安装在类设备下,需要安装到usbstor的特定的设备下,例如我的电脑我安装如下:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USBSTOR\Disk&Ven_RockChip&Prod_USB__SD&Rev_1.00

\USBV1.00&1下面,因为没有找到统一的地方可以队所有的设备生效,因此我采用了应用层监控usb设备的动作实

施动态加载。

 

1、6 过滤驱动程序的AddDevice例程

对于过滤驱动程序的AddDevice例程,需要创建一个过滤设备对象。这一步是通过IoCreateDevice内核函数实现的

然后将该过滤设备对象附加到设备栈上,并用设备扩展记录低于本层的驱动对象地址。这一步是通过IoAttackDeviceToDeviceStack内核函数实现的。

由于不知道底层驱动是直接读取设备还是缓冲区读取设备,因此,对于过滤设备的标志应该将两个都设置,即DO_DIRECT_IO|DO_BUFFERED_IO。

NTSTATUS AddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT pdo)
{							// AddDevice
	PAGED_CODE();
	NTSTATUS status;

	PDEVICE_OBJECT fido;
	status = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), NULL,
		GetDeviceTypeToUse(pdo), 0, FALSE, &fido);
	if (!NT_SUCCESS(status))
	{						// can't create device object
		KdPrint((DRIVERNAME " - IoCreateDevice failed - %X\n", status));
		return status;
	}						// can't create device object
	//获得设备扩展
	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fido->DeviceExtension;

	do
	{						// finish initialization
		// 初始化自旋锁
		IoInitializeRemoveLock(&pdx->RemoveLock, 0, 0, 0);
		pdx->DeviceObject = fido;
		pdx->Pdo = pdo;
		//将过滤驱动附加在底层驱动之上
		PDEVICE_OBJECT fdo = IoAttachDeviceToDeviceStack(fido, pdo);
		if (!fdo)
		{					// can't attach								 
			KdPrint((DRIVERNAME " - IoAttachDeviceToDeviceStack failed\n"));
			status = STATUS_DEVICE_REMOVED;
			break;
		}					// can't attach
		//记录底层驱动
		pdx->LowerDeviceObject = fdo;
		//由于不知道底层驱动是直接IO还是BufferIO,因此将标志都置上
		fido->Flags |= fdo->Flags & (DO_DIRECT_IO | DO_BUFFERED_IO | DO_POWER_PAGABLE);
		// Clear the "initializing" flag so that we can get IRPs
		fido->Flags &= ~DO_DEVICE_INITIALIZING;
	}	while (FALSE);					// finish initialization

	if (!NT_SUCCESS(status))
	{					// need to cleanup
		// 如果没有成功,则从设备堆栈中删除设备
		if (pdx->LowerDeviceObject)
			IoDetachDevice(pdx->LowerDeviceObject);
		IoDeleteDevice(fido);
	}					// need to cleanup

	return status;
}							// AddDevice


1、7 磁盘命令过滤

对于磁盘过滤的关键在于IRP_MJ_SCSI这个IRP的截获,其实IRP_MJ_SCSI就是IRP_MJ_INTERNAL_DEVICE_CONTROL的一个别名,在DISK.sys和USBSTOR.sys

之间传递的是标准的SCSI指令。

#define IRP_MJ_SCSI                     IRP_MJ_INTERNAL_DEVICE_CONTROL


 

在IRP_MJ_SCSI的派遣函数中,首先将IRP发送到底层驱动,然后设置完成例程。其主要是希望在完成例程中修改底层驱动所做的处理。

#pragma LOCKEDCODE
NTSTATUS DispatchForSCSI(IN PDEVICE_OBJECT fido, IN PIRP Irp)
{
//	KdPrint((DRIVERNAME " - Enter DispatchForSCSI \n"));

	// 获得设备扩展
	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fido->DeviceExtension;
	// 获得I/O堆栈
	PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); 

	// Pass request down without additional processing
	NTSTATUS status;
	// 获取自旋锁
	status = IoAcquireRemoveLock(&pdx->RemoveLock, Irp);
	if (!NT_SUCCESS(status))
		return CompleteRequest(Irp, status, 0);
    // 将当前I/O堆栈复制至下层I/O堆栈
	IoCopyCurrentIrpStackLocationToNext(Irp);
	// 设置完成例程
	IoSetCompletionRoutine( Irp,
							USBSCSICompletion,
							NULL,
							TRUE,
							TRUE,
							TRUE ); 
	// 调用底层设备对象行
	status = IoCallDriver(pdx->LowerDeviceObject, Irp);
	IoReleaseRemoveLock(&pdx->RemoveLock, Irp);
	return status;
}

对于SCSI命令,在DDK中的srb.h和scsi.h中相应定义,对于不同的SCSI,分为如下几种:

#define SCSIOP_COPY                0x18
#define SCSIOP_ERASE               0x19
#define SCSIOP_MODE_SENSE          0x1A
#define SCSIOP_START_STOP_UNIT     0x1B
#define SCSIOP_STOP_PRINT          0x1B
#define SCSIOP_LOAD_UNLOAD         0x1B
#define SCSIOP_RECEIVE_DIAGNOSTIC  0x1C
#define SCSIOP_SEND_DIAGNOSTIC     0x1D
#define SCSIOP_MEDIUM_REMOVAL      0x1E
#define SCSIOP_READ_FORMATTED_CAPACITY 0x23
#define SCSIOP_READ_CAPACITY       0x25
#define SCSIOP_READ                0x28
#define SCSIOP_WRITE               0x2A
#define SCSIOP_SEEK                0x2B
#define SCSIOP_LOCATE              0x2B
#define SCSIOP_POSITION_TO_ELEMENT 0x2B
#define SCSIOP_WRITE_VERIFY        0x2E
#define SCSIOP_VERIFY              0x2F
#define SCSIOP_SEARCH_DATA_HIGH    0x30
#define SCSIOP_SEARCH_DATA_EQUAL   0x31
#define SCSIOP_SEARCH_DATA_LOW     0x32
#define SCSIOP_SET_LIMITS          0x33

例如,如果想让设备变成只读,只需要将SCSI命令中的SCSIOP_WRITE请求设置为失败

所有对U盘的写请求,最终会变成SCSIOP_WRIT请求,如果将该请求设置为失败,该设备就会变成只读设备,然而在直接将SCSIOP_WRIT请求设置为失败,

当对磁盘写一个文件时,会很久才会有系统提示失败。这大概是因为操作系统一旦发现SCSIOP_WRIT请求失败,会多次发送这个请求,直到超时为止。

还有一个更为简便的方法,即SCSIOP_MODE_SENSE 请求时,设置MODE_DSP_WRITE_PROTECT位,使其变为只读设备

这种操作类似只有只读开关的U盘。当只读开关关闭时,该U盘变得得只读。
修改SCSIOP_MODE_SENSE请求,应该在IRP_MJ_SCSI派遣例程的完成例程中实现,代码如下:

NTSTATUS
USBSCSICompletion( IN PDEVICE_OBJECT DeviceObject,
                   IN PIRP Irp,
                   IN PVOID Context )
{
	//  获得设备扩展
    PDEVICE_EXTENSION pdx = ( PDEVICE_EXTENSION )
                                   DeviceObject->DeviceExtension;

	// 获得自旋锁
	IoAcquireRemoveLock(&pdx->RemoveLock,Irp);
	// 获得当前I/O堆栈
    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation( Irp );
    // 获取当前IRP处理状态
	PSCSI_REQUEST_BLOCK CurSrb=irpStack->Parameters.Scsi.Srb; 
	PCDB cdb = (PCDB)CurSrb->Cdb; 
	// 获取操作码
	UCHAR opCode=cdb->CDB6GENERIC.OperationCode; 
	// 判断是否是SCSIOP_MODE_SENSE的操作
	if(opCode==SCSIOP_MODE_SENSE  && CurSrb->DataBuffer 
		&& CurSrb->DataTransferLength >= 
		sizeof(MODE_PARAMETER_HEADER))
	{
		KdPrint(("SCSIOP_MODE_SENSE comming!\n"));
		// 得到参数头
		PMODE_PARAMETER_HEADER modeData = (PMODE_PARAMETER_HEADER)CurSrb->DataBuffer;
		// 设置U盘为只读磁盘。
		modeData->DeviceSpecificParameter |= MODE_DSP_WRITE_PROTECT;
	} 
	// 判断是否需要挂起
	if ( Irp->PendingReturned )
	{
		// 挂起IRP
		IoMarkIrpPending( Irp );
	} 

	IoReleaseRemoveLock(&pdx->RemoveLock,Irp);

	return Irp->IoStatus.Status ;
} 



如果上述过滤驱动加载成功,当写磁盘时Windows会报告错误,如下图:

 

2、NT过滤驱动程序

上节我们介绍的是WDM过滤驱动,安装它时需要修改注册表,本节介绍的NT过滤驱动,无须修改注册表,它通过驱动名直接寻找需要过滤的驱动设备指针,

然后将自己挂载在上面

2、1 NT过滤驱动程序

编写NT过滤驱动更加简便,因为WDM是通过注册表指定挂载哪种驱动的,而对于NT驱动程序可以通过寻找不同的设备对象指定

例如选择键盘驱动,再将自身的设备对象挂载在键盘驱动之上。

WDM驱动无非是微软在NT式驱动之上进行了扩充,过滤驱动也不例外 。

本节将介绍如何开发一种键盘过滤驱动,即对键盘的操作进行过滤,这在黑客或木马程序中经常用到,例如可以通过用户的敲击键盘记录,来用分析用户用户密码等。

本节的例子来自Mark Russinovich编写的ctrl2cap,此程序主要将自己的过滤驱动加载到键盘驱动之上,可以用来监视键盘记录,也可以改变键盘输入。

在Windows中,键盘驱动所创建的设备对象叫做\Device\KeyboardClass0

过滤驱动自己首先创建一个设备对象,并设备派遣函数,然后将自己挂载在\Device\KeyboardClass0之上即可。如下图所示:

2、2 NT过滤驱动的入口函数

NT式过滤驱动的入口函数和WDM的过滤驱动一样,都是将所有的IRP进行过滤,为了保证能接收到上层传下来的IRP,要对所有RP进行过滤:

NTSTATUS DriverEntry(
    IN PDRIVER_OBJECT  DriverObject,
    IN PUNICODE_STRING RegistryPath 
    )
{
    ULONG                  i;

    DbgPrint (("Ctrl2cap.SYS: entering DriverEntry\n"));

    // 
    // Fill in all the dispatch entry points with the pass through function
    // and the explicitly fill in the functions we are going to intercept
    // 
	// 对所有IRP进行过滤
    for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) {
		// 将所有的IRP派遣函数都设置为Ctrl2capDispatchGeneral
        DriverObject->MajorFunction[i] = Ctrl2capDispatchGeneral;
    }

    //
    // Our read function is where we do our real work.
    // 设置IRP_MJ_READ的派遣函数
    DriverObject->MajorFunction[IRP_MJ_READ] = Ctrl2capDispatchRead;
    //
	// 负责挂载过滤驱动
    return Ctrl2capInit( DriverObject );
}
2、3 挂载过滤驱动

挂载过滤驱动的步骤被封装在Ctrl2capInit函数中,这里通过构造一个UNICODE字符串,该字符串是键盘驱动的设备名,然后创建一个新的设备对象,并

将自己附加在键盘设备的驱动上

NTSTATUS Ctrl2capInit( 
    IN PDRIVER_OBJECT DriverObject 
    )
{
    CCHAR		      ntNameBuffer[64];
    STRING		      ntNameString;
    UNICODE_STRING    ntUnicodeString;
    PDEVICE_OBJECT    device;
    NTSTATUS          status;
    PDEVICE_EXTENSION devExt;
    WCHAR             messageBuffer[]  = L"Ctrl2cap Initialized\n";
    UNICODE_STRING    messageUnicodeString;

    //
    // Only hook onto the first keyboard's chain.
    // 构造 UNICODE 字符串
    sprintf( ntNameBuffer, "\\Device\\KeyboardClass0" );
    RtlInitAnsiString( &ntNameString, ntNameBuffer );
    RtlAnsiStringToUnicodeString( &ntUnicodeString, &ntNameString, TRUE );

    //
    // Create device object for the keyboard.
    // 创建设备对象
    status = IoCreateDevice( DriverObject,
                             sizeof(DEVICE_EXTENSION),
                             NULL,
                             FILE_DEVICE_KEYBOARD,
                             0,
                             FALSE,
                             &device );
	// 判断是否成功创建设备对象
    if( !NT_SUCCESS(status) ) {

        DbgPrint(("Ctrl2cap: Keyboard hook failed to create device!\n"));

        RtlFreeUnicodeString( &ntUnicodeString );
        return STATUS_SUCCESS;
    }
	// 将内存清零
    RtlZeroMemory(device->DeviceExtension, sizeof(DEVICE_EXTENSION));
	// 获得设备扩展
    devExt = (PDEVICE_EXTENSION) device->DeviceExtension;
   
    //
    // Keyboard uses buffered I/O so we must as well.
    // 设置对象标志
    device->Flags |= DO_BUFFERED_IO;
    device->Flags &= ~DO_DEVICE_INITIALIZING;

    //
    // Attach to the keyboard chain.
    //这里是重点,附加过滤驱动
    status = IoAttachDevice( device, &ntUnicodeString, &devExt->TopOfStack );
	// 判断是否成功
    if( !NT_SUCCESS(status) ) {

        DbgPrint(("Ctrl2cap: Connect with keyboard failed!\n"));
        IoDeleteDevice( device );
        RtlFreeUnicodeString( &ntUnicodeString );
        return STATUS_SUCCESS;
    }

    //
    // Done! Just free our string and be on our way...
    // 释放UNICODE字符串所占用的内存
    RtlFreeUnicodeString( &ntUnicodeString );
    DbgPrint(("Ctrl2cap: Successfully connected to keyboard device\n"));

    //
    // This line simply demonstrates how a driver can print
    // stuff to the bluescreen during system initialization. 
    //
    RtlInitUnicodeString (&messageUnicodeString,
                          messageBuffer );
    ZwDisplayString( &messageUnicodeString );
    return STATUS_SUCCESS;
}


2、4 过滤键盘读操作

这个程序监视用户所有的键盘操作,每一次键盘操作,都会间接地向键盘驱动发送一个IRP_MJ_READ请求,因此可以监视IRP_MJ_READ来达到这个目的

另外,当用户敲击大写键后,过滤驱动这个动作将被改写成敲击在Ctrl键盘,也就是说大写键被解释成了左Ctrl键,其过程是:首先在IRP_MJ_READ的派遣例程中

设置完成例程,通过完成例程来改变IRP的设置,以下是IRP_MJ_READ 的派遣函数:

NTSTATUS Ctrl2capDispatchRead( 
    IN PDEVICE_OBJECT DeviceObject, 
    IN PIRP Irp )
{
    PDEVICE_EXTENSION   devExt;
    PIO_STACK_LOCATION  currentIrpStack;
    PIO_STACK_LOCATION  nextIrpStack;

    //
    // Gather our variables.
    // 获得设备扩展
    devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
	// 获得当前I/O堆栈
    currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
	// 获得下一层IO
    nextIrpStack = IoGetNextIrpStackLocation(Irp);    

    //
    // Push params down for keyboard class driver.
    //
    *nextIrpStack = *currentIrpStack;

    //  
    // Set the completion callback, so we can "frob" the keyboard data.
    // 设置完成例程
    IoSetCompletionRoutine( Irp, Ctrl2capReadComplete, 
                            DeviceObject, TRUE, TRUE, TRUE );

    //
    // Return the results of the call to the keyboard class driver.
    // 调用底层驱动
    return IoCallDriver( devExt->TopOfStack, Irp );
}

在完成例程中,主要是为了修改已经从底层驱动返回来的结果,例如,当底层驱动返回来的键盘扫描码是大写键(Caps Lock键)时,在完成例程中强迫

将其改成Ctrl键,从而完成修改IRP的目的:

 

NTSTATUS Ctrl2capReadComplete( 
    IN PDEVICE_OBJECT DeviceObject, 
    IN PIRP Irp,
    IN PVOID Context 
    )
{
    PIO_STACK_LOCATION        IrpSp;
    PKEYBOARD_INPUT_DATA      KeyData;
    int                       numKeys, i;

    //
    // Request completed - look at the result.
    // 获得当前设备堆栈
    IrpSp = IoGetCurrentIrpStackLocation( Irp );
    if( NT_SUCCESS( Irp->IoStatus.Status ) ) {

        //
        // Do caps-lock down and caps-lock up. Note that
        // just frobbing the MakeCode handles both the up-key
        // and down-key cases since the up/down information is specified
        // seperately in the Flags field of the keyboard input data 
        // (0 means key-down, 1 means key-up).
        // 获取IRP中的数据
        KeyData = Irp->AssociatedIrp.SystemBuffer;
        numKeys = Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);

        for( i = 0; i < numKeys; i++ ) {

            DbgPrint(("ScanCode: %x ", KeyData[i].MakeCode ));
            DbgPrint(("%s\n", KeyData[i].Flags ? "Up" : "Down" ));
			// 修改键盘扫描码
            if( KeyData[i].MakeCode == CAPS_LOCK) {

                KeyData[i].MakeCode = LCONTROL;
            } 
        }
    }

    //
    // Mark the Irp pending if required
    // 如果需要阻塞,调用IoMarkIrpPending
    if( Irp->PendingReturned ) {
		// 挂起IRP
        IoMarkIrpPending( Irp );
    }
    return Irp->IoStatus.Status;
}


 我们用DriverMonitor安装此.sys文件。

  • 8
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值