Windows驱动开发入门-文件系统透明加密 这一节的内容就是讲讲知识点,没法做作品,本节的例子针对Windows XP和FAT32文件系统,且透明加密仅针对记事本软件进程。
区分进程 找到进程名字位置 进程名字保存在EPROCESS结构中,但每个Windows版本都不一样。又已知当前驱动进程名一定为“System”,所以在EPROCESS结构中搜索“System”字样即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static size_t s_cf_proc_name_offset = 0 ; VOID cfCurProcNameInit (VOID) { ULONG i; PEPROCESS curproc; curproc = PsGetCurrentProcess (); for (i = 0 ; i < 3 * 4 * 1024 ; i++) if (!strncmp ("System" , (PCHAR)curproc + i, strlen ("System" ))) { s_cf_proc_name_offset = i; break ; }; return ; };
得到进程名字 EPROCESS结构中保存的进程名是窄字符的,所以要把他转换成UNICODE_STRING宽字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ULONG cfCurProcName (PUNICODE_STRING name) { PEPROCESS curproc; ULONG i, need_len; ANSI_STRING ansi_name; if (s_cf_proc_name_offset == 0 ) return 0 ; curproc = PsGetCurrentProcess (); RtlInitAnsiString (&ansi_name, ((PCHAR)curproc + s_cf_proc_name_offset)); need_len = RtlAnsiStringToUnicodeSize (&ansi_name); if (need_len > name->MaximumLength) return RtlAnsiStringToUnicodeSize (&ansi_name); RtlAnsiStringToUnicodeString (name, &ansi_name, FALSE); return need_len; };
判断当前进程 只是判断进程名字罢了,不包括防同名进程伪造的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 BOOLEAN cfIsCurProcSec (VOID) { WCHAR name_buf[32 ] = { 0 }; UNICODE_STRING proc_name = { 0 }; UNICODE_STRING note_pad = { 0 }; ULONG length; RtlInitEmptyUnicodeString (&proc_name, name_buf, 32 * sizeof (WCHAR)); length = cfCurProcName (&proc_name); RtlInitUnicodeString (¬e_pad, TEXT ("notepad.exe" )); if (RtlCompareUnicodeString (¬e_pad, &proc_name, TRUE) == 0 ) return TRUE; return FALSE; };
内存映射与文件缓冲 内存映射 在记事本打开某个文件时根本不会发生读请求,只有“保存”时文件才被打开,发出写请求后紧接着关闭。
Windows将一个文件映射到某个内存空间,访问这个文件内容的进程只需要访问这个内存空间即可,进程对内存的访问无法被文件过滤驱动捕获。被内存映射的文件实际成了一个分页交换文件,一旦进程访问到实际不存在的内存页(还在硬盘上),就会有一个文件上面的页面被交换到内存,即发送irp->Flags
带有IRP_PAGING_IO或IRP_SYNCHRONOUS_PAGING_IO的IRP_MJ_READ请求到文件驱动,这叫做分页读写请求。
文件缓冲 当一个文件被以缓冲方式打开过,其内容全部或一部分就被保存到内存里了,这部分信息为文件缓存。文件缓存是全局的,一个文件无论被多少进程访问都只有一份文件传冲。应用层发来的IRP读写请求都是普通的读写请求(不带IRP_PAGING_IO、IRP_SYNCHRONOUS_PAGING_IO、IRP_NOCACHE等),对于这种缓冲读写请求文件系统会调用CcCopyRead
和CcCopyWrite
来完成。这俩函数从缓冲区读取数据,缓冲区没有时将转换为分页读写请求。
还有直接从硬盘读写数据但不缓冲的非缓冲读写请求,连同缓冲读写请求和分页读写请求这六个都可被文件过滤过滤到。下面是区分四种IRP,非缓冲读写请求并入分页读写请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 if (irpsp->MajorFunction == IRP_MJ_READ && (irp->Flags & (IRP_PAGING_IO | IRP_SYNCHRONOUS_PAGING_IO | IRP_NOCACHE))) { cfIrpReadPre (irp, irpsp); return SF_IRP_GO_ON; }; if (irpsp->MajorFunction == IRP_MJ_WRITE && (irp->Flags & (IRP_PAGING_IO | IRP_SYNCHRONOUS_PAGING_IO | IRP_NOCACHE))) { if (cfIrpWritePre (irp, irpsp, context)) return SF_IRP_GO_ON; else { IoCompleteRequest (irp, IO_NO_INCREMENT); return SF_IRP_COMPLETED; }; };
本节例子决定文件缓冲有时是明文,有时是密文。当普通进程打开一个文件时文件缓冲为密文,不允许机密进程打开这个文件。当机密进程打开一个文件时文件缓冲为明文,不允许普通进程打开这个文件。二者切换时中间清除文件缓存。
文件缓冲的清除 微软没想着让开发者清除文件缓冲,下面是开源FastFat的清除缓冲的方式,确实看不懂但确实有效:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 VOID cfFileCacheClear (PFILE_OBJECT pFileObject) { PFSRTL_COMMON_FCB_HEADER pFcb; LARGE_INTEGER liInterval; BOOLEAN bNeedReleaseResource = FALSE; BOOLEAN bNeedReleasePagingIoResource = FALSE; KIRQL irql; pFcb = (PFSRTL_COMMON_FCB_HEADER)pFileObject->FsContext; if (pFcb == NULL ) return ; irql = KeGetCurrentIrql (); if (irql >= DISPATCH_LEVEL) return ; liInterval.QuadPart = -1 * (LONGLONG)50 ; while (TRUE) { BOOLEAN bBreak = TRUE; BOOLEAN bLockedResource = FALSE; BOOLEAN bLockedPagingIoResource = FALSE; bNeedReleaseResource = FALSE; bNeedReleasePagingIoResource = FALSE; if (pFcb->PagingIoResource) bLockedPagingIoResource = ExIsResourceAcquiredExclusiveLite (pFcb->PagingIoResource); if (pFcb->Resource) { bLockedResource = TRUE; if (ExIsResourceAcquiredExclusiveLite (pFcb->Resource) == FALSE) { bNeedReleaseResource = TRUE; if (bLockedPagingIoResource) { if (ExAcquireResourceExclusiveLite (pFcb->Resource, FALSE) == FALSE) { bBreak = FALSE; bNeedReleaseResource = FALSE; bLockedResource = FALSE; }; } else ExAcquireResourceExclusiveLite (pFcb->Resource, TRUE); }; }; if (bLockedPagingIoResource == FALSE) if (pFcb->PagingIoResource) { bLockedPagingIoResource = TRUE; bNeedReleasePagingIoResource = TRUE; if (bLockedResource) { if (ExAcquireResourceExclusiveLite (pFcb->PagingIoResource, FALSE) == FALSE) { bBreak = FALSE; bLockedPagingIoResource = FALSE; bNeedReleasePagingIoResource = FALSE; }; } else ExAcquireResourceExclusiveLite (pFcb->PagingIoResource, TRUE); }; if (bBreak) break ; if (bNeedReleasePagingIoResource) ExReleaseResourceLite (pFcb->PagingIoResource); if (bNeedReleaseResource) ExReleaseResourceLite (pFcb->Resource); if (irql == PASSIVE_LEVEL) KeDelayExecutionThread (KernelMode, FALSE, &liInterval); else { KEVENT waitEvent; KeInitializeEvent (&waitEvent, NotificationEvent, FALSE); KeWaitForSingleObject (&waitEvent, Executive, KernelMode, FALSE, &liInterval); }; }; if (pFileObject->SectionObjectPointer) { IO_STATUS_BLOCK ioStatus; CcFlushCache (pFileObject->SectionObjectPointer, NULL , 0 , &ioStatus); if (pFileObject->SectionObjectPointer->ImageSectionObject) MmFlushImageSection (pFileObject->SectionObjectPointer, MmFlushForWrite); CcPurgeCacheSection (pFileObject->SectionObjectPointer, NULL , 0 , FALSE); }; if (bNeedReleasePagingIoResource) ExReleaseResourceLite (pFcb->PagingIoResource); if (bNeedReleaseResource) ExReleaseResourceLite (pFcb->Resource); return ; };
加密标识 加密标识放在文件头处理最简单。如果加密标识在文件外其他位置保存的话拷贝来拷贝去移动加密标识非常麻烦。如果使用NTFS额外流信息,则不兼容FAT32,U盘拷贝就没了。文件尾要随文件大小变化而变化,数据可能会丢失不安全。但我们要对机密进程隐藏这个加密标识头,那么所有文件操作都要增加一个偏移。
隐藏文件头大小 一般查询文件头大小的请求是IRP_MJ_QUERY_INFORMATION,例如当irpsp->Parameters.QueryFile.FileInformationClass
为FileStandardInformation时,请求完成后缓冲区是个FILE_STANDARD_INFORMATION结构:
1 2 3 4 5 6 7 typedef struct FILE_STANDARD_INFORMATION { LARGE_INTEGER AllocationSize; LARGE_ITNEGER EndOfFile; ULONG NumberOfLinks; BOOLEAN DeletePending; BOOLEAN Directory; } FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;
处理方法如下:
1 2 3 4 5 6 7 case FileStandardInformation: { PFILE_STANDARD_INFORMATION stand_infor = (PFILE_STANDARD_INFORMATION)buffer; ASSERT (stand_infor->AllocationSize.QuadPart >= CF_FILE_HEADER_SIZE); stand_infor->AllocationSize.QuadPart -= CF_FILE_HEADER_SIZE; stand_infor->EndOfFile.QuadPart -= CF_FILE_HEADER_SIZE; break ; };
还有FileAllocationInformation、FileValidDataLengthInformation、FileStandardInformation、FileEndOfFileInformation、FilePositionInformation等,应付记事本足够了。要注意FileAllInformation文件inxi类返回的缓冲区结构是这个:
1 2 3 4 5 6 7 8 9 10 11 typedef struct _FILE_ALL_INFORMATION { FILE_BASIC_INFORMATION BasicInformation; FILE_STANDARD_INFORMATION StandardInformation; FILE_INTERNAL_INFORMATION InternalInformation; FILE_EA_INFORMAION EaInformation; FILE_ACCESS_INFORMATION AccessInformation; FILE_POSITION_INFORMATION PositionInformation; FILE_MODE_INFORMATION ModeInformation; FILE_ALIGNMENT_INFORMATION AlignmentInformation; FILE_NAME_INFORMATION NameInformation; } FILE_ALL_INFORMATION, *PFILE_ALL_INFORMATION;
FileAllInformation的特殊处理如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 case FileAllInformation: { PFILE_ALL_INFORMATION all_infor = (PFILE_ALL_INFORMATION)buffer; if (irp->IoStatus.Information >= sizeof (FILE_BASIC_INFORMATION) + sizeof (FILE_STANDARD_INFORMATION)) { ASSERT (all_infor->StandardInformation.EndOfFile.QuadPart >= CF_FILE_HEADER_SIZE); all_infor->StandardInformation.EndOfFile.QuadPart -= CF_FILE_HEADER_SIZE; all_infor->StandardInformation.AllocationSize.QuadPart -= CF_FILE_HEADER_SIZE; if (irp->IoStatus.Information >=sizeof (FILE_BASIC_INFORMATION) +sizeof (FILE_STANDARD_INFORMATION) +sizeof (FILE_INTERNAL_INFORMATION) +sizeof (FILE_EA_INFORMATION) +sizeof (FILE_ACCESS_INFORMATION) +sizeof (FILE_POSITION_INFORMATION)) if (all_infor->PositionInformation.CurrentByteOffset.QuadPart >= CF_FILE_HEADER_SIZE) all_infor->PositionInformation.CurrentByteOffset.QuadPart -= CF_FILE_HEADER_SIZE; }; break ; };
文件头偏移设置 拦截设置请求进行修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 VOID cfIrpSetInforPre (PIRP irp, PIO_STACK_LOCATION irpsp) { PUCHAR buffer = irp->AssociatedIrp.SystemBuffer; NTSTATUS status; ASSERT (irpsp->MajorFunction == IRP_MJ_SET_INFORMATION); switch (irpsp->Parameters.SetFile.FileInformationClass) { case FileAllocationInformation: { PFILE_ALLOCATION_INFORMATION alloc_infor = (PFILE_ALLOCATION_INFORMATION)buffer; alloc_infor->AllocationSize.QuadPart += CF_FILE_HEADER_SIZE; break ; }; case FileEndOfFileInformation: { PFILE_END_OF_FILE_INFORMATION end_infor = (PFILE_END_OF_FILE_INFORMATION)buffer; end_infor->EndOfFile.QuadPart += CF_FILE_HEADER_SIZE; break ; }; case FileValidDataLengthInformation: { PFILE_VALID_DATA_LENGTH_INFORMATION valid_length = (PFILE_VALID_DATA_LENGTH_INFORMATION)buffer; valid_length->ValidDataLength.QuadPart += CF_FILE_HEADER_SIZE; break ; }; case FilePositionInformation: { PFILE_POSITION_INFORMATION position_infor = (PFILE_POSITION_INFORMATION)buffer; position_infor->CurrentByteOffset.QuadPart += CF_FILE_HEADER_SIZE; break ; }; case FileStandardInformation: ((PFILE_STANDARD_INFORMATION)buffer)->EndOfFile.QuadPart += CF_FILE_HEADER_SIZE; break ; case FileAllInformation: ((PFILE_ALL_INFORMATION)buffer)->PositionInformation.CurrentByteOffset.QuadPart += CF_FILE_HEADER_SIZE; ((PFILE_ALL_INFORMATION)buffer)->StandardInformation.EndOfFile.QuadPart += CF_FILE_HEADER_SIZE; break ; default : ASSERT (FALSE); }; return ; };
读写偏移 1 2 3 4 5 6 7 8 9 10 11 12 13 VOID cfIrpReadPre (PIRP irp, PIO_STACK_LOCATION irpsp) { PLARGE_INTEGER offset; PFCB fcb = (PFCB)irpsp->FileObject->FsContext; offset = &irpsp->Parameters.Read.ByteOffset; if (offset->LowPart == FILE_USE_FILE_POINTER_POSITION && offset->HighPart == -1 ) ASSERT (FALSE); offset->QuadPart += CF_FILE_HEADER_SIZE; KdPrint (("cfIrpReadPre: offset = %8x\r\n" , offset->LowPart)); return ; };
文件加密表 FCB 每个文件可能被多次打开,每次打开生成一个文件对象FILE_OBJECT,一个文件对象可被获得多个文件句柄,但同一个文件的多个文件对象都对应着唯一的文件控制块FCB(特殊情况下产生多个FCB但忽略不计)。FCB不公开且各个文件系统都不一样,甚至例如FastFat同一个文件系统中同时出现不同结构FCB。但FCB的指针的获得很简单即为PFCB fcb=(FCB)file->FsContext
,
加密表操作 将需要加密的文件FCB指针加入加密表,这里使用链表但实际应用哈希表。链表节点结构:
1 2 3 4 typedef struct { LIST_ENTRY list_entry; PFCB fcb; } CF_NODE,*PCF_NODE;
定义链表头、自旋锁,并记录取锁中断级:
1 2 3 4 5 6 7 static LIST_ENTRY s_cf_list;static KSPIN_LOCK s_cf_list_lock;static KIRQL s_cf_list_lock_irql;static BOOLEAN s_cf_list_inited = FALSE;BOOLEAN cfListInited (VOID) { return s_cf_list_inited; };
初始化链表和锁:
1 2 3 4 5 6 VOID cfListInit (VOID) { InitializeListHead (&s_cf_list); KeInitializeSpinLock (&s_cf_list_lock); s_cf_list_inited = TRUE; return ; };
加锁和解锁:
1 2 3 4 5 6 7 8 9 10 11 VOID cfListLock (VOID) { ASSERT (s_cf_list_inited); KeAcquireSpinLock (&s_cf_list_lock, &s_cf_list_lock_irql); return ; }; VOID cfListUnlock (VOID) { ASSERT (s_cf_list_inited); KeReleaseSpinLock (&s_cf_list_lock, s_cf_list_lock_irql); return ; };
加密表查询 从IRP中得到文件对象,再从文件对象中得到FCB指针,放入文件加密表中查询:
1 2 3 4 5 6 7 8 9 10 11 12 BOOLEAN cfIsFileCrypting (PFILE_OBJECT file) { PLIST_ENTRY p; PCF_NODE node; for (p = s_cf_list.Flink; p != &s_cf_list; p = p->Flink) { node = (PCF_NODE)p; if (node->fcb == file->FsContext) return TRUE; }; return FALSE; };
加密表添加 FCB要被插入文件加密表,需要清除文件缓冲。已存在文件加密表中FCB不能重复插入,否则卸下时卸不全。被非加密进程打开着的文件不可以再次被加密打开,只能返回打开失败。为了判断一个文件是否还被非加密进程打开着,需要用到FCB的公开结构,所以这里只能兼容FAT32。FCB下有个UncleanCount域记录还有多少未被关闭的句柄,比1大则至少还有进程打开这个文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 BOOLEAN cfFileCryptAppendLk (PFILE_OBJECT file) { PCF_NODE node = (PCF_NODE) ExAllocatePoolWithTag (NonPagedPool, sizeof (CF_NODE), CF_MEM_TAG); node->fcb = (PFCB)file->FsContext; cfFileCacheClear (file); cfListLock (); if (cfIsFileCrypting (file)) { ASSERT (FALSE); return TRUE; } else if (node->fcb->UncleanCount > 1 ) { cfListUnlock (); ExFreePool (node); return FALSE; }; InsertHeadList (&s_cf_list, (PLIST_ENTRY)node); cfListUnlock (); return TRUE; };
加密表删除 从文件加密表中删除文件FCB时应当加密进程已经关闭了该文件。当一个文件对象上所有句柄都被关闭时文件对象会收到IRP_MJ_CLEAN_UP,但文件对象不一定马上销毁,因为文件对象上没有任何句柄仍然可被内核操作,只是无法被应用层操作。当文件对象引用数清为0时才会收到IRP_MJ_CLOSE,然后被销毁。但实际上期待FCB上打开着的文件对象数量降为0不现实,Windows几乎会一直维护一个被打开过的文件缓冲,除非这个文件被删除或改名。所以现在要降低要求,只需期待它们收到IRP_MJ_CLEAN_UP被清理。被清理就没有文件句柄了,上层应用程序就无法对这个文件进行数据读写,可认为文件缓冲不再变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 BOOLEAN cfCryptFileCleanupComplete (PFILE_OBJECT file) { PLIST_ENTRY p; PCF_NODE node; PFCB fcb = (PFCB)file->FsContext; KdPrint (("cfCryptFileCleanupComplete: file name = %wZ, fcb->UncleanCount = %d\r\n" , &file->FileName, fcb->UncleanCount)); if (fcb->UncleanCount <= 1 || (fcb->FcbState & FCB_STATE_DELETE_ON_CLOSE)) cfFileCacheClear (file); else return FALSE; cfListLock (); for (p = s_cf_list.Flink; p != &s_cf_list; p = p->Flink) { node = (PCF_NODE)p; if (node->fcb == file->FsContext && (node->fcb->UncleanCount == 0 || (fcb->FcbState & FCB_STATE_DELETE_ON_CLOSE))) { RemoveEntryList ((PLIST_ENTRY)node); cfListUnlock (); ExFreePool (node); return TRUE; }; }; cfListUnlock (); return FALSE; };
文件打开处理 IRP的查询与设置请求 直接通过文件对象进行查询,填写了主功能号为IRP_MJ_QUERY_INFORMATION的请求,实现了类似ZwQueryInformation
的操作。因为IRP的结构没有公开,所以只能保证以下代码在Windows 2000和Windows Vista上可行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 NTSTATUS cfFileQueryInformation (PDEVICE_OBJECT dev, PFILE_OBJECT file, FILE_INFORMATION_CLASS infor_class, PVOID buf, ULONG buf_len) { PIRP irp; KEVENT event; IO_STATUS_BLOCK IoStatusBlock; PIO_STACK_LOCATION ioStackLocation; KeInitializeEvent (&event, SynchronizationEvent, FALSE); irp = IoAllocateIrp (dev->StackSize, FALSE); if (irp == NULL ) return STATUS_INSUFFICIENT_RESOURCES; irp->AssociatedIrp.SystemBuffer = buf; irp->UserEvent = &event; irp->UserIosb = &IoStatusBlock; irp->Tail.Overlay.Thread = PsGetCurrentThread (); irp->Tail.Overlay.OriginalFileObject = file; irp->RequestorMode = KernelMode; irp->Flags = 0 ; ioStackLocation = IoGetNextIrpStackLocation (irp); ioStackLocation->MajorFunction = IRP_MJ_QUERY_INFORMATION; ioStackLocation->DeviceObject = dev; ioStackLocation->FileObject = file; ioStackLocation->Parameters.QueryFile.Length = buf_len; ioStackLocation->Parameters.QueryFile.FileInformationClass = infor_class; IoSetCompletionRoutine (irp, cfFileIrpComp, 0 , TRUE, TRUE, TRUE); (VOID)IoCallDriver (dev, irp); KeWaitForSingleObject (&event, Executive, KernelMode, TRUE, 0 ); return IoStatusBlock.Status; };
还有个完成函数,只需设置完成事件即可:
1 2 3 4 5 6 static NTSTATUS cfFileIrpComp (PDEVICE_OBJECT dev, PIRP irp, PVOID context) { *irp->UserIosb = irp->IoStatus; KeSetEvent (irp->UserEvent, 0 , FALSE); IoFreeIrp (irp); return STATUS_MORE_PROCESSING_REQUIRED; };
另一个查询函数,查询文件大小、是否为目录等信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 NTSTATUS cfFileGetStandInfo (PDEVICE_OBJECT dev, PFILE_OBJECT file, PLARGE_INTEGER allocate_size, PLARGE_INTEGER file_size, PBOOLEAN dir) { NTSTATUS status; PFILE_STANDARD_INFORMATION infor = NULL ; infor = (PFILE_STANDARD_INFORMATION) ExAllocatePoolWithTag (NonPagedPool, sizeof (FILE_STANDARD_INFORMATION), CF_MEM_TAG); if (infor == NULL ) return STATUS_INSUFFICIENT_RESOURCES; status = cfFileQueryInformation (dev, file, FileStandardInformation, (void *)infor, sizeof (FILE_STANDARD_INFORMATION)); if (NT_SUCCESS (status)) { if (allocate_size != NULL ) *allocate_size = infor->AllocationSize; if (file_size != NULL ) *file_size = infor->EndOfFile; if (dir != NULL ) *dir = infor->Directory; }; ExFreePool (infor); return status; };
IRP读写操作 处理IRP_MJ_SET_INFORMATION:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 NTSTATUS cfFileSetInformation (PDEVICE_OBJECT dev, PFILE_OBJECT file, FILE_INFORMATION_CLASS infor_class, PFILE_OBJECT set_file, PVOID buf, ULONG buf_len) { PIRP irp; KEVENT event; IO_STATUS_BLOCK IoStatusBlock; PIO_STACK_LOCATION ioStackLocation; KeInitializeEvent (&event, SynchronizationEvent, FALSE); irp = IoAllocateIrp (dev->StackSize, FALSE); if (irp == NULL ) return STATUS_INSUFFICIENT_RESOURCES; irp->AssociatedIrp.SystemBuffer = buf; irp->UserEvent = &event; irp->UserIosb = &IoStatusBlock; irp->Tail.Overlay.Thread = PsGetCurrentThread (); irp->Tail.Overlay.OriginalFileObject = file; irp->RequestorMode = KernelMode; irp->Flags = 0 ; ioStackLocation = IoGetNextIrpStackLocation (irp); ioStackLocation->MajorFunction = IRP_MJ_SET_INFORMATION; ioStackLocation->DeviceObject = dev; ioStackLocation->FileObject = file; ioStackLocation->Parameters.SetFile.FileObject = set_file; ioStackLocation->Parameters.SetFile.Length = buf_len; ioStackLocation->Parameters.SetFile.FileInformationClass = infor_class; IoSetCompletionRoutine (irp, cfFileIrpComp, 0 , TRUE, TRUE, TRUE); (VOID)IoCallDriver (dev, irp); KeWaitForSingleObject (&event, Executive, KernelMode, TRUE, 0 ); return IoStatusBlock.Status; };
读写操作采用IRP_NOCACHE非缓冲方式,以防影响到文件缓冲。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 NTSTATUS cfFileReadWrite (PDEVICE_OBJECT dev, PFILE_OBJECT file, PLARGE_INTEGER offset, PULONG length, PVOID buffer, BOOLEAN read_write) { ULONG i; PIRP irp; KEVENT event; PIO_STACK_LOCATION ioStackLocation; IO_STATUS_BLOCK IoStatusBlock = { 0 }; KeInitializeEvent (&event, SynchronizationEvent, FALSE); irp = IoAllocateIrp (dev->StackSize, FALSE); if (irp == NULL ) return STATUS_INSUFFICIENT_RESOURCES; irp->AssociatedIrp.SystemBuffer = NULL ; irp->MdlAddress = NULL ; irp->UserBuffer = buffer; irp->UserEvent = &event; irp->UserIosb = &IoStatusBlock; irp->Tail.Overlay.Thread = PsGetCurrentThread (); irp->Tail.Overlay.OriginalFileObject = file; irp->RequestorMode = KernelMode; if (read_write) irp->Flags = IRP_DEFER_IO_COMPLETION | IRP_READ_OPERATION | IRP_NOCACHE; else irp->Flags = IRP_DEFER_IO_COMPLETION | IRP_WRITE_OPERATION | IRP_NOCACHE; ioStackLocation = IoGetNextIrpStackLocation (irp); if (read_write) ioStackLocation->MajorFunction = IRP_MJ_READ; else ioStackLocation->MajorFunction = IRP_MJ_WRITE; ioStackLocation->MinorFunction = IRP_MN_NORMAL; ioStackLocation->DeviceObject = dev; ioStackLocation->FileObject = file; if (read_write) { ioStackLocation->Parameters.Read.ByteOffset = *offset; ioStackLocation->Parameters.Read.Length = *length; } else { ioStackLocation->Parameters.Write.ByteOffset = *offset; ioStackLocation->Parameters.Write.Length = *length; }; IoSetCompletionRoutine (irp, cfFileIrpComp, 0 , TRUE, TRUE, TRUE); (VOID)IoCallDriver (dev, irp); KeWaitForSingleObject (&event, Executive, KernelMode, TRUE, 0 ); *length = IoStatusBlock.Information; return IoStatusBlock.Status; };
文件非重入打开 打开操作不但涉及一个请求发送到文件系统,还涉及在IO管理器中生成一个新的文件对象。这里解决方法是用IoCreateFileSpecifyDeviceObjectHint
实现,直接向下层设备打开一个文件对象,本层及以上的文件过滤捕捉不到。原型如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 NTSTATUS IoCreateFileSpecifyDeviceObjectHint ( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, OUT PIO_STATUS_BLOCK IoStatusBlock, IN OPTIONAL PLARGE_INTEGER AllocationSize, IN ULONG FileAttributes, IN ULONG ShareAccess, IN ULONG Disposition, IN ULONG CreateOptions, IN OPTIONAL PVOID EaBuffer, IN ULONG EaLength, IN CREATE_FILE_TYPE CreateFileType, IN OPTIONAL PVOID ExtraCreateParameters, IN ULONG Options, IN PVOID DeviceObject )
用这个函数“试探性”打开文件,打开后判断是否需要加密,需要的话在完成后加入到加密表中,否则直接放过了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 HANDLE cfCreateFileAccordingIrp (IN PDEVICE_OBJECT dev, IN PUNICODE_STRING file_full_path, IN PIO_STACK_LOCATION irpsp, OUT PNTSTATUS status, OUT PFILE_OBJECT* file, OUT PULONG information) { HANDLE file_h = NULL ; IO_STATUS_BLOCK io_status; ULONG desired_access; ULONG disposition; ULONG create_options; ULONG share_access; ULONG file_attri; OBJECT_ATTRIBUTES obj_attri; ASSERT (irpsp->MajorFunction == IRP_MJ_CREATE); *information = 0 ; InitializeObjectAttributes (&obj_attri, file_full_path, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL , NULL ); desired_access = irpsp->Parameters.Create.SecurityContext->DesiredAccess; disposition = (irpsp->Parameters.Create.Options >> 24 ); create_options = (irpsp->Parameters.Create.Options & 0x00ffffff ); share_access = irpsp->Parameters.Create.ShareAccess; file_attri = irpsp->Parameters.Create.FileAttributes; *status = IoCreateFileSpecifyDeviceObjectHint (&file_h, desired_access, &obj_attri, &io_status, NULL , file_attri, share_access, disposition, create_options, NULL , 0 , CreateFileTypeNone, NULL , 0 , dev); if (!NT_SUCCESS (*status)) return file_h; *information = io_status.Information; *status = ObReferenceObjectByHandle (file_h, 0 , *IoFileObjectType, KernelMode, file, NULL ); if (!NT_SUCCESS (*status)) { ASSERT (FALSE); ZwClose (file_h); }; return file_h; };
打开过的文件可以直接用ZwClose
简单地关闭,因为IRP_MJ_CLOSE中处理较少,重入也问题不大。
文件打开预处理 所谓的预处理就是当检测到机密进程试图打开一个文件时所需要进行的处理,这个过程决定该文件打开后是否要加入文件加密表中。这个函数将返回SF_IRP_PASS(直接下发)、SF_IRP_GO_ON(等待完成)、SF_IRP_COMPLETED(直接结束)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 ULONG cfIrpCreatePre (PIRP irp,PIO_STACK_LOCATION irpsp,PFILE_OBJECT file,PDEVICE_OBJECT next_dev) { UNICODE_STRING path = { 0 }; ULONG length = cfFileFullPathPreCreate (file,&path); NTSTATUS status; ULONG ret = SF_IRP_PASS; PFILE_OBJECT my_file = NULL ; HANDLE file_h; ULONG information = 0 ; LARGE_INTEGER file_size,offset = { 0 }; BOOLEAN dir,sec_file; ULONG desired_access = irpsp->Parameters.Create.SecurityContext->DesiredAccess; WCHAR header_flags[4 ] = {L'C' ,L'F' ,L'H' ,L'D' }; WCHAR header_buf[4 ] = { 0 }; ULONG disp; if (length == 0 ) return SF_IRP_PASS; if (irpsp->Parameters.Create.Options & FILE_DIRECTORY_FILE) return SF_IRP_PASS; do { path.Buffer = ExAllocatePoolWithTag (NonPagedPool,length+4 ,CF_MEM_TAG); path.Length = 0 ; path.MaximumLength = (USHORT)length + 4 ; if (path.Buffer == NULL ) { status = STATUS_INSUFFICIENT_RESOURCES; ret = SF_IRP_COMPLETED; break ; }; length = cfFileFullPathPreCreate (file,&path); file_h = cfCreateFileAccordingIrp (next_dev,&path,irpsp,&status,&my_file,&information); if (!NT_SUCCESS (status)) { ret = SF_IRP_COMPLETED; break ; }; cfListLock (); sec_file = cfIsFileCrypting (my_file); cfListUnlock (); if (sec_file) { ret = SF_IRP_PASS; break ; }; status = cfFileGetStandInfo (next_dev,my_file,NULL ,&file_size,&dir); if (!NT_SUCCESS (status)) { ret = SF_IRP_COMPLETED; break ; }; if (dir) { ret = SF_IRP_PASS; break ; }; if (file_size.QuadPart == 0 && (desired_access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) { cfWriteAHeader (my_file, next_dev); ret = SF_IRP_GO_ON; break ; }; if (file_size.QuadPart < CF_FILE_HEADER_SIZE) { ret = SF_IRP_PASS; break ; }; length = 8 ; status = cfFileReadWrite (next_dev,my_file,&offset,&length,header_buf,TRUE); if (status != STATUS_SUCCESS) { ASSERT (FALSE); ret = SF_IRP_PASS; break ; }; if (RtlCompareMemory (header_flags, header_buf, 8 ) == 8 ) { ret = SF_IRP_GO_ON; break ; }; ret = SF_IRP_PASS; } while (0 ); if (path.Buffer != NULL ) ExFreePool (path.Buffer); if (file_h != NULL ) ZwClose (file_h); if (ret == SF_IRP_GO_ON) cfFileCacheClear (my_file); if (my_file != NULL ) ObDereferenceObject (my_file); if (ret == SF_IRP_COMPLETED) { irp->IoStatus.Status = status; irp->IoStatus.Information = information; IoCompleteRequest (irp, IO_NO_INCREMENT); }; disp = FILE_OPEN; irpsp->Parameters.Create.Options &= 0x00ffffff ; irpsp->Parameters.Create.Options |= (disp << 24 ); return ret; };
读写加解密 如果使用分组加密算法的话,文件结尾部分不足一个块的不好处理,要么放弃最后这几个字节的加密,要么补0凑齐并记录文件真实长度。但这里实例就异或加密吧。
读取时解密 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 VOID cfIrpReadPost (PIRP irp, PIO_STACK_LOCATION irpsp) { PUCHAR buffer; ULONG i, length = irp->IoStatus.Information; ASSERT (irp->MdlAddress != NULL || irp->UserBuffer != NULL ); if (irp->MdlAddress != NULL ) buffer = MmGetSystemAddressForMdlSafe (irp->MdlAddress, NormalPagePriority); else buffer = irp->UserBuffer; for (i = 0 ; i < length; ++i) buffer[i] ^= 0X77 ; KdPrint (("cfIrpReadPost: flags = %x length = %x content = %c%c%c%c%c\r\n" , irp->Flags, length, buffer[0 ], buffer[1 ], buffer[2 ], buffer[3 ], buffer[4 ])); return ; };
分配与释放MDL 写请求中缓冲区原则上不能修改。系统进行写操作,分配空间,填入数据,再写入硬盘,没有预料到写入后数据可能被修改。所以系统可能重用这些空间,再次写入文件某些区域,造成重复加密现象。处理方法为:自己分配一个缓冲区暂时指派给IRP并保存IRP旧缓冲区,新缓冲区随意修改,请求完成后恢复IRP旧缓冲区。
分页请求常使用DirectIO方式,用MDL指针传递数据。如果请求用了MDL,对它的替换也必须用MDL。分配一个带空间的MDL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PMDL cfMdlMemoryAlloc (ULONG length) { PVOID buf = ExAllocatePoolWithTag (NonPagedPool, length, CF_MEM_TAG); PMDL mdl; if (buf == NULL ) return NULL ; mdl = IoAllocateMdl (buf, length, FALSE, FALSE, NULL ); if (mdl == NULL ) { ExFreePool (buf); return NULL ; }; MmBuildMdlForNonPagedPool (mdl); mdl->Next = NULL ; return mdl; };
释放MDL:
1 2 3 4 5 6 7 VOID cfMdlMemoryFree (PMDL mdl) { PVOID buffer = MmGetSystemAddressForMdlSafe (mdl, NormalPagePriority); IoFreeMdl (mdl); ExFreePool (buffer); return ; };
写请求加密 保留旧缓冲区指针时不知道保存哪个,定义一个上下文结构,这俩都保存:
1 2 3 4 5 typedef struct CF_WRITE_CONTEXT_ { PMDL mdl_address; PVOID user_buffer; } CF_WRITE_CONTEXT,*PCF_WRITE_CONTEXT;
写请求完成前的处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 BOOLEAN cfIrpWritePre (PIRP irp, PIO_STACK_LOCATION irpsp, PVOID* context) { PLARGE_INTEGER offset; ULONG i, length = irpsp->Parameters.Write.Length; PUCHAR buffer, new_buffer; PMDL new_mdl = NULL ; PCF_WRITE_CONTEXT my_context = (PCF_WRITE_CONTEXT)ExAllocatePoolWithTag (NonPagedPool, sizeof (CF_WRITE_CONTEXT), CF_MEM_TAG); if (my_context == NULL ) { irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; irp->IoStatus.Information = 0 ; return FALSE; }; ASSERT (irp->MdlAddress != NULL || irp->UserBuffer != NULL ); if (irp->MdlAddress != NULL ) { buffer = MmGetSystemAddressForMdlSafe (irp->MdlAddress, NormalPagePriority); new_mdl = cfMdlMemoryAlloc (length); if (new_mdl == NULL ) new_buffer = NULL ; else new_buffer = MmGetSystemAddressForMdlSafe (new_mdl, NormalPagePriority); } else { buffer = irp->UserBuffer; new_buffer = ExAllocatePoolWithTag (NonPagedPool, length, CF_MEM_TAG); }; if (new_buffer == NULL ) { irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; irp->IoStatus.Information = 0 ; ExFreePool (my_context); return FALSE; }; RtlCopyMemory (new_buffer, buffer, length); my_context->mdl_address = irp->MdlAddress; my_context->user_buffer = irp->UserBuffer; *context = (PVOID)my_context; if (new_mdl == NULL ) irp->UserBuffer = new_buffer; else irp->MdlAddress = new_mdl; offset = &irpsp->Parameters.Write.ByteOffset; KdPrint (("cfIrpWritePre: fileobj = %x flags = %x offset = %8x length = %x content = %c%c%c%c%c\r\n" , irpsp->FileObject, irp->Flags, offset->LowPart, length, buffer[0 ], buffer[1 ], buffer[2 ], buffer[3 ], buffer[4 ])); for (i = 0 ; i < length; ++i) new_buffer[i] ^= 0x77 ; if (offset->LowPart == FILE_USE_FILE_POINTER_POSITION && offset->HighPart == -1 ) ASSERT (FALSE); offset->QuadPart += CF_FILE_HEADER_SIZE; return TRUE; };
组装 初始化 本类驱动不需要动态卸载,所以OnSfilterDriverUnload
中啥也没写,资源没释放导致内存泄露也没啥问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 NTSTATUS OnSfilterDriverEntry (IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath, OUT PUNICODE_STRING userNameString, OUT PUNICODE_STRING syblnkString, OUT PULONG extensionSize) { UNICODE_STRING user_name, syb_name; NTSTATUS status = STATUS_SUCCESS; #if DBG #endif cfListInit (); RtlInitUnicodeString (&user_name, TEXT ("crypt_file_cdo" )); RtlInitUnicodeString (&syb_name, TEXT ("crypt_file_cdo_syb" )); RtlCopyUnicodeString (userNameString, &user_name); RtlCopyUnicodeString (syblnkString, &syb_name); sfilterSetCdoAccessForAll (); cfCurProcNameInit (); return STATUS_SUCCESS; }; VOID OnSfilterDriverUnload (VOID) { return ; };
IRP预处理 之前sfilter讲过所有文件操作IRP在发送下去前都会经过OnSfilterIrpPre
,处理的返回值有三种上面讲过了:SF_IRP_PASS、SF_IRP_GO_ON、SF_IRP_COMPLETED,这仨都在sfilter.h中定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 SF_RET OnSfilterIrpPre (IN PDEVICE_OBJECT dev, IN PDEVICE_OBJECT next_dev, IN PVOID extension, IN PIRP irp, OUT PNTSTATUS status, PVOID* context) { PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation (irp); PFILE_OBJECT file = irpsp->FileObject; BOOLEAN proc_sec = cfIsCurProcSec (); BOOLEAN file_sec; if (file == NULL ) return SF_IRP_PASS; if (irpsp->MajorFunction != IRP_MJ_CREATE && irpsp->MajorFunction != IRP_MJ_CLOSE && irpsp->MajorFunction != IRP_MJ_READ && irpsp->MajorFunction != IRP_MJ_WRITE && irpsp->MajorFunction != IRP_MJ_CLOSE && irpsp->MajorFunction != IRP_MJ_CLEANUP && irpsp->MajorFunction != IRP_MJ_SET_INFORMATION && irpsp->MajorFunction != IRP_MJ_DIRECTORY_CONTROL && irpsp->MajorFunction != IRP_MJ_QUERY_INFORMATION) return SF_IRP_PASS; if (!cfListInited ()) return SF_IRP_PASS; if (irpsp->MajorFunction == IRP_MJ_CREATE) { if (proc_sec) return cfIrpCreatePre (irp, irpsp, file, next_dev); else return SF_IRP_GO_ON; }; cfListLock (); file_sec = cfIsFileCrypting (file); cfListUnlock (); if (!file_sec) return SF_IRP_PASS; if (irpsp->MajorFunction == IRP_MJ_CLOSE) return SF_IRP_GO_ON; if (irpsp->MajorFunction == IRP_MJ_SET_INFORMATION && (irpsp->Parameters.SetFile.FileInformationClass == FileAllocationInformation || irpsp->Parameters.SetFile.FileInformationClass == FileEndOfFileInformation || irpsp->Parameters.SetFile.FileInformationClass == FileValidDataLengthInformation || irpsp->Parameters.SetFile.FileInformationClass == FileStandardInformation || irpsp->Parameters.SetFile.FileInformationClass == FileAllInformation || irpsp->Parameters.SetFile.FileInformationClass == FilePositionInformation)) { cfIrpSetInforPre (irp, irpsp); return SF_IRP_PASS; }; if (irpsp->MajorFunction == IRP_MJ_QUERY_INFORMATION) { if (irpsp->Parameters.QueryFile.FileInformationClass == FileAllInformation || irpsp->Parameters.QueryFile.FileInformationClass == FileAllocationInformation || irpsp->Parameters.QueryFile.FileInformationClass == FileEndOfFileInformation || irpsp->Parameters.QueryFile.FileInformationClass == FileStandardInformation || irpsp->Parameters.QueryFile.FileInformationClass == FilePositionInformation || irpsp->Parameters.QueryFile.FileInformationClass == FileValidDataLengthInformation) return SF_IRP_GO_ON; else return SF_IRP_PASS; }; if (irpsp->MajorFunction == IRP_MJ_READ && (irp->Flags & (IRP_PAGING_IO | IRP_SYNCHRONOUS_PAGING_IO | IRP_NOCACHE))) { cfIrpReadPre (irp, irpsp); return SF_IRP_GO_ON; }; if (irpsp->MajorFunction == IRP_MJ_WRITE && (irp->Flags & (IRP_PAGING_IO | IRP_SYNCHRONOUS_PAGING_IO | IRP_NOCACHE))) { if (cfIrpWritePre (irp, irpsp, context)) return SF_IRP_GO_ON; else { IoCompleteRequest (irp, IO_NO_INCREMENT); return SF_IRP_COMPLETED; }; }; return SF_IRP_PASS; };
IRP后处理 只有返回SF_IRP_GO_ON时才调用OnSfilterIrpPost
继续处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 VOID OnSfilterIrpPost (IN PDEVICE_OBJECT dev, IN PDEVICE_OBJECT next_dev, IN PVOID extension, IN PIRP irp, IN NTSTATUS status, PVOID context) { PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation (irp); BOOLEAN crypting, sec_proc, need_crypt, need_write_header; PFILE_OBJECT file = irpsp->FileObject; ULONG desired_access; BOOLEAN proc_sec = cfIsCurProcSec (); sec_proc = cfIsCurProcSec (); if (!NT_SUCCESS (status) && !(irpsp->MajorFunction == IRP_MJ_QUERY_INFORMATION && irpsp->Parameters.QueryFile.FileInformationClass == FileAllInformation && irp->IoStatus.Information > 0 ) && irpsp->MajorFunction != IRP_MJ_WRITE) { if (irpsp->MajorFunction == IRP_MJ_READ) KdPrint (("OnSfilterIrpPost: IRP_MJ_READ failed. status = %x information = %x\r\n" , status, irp->IoStatus.Information)); else if (irpsp->MajorFunction == IRP_MJ_WRITE) KdPrint (("OnSfilterIrpPost: IRP_MJ_WRITE failed. status = %x information = %x\r\n" , status, irp->IoStatus.Information)); return ; }; cfListLock (); crypting = cfIsFileCrypting (file); cfListUnlock (); if (irpsp->MajorFunction == IRP_MJ_CREATE) { if (proc_sec) { ASSERT (crypting == FALSE); if (!cfFileCryptAppendLk (file)) { IoCancelFileOpen (next_dev, file); irp->IoStatus.Status = STATUS_ACCESS_DENIED; irp->IoStatus.Information = 0 ; KdPrint (("OnSfilterIrpPost: file %wZ failed to call cfFileCryptAppendLk!!!\r\n" , &file->FileName)); } else KdPrint (("OnSfilterIrpPost: file %wZ begin to crypting.\r\n" , &file->FileName)); } else if (crypting) { IoCancelFileOpen (next_dev, file); irp->IoStatus.Status = STATUS_ACCESS_DENIED; irp->IoStatus.Information = 0 ; }; } else if (irpsp->MajorFunction == IRP_MJ_CLOSE) { ASSERT (crypting); cfCryptFileCleanupComplete (file); } else if (irpsp->MajorFunction == IRP_MJ_QUERY_INFORMATION) { ASSERT (crypting); cfIrpQueryInforPost (irp, irpsp); } else if (irpsp->MajorFunction == IRP_MJ_READ) { ASSERT (crypting); cfIrpReadPost (irp, irpsp); } else if (irpsp->MajorFunction == IRP_MJ_WRITE) { ASSERT (crypting); cfIrpWritePost (irp, irpsp, context); } else ASSERT (FALSE); return ; };