Windows 커널 & 드라이버 시리즈 — 22화
파일시스템 미니필터 드라이버 — FltRegisterFilter로 파일 I/O 완전 제어
파일 접근을 모니터링하거나 차단하는 기능이 필요하다면 미니필터(Minifilter)가 최선의 선택이에요. 21화의 레거시 필터보다 훨씬 안전하고 개발하기 쉽습니다. Windows의 모든 파일시스템 I/O를 Pre/Post 방식으로 가로챌 수 있고, 요청을 허용/거부/수정하는 것도 자유자재예요. 백신, DLP, 감사 시스템 등의 핵심 기술입니다.
미니필터 프레임워크란?
레거시 파일시스템 필터는 개발이 어렵고 여러 필터가 공존할 때 충돌이 잦았어요. Microsoft는 이 문제를 해결하기 위해 Filter Manager라는 중간 계층을 도입했습니다. 미니필터는 Filter Manager에 등록하면, Filter Manager가 알아서 순서 관리와 통신을 처리해줘요.
Pre-Operation 콜백 — I/O 요청이 파일시스템에 도달하기 전
요청 검사, 차단, 파라미터 수정 가능
요청 검사, 차단, 파라미터 수정 가능
↓ (허용 시 파일시스템으로 전달)
파일시스템 드라이버 (NTFS.sys 등)
실제 파일 I/O 처리
실제 파일 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_SYNCHRONIZE | Post 콜백을 같은 스레드 컨텍스트에서 호출하게 요청 |
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
'Programming > 7. Device Driver' 카테고리의 다른 글
| 고급 기법 & 실무 적용 - 21화 필터 드라이버 아키텍처 (0) | 2026.06.11 |
|---|---|
| 고급 기법 & 실무 적용 - 20화 KMDF 드라이버 개발 실습 (0) | 2026.06.10 |
| 고급 기법 & 실무 적용 - 19화 WDF 프레임워크 소개 (0) | 2026.06.09 |
| 드라이버 개발 핵심 - 18화 인터럽트, ISR, DPC (0) | 2026.06.08 |
| 드라이버 개발 핵심 - 17화 MDL (0) | 2026.06.05 |