Windows 커널 & 드라이버 시리즈 — 21화
필터 드라이버 아키텍처 — 기존 스택에 조용히 끼어들기
백신, 암호화, 모니터링 소프트웨어가 어떻게 기존 장치의 I/O를 가로채서 분석할 수 있을까요? 바로 필터 드라이버 덕분이에요. 기존 드라이버 코드를 전혀 건드리지 않고, 그 위나 아래에 슬며시 끼어들어 모든 I/O를 들여다볼 수 있는 강력한 메커니즘입니다.
필터 드라이버란?
11화에서 디바이스 스택에 필터 DO를 붙이는 것을 봤어요. 필터 드라이버는 그 원리를 활용해 기존 스택에 삽입됩니다. I/O 요청이 지나가는 길목에 서서 원하는 작업을 수행한 뒤, 요청을 그대로 아래로 흘려보내거나 응답을 가로채 수정할 수 있어요.
실제로 쓰이는 곳은 매우 많아요:
- 보안 솔루션 — 파일 접근 모니터링, 프로세스 생성 감시
- 디스크 암호화 (BitLocker) — 쓸 때 암호화, 읽을 때 복호화
- 백업 소프트웨어 — 파일 변경 추적
- USB 제어 — 특정 USB 장치 차단
- 네트워크 방화벽 — 패킷 필터링
Upper Filter vs Lower Filter
필터가 스택 어디에 삽입되느냐에 따라 두 종류로 나뉩니다:
Upper Filter 드라이버
FDO 위에 위치
↕ IRP 흐름
함수 드라이버 (FDO)
실제 장치 기능
↕
Lower Filter 드라이버
PDO 위, FDO 아래에 위치
↕
버스 드라이버 (PDO)
물리 장치 대표
| 구분 | Upper Filter | Lower Filter |
|---|---|---|
| 위치 | 함수 드라이버 위 | 함수 드라이버 아래, 버스 드라이버 위 |
| I/O 흐름에서 보는 것 | 함수 드라이버가 처리하기 전 요청 | 함수 드라이버가 변환한 후 요청 |
| 주요 용도 | 앱 레벨 I/O 모니터링, 접근 제어 | 물리 I/O 변환, 암호화, 압축 |
| 예시 | AV 파일 스캐너, USB 차단 | BitLocker(볼륨 암호화) |
WDM 필터 드라이버 코드 구조
// 필터 드라이버의 DeviceExtension typedef struct _FILTER_DEVICE_EXTENSION { PDEVICE_OBJECT LowerDevice; // 아래 디바이스 오브젝트 (attach 대상) PDEVICE_OBJECT PhysicalDeviceObject; } FILTER_DEVICE_EXTENSION, *PFILTER_DEVICE_EXTENSION; // AddDevice — PnP Manager가 새 장치 발견 시 호출 NTSTATUS FilterAddDevice( _In_ PDRIVER_OBJECT DriverObject, _In_ PDEVICE_OBJECT PhysicalDeviceObject) // PDO { PDEVICE_OBJECT filterDO = NULL; NTSTATUS status = IoCreateDevice( DriverObject, sizeof(FILTER_DEVICE_EXTENSION), NULL, // 이름 없음 — 필터는 보통 익명 PhysicalDeviceObject->DeviceType, FILE_DEVICE_SECURE_OPEN, FALSE, &filterDO); if (!NT_SUCCESS(status)) return status; PFILTER_DEVICE_EXTENSION ext = filterDO->DeviceExtension; ext->PhysicalDeviceObject = PhysicalDeviceObject; // 스택에 붙이기 ext->LowerDevice = IoAttachDeviceToDeviceStack(filterDO, PhysicalDeviceObject); if (!ext->LowerDevice) { IoDeleteDevice(filterDO); return STATUS_NO_SUCH_DEVICE; } // 아래 디바이스의 플래그 동기화 (중요!) filterDO->Flags |= (ext->LowerDevice->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO)); filterDO->Flags &= ~DO_DEVICE_INITIALIZING; return STATUS_SUCCESS; } // 패스스루 — 내가 처리 안 하는 요청은 그냥 아래로 NTSTATUS FilterPassThrough(PDEVICE_OBJECT DevObj, PIRP Irp) { PFILTER_DEVICE_EXTENSION ext = DevObj->DeviceExtension; IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(ext->LowerDevice, Irp); } // 읽기 요청 가로채기 — 완료 후 내용 검사 NTSTATUS FilterRead(PDEVICE_OBJECT DevObj, PIRP Irp) { PFILTER_DEVICE_EXTENSION ext = DevObj->DeviceExtension; // 완료 루틴 등록 후 아래로 전달 IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, FilterReadComplete, ext, TRUE, TRUE, TRUE); return IoCallDriver(ext->LowerDevice, Irp); } // 읽기 완료 후 호출되는 루틴 — 여기서 데이터를 들여다볼 수 있음 NTSTATUS FilterReadComplete( PDEVICE_OBJECT DevObj, PIRP Irp, PVOID Context) { if (NT_SUCCESS(Irp->IoStatus.Status) && Irp->MdlAddress) { PVOID buffer = MmGetSystemAddressForMdlSafe( Irp->MdlAddress, NormalPagePriority); ULONG length = (ULONG)Irp->IoStatus.Information; if (buffer) { // 여기서 읽어온 데이터를 검사/수정 가능! DbgPrint("[Filter] Read %lu bytes\n", length); } } if (Irp->PendingReturned) IoMarkIrpPending(Irp); return STATUS_CONTINUE_COMPLETION; // 완료 계속 진행 } // DriverEntry에서 모든 MajorFunction을 패스스루로 기본 설정 NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); DriverObject->DriverExtension->AddDevice = FilterAddDevice; DriverObject->DriverUnload = FilterUnload; // 모든 요청 기본 패스스루 for (int i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) DriverObject->MajorFunction[i] = FilterPassThrough; // 필요한 것만 오버라이드 DriverObject->MajorFunction[IRP_MJ_READ] = FilterRead; DriverObject->MajorFunction[IRP_MJ_PNP] = FilterPnp; DriverObject->MajorFunction[IRP_MJ_POWER] = FilterPower; return STATUS_SUCCESS; }INF 파일로 필터 드라이버 등록하기
필터 드라이버는 레지스트리의 특정 키에 자신을 등록해야 PnP Manager가 적절한 타이밍에 로드해줘요. INF 파일(드라이버 설치 스크립트)에 이를 기술합니다:
; INF 파일 - Upper Filter로 USB 장치에 등록하는 예시 [Version] Signature = "$WINDOWS NT$" Class = USB ClassGuid = {36FC9E60-C465-11CF-8056-444553540000} [MyFilter.AddReg] ; UpperFilters에 드라이버 서비스 이름 추가 HKR,,"UpperFilters",0x00010008,"MyFilterDrv" [MyFilter.DelReg] ; 언인스톨 시 제거 HKR,,"UpperFilters",0x00018002,"MyFilterDrv"
⚠️ PnP, Power IRP는 반드시 패스스루해야 해요
필터 드라이버가 IRP_MJ_PNP나 IRP_MJ_POWER를 처리하지 않고 막아버리면, 장치가 연결/제거될 때 시스템이 제대로 반응하지 못해 BSOD가 날 수 있어요. 이 두 가지는 특히 신경 써서 아래 드라이버로 전달해야 합니다.
💡 미니필터 vs 레거시 필터
파일시스템 I/O를 필터링하려면 이 레거시 방식(IoAttachDeviceToDeviceStack) 대신 미니필터(Minifilter)를 쓰세요. 22화에서 미니필터를 다루는데, 파일시스템에 특화된 훨씬 안전하고 편한 API입니다. 레거시 필터는 구현이 복잡하고 실수하기 쉬워요.
✅ 21화 요약
- 필터 드라이버는 기존 드라이버 스택에 삽입해 I/O를 가로채는 드라이버입니다.
- Upper Filter는 FDO 위, Lower Filter는 FDO 아래에 위치합니다.
- IoAttachDeviceToDeviceStack으로 스택에 삽입하고, LowerDevice로 아래에 전달합니다.
- 모든 MajorFunction을 기본 패스스루로 설정하고, 필요한 것만 오버라이드하세요.
- 완료 루틴(CompletionRoutine)으로 응답 데이터를 검사하거나 수정할 수 있습니다.
다음 화
22화 — 파일시스템 미니필터 드라이버: FltRegisterFilter로 파일 I/O 완전 제어 →
#필터드라이버 #UpperFilter #LowerFilter #IoAttachDevice #CompletionRoutine
'Programming > 7. Device Driver' 카테고리의 다른 글
| 고급 기법 & 실무 적용 - 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 |
| 드라이버 개발 핵심 - 16화 커널 메모리 풀 관리 (0) | 2026.06.04 |