65.9K
CodeProject 正在变化。 阅读更多。
Home

文件系统通知工具包 (FSNK) 简介

2014年7月9日

CPOL

15分钟阅读

viewsIcon

17535

文件系统通知工具包 (FSNK) 产品简介,用于实时监控文件系统活动。

引言

文件系统通知工具包 (FSNK) 是一款用于实时监控文件系统活动的产品。

如果您想了解文件系统发生了什么,您无需花费大量时间编写驱动程序。只需下载并试用 FSNK

FSNK 使用一个内核模式驱动程序,它是一个文件系统过滤器。当过滤器收到文件系统请求通知时,它会将相关信息通过应用程序连接到 FSNK 引擎时指定的**回调函数**发送给应用程序。

该引擎能够查看所有基本的文件操作,例如在文件中写入和读取数据、修改文件或文件夹属性、创建文件或文件夹、删除或移动文件等。当您收到通知时,您可以获取有关该事件的更详细信息。

除了标准文件操作外,该工具包还可以跟踪卷的挂载和卸载操作、安全描述符修改、对象 ID 更改、缓存刷新操作、硬链接和符号链接的创建以及其他文件系统事件。

FSNK 内容

FSNK 安装包中的文件说明

  • Fsnk.sys 是一个内核模式驱动程序,它会截获文件系统操作。它可以动态加载和卸载,无需重启系统。
  • Fsnklib.dll 是一个动态链接库,它允许应用程序与驱动程序进行交互并管理其状态。它为您的应用程序提供文件系统活动信息。
  • Fsnkd.exe 是一个控制台实用程序,演示了产品的功能。
  • Install.exe 是一个简单的应用程序,用于安装和卸载产品。
  • Fsnklib.lib 是一个静态库,用于在编译时链接到动态库。
  • *.h 文件是头文件,包含 C/C++ 项目所需的 API 函数和数据类型的定义。

要求

目前,该产品支持从 XP SP2 到 Server 2012 R2 的所有 Windows 版本,包括 x86-64 架构的 64 位系统(也可根据要求提供基于 Itanium 的系统版本)。由于 FSNK 驱动程序 fsnk.sys 需要 Filter Manager 组件,而该组件不能单独安装,因此需要 SP2 更新。服务器系统需要 Windows Server 2003 SP1 或更高版本。

特点

文件系统通知工具包为您提供了一个绝佳的机会,可以了解给定时间点文件系统中发生的一切。它提供了所有文件操作的通知:创建文件、删除、读取、写入、读取和修改文件元数据、安全描述符、属性等。

FSNK 支持熟悉的 DOS 风格路径 C:\Windows 和操作系统原生的 NT 风格路径 \Device\HarddiskVolume1\Windows。

使用 FSNK,您将收到有关每次文件操作的详细信息。这些信息包括操作码、操作状态、唯一线程、进程和会话 ID、登录会话 ID、用户安全标识符 (SID)、文件对象卷 ID、完整规范化的 NT 风格文件系统对象名称以及字符偏移量到相对文件路径。

FSNK 收集有关文件系统性能的统计信息,包括读取和写入的字节数、读取和写入操作的次数等。它有一个内置的通知过滤器,用于只接收您感兴趣的事件的通知。

对于每个文件系统事件,您可以调用 FsnkGetName(以及适用的 FsnkGetTargetName)函数来获取与该操作相关的文件系统对象的 DOS 风格名称。您还可以调用 FsnkGetAccountName 例程来获取请求该操作的用户的名称。您还可以调用 FsnkGetProcessImagePath 例程来获取启动该操作的进程的可执行文件的路径。

它提供了一套 API 函数,用于低于 Win32 子系统和 Native 层的文件 I/O。这使您能够绕过这些级别的潜在拦截器。FsnkIoCreate 函数可以使用 NT 风格的文件名打开和创建文件对象。该组中的其他函数使您能够使用接收到的句柄处理文件。

FSNK 对操作系统支持的任何文件系统都能很好地工作,包括磁盘文件系统、CD 甚至网络文件系统。

请注意,FSNK 的当前版本无法阻止操作或更改其参数。目前,它只能提供已发生事件的通知。

API

本节进一步描述了产品基本功能的 API 函数。

首先,您需要通过 FsnkInitialize API 函数初始化引擎;在此之前,您无法调用 FSNK 的其他 API 函数。您应该注意,一次只有一个进程可以连接到引擎。这意味着如果您的一个应用程序连接到引擎以获取通知,另一个应用程序将无法做到。要取消初始化引擎并释放库使用的所有资源,您应该调用 FsnkUninitialize 函数。

安装

FsnkInstall 函数安装一个内核模式过滤器驱动程序 (fsnk.sys)。输入参数包括将在系统服务列表中显示的*服务名称*、驱动程序文件的*完整路径*、负责在系统启动时自动启动驱动程序的*标志*以及*Altitude ID*。此 API 函数成功安装引擎时返回“true”,否则返回“false”。

// Installing the engine.
if (! FsnkInstall (
    L"My FSNK engine",
    L"C:\\Program Files\\My product\\fsnk.sys",
    TRUE,        // For automatic startup.
    L"265000",    // Specify your own altitude here.
    0))        // No flags defined at this moment.
{
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
}

调用该函数的线程必须具有管理员权限才能成功安装引擎。如果您希望在您的产品中大规模分发 FSNK,请不要忘记从 Microsoft 获取 Altitude ID。Altitude ID 应从 FSFilter Activity Monitor 组请求,因为监控过滤器应该在此级别工作。

FsnkUninstall API 函数卸载引擎,但不会将其从内存中卸载。您应该提前通过 FsnkUnload 例程停止引擎,或者重启系统以完全删除引擎。引擎可以根据需要启动和停止多次,而无需重启系统。操作成功时返回“true”,否则返回“false”。

// Remove the engine from the system.
if (! FsnkUninstall ())
{
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
}

与安装类似,此函数必须从以管理员用户上下文运行的线程调用。

管理过滤器状态

FsnkLoad API 函数启动引擎。启动无需任何参数。函数成功启动时返回“true”,否则返回“false”。之后,应用程序可以通过 FsnkInitialize 连接到引擎。要将引擎从内存中卸载,请调用 FsnkUnload 函数。

// Load the engine, this will actually load the FSNK filter driver.
if (! FsnkLoad ())
{
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
}

// Activate filter to start receiving notifications about file system events.
if (! FsnkActivate ())
{
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
    // Perform any cleanup needed.
    FsnkUnload ();
    ...
}

FsnkActivate API 函数打开从过滤器到应用程序的文件系统事件通知的发送,通过在调用 FsnkInitialize 时指定的*回调例程*。之后,就可以接收来自过滤器的事件了。请注意,在激活过滤器之前,您的回调函数应准备好接收通知。要暂时禁用过滤器,请调用 FsnkDeactivate 函数。

// Stop receiving notifications.
if (! FsnkDeactivate ())
{
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
}

// Unload FSNK filter from memory.
if (! FsnkUnload ())
{
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
}

管理通知过滤器规则

可以通过 FsnkAddRule 和 FsnkDeleteRule API 函数设置和删除您想接收通知的事件的过滤器。这些函数将规则插入和删除用于过滤文件系统事件的规则列表中。过滤器使用函数输入参数中描述的各种参数进行设置。最初,没有设置任何过滤器,应用程序将收到所有支持的文件操作的通知。

每个规则至少必须包含一个非零的操作位掩码,而文件名模式和进程 ID 是可选的(要指定“任何”进程 ID,请在相应参数中指定 FsnkPidInvalid 值)。如果指定的对象名称模式已在规则列表中,则指定的位掩码和进程 ID 将替换该规则,不会创建新规则。

uFlags 参数设置各种标志来控制函数行为。例如,如果您希望规则保存在注册表中的规则数据库中,可以指定 FsnkRuleFlagSave 标志;此数据库中的规则在引擎启动时会自动加载。默认情况下,规则仅存储在内存中,在系统或引擎重启后不会保存。

// Include all executable files.
if (! FsnkAddRule (
    L"*.exe",
    0,            // 0 = all operations.
    FsnkPidInvalid,        // Means all processes.
    FsnkRuleFlagSave))    // Store rule permanently.
{ 
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
}
...

// Exclude temporary files.
if (! FsnkAddRule (
    L"*.tmp",
    FsnkOpRead | FsnkOpWrite,
    FsnkPidInvalid,
    FsnkRuleFlagExclude))    // Should not match to include.
{
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
}

FsnkDeleteRule 函数从数据库中删除规则。输入时,该函数接收文件名模式和用于从持久数据库中删除规则的参数。FsnkClearRules 函数从规则列表或持久数据库中删除所有现有规则。

// Delete rule for temporary files.
if (! FsnkDeleteRule (
    L"*.tmp",
    FsnkRuleDeleteActive))
{
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
}

// Delete all permanent rules.
if (! FsnkClearRules (
    FsnkRuleClearSaved))
{
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
}

文件对象名称

FsnkGetName 函数返回用于执行操作的文件系统对象的完整 DOS 风格名称,例如创建的文件或文件夹的名称,或者删除的文件夹的名称。输入参数包括存储操作数据的结构地址,或者提供具有网络文件系统上对象名称的网络路径的标志。输出时,如果操作成功,该函数会用对象名称信息填充 FSNK_NAME_INFORMATION。函数成功时返回非零值,否则返回零。

FsnkGetTargetName 函数返回对象的完整名称,但与 FsnkGetName 不同,它返回目标对象的名称。例如,移动操作的目标文件名,或者正在创建的硬链接或符号链接的名称。

void
__stdcall
MyFsCallbackRoutine (
    IN PVOID pContext,        // Application-defined value.
    IN PFSNK_OP_DATA pData,        // General information of the request.
    IN PFSNK_OP_PARAMS pParams)    // Operation-specific information.
{
    ULONG uError = 0;
    FSNK_NAME_INFORMATION NameInfo = {0};
    FSNK_NAME_INFORMATION TargetNameInfo = {0};
    ...
    
    // Preallocate string buffers. You should do that before you call FsnkInitialize() routine so these strings can be passed here as context for performance purposes.
    FsnkCreateString (
        &NameInfo.Name,        // String structure to initialise.
        65554,            // Maximum possible size of a string.
        NULL, 0);        // Let the library to allocate new buffers.
    ...
    
    // Query DOS-style name for the indicated file system object.
    if (! FsnkGetName (
        pData,
        FsnkGetNameNetworkInfo, // Ask for the network path as well.
        &NameInfo))
    {
        // Error handling goes here.
        uError = FsnkGetErrorCode ();
        ...
    }
    
    // Check if a name string is present in the returned name information.
    if (NameInformation.IsName)
    {
        // Deal with name as required.
        MyLogWriteFileName (&NameInfo.Name);
        ...
    }
    
    // Check if a share string is present in the returned name information.
    if (NameInformation.IsShare)
    {
        // The specified object is in the network file system and we have the name of the share.
        MyLogWriteFileName (&NameInfo.Share);
        ...
    }
    
    // Query DOS-style name for the target object.
    // Only applicable to some file operations.
    if (! FsnkGetTargetName (
        pData,
        pParams,        // Required as source.
        FsnkGetNameNetworkInfo, // Ask for network path as well.
        &TargetNameInfo))
    {
        // Error handling goes here.
        uError = FsnkGetErrorCode ();
        ...
    }
    ...
    
    // Perform cleanup tasks.
    FsnkFreeName (&NameInfo);
    FsnkFreeName (&TargetNameInfo);
    ...
}

管理字符串

库中的字符串管理通过 FSNK_STRING 结构进行。FsnkCreateString 初始化字符串结构,FsnkDeleteString 函数删除字符串结构,其中删除意味着必要时清理使用的资源。FsnkCreateString 函数既可以用预分配的缓冲区初始化结构,也可以自己创建新缓冲区。

FsnkCreateString 函数中的字符串缓冲区大小在 uMaximumSize 输入参数中指定,以字节为单位。此参数是必需的,不能为 NULL。第一个参数指示正在初始化的字符串结构。pwInitialData 参数是预先准备好的缓冲区;如果它不是 NULL,则字符串将初始化为使用此缓冲区,否则函数将分配一个新缓冲区。最后一个参数指定缓冲区的大小(以字节为单位)。

FSNK_STRING FileName = {0};
PWSTR pwFileName = L"C:\\Windows\\notepad.exe";
USHORT uBufferSize = (lstrlen (pwFileName) + 1) * sizeof (WCHAR);

// Initialise for the existing string buffer.
if (! FsnkCreateString (
    &FileName,
    uBufferSize,    // Total size of the buffer.
    pwFileName,    // No buffers will be allocated.
    0,        // Actual size of the string, to be calculated.
{
    // Error handling goes here. You can't call FsnkGetErrorCode because string routines don't use error codes (for performance).
    ...
}

用于删除字符串的 FsnkDeleteString 函数接收需要清理的结构地址;而字符串缓冲区仅在由库分配时才会被释放。例如,通过 FsnkInitString 初始化后,字符串中的缓冲区将不会被释放,应用程序负责释放。

FSNK_STRING String = {0};
...

// Free resources used for the string.
FsnkDeleteString (
    &String);

用户帐户名

FsnkGetAccountName API 函数允许您使用安全标识符来获取关联用户帐户的名称。这可以是 FSNK_OP_DATA 结构中的 SID 或任何其他有效的 SID。输出时,您将获得一个包含帐户当前名称的 Unicode 字符串的地址。在完成使用后,此字符串应通过 FsnkFreeString 删除。

由于用户帐户名称随时可能更改,因此您不应依赖它来识别帐户。请使用 SID 进行识别,仅在必要时请求帐户名称。例如,如果您需要向用户显示或写入事件日志。

void
__stdcall
MyFsCallbackRoutine (
    IN PVOID pContext,        // Application-defined value.
    IN PFSNK_OP_DATA pData,        // General information of the request.
    IN PFSNK_OP_PARAMS pParams)    // Operation-specific information.
{
    ULONG uError = 0;
    SID_NAME_USE uNameUse = 0;
    PWSTR pwAccountName = NULL;
    ...
    
    // Query user account name.
    if (! FsnkGetAccountName (
        pData -> Sid,        // User account SID.
        &pwAccountName,        // Name string will be returned here.
        &uNameUse))        // Account name usage type.
    {
        // Error handling goes here.
        uError = FsnkGetErrorCode ();
        ...
    }
    
    // Use account name string.
    MyLogWriteString (pwAccountName);
    ...
    
    // Perform cleanup tasks.
    FsnkFreeString (&pwAccountName);
}

获取进程映像文件名

FsnkGetProcessImagePath 函数返回进程的可执行映像文件的完整 DOS 风格路径。输入时,您应指定从 FSNK_OP_DATA 结构中的 ProcessId 字段获得的进程 ID。输出时,您将获得一个包含进程映像文件路径的 Unicode 字符串的地址。返回的字符串最终必须通过 FsnkFreeString 释放。有关更多信息,请参阅产品文档。

void
__stdcall
MyFsCallbackRoutine (
    IN PVOID pContext,        // Application-defined value.
    IN PFSNK_OP_DATA pData,        // General information of the request.
    IN PFSNK_OP_PARAMS pParams)    // Operation-specific information.
{
    ULONG uError = 0;
    PWSTR pwImageFilePath = NULL;
    ...
    
    // Query image file path for the requestor process.
    if (! FsnkGetProcessImagePath (
        pData -> ProcessId    // Unique requestor ID.
        &pwImageFilePath))    // Image path string will be returned here.
    {
        // Error handling goes here.
        uError = FsnkGetErrorCode ();
        ...
    }
    
    // Use process image path string.
    MyLogWriteString (pwImageFilePath);
    ...
    
    // Perform cleanup tasks.
    FsnkFreeString (&pwImageFilePath);
}

文件系统性能统计

FsnkGetStatistics 函数提供文件系统性能的统计数据,例如读取、写入和打开/创建文件的总次数(包括成功和失败的操作),成功读取和写入的总字节数,统计信息收集的开始时间,文件系统中最后一次读取和写入操作的时间。要查看此功能的演示,请在控制台演示中使用 fsnkd stat 命令。

ULONG uError = 0;
FSNK_STATISTICS_DATA FsStatistics = {0};

// Query current statistics.
if (! FsnkGetStatistics (
    &FsStatistics))
{
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
}

// Write statistics to the log.
MyLogPrintString ("Bytes read: %I64u", FsStatistics.BytesRead);
MyLogPrintString ("Bytes written: %I64u", FsStatistics.BytesWritten);
...

请注意,如果在库初始化期间将标志设置为 FsnkInitFullStats,则统计信息将包括所有支持的 I/O 操作。否则,统计信息将根据当前过滤器规则(如果添加到规则列表中)进行收集。

低级文件输入/输出

FsnkIoCreate API 函数创建一个或打开一个具有指定名称的文件系统对象。此对象可以是文件、文件夹或逻辑磁盘(卷)。如果使用了 FsnkIoBypassFilters 标志,则通过此函数执行的打开或创建文件操作不会在库中生成任何通知。此外,此标志允许您绕过顶层文件过滤器(即位于 FSNK 引擎之上的过滤器)。此函数有很多参数,建议在文档中阅读。要关闭文件,请使用 FsnkIoClose 例程。

void
__stdcall
MyFsCallbackRoutine (
    IN PVOID pContext,        // Application-defined value.
    IN PFSNK_OP_DATA pData,        // General information of the request.
    IN PFSNK_OP_PARAMS pParams)    // Operation-specific information.
{
    ULONG uError = 0;
    HANDLE hFileHandle = NULL;
    FSNK_CREATE_RESULT uCreateResult = 0;
    FSNK_CREATE_OPTION uCreateOptions = 0;

    // Create options bitmask.
    uCreateOptions =
        FsnkOptionNonDirectory |        // Open if not a directory only.
        FsnkOptionSynchronousIoNonAlert |    // Open for synchronous access.
        FsnkOptionRandomAccess;            // Don't read ahead and cache.

    // Open the indicated file object.
    if (! FsnkIoCreate (
        &hFileHandle,        // File handle will be returned here.
        &uCreateResult,        // What was done will be returned here.
        pData -> FileName,    // Full name of the object to open.
        FILE_GENERIC_READ,    // Request read access only.
        FsnkDispositionOpen,    // Open existing file only.
        FsnkShareRead,        // Don't allow write access while reading.
        uCreateOptions,        // Options for create operation.
        FsnkIoBypassFilters))    // Don't generate recursive calls to us.
    {
        // Error handling goes here.
        uError = FsnkGetErrorCode ();
        ...
    }
    ...
    // Not needed anymore, close file.
    FsnkIoClose (&hFileHandle);
}

打开文件后,您可以使用此组中的其他函数来处理它。例如,FsnkIoRead 函数用于从已打开的文件中读取数据。与此组中的其他函数类似,读取请求将绕过 Win32 和 Native 层,并且顶层过滤器也不会看到该请求。该函数的输入参数包括对象句柄、从文件中读取的数据的缓冲区地址、缓冲区大小(以字节为单位)以及一些用于操作控制的标志。输出时,如果成功,实际读取的字节数将在 *puBytesRead 中返回。

ULONG uError = 0;
ULONG uBytesRead = 0;
HANDLE hFileHandle = NULL;
BYTE auDataBuffer [1024] = {0};
...

// Read file header from the beginning.
if (! FsnkIoRead (
    hFileHandle,        // File handle with read access.
    auDataBuffer,        // Buffer for the data.
    sizeof (auDataBuffer),    // Size of the specified buffer, in bytes.
    0,            // Offset value to start reading from, in bytes.
    FsnkIoNoBuffering,    // Don't use caching for the particular operation.
    &uBytesRead))        // Number of bytes that was actually read.
{
    // Error handling goes here.
    uError = FsnkGetErrorCode ();
    ...
}

此组包含大量函数,所有这些函数都在文档中有详细描述。我们建议您仔细阅读文档。

回调函数和结构

本节描述了回调函数,该函数为文件系统中的每个事件调用,以及用于告知引擎中发生的事件的回调函数。此外,还有存储有关拦截的文件系统事件信息的结构的描述。

文件系统事件回调

此回调例程向应用程序报告文件系统事件。事件信息通过两个结构在回调中表示:FSNK_OP_DATAFSNK_OP_PARAMS。可以通过库 API 函数获取一些额外信息。

pData 参数是存储每种操作通用信息的结构的地址:操作代码(创建、删除、写入等)、操作状态、请求者的进程 ID、用作操作安全上下文的用户帐户的 SID,以及其他一些信息。pParams 输入包含保存特定于某个操作的信息的结构的地址。例如,对于读取或写入操作,它是从文件中读取或写入的字节数,对于创建链接,它是链接文件名。

某些值仅在其用于系统时才具有唯一性。例如卷 ID:在卷未卸载之前它是唯一的。如果您想在应用程序中使用这些标识符,您应该拥有一个最新的列表,而回调将帮助您做到这一点。

此例程在库线程之一的上下文中调用。您应该尽快将控制权返回给库,因为处理文件系统事件通知的线程数量是有限的。库仅对已完成的操作调用此例程,因此没有可能阻止操作或更改其参数。

引擎事件回调

此回调是可选的,但它可以方便地告知产品引擎中的各种事件。回调参数接收以下数据:应用程序定义的上下文(例如,结构的地址)、库事件代码以及适用的 Win32 错误代码。最后一个参数表示事件的特定详细信息。例如,它可能指示发生故障的引擎组件,这对于调试很有用。

此例程可以在任意线程的上下文中调用。建议始终设置此回调,因为在发生故障时,有机会重新初始化库并通过从回调返回“true”来继续工作。有关更多信息,请参阅文档。

文件系统事件一般信息

FSNK_OP_DATA 结构存储有关文件系统事件的一般信息。通过使用它,您可以获取有关操作类型、进程、会话以及请求操作的用户的信息。

该结构存储以下数据:操作标志位掩码、操作代码(读取、写入等)以及操作状态。接下来是相关标识符:线程、进程和会话的唯一标识符、登录会话 ID、用于执行操作的帐户 SID、文件对象所在的卷的 ID。最后,文件系统对象的 NT 风格名称以及到文件相对路径的字符偏移量。

请记住,指示的线程、进程和会话 ID 并非在所有时间都唯一。例如,线程标识符仅在线程终止之前有效。当前的 FSNK 版本无法跟踪线程和进程的创建和终止,但计划在未来的版本中添加此功能。

文件系统事件特定信息

使用 FSNK_OP_PARAMS 结构,您可以获取许多操作的特定于操作的信息,例如创建、关闭、读取和写入、挂载、更改卷标、创建链接、移动或重命名、更改安全描述符、卷扩展和收缩等。

例如,以下数据可用于打开/创建操作:请求类型、对对象的请求访问、文件属性、文件已存在时应采取的操作、文件对其他应用程序的可用性、定义创建操作执行各种方面的参数的位掩码、新文件的初始大小以及操作的最终结果(已创建、已替换、已打开等)。

版本历史

2014.04.10 v1.0

© . All rights reserved.