檔案過濾驅動廣泛應用於防毒、加密、備份、存取稽核等領域。Sysinternals Filemon 是學習檔案系統過濾驅動的經典範例。本系列筆記詳細剖析其架構、裝置物件設計、IRP 分派邏輯、檔案名稱解析等核心技術。
Filemon 建立兩類裝置物件:
| 類型 | 用途 | 命名 | 數量 |
|---|---|---|---|
| 控制裝置物件 | 與用戶模式 GUI (Filemon.exe) 通訊:接收掛載磁碟、啟用/停用過濾、設定過濾規則等命令 | 具名:\Device\Filemon + 符號連結 \DosDevices\Filemon | 1 個 |
| 過濾裝置物件 | 掛載至目標檔案系統磁碟區(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此函式負責:
IO_STACK_LOCATION。MajorFunction 記錄操作類型(建立、讀寫、查詢資訊等)。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 處理三種情境:
FileObject->FileName (相對路徑) + RelatedFileObject->FileName (父目錄) 組合。ZwQueryInformationFile 查詢 FileNameInformation 取得絕對路徑。IRP_MJ_QUERY_INFORMATION 取得檔名。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);
}
MajorFunction == IRP_MJ_CREATE_NAMED_PIPE,前綴 \\.\pipe\。MajorFunction == IRP_MJ_CREATE_MAILSLOT,前綴 \\.\mailslot\。下篇將深入剖析:
CompletionRoutine) 在 IRP_MJ_CREATE 後取得最終路徑的機制。FastIOHook) 攔截快速讀寫。作者:連城碧 | QQ: 276265852 | MSN: haochao0000@hotmail.com
Filemon 原始碼下載:vckbase.com