如何在Minifilter驱动的IRP中获取操作文件路径?
Posted zj510
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在Minifilter驱动的IRP中获取操作文件路径?相关的知识,希望对你有一定的参考价值。
如何在IRP中获取操作的文件路径?
文件路径
在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操作在IRP_MJ_WRITE等IRP中的文件路径呢?
既然直接获取不到,那么就曲线救国呗。
一般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获取到的路径就行了。
做法如下:
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,
封装一个函数,里面做两个事情
- 如果没有file context就创建,如果有了就返回
- 针对rename情况,需要更新里面的路径
如:
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
IRP_MJ_WRITE中获取路径
因为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 VS File Context 踩坑
其实我一开始用的是stream context。这里踩了一个坑。
考虑这种情况:
- 创建文件
- 重启驱动
- 在用FileMapping写文件
这个使用#3里面,用Stream context就获取不到之前保存的context。然后就用了File Context。
用FileContext需要注意,一个文件只有一个context,那么rename的时候,需要去手工更新context里面存放的路径
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;
以上是关于如何在Minifilter驱动的IRP中获取操作文件路径?的主要内容,如果未能解决你的问题,请参考以下文章
minifilter/sfilter较为精确的判断是打开还是新建操作