智能终端定制开发 ad
MTK/瑞芯微/高通-Android,智能模块/智能终端方案商

深度定制各类智能终端和智能硬件产品,提供硬件选型咨询、参考设计、元器件推荐、驱动开发、行业模块集成、操作系统定制与算法集成等软硬件定制服务。
contact.aspx

Android核心板产品覆盖2G、3G、4G通讯,双核、四核、八核CPU,可选的平台有MTK6580、MTK6737、MTK6750等,Android版本有5.1 6.0 7.0等。
contact.aspx

可广泛应用于低端智能POS、安防监控、车载设备、低端智能机器人、智能家居、智能硬件、工业智能手持设备、低端智能对讲设备、低端警务或执法设备、智能穿戴、贩卖机、物流柜、智能门禁系统等行业和设备。
contact.aspx

可提供以太网转串口透传,WIFI转串口透传,蓝牙转串口透传,CAN总线模拟量控制输出模块等。
contact.aspx

带3G或4G通讯功能,运行android系统,有多个串口,可以外挂各种模块:条码扫描、RFID、指纹识别、身份证识别、磁条卡、ID卡、GPS/北斗模块等。
contact.aspx

具有4G通讯功能,多个RS232或RS485接口,以太网接口,USB接口,CAN接口,多个AD输入。基于Android系统智能平台,方便APP应用开发。器件严格选型,运行稳定,质量可靠。
contact.aspx

文件过滤系统驱动开发Filemon学习笔记
[VC 编程] 2008-04-03



郝朝

下载filemon源代码

WINDOWS文件过滤系统驱动开发,可用于硬盘还原,防病毒,文件安全防护,文件加密等诸多领域。而掌握核心层的理论及实践,对于成为一名优秀的开发人员不可或缺。

WINDOWS文件过滤系统驱动开发的两个经典例子,Filemon与SFilter,初学者在经过一定的理论积累后,对此两个例子代码的研究分析,会是步入驱动开发殿堂的重要一步,相信一定的理论积累以及贯穿剖析理解此两个例程后,就有能力开始进行文件过滤系统驱动开发的实际工作了。
对于SFilter例子的讲解,楚狂人的教程已经比较流行,而Filemon例子也许因框架结构相对明晰,易于剖析理解,无人贴出教程,本人在剖析Filemon的过程中积累的一些笔记资料,陆续贴出希望对初学者有所帮助,并通过和大家的交流而互相提高。

Filemon学习笔记 第一篇:

Filemon的大致架构为,在此驱动程序中,创建了两类设备对象。
一类设备对象用于和Filemon对应的exe程序通信,以接收用户输入信息,比如挂接或监控哪个分区,是否要挂接,是否要监控,监控何种操作等。此设备对象只创建了一个,在驱动程序的入口函数DriverEntry中。此类设备对象一般称为控制设备对象,并有名字,以方便应用层与其通信操作。
第二类设备对象用于挂接到所须监控的分区,比如c:,d:或e:,f:,以便拦截到引应用层对该分区所执行的读,写等操作。此类设备对象为安全起见,一般不予命名,可根据须监控多少分区而创建一个或多个。

驱动入口函数大致如下:

NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { NTSTATUS ntStatus; PDEVICE_OBJECT guiDevice; WCHAR deviceNameBuffer[] = L"\\Device\\Filemon"; UNICODE_STRING deviceNameUnicodeString; WCHAR deviceLinkBuffer[] = L"\\DosDevices\\Filemon"; UNICODE_STRING deviceLinkUnicodeString; ULONG i; DbgPrint (("Filemon.SYS: entering DriverEntry\n")); FilemonDriver = DriverObject; // // Setup the device name // RtlInitUnicodeString (&deviceNameUnicodeString, deviceNameBuffer ); // // Create the device used for GUI communications //此设备对象用来和用户交互信息 ntStatus = IoCreateDevice ( DriverObject, sizeof(HOOK_EXTENSION), &deviceNameUnicodeString, FILE_DEVICE_FILEMON, 0, TRUE, &guiDevice ); // // If successful, make a symbolic link that allows for the device // object~s access from Win32 programs // if(NT_SUCCESS(ntStatus)) { // // Mark this as our GUI device // ((PHOOK_EXTENSION) guiDevice->DeviceExtension)->Type = GUIINTERFACE; // // Create a symbolic link that the GUI can specify to gain access // to this driver/device // RtlInitUnicodeString (&deviceLinkUnicodeString, deviceLinkBuffer ); ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString, &deviceNameUnicodeString ); if(!NT_SUCCESS(ntStatus)) { DbgPrint (("Filemon.SYS: IoCreateSymbolicLink failed\n")); IoDeleteDevice( guiDevice ); return ntStatus; } // // Create dispatch points for all routines that must be handled. // All entry points are registered since we might filter a // file system that processes all of them. // for( i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++ ) { DriverObject->MajorFunction[i] = FilemonDispatch; } #if DBG // // Driver unload is only set if we are debugging Filemon. This is // because unloading a filter is not really safe - threads could // be in our fastio routines (or about to enter them), for example, // and there is no way to tell. When debugging, we can risk the // occasional unload crash as a trade-off for not having to // reboot as often. // // DriverObject->DriverUnload = FilemonUnload; #endif // DBG // // Set up the Fast I/O dispatch table // DriverObject->FastIoDispatch = &FastIOHook; } else { // // If something went wrong, cleanup the device object and don~t load // DbgPrint(("Filemon: Failed to create our device!\n")); return ntStatus; } // // Initialize the name hash table // for(i = 0; i < NUMHASH; i++ ) HashTable[i] = NULL; // // Find the process name offset // ProcessNameOffset = FilemonGetProcessNameOffset();//为了得到当前进程名字 // // Initialize the synchronization objects // #if DBG KeInitializeSpinLock( &CountMutex ); #endif ExInitializeFastMutex( &LogMutex ); ExInitializeResourceLite( &FilterResource ); ExInitializeResourceLite( &HashResource ); // // Initialize a lookaside for file names // ExInitializeNPagedLookasideList( &FullPathLookaside, NULL, NULL, 0, MAXPATHLEN, ~mliF~, 256 ); // // Allocate the first output buffer // CurrentLog = ExAllocatePool( NonPagedPool, sizeof(*CurrentLog) ); if( !CurrentLog ) { // // Oops - we can~t do anything without at least one buffer // IoDeleteSymbolicLink( &deviceLinkUnicodeString ); IoDeleteDevice( guiDevice ); return STATUS_INSUFFICIENT_RESOURCES; } // // Set the buffer pointer to the start of the buffer just allocated // CurrentLog->Len = 0; CurrentLog->Next = NULL; NumLog = 1; return STATUS_SUCCESS; }
在此驱动入口点函数中,主要做了生成新的设备对象,此设备对象用来和应用层信息交互,比如应用层向驱动传递需要挂接或者监控的分区盘符,或者是否挂接盘符,是否监控操作等。
上面创建设备对象的代码为:
ntStatus = IoCreateDevice ( DriverObject, sizeof(HOOK_EXTENSION), &deviceNameUnicodeString, FILE_DEVICE_FILEMON, 0, TRUE, &guiDevice ); // // If successful, make a symbolic link that allows for the device // object~s access from Win32 programs // if(NT_SUCCESS(ntStatus)) { // // Mark this as our GUI device // ((PHOOK_EXTENSION) guiDevice->DeviceExtension)->Type = GUIINTERFACE; // // Create a symbolic link that the GUI can specify to gain access // to this driver/device // RtlInitUnicodeString (&deviceLinkUnicodeString, deviceLinkBuffer ); ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString, &deviceNameUnicodeString ); if(!NT_SUCCESS(ntStatus)) { DbgPrint (("Filemon.SYS: IoCreateSymbolicLink failed\n")); IoDeleteDevice( guiDevice ); return ntStatus; }
上面代码完成的功能为创建了用于与应用层交互的控制设备对象,名字在参数&deviceNameUnicodeString,中。设备对象创建成功后又调用IoCreateSymbolicLink创建了一个符号连接,以便于应用层交互。 在入口点函数DriverEntry代码中,还有一处代码: ProcessNameOffset = FilemonGetProcessNameOffset();//为了得到当前进程名字。 此函数体如下:
ULONG FilemonGetProcessNameOffset( VOID ) { PEPROCESS curproc; int i; curproc = PsGetCurrentProcess();//调用PsGetCurrentProcess取得KPEB基址 //然后搜索KPEB,得到ProcessName相对KPEB的偏移量 // Scan for 12KB, hoping the KPEB never grows that big! // for( i = 0; i < 3*PAGE_SIZE; i++ ) { if( !strncmp( SYSNAME, (PCHAR) curproc + i, strlen(SYSNAME) )) { return i; } } // // Name not found - oh, well // return 0;

这个函数通过查找KPEB (Kernel Process Environment Block),取得进程名,GetProcessNameOffset主要是调用PsGetCurrentProcess取得KPEB基址,然后搜索KPEB,得到ProcessName相对KPEB的偏移量,存放在全局变量ProcessNameOffset中,得到此偏移量的作用是:无论当前进程为哪个,其名字在KPEB中的偏移量不变,所以都可以通过此偏移量得到。而在入口点函数DriverEntry执行时,当前进程必为系统进程,所以在此函数中方便地根据系统进程名SYSNAME(#define SYSNAME "System")得到此偏移量。

分发函数剖析:
在入口点函数中,通过代码:

for( i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++ ) { DriverObject->MajorFunction[i] = FilemonDispatch; }
简单地把各个分发例程设置成了FilemonDispatch; 然后我们追踪其函数体:
NTSTATUS FilemonDispatch( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { // // Determine if its a request from the GUI to us, or one that is // directed at a file system driver that we~ve hooked // if( ((PHOOK_EXTENSION) DeviceObject->DeviceExtension)->Type == GUIINTERFACE ) { return FilemonDeviceRoutine( DeviceObject, Irp ); } else { return FilemonHookRoutine( DeviceObject, Irp ); } }

函数体先判断需要处理IRP包的设备对象的类型,看是属于控制设备对象,还是属于用于挂接并监控文件读写操作的过滤设备对象。如果是属于后者 则进入:FilemonHookRoutine( DeviceObject, Irp )
此函数是拦截文件操作的中心,在其中获得了被操作的文件名字,并且根据操作类型,在
switch( currentIrpStack->MajorFunction ) {
}
中针对不同的MajorFunction,打印出相关操作信息。
因此函数体太长 不再全部列出。
其函数体总体框架为:得到被操作的文件名字,打印相关操作信息,然后下发IRP到底层驱动。
在下发IRP到底层驱动处理前,本层驱动必须负责设置下层IO堆栈的内容。这样下一层驱动调用IoGetCurrentIrpStackLocation()时能得到相应的数据。
设置下层IO堆栈的内容,一般用两个函数来实现:
IoCopyCurrentIrpStackLocationToNext( Irp )
此函数一般用在本驱动设置了完成例程时调用,把本层IO _STACK_LOCATION 中的参数copy到下层,但与完成例程相关的参数信息例外。因为本驱动设置的完成例程只对本层驱动有效。
IoSkipCurrentIrpStackLocationToNext(Irp)
此函数的作用是:直接把本层驱动IO堆栈的内容设置为下层驱动IO堆栈指针的指向。因两层驱动IO堆栈的内容完全一致,省却copy过程。

而在Filemon的处理中,它用了一个特别的办法,没有调用此两个函数,FilemonHookRoutine函数体里面有三句代码:

PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp); PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp); *nextIrpStack = *currentIrpStack;//此步设置了下层驱动的IO_STACK_LOCATION 直接设置了下层驱动IO堆栈的值。
在FilemonHookRoutine函数里,用一个宏实现了复杂的获得拦截到的被操作文件的名字:
if( FilterOn && hookExt->Hooked ) { GETPATHNAME( createPath ); }
GETPATHNAME( createPath )宏展开为:
#define GETPATHNAME(_IsCreate) \ fullPathName = ExAllocateFromNPagedLookasideList( &FullPathLookaside ); \ if( fullPathName ) { \ FilemonGetFullPath( _IsCreate, FileObject, hookExt, fullPathName ); \ } else { \ fullPathName = InsufficientResources; \ }

在函数:FilemonGetFullPath( _IsCreate, FileObject, hookExt, fullPathName )中实现了获得被操作的文件名字,此函数代码较多,判断条件复杂,理解起来比较麻烦,下面重点讲解。
对函数FilemonGetFullPath的理解关键在于理顺结构,
此函数的功能就是获得文件名字,获得文件名字一般在三种状态下:
一:在打开文件请求中,但在打开文件前。
二:在打开文件请求中,但在打开文件后,通过在本层驱动中设置完成例程。在完成例程中获得。
三:在过滤到读写等操作时。
而在此函数中,它包含了第一种和第三种方法,通过一些烦琐的条件判断,先判断出目前是处于什么状态中,然后根据不同状态采取不同方法。

先分析当在第一种条件下,此函数的处理方法,可以精炼为如下:
VOID FilemonGetFullPath( BOOLEAN createPath, PFILE_OBJECT fileObject, PHOOK_EXTENSION hookExt, PCHAR fullPathName ) { ULONG pathLen, prefixLen, slashes; PCHAR pathOffset, ptr; BOOLEAN gotPath; PFILE_OBJECT relatedFileObject; ANSI_STRING fileName; ANSI_STRING relatedName; UNICODE_STRING fullUniName; prefixLen = 2; // "C:" if( !fileObject ) { sprintf( fullPathName, "%C:", hookExt->LogicalDrive ); return; } // // Initialize variables // fileName.Buffer = NULL; relatedName.Buffer = NULL; gotPath = FALSE; if( !fileObject->FileName.Buffer) { sprintf( fullPathName, "%C:", hookExt->LogicalDrive); return; }else DbgPrint("fileOjec->FileName:%s",fileObject->FileName); if( !NT_SUCCESS( RtlUnicodeStringToAnsiString( &fileName, &fileObject->FileName, TRUE ))) { sprintf( fullPathName, "%C: <Out of Memory>", hookExt->LogicalDrive ); return; } pathLen = fileName.Length + prefixLen; relatedFileObject = fileObject->RelatedFileObject; // // Only look at related file object if this is a relative name // if( fileObject->FileName.Buffer[0] != L~\\~ && relatedFileObject && relatedFileObject->FileName.Length ) { DbgPrint("relatedFileObject filename : %s",relatedFileObject->FileName); if( !NT_SUCCESS( RtlUnicodeStringToAnsiString( &relatedName, &relatedFileObject->FileName, TRUE ))) { sprintf( fullPathName, "%C: <Out of Memory>", hookExt->LogicalDrive ); RtlFreeAnsiString( &fileName ); return; } pathLen += relatedName.Length+1; } if( fileObject->DeviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM ) { sprintf( fullPathName, "%C:", hookExt->LogicalDrive ); } if( pathLen >= MAXPATHLEN ) { strcat( fullPathName, " <Name Too Long>" ); } else { // // Now we can build the path name // fullPathName[ pathLen ] = 0; pathOffset = fullPathName + pathLen - fileName.Length; memcpy( pathOffset, fileName.Buffer, fileName.Length + 1 ); if( fileObject->FileName.Buffer[0] != L~\\~ && relatedFileObject && relatedFileObject->FileName.Length ) { // // Copy the component, adding a slash separator // *(pathOffset - 1) = ~\\~; pathOffset -= relatedName.Length + 1; memcpy( pathOffset, relatedName.Buffer, relatedName.Length ); // // If we~ve got to slashes at the front zap one // if( pathLen > 3 && fullPathName[2] == ~\\~ && fullPathName[3] == ~\\~ ) { strcpy( fullPathName + 2, fullPathName + 3 ); } } } }

上面的精简后的函数代码为只考虑目前处于第一种情况,即打开文件请求中,但文件尚未打开时。
在此时,文件的名字信息存储在文件对象fileObject->FileName,与fileObject->RelatedFileObject->FileName, FileObject->FileName 是RelatedObject 的相对路径,通过对两者的解析组合出文件名字。

而在FilemonGetFullPath 函数体中的另一段代码:
FilemonGetFullPath { ………………….. ………………….. ………………….. if( !gotPath && !createPath ) { fileNameInfo = (PFILE_NAME_INFORMATION) ExAllocatePool( NonPagedPool, MAXPATHLEN*sizeof(WCHAR) ); if( fileNameInfo && FilemonQueryFile(hookExt->FileSystem, fileObject, FileNameInformation, fileNameInfo, (MAXPATHLEN - prefixLen - 1)*sizeof(WCHAR) )) { fullUniName.Length = (SHORT) fileNameInfo->FileNameLength; fullUniName.Buffer = fileNameInfo->FileName; if( NT_SUCCESS( RtlUnicodeStringToAnsiString( &fileName, &fullUniName, TRUE ))) { fullPathName[ fileName.Length + prefixLen ] = 0; if( hookExt->Type == NPFS ) { strcpy( fullPathName, NAMED_PIPE_PREFIX ); } else if( hookExt->Type == MSFS ) { strcpy( fullPathName, MAIL_SLOT_PREFIX ); } else if( fileObject->DeviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM ) { sprintf( fullPathName, "%C:", hookExt->LogicalDrive ); } else { // // No prefix for network devices // } memcpy( &fullPathName[prefixLen], fileName.Buffer, fileName.Length ); gotPath = TRUE; RtlFreeAnsiString( &fileName ); fileName.Buffer = NULL; } } if( fileNameInfo ) ExFreePool( fileNameInfo ); } ………………… ………………… ………………… }

上面这段代码是处理另外一种情况,即是在其他读写请求中,自己根据拦截到的fileObject构建IRP,下发到底层,以此来查询文件名信息。整个过程还是易于理解的。

在理清这两种脉络后,再剖析此整个函数,就很容易理解整个函数代码了。
代码中对 MajorFunction == IRP_MJ_CREATE_NAMED_PIPE
MajorFunction == IRP_MJ_CREATE_MAILSLOT 的判断是为了辨别对拦截到的进程间的两种通信方式:命名管道与邮槽的处理。

周末来了,祝大家周末愉快。余下的整理后再帖,希望和大家多交流。

连城碧 QQ 276265852 MSN:haochao0000@hotmail.com

[VC 编程添加评论 | 评论/阅读(0/336)
评论
昵称
主页
内容
递交


Copyright @ 我的开发笔记     2008 - 2017         粤ICP备19155526号-1