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

고급 기법 & 실무 적용 - 21화 필터 드라이버 아키텍처

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

필터 드라이버 아키텍처 — 기존 스택에 조용히 끼어들기

📅 2026 ⏱ 읽기 약 14분 🏷 필터 드라이버 / Upper Filter / Lower Filter / INF 설치
백신, 암호화, 모니터링 소프트웨어가 어떻게 기존 장치의 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 FilterLower 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