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

드라이버 개발 핵심 - 13화 IRP 디스패치 루틴 구현

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

IRP 디스패치 루틴 구현 — DeviceIoControl 완전 정복

📅 2026 ⏱ 읽기 약 14분 🏷 DeviceIoControl / IOCTL / 디스패치 루틴
드라이버와 앱이 대화하는 가장 일반적인 방법이 DeviceIoControl이에요. 앱이 "이 명령을 실행해줘"라고 보내면 드라이버가 처리하는 구조인데, 커스텀 IOCTL 코드를 정의하는 방법부터 드라이버에서 받아서 처리하는 것까지 예제와 함께 살펴볼게요.

IOCTL 코드란?

IOCTL(I/O Control)은 앱이 드라이버에 임의의 명령을 보내는 메커니즘이에요. 파일 읽기/쓰기처럼 정해진 동작이 아니라, 드라이버가 정의하는 커스텀 명령이에요. 예를 들어:

  • 안티바이러스 드라이버: "이 프로세스 정보 알려줘"
  • GPU 드라이버: "이 그래픽 명령 실행해줘"
  • 디스크 드라이버: "이 섹터 암호화해줘"

IOCTL 코드는 32비트 값이고, CTL_CODE 매크로로 만들어요:

// 공유 헤더 파일 (드라이버와 앱이 둘 다 include) #define IOCTL_MYDRV_GET_VERSION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_MYDRV_DO_SOMETHING CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) // CTL_CODE(DeviceType, Function, Method, Access) // DeviceType: 디바이스 종류 (0x8000 이상이면 커스텀) // Function: 명령 번호 (0x800 이상이면 커스텀) // Method: 버퍼 전달 방식 // Access: 필요한 접근 권한

Method (버퍼 전달 방식)

Method특징In 버퍼Out 버퍼
METHOD_BUFFERED가장 간단하고 안전. 커널이 자동으로 복사SystemBufferSystemBuffer
METHOD_IN_DIRECT입력: 커널 복사, 출력: MDL 직접 매핑SystemBufferMdlAddress
METHOD_OUT_DIRECT입력: MDL, 출력: 커널 복사MdlAddressSystemBuffer
METHOD_NEITHER둘 다 유저 포인터 직접 사용 (위험)Type3InputBufferUserBuffer
💡 처음엔 METHOD_BUFFERED만 쓰세요 METHOD_BUFFERED는 커널이 알아서 버퍼를 복사해주기 때문에 실수할 여지가 가장 적어요. 성능이 중요한 드라이버에서는 다른 방식을 고려하지만, 입문 단계에서는 BUFFERED로 충분합니다.

드라이버 쪽 구현 — DeviceIoControl 처리 루틴

NTSTATUS DispatchIoControl( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); ULONG ioControlCode = stack->Parameters.DeviceIoControl.IoControlCode; PVOID buffer = Irp->AssociatedIrp.SystemBuffer; // METHOD_BUFFERED ULONG inBufLen = stack->Parameters.DeviceIoControl.InputBufferLength; ULONG outBufLen = stack->Parameters.DeviceIoControl.OutputBufferLength; NTSTATUS status = STATUS_SUCCESS; ULONG_PTR info = 0; switch (ioControlCode) { case IOCTL_MYDRV_GET_VERSION: { if (outBufLen < sizeof(ULONG)) { status = STATUS_BUFFER_TOO_SMALL; break; } ULONG version = 0x00010000; // v1.0 RtlCopyMemory(buffer, &version, sizeof(ULONG)); info = sizeof(ULONG); break; } case IOCTL_MYDRV_DO_SOMETHING: { // 입력 데이터 처리 예시 if (inBufLen < sizeof(MY_INPUT_STRUCT)) { status = STATUS_INVALID_PARAMETER; break; } PMY_INPUT_STRUCT input = (PMY_INPUT_STRUCT)buffer; DbgPrint("[MyDrv] Got command: %d\n", input->Command); // ... 처리 로직 break; } default: status = STATUS_INVALID_DEVICE_REQUEST; break; } Irp->IoStatus.Status = status; Irp->IoStatus.Information = info; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; }

앱 쪽 구현 — DeviceIoControl 호출

// 유저 모드 앱 #include <windows.h> #include "shared.h" // IOCTL 코드 공유 헤더 int main() { HANDLE hDevice = CreateFile( L"\\\\.\\MyDevice", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hDevice == INVALID_HANDLE_VALUE) { printf("Open failed: %d\n", GetLastError()); return 1; } // IOCTL_MYDRV_GET_VERSION 호출 ULONG version = 0; DWORD returned = 0; BOOL ok = DeviceIoControl( hDevice, IOCTL_MYDRV_GET_VERSION, // IOCTL 코드 NULL, 0, // 입력 버퍼 (없음) &version, sizeof(version), // 출력 버퍼 &returned, NULL); // NULL = 동기 I/O if (ok) { printf("Driver version: 0x%08X\n", version); } CloseHandle(hDevice); return 0; }

DriverEntry에서 DeviceIoControl 핸들러 등록

// DriverEntry에 추가 DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreateClose; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoControl; // ← 추가

입력 검증을 항상 하세요

유저 모드에서 넘어온 버퍼 크기와 내용은 절대 믿으면 안 돼요. 악의적인 앱이나 버그 있는 앱이 이상한 값을 보낼 수 있기 때문에, 드라이버는 방어적으로 작성해야 합니다:

  • 입력 버퍼 길이가 기대하는 구조체 크기 이상인지 확인
  • 출력 버퍼 길이가 돌려줄 데이터보다 크거나 같은지 확인
  • 포인터 필드가 있다면 ProbeForRead/ProbeForWrite로 접근 가능한지 검증
  • 정수 오버플로우 가능성 고려 (예: Length + Offset이 최대값을 넘는 경우)
⚠️ 커널 드라이버 취약점의 대부분이 입력 검증 미흡이에요 권한 상승(Privilege Escalation) 공격의 많은 부분이 드라이버의 IOCTL 핸들러에서 입력을 제대로 검증하지 않아 발생해요. 방어적 프로그래밍 습관을 처음부터 들이세요.

✅ 13화 요약
  • IOCTL 코드CTL_CODE 매크로로 정의하고, 드라이버와 앱이 공유 헤더로 공유합니다.
  • METHOD_BUFFERED가 가장 간단하고 안전한 버퍼 전달 방식이에요.
  • IoGetCurrentIrpStackLocation으로 IOCTL 코드와 버퍼 크기를 꺼내고, switch로 분기합니다.
  • 입력 버퍼 크기와 내용을 항상 검증하는 것이 보안의 기본입니다.
다음 화 — 드라이버 개발의 최대 난관!
14화 — IRQL 완전 정복: Interrupt Request Level의 모든 것 →

#DeviceIoControl #IOCTL #CTL_CODE #디스패치루틴 #입력검증