在minifilter中,主要处理的是各种IRP,做DLP也好,做加解密也好。文件路径总是绕不开的。比如在IRP_MJ_WRITE中,绝大多数情况都得知道当前这次操作的文件路径。
Minifilter框架有个函数:FltGetFileNameInformation
这个函数可以用来获取文件路径,比如下面是段常用的获取文件路径的代码
UNICODE_STRING ExtractFileName(_In_ PFLT_CALLBACK_DATA Data)
{
NTSTATUS status;
UNICODE_STRING ret = { 0 };
KIRQL irql = KeGetCurrentIrql();
if (irql > APC_LEVEL) {
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("OOOOOOOOOOOOOOPS, IRQL > APC_LEVEL, Can't call FltGetFileNameInformation. current irql: %d\n", irql));
return ret;
}
if (Data)
{
PFLT_FILE_NAME_INFORMATION pNameInfo = NULL;
status = FltGetFileNameInformation(Data,
FLT_FILE_NAME_NORMALIZED |
FLT_FILE_NAME_QUERY_DEFAULT,
&pNameInfo); // <= APC_LEVEL
if (NT_SUCCESS(status) && pNameInfo)
{
status = FltParseFileNameInformation(pNameInfo);
if (NT_SUCCESS(status)) // <= APC_LEVEL
{
Init(&ret, pNameInfo->Name.MaximumLength);
CopyUnicodeString(&ret, &(pNameInfo->Name));
}
else {
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("Calling FltParseFileNameInformation() failed, err: %d\n", status));
}
FltReleaseFileNameInformation(pNameInfo); // <= APC_LEVEL
}
}
return ret;
}
ok,那么这么就完了吗?对于普通WRITE请求,确实可以。比如WriteFile触发的IRP.
但是如何是FileMapping,你就会发现FltGetFileNameInformation会失败。
查一下msdn就会发现:
其中有一条,FileGetNameInformation cannot get file name information in the paging I/O path.
好,FileMapping就是paging IO, 所以失败。
既然直接获取不到,那么就曲线救国呗。
一般FileMapping的写法如下:
void TestIOFileMapping() {
HANDLE hFile;
hFile = CreateFile(L"testfilemapping.foo",
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return;
}
HANDLE hMapping = CreateFileMapping(hFile,
NULL,
PAGE_READWRITE,
0,
MAX_FILESIZE,
NULL);
if (hMapping == NULL)
{
CloseHandle(hFile);
return;
}
char* puchData = (char*)MapViewOfFile(hMapping,
FILE_MAP_WRITE,
0,
0,
0);
if (puchData == NULL)
{
CloseHandle(hMapping);
CloseHandle(hFile);
return;
}
for (int i = 0; i < 10; i++)
{
OutputDebugStringW(L"Try to write data with memset()");
memset(puchData + i * 1024, 0x41 + i, 1024);
}
UnmapViewOfFile(puchData);
// FlushFileBuffers(hFile);
CloseHandle(hMapping);
CloseHandle(hFile);
}
第一步总是调用CreateFile,那么Createfile产生的IRP_MJ_CREATE能获取到文件路径吗?
这个答案是肯定的,用FltGetFileNameInformation就可以。既然这里可以获取到,那么就差个关联了。
在IRP_MJ_WRITE中只要能读取IRP_MJ_CREATE获取到的路径就行了。
做法如下:
通常是在PostCreate中。使用FltGetFileNameInformation获取文件路径相关信息。然后保存到一个地方。
微软有个驱动的例子库,在github可以找到, https://github.com/Microsoft/Windows-driver-samples
里面有很多例子,其中有一个是ctx:
里面演示了一个如果创建context并在不同IRP中共享的办法。
context有很多种,如INSTANCE CONTEXT, FILE CONTEXT, STREAM CONTEXT等等,如下:
const FLT_CONTEXT_REGISTRATION ContextRegistration[] = {
{ FLT_INSTANCE_CONTEXT,
0,
CtxContextCleanup,
CTX_INSTANCE_CONTEXT_SIZE,
CTX_INSTANCE_CONTEXT_TAG },
{ FLT_FILE_CONTEXT,
0,
CtxContextCleanup,
CTX_FILE_CONTEXT_SIZE,
CTX_FILE_CONTEXT_TAG },
{ FLT_STREAM_CONTEXT,
0,
CtxContextCleanup,
CTX_STREAM_CONTEXT_SIZE,
CTX_STREAM_CONTEXT_TAG },
{ FLT_STREAMHANDLE_CONTEXT,
0,
CtxContextCleanup,
CTX_STREAMHANDLE_CONTEXT_SIZE,
CTX_STREAMHANDLE_CONTEXT_TAG },
{ FLT_CONTEXT_END }
};
具体如何实现,以及各种context的差异这里不讲,有兴趣可以直接查看那个例子或者翻看其他资料。
这个case我们可以考虑使用File Context,
封装一个函数,里面做两个事情
NTSTATUS SaveFileInfoToContext(_Inout_ PFLT_CALLBACK_DATA Data,
PUNICODE_STRING FileName) {
NTSTATUS status = STATUS_SUCCESS;
PCTX_FILE_CONTEXT fileContext = NULL;
BOOLEAN fileContextCreated;
FILE_INFORMATION_CLASS cls = 0;
status = CtxFindOrCreateFileContext(Data,
TRUE,
FileName,
&fileContext,
&fileContextCreated);
if (NT_SUCCESS(status) &&
Data->Iopb->MajorFunction == IRP_MJ_SET_INFORMATION &&
!fileContextCreated) {
cls = Data->Iopb->Parameters.SetFileInformation.FileInformationClass;
if (FileRenameInformation == cls ||
FileRenameInformationEx == cls) {
// update file name
CtxAcquireResourceExclusive(fileContext->Resource);
status = CtxUpdateNameInFileContext(FileName, fileContext);
CtxReleaseResource(fileContext->Resource);
}
}
if (fileContext != NULL) {
FltReleaseContext(fileContext);
}
return status;
}
这样,在PostCreate里面,先用FltGetFileNameInformation获取路径,然后调用上面的函数把路径存入File Context
因为PostCreate里已经把路径保存到File Context了,那么在IRP_MJ_WRITE中就可以获取了。
封装一个函数,先用FltGetFileNameInformation来获取,如何失败就尝试从File Context中获取。
函数参数中的FromContext和FromWhere不过是两个控制参数,与核心逻辑无关。
BOOLEAN CheckContextAndFetchInfo(_Inout_ PFLT_CALLBACK_DATA Data,
_Out_ PUNICODE_STRING FileName,
_Out_ PULONG pid,
_In_ BOOLEAN FromContext,
_Out_ FILE_NAME_SOURCE_TYPE* FromWhere) {
NTSTATUS status = STATUS_SUCCESS;
PCTX_FILE_CONTEXT fileContext = NULL;
BOOLEAN fileContextCreated;
KIRQL irql = KeGetCurrentIrql();
if (irql > DISPATCH_LEVEL) {
return FALSE;
}
if (Data && Data->Iopb)
{
// nothing to do
}
else
{
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("hzskxxdlpminimon!NeedSelfProtect, Data or Data->Iopb is NULL\n"));
return FALSE;
}
if (Data && pid != NULL)
{
*pid = FltGetRequestorProcessId(Data); // <= DISPATCH_LEVEL
}
if (FileName != NULL) {
*FileName = ExtractFileName(Data);
if (FromWhere && FileName->Length > 0) {
*FromWhere = FILE_NAME_SOURCE_API;
}
if (FileName->Length <= 0 && FromContext) {
status = CtxFindOrCreateFileContext(Data,
FALSE, // do not create if one does not exist
NULL,
&fileContext,
&fileContextCreated);
if (NT_SUCCESS(status)) {
Init(FileName, fileContext->FileName.MaximumLength);
CopyUnicodeString(FileName, &fileContext->FileName);
if (FromWhere) {
*FromWhere = FILE_NAME_SOURCE_CONTEXT;
}
if (fileContext != NULL) {
FltReleaseContext(fileContext);
}
}
}
if (FileName->Length <= 0) {
return FALSE;
}
}
return TRUE;
}
这样,针对FileMapping (paging I/O),我们也可以在IRP_MJ_WRITE中得到路径了。
其实我一开始用的是stream context。这里踩了一个坑。
考虑这种情况:
以下代码是在微软例子代码基础上稍微修改了一下:
NTSTATUS
CtxFindOrCreateFileContext(
_In_ PFLT_CALLBACK_DATA Cbd,
_In_ BOOLEAN CreateIfNotFound,
_When_(CreateIfNotFound != FALSE, _In_) _When_(CreateIfNotFound == FALSE, _In_opt_) PUNICODE_STRING FileName,
_Outptr_ PCTX_FILE_CONTEXT *FileContext,
_Out_opt_ PBOOLEAN ContextCreated
)
/*++
Routine Description:
This routine finds the file context for the target file.
Optionally, if the context does not exist this routing creates
a new one and attaches the context to the file.
Arguments:
Cbd - Supplies a pointer to the callbackData which
declares the requested operation.
CreateIfNotFound - Supplies if the file context must be created if missing
FileName - Supplies the file name
FileContext - Returns the file context
ContextCreated - Returns if a new context was created
Return Value:
Status
--*/
{
NTSTATUS status;
PCTX_FILE_CONTEXT fileContext;
PCTX_FILE_CONTEXT oldFileContext;
PAGED_CODE();
*FileContext = NULL;
if (ContextCreated != NULL) *ContextCreated = FALSE;
//
// First try to get the file context.
//
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("[hzskxxdlpminimon]: Trying to get file context (FileObject = %p, Instance = %p)\n",
Cbd->Iopb->TargetFileObject,
Cbd->Iopb->TargetInstance));
status = FltGetFileContext(Cbd->Iopb->TargetInstance,
Cbd->Iopb->TargetFileObject,
&fileContext);
//
// If the call failed because the context does not exist
// and the user wants to creat a new one, the create a
// new context
//
if (!NT_SUCCESS(status) &&
(status == STATUS_NOT_FOUND) &&
CreateIfNotFound) {
//
// Create a file context
//
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("[hzskxxdlpminimon]: Creating file context (FileObject = %p, Instance = %p)\n",
Cbd->Iopb->TargetFileObject,
Cbd->Iopb->TargetInstance));
status = CtxCreateFileContext(FileName, &fileContext);
if (!NT_SUCCESS(status)) {
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("[hzskxxdlpminimon]: Failed to create file context with status 0x%x. (FileObject = %p, Instance = %p)\n",
status,
Cbd->Iopb->TargetFileObject,
Cbd->Iopb->TargetInstance));
return status;
}
//
// Set the new context we just allocated on the file object
//
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("[hzskxxdlpminimon]: Setting file context %p (FileObject = %p, Instance = %p)\n",
fileContext,
Cbd->Iopb->TargetFileObject,
Cbd->Iopb->TargetInstance));
status = FltSetFileContext(Cbd->Iopb->TargetInstance,
Cbd->Iopb->TargetFileObject,
FLT_SET_CONTEXT_KEEP_IF_EXISTS,
fileContext,
&oldFileContext);
if (!NT_SUCCESS(status)) {
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("[hzskxxdlpminimon]: Failed to set file context with status 0x%x. (FileObject = %p, Instance = %p)\n",
status,
Cbd->Iopb->TargetFileObject,
Cbd->Iopb->TargetInstance));
//
// We release the context here because FltSetFileContext failed
//
// If FltSetFileContext succeeded then the context will be returned
// to the caller. The caller will use the context and then release it
// when he is done with the context.
//
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("[hzskxxdlpminimon]: Releasing file context %p (FileObject = %p, Instance = %p)\n",
fileContext,
Cbd->Iopb->TargetFileObject,
Cbd->Iopb->TargetInstance));
FltReleaseContext(fileContext);
if (status != STATUS_FLT_CONTEXT_ALREADY_DEFINED) {
//
// FltSetFileContext failed for a reason other than the context already
// existing on the file. So the object now does not have any context set
// on it. So we return failure to the caller.
//
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("[hzskxxdlpminimon]: Failed to set file context with status 0x%x != STATUS_FLT_CONTEXT_ALREADY_DEFINED. (FileObject = %p, Instance = %p)\n",
status,
Cbd->Iopb->TargetFileObject,
Cbd->Iopb->TargetInstance));
return status;
}
//
// Race condition. Someone has set a context after we queried it.
// Use the already set context instead
//
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("[hzskxxdlpminimon]: File context already defined. Retaining old file context %p (FileObject = %p, Instance = %p)\n",
oldFileContext,
Cbd->Iopb->TargetFileObject,
Cbd->Iopb->TargetInstance));
//
// Return the existing context. Note that the new context that we allocated has already been
// released above.
//
fileContext = oldFileContext;
status = STATUS_SUCCESS;
}
else {
if (ContextCreated != NULL) *ContextCreated = TRUE;
}
}
*FileContext = fileContext;
return status;
}
NTSTATUS
CtxCreateFileContext(
_In_ PUNICODE_STRING FileName,
_Outptr_ PCTX_FILE_CONTEXT *FileContext
)
/*++
Routine Description:
This routine creates a new file context
Arguments:
FileName - Supplies the file name
FileContext - Returns the file context
Return Value:
Status
--*/
{
NTSTATUS status;
PCTX_FILE_CONTEXT fileContext;
PAGED_CODE();
//
// Allocate a file context
//
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("[hzskxxdlpminimon]: Allocating file context \n"));
status = FltAllocateContext(gFilterHandle,
FLT_FILE_CONTEXT,
CTX_FILE_CONTEXT_SIZE,
PagedPool,
&fileContext);
if (!NT_SUCCESS(status)) {
PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
("[hzskxxdlpminimon]: Failed to allocate file context with status 0x%x \n",
status));
return status;
}
//
// Initialize the newly created context
//
//
// Allocate and copy off the file name
//
RtlZeroMemory(fileContext, CTX_FILE_CONTEXT_SIZE);
fileContext->FileName.MaximumLength = FileName->Length + sizeof(WCHAR);
status = CtxAllocateUnicodeString(&fileContext->FileName);
if (NT_SUCCESS(status)) {
RtlCopyUnicodeString(&fileContext->FileName, FileName);
}
fileContext->Resource = CtxAllocateResource();
if (fileContext->Resource == NULL) {
FltReleaseContext(fileContext);
return STATUS_INSUFFICIENT_RESOURCES;
}
ExInitializeResourceLite(fileContext->Resource);
*FileContext = fileContext;
return STATUS_SUCCESS;
}
NTSTATUS
CtxUpdateNameInFileContext(
_In_ PUNICODE_STRING DirectoryName,
_Inout_ PCTX_FILE_CONTEXT FileContext
)
/*++
Routine Description:
This routine updates the name of the target in the supplied file context
Arguments:
DirectoryName - Supplies the directory name
FileContext - Returns the updated name in the file context
Return Value:
Status
Note:
The caller must synchronize access to the context. This routine does no
synchronization
--*/
{
NTSTATUS status;
PAGED_CODE();
//
// Free any existing name
//
if (FileContext->FileName.Buffer != NULL) {
CtxFreeUnicodeString(&FileContext->FileName);
}
//
// Allocate and copy off the directory name
//
FileContext->FileName.MaximumLength = DirectoryName->Length + sizeof(WCHAR);
status = CtxAllocateUnicodeString(&FileContext->FileName);
if (NT_SUCCESS(status)) {
RtlCopyUnicodeString(&FileContext->FileName, DirectoryName);
}
return status;
}