본문 바로가기

勉強/C,C++

문서 중앙화 DLP - Windows Minifilter 커널 드라이버 구현 (USB 차단, 파일 I/O 필터링, CDR 정사니타이징)

프로젝트 개요

문서 중앙화 DLP 시스템의 Windows 커널 레벨 파일 필터 드라이버입니다. Windows Filter Manager 프레임워크(Minifilter)를 사용해 파일 I/O를 커널에서 가로채어 USB 쓰기 차단, 네트워크 드라이브 복사 차단, CDR(Content Disarm & Reconstruction) 정책을 적용합니다.

GitHub: https://github.com/LEntropy/Document_Management

전체 DLP 구조

[커널 레벨]
DlpMiniFilter.sys (Minifilter 드라이버)
    ├─ IRP_MJ_CREATE       → 파일 열기 시 CDR 후보 마킹
    ├─ IRP_MJ_WRITE        → USB/네트워크 쓰기 차단
    ├─ IRP_MJ_SET_INFORMATION → Rename/Move 차단
    └─ Named Pipe 포트     → 유저레이어 에이전트와 통신

[유저 레벨]
DlpAgent.Service (C# Windows 서비스)
    ├─ NativeFltLib.cs     → FilterConnectCommunicationPort, FilterGetMessage
    ├─ PolicyEngine.cs     → DLP 정책 의사결정
    └─ PolicyModel.cs      → 정책 JSON 구조 (USB/CDR/프로세스 차단)

1. Minifilter 드라이버 등록 (DlpMiniFilter.c)

// Filter 등록 콜백 목록
const FLT_OPERATION_REGISTRATION Callbacks[] =
{
    {
        IRP_MJ_CREATE,
        0,
        DlpPreCreate,   // 파일 열기 전 처리 (CDR 후보 마킹)
        DlpPostCreate   // 파일 열기 후 처리
    },
    {
        IRP_MJ_WRITE,
        0,
        DlpPreWrite,    // 쓰기 전: USB/네트워크 차단
        NULL
    },
    {
        IRP_MJ_SET_INFORMATION,
        0,
        DlpPreSetInfo,  // Rename/Move 차단
        NULL
    },
    { IRP_MJ_OPERATION_END }
};

// DriverEntry: Filter Manager에 드라이버 등록
NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING registryPath)
{
    FLT_REGISTRATION reg = { ... };
    NTSTATUS st = FltRegisterFilter(driverObject, &reg, &gFilter);
    if (!NT_SUCCESS(st)) return st;
    return FltStartFiltering(gFilter);
}

2. 파일 위치 분류 - 볼륨 타입 판별

파일이 로컬 디스크인지, USB(이동식 미디어)인지, 네트워크 드라이브인지를 커널에서 판별합니다.

typedef enum _DLP_LOCATION_TYPE {
    DlpLocUnknown = 0,
    DlpLocLocal,        // 내부 하드디스크
    DlpLocRemovable,    // USB 등 이동식 미디어
    DlpLocNetwork       // SMB/NFS 네트워크 드라이브
} DLP_LOCATION_TYPE;

static DLP_LOCATION_TYPE DlpClassifyInstanceVolume(PFLT_INSTANCE Instance)
{
    PFLT_VOLUME_PROPERTIES props = ...;
    FltGetVolumeProperties(vol, props, size, &size);

    if (props->DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM ||
        props->DeviceType == FILE_DEVICE_NETWORK)
        return DlpLocNetwork;   // 네트워크 드라이브

    if (props->Characteristics & FILE_REMOVABLE_MEDIA)
        return DlpLocRemovable; // USB/SD카드

    return DlpLocLocal;         // 내장 디스크
}

3. CDR 후보 파일 마킹 (PreCreate)

파일이 열릴 때(.docx, .xlsx, .pptx, .hwp 등) CDR 후보로 마킹합니다. Word 임시파일(~$, .tmp, .asd)은 제외합니다.

static BOOLEAN DlpIsCdrCandidatePath(PCWSTR Path)
{
    if (DlpIsInAgentWorkingDir(Path)) return FALSE;  // 에이전트 작업 폴더 제외
    if (DlpIsOfficeTempLikePath(Path)) return FALSE; // Office 임시파일 제외

    const WCHAR* exts[] = { L".docx", L".xlsx", L".pptx", L".txt",
                             L".hwp", L".hwpx", L".enc" };
    for (UINT32 i = 0; i < RTL_NUMBER_OF(exts); i++)
        if (DlpEndsWithExt(&name, exts[i])) return TRUE;

    return FALSE;
}

// Office 임시파일 패턴 탐지
static BOOLEAN DlpIsOfficeTempLikePath(PCWSTR Path)
{
    if (name[0] == L'~' && name[1] == L'$') return TRUE; // Word: ~$document.docx
    if (name[0] == L'.' && name[1] == L'~') return TRUE; // LibreOffice lock 파일
    if (DlpEndsWithExt(&u, L".tmp"))  return TRUE;
    if (DlpEndsWithExt(&u, L".asd"))  return TRUE; // Word 자동 저장
    if (DlpEndsWithExt(&u, L".wbk"))  return TRUE; // Word 백업
    return FALSE;
}

// StreamHandle Context에 CDR 후보 여부 저장
typedef struct _DLP_HANDLE_CONTEXT {
    BOOLEAN IsProtected;
    BOOLEAN IsCdrCandidate;     // CDR 처리 대상 여부
    BOOLEAN SnapshotValid;
    LARGE_INTEGER InitialEof;
    LARGE_INTEGER InitialLastWriteTime;
} DLP_HANDLE_CONTEXT;

4. USB 쓰기 차단 (PreWrite)

FLT_PREOP_CALLBACK_STATUS DlpPreWrite(
    PFLT_CALLBACK_DATA Data,
    PCFLT_RELATED_OBJECTS FltObjects,
    PVOID* CompletionContext)
{
    DLP_LOCATION_TYPE loc = DlpClassifyInstanceVolume(FltObjects->Instance);

    if (loc == DlpLocRemovable && gPolicy.BlockRemovableWrite)
    {
        // USB 쓰기 차단: STATUS_ACCESS_DENIED 반환
        Data->IoStatus.Status = STATUS_ACCESS_DENIED;
        Data->IoStatus.Information = 0;

        // 유저 에이전트에 이벤트 전송 (Named Pipe)
        DlpSendEventToAgent(Data, DlpEventUsbWriteBlocked);

        return FLT_PREOP_COMPLETE; // IRP 차단 (하위 드라이버로 전달 안함)
    }

    return FLT_PREOP_SUCCESS_NO_CALLBACK;
}

5. Named Pipe 통신 (드라이버 ↔ C# 서비스)

드라이버가 차단 이벤트나 CDR 요청을 유저레이어 서비스로 전달하고, 서비스의 응답에 따라 허용/차단을 결정합니다.

// 드라이버 쪽: 포트 생성
FltCreateCommunicationPort(
    gFilter, &gServerPort,
    &portName,           // L"\\DlpPort"
    NULL, NULL,
    DlpConnectNotify,   // 클라이언트(서비스) 연결 시
    DlpDisconnectNotify,
    DlpMessageNotify,   // 메시지 수신 시
    1                   // 최대 연결 수
);

// C# 서비스 쪽: fltlib.dll P/Invoke
[DllImport("fltlib.dll", CharSet = CharSet.Unicode)]
public static extern int FilterConnectCommunicationPort(
    string lpPortName,       // "\\.\\DlpPort"
    uint dwOptions,
    IntPtr lpContext,
    ushort wSizeOfContext,
    IntPtr lpSecurityAttributes,
    out SafeFileHandle hPort
);

// 드라이버 메시지 수신
[DllImport("fltlib.dll")]
public static extern int FilterGetMessage(
    SafeFileHandle hPort,
    IntPtr lpMessageBuffer,
    uint dwMessageBufferSize,
    IntPtr lpOverlapped
);

// 드라이버에 응답 (허용/차단)
[DllImport("fltlib.dll")]
public static extern int FilterReplyMessage(
    SafeFileHandle hPort,
    IntPtr lpReplyBuffer,
    uint dwReplyBufferSize
);

6. DLP 정책 모델 - PolicyModel.cs

public sealed class DlpPolicy
{
    public bool BlockNetworkRename   { get; set; } = false; // 네트워크 드라이브 Rename 차단
    public bool BlockRemovableWrite  { get; set; } = false; // USB 쓰기 차단
    public bool BlockDelete          { get; set; } = false; // 파일 삭제 차단

    public List<string> BlockProcesses { get; set; }  // 차단할 프로세스 목록
    public List<string> AllowProcesses { get; set; }  // 허용할 프로세스 목록

    public CdrPolicy CDR { get; set; } = new CdrPolicy();
}

public sealed class CdrPolicy
{
    public bool Enabled { get; set; } = true;
    public List<string> FileTypes { get; set; } =
        new List<string> { "docx","xlsx","pptx","txt","hwp","hwpx" };

    public bool BlockUntilSanitized { get; set; } = false; // 정화 전 열람 차단
    public CdrRedirectPolicy Redirect { get; set; };
    public CdrLocalPaths Local { get; set; };
}

public sealed class CdrLocalPaths
{
    public string QuarantineDir { get; set; } = @"C:\ProgramData\DlpAgent\Quarantine";
    public string SanitizedDir  { get; set; } = @"C:\ProgramData\DlpAgent\Sanitized";
    public string ReportsDir    { get; set; } = @"C:\ProgramData\DlpAgent\CdrReports";
    public int    MaxFileMB     { get; set; } = 50;
}

7. 드라이버 배포 방법

-- deploy/ 폴더 구조 --
payload/
  driver/
    DlpMiniFilter.sys   ← 드라이버 파일
    DlpMiniFilter.inf   ← 설치 정보 파일
    DlpMiniFilter.cat   ← 코드 서명 카탈로그
  service/
    DlpAgent.Service.exe
    DlpAgent.Common.dll

-- 설치 스크립트 (install_pnputil.bat) --
@echo off
pnputil /add-driver payload\driver\DlpMiniFilter.inf /install

-- 수동 설치 (install_manual_driver.bat) --
sc create DlpMiniFilter type= kernel start= demand binPath= "%~dp0payload\driver\DlpMiniFilter.sys"
sc start DlpMiniFilter

-- 제거 (uninstall_all.bat) --
sc stop DlpMiniFilter
sc delete DlpMiniFilter

8. NT 경로 ↔ DOS 경로 변환 (QueryDosDevice)

커널 드라이버는 NT 경로(\Device\HarddiskVolume3\...)를 사용하지만, 유저 레이어는 DOS 경로(C:\...)를 씁니다. 이 변환이 필요합니다.

// C# 서비스에서 NT 경로를 DOS 경로로 변환
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern uint QueryDosDevice(
    string lpDeviceName,
    StringBuilder lpTargetPath,
    int ucchMax);

public static string QueryDosDevice(string deviceName)
{
    string dn = deviceName.Trim().TrimEnd('\\');
    if (dn.Length >= 2 && dn[1] == ':' && dn.Length > 2)
        dn = dn.Substring(0, 2);

    var sb = new StringBuilder(1024);
    QueryDosDevice(dn, sb, sb.Capacity);
    return sb.ToString();
    // "C:" → "\Device\HarddiskVolume3"
}

undefined