Windows驱动开发入门-文件系统透明加密

这一节的内容就是讲讲知识点,没法做作品,本节的例子针对Windows XP和FAT32文件系统,且透明加密仅针对记事本软件进程。

区分进程

找到进程名字位置

进程名字保存在EPROCESS结构中,但每个Windows版本都不一样。又已知当前驱动进程名一定为“System”,所以在EPROCESS结构中搜索“System”字样即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 这个函数必须在DriverEntry中调用,否则cfCurProcName将不起作用。
static size_t s_cf_proc_name_offset = 0; //要寻找的偏移位置
VOID cfCurProcNameInit(VOID){
ULONG i;
PEPROCESS curproc;
curproc = PsGetCurrentProcess();
// 搜索EPROCESS结构,在其中找到字符串
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;
// 获得当前进程PEB,然后移动一个偏移得到进程名所在位置。
curproc = PsGetCurrentProcess();
// 这个名字是ansi字符串,现在转化为unicode字符串。
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
// 判断当前进程是不是notepad.exe
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(&note_pad, TEXT("notepad.exe"));
if (RtlCompareUnicodeString(&note_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等),对于这种缓冲读写请求文件系统会调用CcCopyReadCcCopyWrite来完成。这俩函数从缓冲区读取数据,缓冲区没有时将转换为分页读写请求。

还有直接从硬盘读写数据但不缓冲的非缓冲读写请求,连同缓冲读写请求和分页读写请求这六个都可被文件过滤过滤到。下面是区分四种IRP,非缓冲读写请求并入分页读写请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 最后两种是read和write,这两种都要修改请求后再下传。同时,read要有完成处理。请注意:只处理直接读硬盘的请求。对缓冲文件请求不处理。
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;
// 到fcb中去拿锁。
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); // MmFlushForDelete
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); //CF_FILE_HEADER_SIZE是自定义的加密标识头长度 这里正好为一个页面4KB
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: {
// 注意FileAllInformation,是由以下结构组成。即使长度不够,依然可以返回前面的字节。我们需要注意的是返回的字节里是否包含了StandardInformation这个可能影响文件的大小的信息。
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
// 对这些set information给予修改,使之隐去前面的4k文件头。
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) //如果IRP不指明请求偏移 而是指定要求按当前偏移请求操作
// 事实证明记事本不会出现这样的情况。
ASSERT(FALSE);
// 偏移必须修改为增加4k。
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)
//KdPrint(("cfIsFileCrypting: file %wZ is crypting. fcb = %x \r\n",&file->FileName,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) {
// 要成功的加入,必须要符合一个条件。就是FCB->UncleanCount <= 1. 这样的话说明没有其他程序打开着这个文件。否则的话可能是一个普通进程打开着它。此时不能加密。返回拒绝打开。
cfListUnlock();
// 释放掉。
ExFreePool(node);
return FALSE;
};
// 否则的话,在这里插入到链表里。
InsertHeadList(&s_cf_list, (PLIST_ENTRY)node);
cfListUnlock();
//cfFileCacheClear(file);
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
// 当有文件被clean up的时候调用此函数。如果检查发现FileObject->FsContext在列表中
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
irp = IoAllocateIrp(dev->StackSize, FALSE);
if (irp == NULL)
return STATUS_INSUFFICIENT_RESOURCES;
// 填写irp的主体
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;
// 设置irpsp
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; //释放掉IRP后 只需从UserIosb中读取返回结果
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
// 自发送SetInformation请求.
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
irp = IoAllocateIrp(dev->StackSize, FALSE);
if (irp == NULL)
return STATUS_INSUFFICIENT_RESOURCES;
// 填写irp的主体
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;
// 设置irpsp
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.
irp = IoAllocateIrp(dev->StackSize, FALSE);
if (irp == NULL)
return STATUS_INSUFFICIENT_RESOURCES;
// 填写主体。
irp->AssociatedIrp.SystemBuffer = NULL;
// 在paging io的情况下,似乎必须要使用MDL才能正常进行。不能使用UserBuffer. 但是我并不肯定这一点。所以这里加一个断言。以便我可以跟踪错误。
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;
// 填写irpsp
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, //参数与ZwCreateFile相同
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
// 根据一个打开文件请求的IRP的特性,模拟他的意图,用IoCreateFileSpecifyDeviceObjectHint来打开文件。这个文件打开之后不进入加密链表,所以可以直接Read和Write,不会被加密。
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;
// 填写object attribute
InitializeObjectAttributes(&obj_attri, file_full_path, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);
// 获得IRP中的参数。
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;
// 调用IoCreateFileSpecifyDeviceObjectHint打开文件。
*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,便于外面使用。
*information = io_status.Information;
// 从句柄得到一个fileobject便于后面的操作。记得一定要解除引用。
*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分配缓冲区
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;
};
// 得到了my_file之后,首先判断这个文件是不是已经在加密的文件之中。如果在,直接返回passthru即可
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;
};
// 如果文件大小为0,且有写入或者追加数据的意图,就应该加密文件,因为这个文件是刚刚新建或者覆盖的。应该在这里写入文件头。这也是唯一需要写入文件头的地方。
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;
};
// 现在读取文件。比较来看是否需要加密,直接读个8字节就足够了。这个文件有大小,而且比CF_FILE_HEADER_SIZE长。此时读出前8个字节,判断是否要加密。
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) {
// 到这里认为是必须加密的。这种情况下,必须返回GO_ON.
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);
};
// 要注意:
// 1.文件的CREATE改为OPEN.
// 2.文件的OVERWRITE去掉。不管是不是要加密的文件,都必须这样做。否则的话,本来是试图生成文件的,结果发现文件已经存在了。本来试图覆盖文件的,再覆盖一次会去掉加密头。
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) {
// 得到缓冲区,然后解密之。解密很简单,就是xor 0x77.
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;
// 解密也很简单,xor 0x77
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
// 分配一个MDL,带有一个长度为length的缓冲区。
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
// 释放掉带有MDL的缓冲区。
VOID cfMdlMemoryFree(PMDL mdl) {
PVOID buffer = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority);
IoFreeMdl(mdl);
ExFreePool(buffer);
return;
};

写请求加密

保留旧缓冲区指针时不知道保存哪个,定义一个上下文结构,这俩都保存:

1
2
3
4
5
// 写请求上下文。因为写请求必须恢复原来的irp->MdlAddress或者irp->UserBuffer,所以才需要记录上下文。
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
// 写请求需要重新分配缓冲区,而且有可能失败。如果失败了就直接报错了。所以要有一个返回。TRUE表示成功,可以继续GO_ON。FALSE表示失败了,错误已经填好,直接完成即可
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;
// 给irp指定行的mdl,到完成之后再恢复回来。
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]));
// 加密也很简单,xor 0x77
for (i = 0; i < length; ++i)
new_buffer[i] ^= 0x77;
if (offset->LowPart == FILE_USE_FILE_POINTER_POSITION && offset->HighPart == -1)
// 记事本不会出现这样的情况。
ASSERT(FALSE);
// 偏移必须修改为增加4KB。
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
// _asm int 3
#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;
// 我仅仅过滤文件请求。 FileObject不存在的情况一般是对卷设备直接操作,一律passthru.
if (file == NULL)
return SF_IRP_PASS;
// 首要决定哪些请求是我们必须过滤的。多余的提前passthru掉。
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;
// 对于文件打开,用cfIrpCreatePre统一处理。
if (irpsp->MajorFunction == IRP_MJ_CREATE) {
if (proc_sec)
return cfIrpCreatePre(irp, irpsp, file, next_dev);
else
// 其他的情况,作为普通进程,不允许打开一个正在加密的文件。但是在这里无法判断这个文件是否正在加密,所以返回GO_ON来判断。
return SF_IRP_GO_ON;
};
//判断文件是否在加密表中
cfListLock();
file_sec = cfIsFileCrypting(file);
cfListUnlock();
// 如果不是加密的文件的话,就可以直接passthru了,没有别的事情了。
if (!file_sec)
return SF_IRP_PASS;
// 如果是close就可以删除节点了
if (irpsp->MajorFunction == IRP_MJ_CLOSE)
return SF_IRP_GO_ON;
// 操作上有偏移。以下三种请求必须特殊处理。进行GO_ON处理。其他的set information操作不需要处理。
// 1.SET FILE_ALLOCATION_INFORMATION
// 2.SET FILE_END_OF_FILE_INFORMATION
// 3.SET FILE_VALID_DATA_LENGTH_INFORMATION
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)) {
// 对这些set information给予修改,使之隐去前面的4k文件头。
cfIrpSetInforPre(irp, irpsp/*,next_dev,file*/);
return SF_IRP_PASS;
};
if (irpsp->MajorFunction == IRP_MJ_QUERY_INFORMATION) {
// 要对这些read information的结果给予修改。所以返回go on. 结束后会调用cfIrpQueryInforPost(irp,irpsp);
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
// KdPrint(("OnSfilterIrpPre: %x\r\n",irpsp->Parameters.QueryFile.FileInformationClass));
return SF_IRP_PASS;
};
// 暂时不处理。
//if(irpsp->MajorFunction == IRP_MJ_DIRECTORY_CONTROL) {
// // 要对这些read information的结果给予修改。所以返回go on. 结束后会调用cfIrpQueryInforPost(irp,irpsp);
// if(irpsp->Parameters.QueryDirectory.FileInformationClass == FileDirectoryInformation || irpsp->Parameters.QueryDirectory.FileInformationClass == FileFullDirectoryInformation || irpsp->Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation)
// return SF_IRP_GO_ON;
// else {
// KdPrint(("OnSfilterIrpPre: Query information: %x passthru.\r\n", irpsp->Parameters.QueryDirectory.FileInformationClass));
// return SF_IRP_PASS;
// };
//};
// 最后两种是read和write,这两种都要修改请求后再下传。同时,read要有完成处理。请注意:只处理直接读硬盘的请求。对缓冲文件请求不处理。
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();
// 如果是create,不需要恢复文件长度。如果是其他请求,在pre的时候就应该已经恢复了。
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) {
// clean up结束了。这里删除加密节点,删除缓冲。
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;
};