본문 바로가기
Programming/7. Device Driver

고급 기법 & 실무 적용 - 22화 파일시스템 미니필터 드라이버

by S.W 2026. 6. 12.
Windows 커널 & 드라이버 시리즈 — 22화

파일시스템 미니필터 드라이버 — FltRegisterFilter로 파일 I/O 완전 제어

📅 2026 ⏱ 읽기 약 15분 🏷 미니필터 / FltRegisterFilter / Pre/PostOperation / Altitude
파일 접근을 모니터링하거나 차단하는 기능이 필요하다면 미니필터(Minifilter)가 최선의 선택이에요. 21화의 레거시 필터보다 훨씬 안전하고 개발하기 쉽습니다. Windows의 모든 파일시스템 I/O를 Pre/Post 방식으로 가로챌 수 있고, 요청을 허용/거부/수정하는 것도 자유자재예요. 백신, DLP, 감사 시스템 등의 핵심 기술입니다.

미니필터 프레임워크란?

레거시 파일시스템 필터는 개발이 어렵고 여러 필터가 공존할 때 충돌이 잦았어요. Microsoft는 이 문제를 해결하기 위해 Filter Manager라는 중간 계층을 도입했습니다. 미니필터는 Filter Manager에 등록하면, Filter Manager가 알아서 순서 관리와 통신을 처리해줘요.

Pre-Operation 콜백 — I/O 요청이 파일시스템에 도달하기 전
요청 검사, 차단, 파라미터 수정 가능
↓ (허용 시 파일시스템으로 전달)
파일시스템 드라이버 (NTFS.sys 등)
실제 파일 I/O 처리
↑ (완료 후)
Post-Operation 콜백 — I/O 완료 후
결과 검사, 데이터 수정, 로깅 가능

Altitude — 미니필터의 순서를 결정하는 값

여러 미니필터가 공존할 때 어떤 순서로 호출될지를 Altitude(고도) 값으로 결정합니다. 높은 Altitude가 먼저 Pre-Operation을 처리하고, Post-Operation에서는 반대 순서로 처리돼요. 마치 공항 보안 검사 레인처럼요.

Altitude 범위용도예시
420000~429999백업VSS 프로바이더
320000~329999안티바이러스실시간 파일 스캔
265000~269999암호화파일 암호화 필터
200000~209999인용/감사파일 접근 로깅
📌 Altitude 값은 Microsoft에 등록해야 해요 배포 드라이버는 Microsoft에 Altitude 값을 신청하고 고유 값을 할당받아야 해요. 개발 중에는 임의의 값을 써도 되지만, 제품 출시 전엔 꼭 공식 등록이 필요합니다.

미니필터 드라이버 전체 코드

#include <fltKernel.h> #include <dontuse.h> #define MINIFILTER_POOL_TAG 'tliF' // 필터 핸들 (전역) PFLT_FILTER g_FilterHandle = NULL; // Operation Registration — 어떤 I/O를 가로챌지 선언 const FLT_OPERATION_REGISTRATION Callbacks[] = { { IRP_MJ_CREATE, // 파일 열기 0, PreCreateCallback, // Pre-Operation 콜백 PostCreateCallback // Post-Operation 콜백 }, { IRP_MJ_READ, // 파일 읽기 0, PreReadCallback, NULL // Post 콜백 없음 }, { IRP_MJ_WRITE, 0, PreWriteCallback, NULL }, { IRP_MJ_OPERATION_END } // 종료 표시 (필수) }; // Filter Registration const FLT_REGISTRATION FilterRegistration = { sizeof(FLT_REGISTRATION), FLT_REGISTRATION_VERSION, 0, // Flags NULL, // Context 등록 (없음) Callbacks, // Operation 콜백 FilterUnloadCallback, // 언로드 콜백 NULL, // InstanceSetupCallback NULL, // InstanceQueryTeardown NULL, // InstanceTeardownStart NULL, // InstanceTeardownComplete }; // DriverEntry NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); NTSTATUS status; // 1. 미니필터 등록 status = FltRegisterFilter(DriverObject, &FilterRegistration, &g_FilterHandle); if (!NT_SUCCESS(status)) return status; // 2. 필터링 시작 (이 순간부터 콜백이 호출됨) status = FltStartFiltering(g_FilterHandle); if (!NT_SUCCESS(status)) { FltUnregisterFilter(g_FilterHandle); return status; } DbgPrint("[MiniFilter] Registered and started.\n"); return STATUS_SUCCESS; } // 언로드 콜백 NTSTATUS FilterUnloadCallback(_In_ FLT_FILTER_UNLOAD_FLAGS Flags) { UNREFERENCED_PARAMETER(Flags); FltUnregisterFilter(g_FilterHandle); DbgPrint("[MiniFilter] Unloaded.\n"); return STATUS_SUCCESS; } // Pre-Create 콜백 — 파일 열기 전 FLT_PREOP_CALLBACK_STATUS PreCreateCallback( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Out_ PVOID *CompletionContext) { UNREFERENCED_PARAMETER(FltObjects); UNREFERENCED_PARAMETER(CompletionContext); // 파일 이름 가져오기 PFLT_FILE_NAME_INFORMATION nameInfo = NULL; NTSTATUS status = FltGetFileNameInformation( Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo); if (NT_SUCCESS(status)) { FltParseFileNameInformation(nameInfo); // 예: .exe 파일 열기를 로깅 if (nameInfo->Extension.Length > 0) { if (RtlEqualUnicodeString(&nameInfo->Extension, &(UNICODE_STRING)RTL_CONSTANT_STRING(L"exe"), TRUE)) { DbgPrint("[MiniFilter] EXE open: %wZ\n", &nameInfo->Name); // 특정 파일 차단 예시: // Data->IoStatus.Status = STATUS_ACCESS_DENIED; // Data->IoStatus.Information = 0; // FltReleaseFileNameInformation(nameInfo); // return FLT_PREOP_COMPLETE; // ← 여기서 막음! } } FltReleaseFileNameInformation(nameInfo); } return FLT_PREOP_SUCCESS_NO_CALLBACK; // 그냥 통과 } // Post-Create 콜백 — 파일 열기 완료 후 FLT_POSTOP_CALLBACK_STATUS PostCreateCallback( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags) { UNREFERENCED_PARAMETER(FltObjects); UNREFERENCED_PARAMETER(CompletionContext); UNREFERENCED_PARAMETER(Flags); if (NT_SUCCESS(Data->IoStatus.Status)) { // 파일이 성공적으로 열렸을 때 추가 처리 } return FLT_POSTOP_FINISHED_PROCESSING; }

Pre-Operation 반환값 의미

반환값의미
FLT_PREOP_SUCCESS_NO_CALLBACK요청 통과, Post 콜백 호출 안 함
FLT_PREOP_SUCCESS_WITH_CALLBACK요청 통과, Post 콜백도 호출
FLT_PREOP_COMPLETE요청 차단 — Data->IoStatus에 결과를 직접 설정해야 함
FLT_PREOP_PENDING비동기 처리 — 나중에 FltCompletePendedPreOperation 호출
FLT_PREOP_SYNCHRONIZEPost 콜백을 같은 스레드 컨텍스트에서 호출하게 요청

INF로 미니필터 설치

; INF 파일 핵심 섹션 [MyMiniFilter.AddReg] HKLM,"SYSTEM\CurrentControlSet\Services\MyMiniFilter\Instances","DefaultInstance",,"%MyMiniFilter.Instance.Name%" HKLM,"SYSTEM\CurrentControlSet\Services\MyMiniFilter\Instances\%MyMiniFilter.Instance.Name%","Altitude",,"320000" HKLM,"SYSTEM\CurrentControlSet\Services\MyMiniFilter\Instances\%MyMiniFilter.Instance.Name%","Flags",0x00010001,0x0 [Strings] MyMiniFilter.Instance.Name = "MyMiniFilter Instance"
💡 fltMC.exe — 미니필터 관리 도구 VM에서 미니필터를 테스트할 때 fltMC load MyMiniFilter로 로드하고, fltMC unload MyMiniFilter로 언로드할 수 있어요. fltMC filters로 현재 등록된 미니필터 목록을 볼 수도 있습니다.

✅ 22화 요약
  • 미니필터는 Filter Manager에 등록하는 방식으로, 레거시 필터보다 안전하고 편합니다.
  • Altitude 값으로 여러 미니필터의 호출 순서를 결정합니다.
  • FLT_OPERATION_REGISTRATION으로 가로챌 I/O와 Pre/Post 콜백을 등록합니다.
  • Pre 콜백에서 FLT_PREOP_COMPLETE를 반환하면 요청을 차단할 수 있습니다.
  • 파일 이름은 FltGetFileNameInformation으로 안전하게 가져옵니다.
다음 화
23화 — WinDbg 마스터 클래스: 실전에서 바로 쓰는 명령어 총정리 →

#미니필터 #FltRegisterFilter #Altitude #파일시스템필터 #PreOperation