首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

Windows 檔案過濾驅動開發:Filemon 核心架構深度剖析(上)

     編輯:關於VC++

前言

檔案過濾驅動廣泛應用於防毒、加密、備份、存取稽核等領域。Sysinternals Filemon 是學習檔案系統過濾驅動的經典範例。本系列筆記詳細剖析其架構、裝置物件設計、IRP 分派邏輯、檔案名稱解析等核心技術。

整體架構:雙層裝置物件模型

Filemon 建立兩類裝置物件:

類型用途命名數量
控制裝置物件與用戶模式 GUI (Filemon.exe) 通訊:接收掛載磁碟、啟用/停用過濾、設定過濾規則等命令具名:\Device\Filemon + 符號連結 \DosDevices\Filemon1 個
過濾裝置物件掛載至目標檔案系統磁碟區(C:、D: 等),攔截 IRP_MJ_READ/WRITE/CREATE 等操作不具名(安全考量)每掛載一個磁碟區建立一個

驅動程式進入點 DriverEntry 詳細流程

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    // 1. 建立控制裝置物件
    UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\Filemon");
    PDEVICE_OBJECT guiDevice;
    NTSTATUS status = IoCreateDevice(DriverObject, sizeof(HOOK_EXTENSION),
                                     &devName, FILE_DEVICE_FILEMON, 0, TRUE, &guiDevice);
    if (!NT_SUCCESS(status)) return status;

    // 2. 標記裝置擴充結構類型
    ((PHOOK_EXTENSION)guiDevice->DeviceExtension)->Type = GUIINTERFACE;

    // 3. 建立符號連結供用戶模式開啟
    UNICODE_STRING linkName = RTL_CONSTANT_STRING(L"\\DosDevices\\Filemon");
    status = IoCreateSymbolicLink(&linkName, &devName);
    if (!NT_SUCCESS(status)) { IoDeleteDevice(guiDevice); return status; }

    // 4. 註冊所有 MajorFunction 統一分派常式
    for (ULONG i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; ++i)
        DriverObject->MajorFunction[i] = FilemonDispatch;

    // 5. 設定 Fast I/O 分派表
    DriverObject->FastIoDispatch = &FastIOHook;

    // 6. 初始化全域資源:雜湊表、同步物件、Lookaside List、日誌緩衝區
    // ... (詳見原文完整代碼)

    return STATUS_SUCCESS;
}

關鍵點解析

  • HOOK_EXTENSION 結構體儲存在 DeviceExtension 中,區分裝置類型(GUINTERFACE vs 掛載磁碟區)。
  • 統一分派常式 FilemonDispatch 根據 DeviceExtension->Type 分流至 FilemonDeviceRoutine (GUI 命令) 或 FilemonHookRoutine (檔案系統 IRP)。
  • FilemonGetProcessNameOffset() 掃描 EPROCESS (KPEB) 找到 ImageFileName 偏移,後續通用於任意進程取得名稱。

分派常式 FilemonDispatch 分流邏輯

NTSTATUS FilemonDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    PHOOK_EXTENSION ext = (PHOOK_EXTENSION)DeviceObject->DeviceExtension;
    if (ext->Type == GUINTERFACE)
        return FilemonDeviceRoutine(DeviceObject, Irp); // GUI 控制命令
    else
        return FilemonHookRoutine(DeviceObject, Irp);   // 檔案系統 IRP 過濾
}

核心過濾常式 FilemonHookRoutine

此函式負責:

  1. 取得目前 IO_STACK_LOCATION。
  2. **解析被操作檔案完整路徑**(最複雜部分,見下節)。
  3. 根據 MajorFunction 記錄操作類型(建立、讀寫、查詢資訊等)。
  4. **設定下一層堆疊位置**並下發 IRP。

設定下層 IRP 堆疊的三種方式

PIO_STACK_LOCATION cur = IoGetCurrentIrpStackLocation(Irp);
PIO_STACK_LOCATION nxt = IoGetNextIrpStackLocation(Irp);

// 方式 1:完整複製(含完成常式相關欄位)
IoCopyCurrentIrpStackLocationToNext(Irp);

// 方式 2:直接指派指標(較快,不複製完成常式)
IoSkipCurrentIrpStackLocationToNext(Irp);

// 方式 3:Filemon 採用的手動結構體賦值(完全控制)
*nxt = *cur;

檔案完整路徑解析:GETPATHNAME 巨集與 FilemonGetFullPath

巨集展開:

#define GETPATHNAME(_IsCreate) \
    fullPathName = ExAllocateFromNPagedLookasideList(&FullPathLookaside); \
    if (fullPathName) { \
        FilemonGetFullPath(_IsCreate, FileObject, hookExt, fullPathName); \
    } else { \
        fullPathName = InsufficientResources; \
    }

FilemonGetFullPath 處理三種情境:

  1. IRP_MJ_CREATE 尚未完成:從 FileObject->FileName (相對路徑) + RelatedFileObject->FileName (父目錄) 組合。
  2. IRP_MJ_CREATE 完成後:在完成常式中透過 ZwQueryInformationFile 查詢 FileNameInformation 取得絕對路徑。
  3. 讀寫等後續操作:自行建構 IRP 發送 IRP_MJ_QUERY_INFORMATION 取得檔名。

情境一:Create 前組合路徑(簡化版)

VOID FilemonGetFullPath(BOOLEAN createPath, PFILE_OBJECT FileObject, ...)
{
    // 1. 取得檔案物件名稱 (Unicode → ANSI)
    ANSI_STRING fileName;
    RtlUnicodeStringToAnsiString(&fileName, &FileObject->FileName, TRUE);

    // 2. 處理相對路徑:檢查 RelatedFileObject
    PFILE_OBJECT relObj = FileObject->RelatedFileObject;
    ANSI_STRING relName = {0};
    if (FileObject->FileName.Buffer[0] != L'\\' && relObj && relObj->FileName.Length) {
        RtlUnicodeStringToAnsiString(&relName, &relObj->FileName, TRUE);
    }

    // 3. 組合完整路徑:磁碟機代號 + 父目錄 + '\' + 相對名稱
    // 詳細邏輯處理雙反斜線、過長路徑、網路裝置等邊界情況
    // ...
}

情境三:主動查詢檔名(讀寫操作)


if (!gotPath && !createPath) {
    PFILE_NAME_INFORMATION nameInfo = (PFILE_NAME_INFORMATION)
        ExAllocatePool(NonPagedPool, MAXPATHLEN * sizeof(WCHAR));
    if (nameInfo && FilemonQueryFile(hookExt->FileSystem, FileObject,
            FileNameInformation, nameInfo, (MAXPATHLEN-3)*sizeof(WCHAR))) {
        // 轉換 Unicode → ANSI,加上磁碟機代號前綴
        // 處理命名管道 (NPFS)、郵件槽 (MSFS) 特殊前綴
        gotPath = TRUE;
    }
    if (nameInfo) ExFreePool(nameInfo);
}

特殊檔案系統處理

  • Named Pipe:MajorFunction == IRP_MJ_CREATE_NAMED_PIPE,前綴 \\.\pipe\。
  • Mailslot:MajorFunction == IRP_MJ_CREATE_MAILSLOT,前綴 \\.\mailslot\。
  • 網路檔案系統:不加磁碟機代號前綴。

下一篇預告

下篇將深入剖析:

  • 完成常式 (CompletionRoutine) 在 IRP_MJ_CREATE 後取得最終路徑的機制。
  • Fast I/O 掛鉤 (FastIOHook) 攔截快速讀寫。
  • 日誌緩衝區管理、輸出至用戶模式 GUI 的通訊協定。
  • 掛載/卸載磁碟區裝置物件的動態建立與清理。

作者:連城碧 | QQ: 276265852 | MSN: haochao0000@hotmail.com
Filemon 原始碼下載:vckbase.com

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved