如何在Minifilter驱动的IRP中获取操作文件路径?

如何在IRP中获取操作的文件路径?

  • 文件路径
  • 普通办法
  • 如何获取FileMapping操作在IRP_MJ_WRITE等IRP中的文件路径呢?
    • IRP_MJ_CREATE获取文件路径并保存
    • IRP_MJ_WRITE中获取路径
  • Stream Context VS File Context 踩坑
  • context 相关的几个函数

文件路径

在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就会发现:
如何在Minifilter驱动的IRP中获取操作文件路径?_第1张图片
其中有一条,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:
如何在Minifilter驱动的IRP中获取操作文件路径?_第2张图片
里面演示了一个如果创建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,
封装一个函数,里面做两个事情

  1. 如果没有file context就创建,如果有了就返回
  2. 针对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。这里踩了一个坑。
考虑这种情况:

  1. 创建文件
  2. 重启驱动
  3. 在用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;
}

你可能感兴趣的:(Windows驱动开发)