들어가며
이 글은 상용 DLP(Data Loss Prevention) 프로그램의 동작 원리를 분석하고, 윈도우 커널 드라이버 기반의 보안 솔루션을 직접 구현해보는 시리즈입니다.
지난 2편에서는 커널 드라이버의 IOCTL 통신, 소프트웨어 실행 차단, 그리고 E2E 파이프라인 구축 과정을 다뤘습니다.
이번 글에서는 사용자에게 동작 상태를 보여줄 WPF 트레이 앱을 만들고, 본격적인 매체 제어인 USB 차단과 Minifilter 기반 파일 확장자 차단을 구현해 봅니다.
이전 편 보러가기
https://imoracle.tistory.com/73
[DLP 개발기] 2. 커널 드라이버와 IOCTL, 그리고 E2E 파이프라인 구축
들어가며지난 1편에서 설계한 아키텍처를 바탕으로, 이번 편에서는 실제로 데이터가 흐르고 동작하는 E2E(End-to-End) 파이프라인을 완성해 보겠습니다. 목표는 심플합니다. "관리자 웹에서 정책을
imoracle.tistory.com
다음 편 보러가기
https://imoracle.tistory.com/75
[DLP 개발기] 4. WFP DNS 차단의 한계와 유저모드 클립보드 제어
들어가며이 글은 상용 DLP(Data Loss Prevention) 프로그램의 동작 원리를 분석하고, 윈도우 커널 드라이버 기반의 보안 솔루션을 직접 구현해보는 시리즈입니다.지난 글에서는 WPF 트레이 앱 통신과 매
imoracle.tistory.com
전체 구조 복습
현재까지 구성된 OpenGuard 솔루션의 전체 구조입니다. 이번 포스팅에서 다룰 핵심 컴포넌트는 OpenGuard_Driver, OpenGuard_Filter, 그리고 OpenGuard_UI 입니다.
OpenGuard.sln
├── OpenGuard_Driver ← KMDF 커널 드라이버 (USB PnP 제어)
├── OpenGuard_Filter ← Minifilter 파일 필터 (파일 I/O 제어)
├── OpenGuard_WFP ← WFP 네트워크 필터
├── OpenGuard_Agent ← C++ Win32 에이전트 (정책 관리 및 통신)
├── OpenGuard_UI ← C# WPF 트레이 앱 (상태 표시)
└── OpenGuard_Common ← 공유 헤더
1. WPF 트레이 앱 (OpenGuard_UI)


목표
백그라운드 서비스로 도는 에이전트(OpenGuard_Agent)가 보안 정책을 묵묵히 수행하겠지만, 사용자 입장에서는 현재 내 PC가 보호받고 있는지 시각적으로 확인할 수 있어야 합니다. 이를 위해 작업 표시줄 트레이에 아이콘을 띄우고, 클릭 시 상태 창을 보여주는 UI를 구현했습니다.
Named Pipe 통신 구조
에이전트(C++)와 UI(C#)는 서로 다른 프로세스이므로 데이터를 주고받을 IPC(Inter-Process Communication) 수단이 필요합니다. 윈도우 환경에서 구현이 직관적이고 기본 지원이 탄탄한 Named Pipe를 선택했습니다.
- OpenGuard_Agent (서버) \\.\pipe\OpenGuardStatus 파이프 생성
- OpenGuard_UI (클라이언트) 해당 파이프에 접속하여 상태 읽기
에이전트 측 파이프 서버 (C++)

UI 측 파이프 클라이언트 (C#)

트레이 아이콘 및 백그라운드 동작 구현

WPF는 기본적으로 트레이 아이콘(시스템 트레이) 컨트롤을 제공하지 않습니다. 따라서 System.Windows.Forms.NotifyIcon을 끌어와 사용해야 합니다.
또한, 메인 창에서 'X' 버튼을 눌렀을 때 앱이 완전히 종료되지 않고 트레이로 숨도록 OnClosing 이벤트를 오버라이딩 해주었습니다.
2. USB 차단 (OpenGuard_Driver)

PnP(Plug and Play) 콜백을 이용한 구현
USB 차단은 장치가 연결되는 순간을 포착하는 것이 핵심입니다. 커널 드라이버에서 제공하는 PnP 콜백인 IoRegisterPlugPlayNotification을 사용하여 이동식 디스크가 마운트 될 때 이벤트를 가로채도록 구현했습니다.

이벤트가 발생하면 정책을 확인하고, 차단 상태라면 해당 장치를 비활성화합니다.

로그도 정상적으로 관리자 대시보드에 찍히는 모습이 보입니다

⚠️ WDK 헤더 Include 순서 함정
USB PnP 관련 GUID를 사용할 때 아주 고전적인 링크 오류 함정이 있습니다. 헤더 파일의 순서가 조금만 틀어져도 오류가 발생합니다. initguid.h가 반드시 wdm.h보다 먼저 선언되어야 GUID가 외부 참조로 빠지지 않고 정상적으로 인스턴스화됩니다.
// 반드시 이 순서를 지켜야 링크 오류가 나지 않습니다.
#include <ntddk.h>
#include <initguid.h> // DEFINE_GUID 인스턴스화
#include <wdm.h>
#include <wdmguid.h> // GUID_DEVICE_INTERFACE_ARRIVAL 등
3. Minifilter 파일 확장자 차단 (OpenGuard_Filter)

Minifilter란 ?
Minifilter는 Windows의 파일시스템 드라이버 스택 중간에 끼어들어 모든 파일 I/O(생성, 읽기, 쓰기, 삭제 등)를 감시하고 제어할 수 있는 강력한 커널 프레임워크입니다. 파일 내용 유출 방지의 핵심 요소죠.

일반 드라이버와 Minifilter의 결정적 차이 : INF와 Altitude

개발 과정에서 겪은 가장 큰 구조적 차이점 중 하나는 드라이버를 시스템에 등록하고 로드하는 방식이었습니다.
앞서 만든 일반 커널 드라이버(OpenGuard_Driver)는 유저모드 에이전트에서 CreateService() API를 호출해 서비스 이름과 .sys 파일 경로만 지정해주고 StartService() API를 통해 간단하게 구동할 수 있었습니다. 즉, 별도의 INF 파일이 굳이 필요하지 않았죠.
하지만 Minifilter(OpenGuard_Filter)는 SCM(Service Control Manager) 등록만으로는 절대 동작하지 않습니다. Windows의 필터 매니저(FltMgr)가 이 드라이버를 파일시스템 스택에 끼워 넣으려면, 레지스트리에 반드시 다음과 같은 추가 메타데이터가 있어야 합니다.
- Instances 키 (Altitude, Flags)
- Group (FSFilter Activity Monitor 등 필터의 성격)
- SupportedFeatures
여기서 가장 핵심이 되는 값이 바로 Altitude(고도) 입니다. Windows 파일시스템에는 백신, 암호화, 백업 등 수많은 필터 드라이버들이 겹겹이 쌓여있습니다. 내 Minifilter가 이 스택의 어느 위치(순서)에 끼어들지 결정하는 고유 값이 바로 Altitude(OpenGuard의 경우 370030 사용)이며, 이 값이 Instances 레지스트리 키에 명시되어 있지 않으면 FilterLoad() 함수는 무조건 실패합니다.
보통은 이 복잡한 레지스트리 등록 과정을 자동화하기 위해 .inf 파일을 사용합니다. 원한다면 INF 없이 아래처럼 명령어를 통해 수동으로 레지스트리를 잡아주는 것도 가능합니다.
:: INF 없이 수동으로 Altitude 값을 레지스트리에 등록하는 예시
reg add "HKLM\SYSTEM\CurrentControlSet\Services\OpenGuard_Filter\Instances\OpenGuard_Filter Instance" /v "Altitude" /t REG_SZ /d "370030"
파일 확장자 차단 로직 (IRP_MJ_CREATE)
파일이 열리거나 생성될 때 발생하는 PreCreateCallback에서 파일의 확장자를 검사합니다. 정책상 차단된 확장자라면 STATUS_ACCESS_DENIED를 반환하여 접근을 원천 봉쇄합니다.


차단 테스트
관리자 웹 대시보드의 정책설정에서 차단할 확장자로 xlsx와 txt를 설정 해두었습니다.

xlsx 와 doc를 잘 받아오는 모습이 보입니다.
txt로 설정해두었는데 왜 doc 인지 의문을 가지실 수 있습니다만, 해당 부분은 처음에 테스트 할 때 찍어두었던 것인데
정책을 받아온다는 의미를 전달하기에는 확장자 오류가 있어도 괜찮다 싶어 새로 스크린샷을 찍지 않고 그대로 업로드 하였습니다

디버그 뷰로 확인 했을때 차단이 잘 되어지고 있는 모습이 보입니다.



트러블슈팅 정리 (삽질 노트)
이번 구현 과정에서 겪은 수많은 0x800... 에러들과 해결 방법을 정리했습니다. WDK 개발을 하시는 분들이라면 한 번쯤 마주칠 법한 이슈들입니다.
| 발생 문제 | 원인 | 해결 방법 |
| FilterLoad 0x80070522 | SeLoadDriverPrivilege 권한 비활성화 | 드라이버 로드 전 EnableLoadDriverPrivilege() 호출 추가 |
| FilterLoad 0x80070002 | INF 설치 시 %DefaultInstance% 변수 치환 실패로 레지스트리 키 깨짐 | INF 파일의 [MiniFilter.AddRegistry] 섹션에서 문자열 변수 대신 값 하드코딩으로 교체 |
| FilterConnectCommunicationPort 실패 | 포트 생성 시 보안 속성(Security Descriptor) 누락으로 유저모드 접근 불가 | FltBuildDefaultSecurityDescriptor를 통해 FLT_PORT_ALL_ACCESS 권한 명시 추가 |
| sys 파일 덮어쓰기 거부 | 드라이버가 커널 메모리에 로드된 상태라 파일이 잠김(Lock) | takeown 및 icacls 명령어로 소유권 취득 후 강제 복사 |
| GUID 링크 오류 | 헤더 참조 순서 꼬임 | initguid.h를 반드시 wdm.h보다 먼저 Include 하도록 순서 조정 |
| Named Pipe 연결 실패 | CriticalSection 초기화 순서 문제 | InitializeCriticalSection을 정책 폴링 스레드 생성 전에 호출하도록 수정 |
마무리 및 다음 편 예고
이번 글에서는 다음 세 가지를 완성했습니다.
- WPF 트레이 앱 : Named Pipe를 이용해 에이전트와 통신하며 사용자에게 상태 표시
- USB 차단 : PnP 콜백으로 이동식 디스크 감지 및 제어
- Minifilter 차단 : I/O 가로채기를 통한 확장자 기반 접근 제어
다음 4편에서는 본격적인 네트워크 제어로 넘어갑니다. WFP(Windows Filtering Platform)를 이용한 DNS 쿼리 감지 및 차단, 그리고 클립보드 차단 구현 과정을 다루겠습니다. 특히 WFP에서 DNS 쿼리를 가로챌 때 마주하게 되는 'Windows DNS Client 서비스' 구조의 한계점과 우회 방법도 함께 분석해 볼 예정입니다.
긴 글 읽어주셔서 감사합니다 !
이전 편 보러가기
https://imoracle.tistory.com/72
[DLP 개발기] 2. 커널 드라이버와 IOCTL, 그리고 E2E 파이프라인 구축
들어가며지난 1편에서 설계한 아키텍처를 바탕으로, 이번 편에서는 실제로 데이터가 흐르고 동작하는 E2E(End-to-End) 파이프라인을 완성해 보겠습니다. 목표는 심플합니다. "관리자 웹에서 정책을
imoracle.tistory.com
다음 편 보러가기
https://imoracle.tistory.com/75
[DLP 개발기] 4. WFP DNS 차단의 한계와 유저모드 클립보드 제어
들어가며이 글은 상용 DLP(Data Loss Prevention) 프로그램의 동작 원리를 분석하고, 윈도우 커널 드라이버 기반의 보안 솔루션을 직접 구현해보는 시리즈입니다.지난 글에서는 WPF 트레이 앱 통신과 매
imoracle.tistory.com
'Project > [DLP] OpenGuard' 카테고리의 다른 글
| [DLP 개발기] 5. Minifilter 확장: 민감정보 패턴 검사와 파일 첨부 차단 (0) | 2026.05.08 |
|---|---|
| [DLP 개발기] 4. WFP DNS 차단의 한계와 유저모드 클립보드 제어 (0) | 2026.05.06 |
| [DLP 개발기] 2. 커널 드라이버와 IOCTL, 그리고 E2E 파이프라인 구축 (0) | 2026.04.29 |
| [DLP 개발기] 1. 커널 드라이버 기반 보안 시스템, OpenGuard 시작하기 (0) | 2026.04.29 |