cgy12306

Windows Driver 본문

Windows/Driver

Windows Driver

cgy12306 2020. 12. 26. 00:00

디바이스 드라이버

  • 운영체제가 키보드, 마우스, 디스크, LAN카드 같은 디바이스 등과 상호작용할 수 있게 한 S/W입니다.

  • 디바이스 드라이버는 드라이버 공급업체에 의해 작성되어 제공되고 있습니다.(서드파티)

  • storahci라는 드라이버는 메모리에 디바이스 객체(Device Object)라는 자료구조를 만듭니다.

  • 디바이스 객체는 디바이스 드라이버가 직접 생성하며, 디바이스에 관여하고 있다는 의미입니다.

  • 필터 드라이버, 기능 드라이버, 버스 드라이버 세개를 생성하는데 이 세개를 묶어 디바이스 스택이라고 부릅니다.

  • 가장 먼저 버스 드라이버가 디바이스를 발견하면 디바이스 객체를 생성하고, 이후 기능 드라이버, 필터 드라이버 순으로 생성하게 됩니다.

  • 애플리케이션은 디바이스 드라이버에 접근을 시도하는데 이때 필터 드라이버를 가장 먼저 접근하게 됩니다.

  • 드라이버 객체(Driver Object)는 드라이버가 로드될 시 I/O manager가 드라이버 객체를 생성하게 됩니다.

typedef struct _DRIVER_OBJECT {
  CSHORT             Type;
  CSHORT             Size;
  PDEVICE_OBJECT     DeviceObject;
  ULONG              Flags;
  PVOID              DriverStart;
  ULONG              DriverSize;
  PVOID              DriverSection;
  PDRIVER_EXTENSION  DriverExtension;
  UNICODE_STRING     DriverName;
  PUNICODE_STRING    HardwareDatabase;
  PFAST_IO_DISPATCH  FastIoDispatch;
  PDRIVER_INITIALIZE DriverInit;
  PDRIVER_STARTIO    DriverStartIo;
  PDRIVER_UNLOAD     DriverUnload;
  PDRIVER_DISPATCH   MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT, *PDRIVER_OBJECT;

드라이버 오브젝트 중 디스패치 루틴 MajorFunction이 존재합니다.

14번째인 IRP_MJ_DEVICE_CONTROL을 통해 user는 커널과 통신을 할 수 있게 됩니다.

이 DispatchDeviceControl 루틴은 I/O control codes(IOCTL)로 어떤 루틴을 수행할지 구분합니다.

User는 커널과 통신하기 위해 IOCTL, Inputbufferlength, Outputbufferlength, systembuffer 등을 포함한 IRP 패킷을 보내게 됩니다.

IRP_MJ_DEVICE_CONTROL은 입력을 드라이버에게 전달되는 방식이 여러가지가 있습니다.

  • METHOD_BUFFERD
    METHOD_BUFFERED 방식은 Inputbuffer와 Outputbuffer를 모두 SystemBuffer를 통해 전달하거나 전달 받습니다.

    #define IOCTL    (ULONG) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
    ...
    inputbuffer = pIrp->AssociatedIrp.SystemBuffer;
    outputbuffer = pIrp->AssociatedIrp.SystemBuffer;
    ...
    RtlZeroMemory(outputbuffer, inputbufferlength);
    RtlCopyMemory(outputbuffer, H, strlen(H));
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = strlen(H);

    Inputbuffer는 애플리케이션 측에서 드라이버에게 전달할 내용이 들어있는 버퍼이고, Outputbuffer는 드라이버 측에서 애플리케이션 측으로 전달할 내용을 담을 버퍼입니다. 이 두개의 버퍼를 Systembuffer 하나만 이용해서 통신을 하는게 불가능 해보이지만 가능합니다.
    DeviceIoControl() 함수가 호출될 때 InputBuffer에 담겨진 내용을 우선 Systembuffer로 복사한 후 드라이버를 호출합니다. 그러면 드라이버는 Systembuffer에 담겨진 내용이 Inputbuffer와 동일하기 때문에 Systembuffer를 읽음으로써 Inpubuffer의 목적을 달성하게 됩니다. 그런 다음, 드라이버는 반대로 사용자에게 전달할 내용을 Systembuffer에 기록합니다. 그러면 I/O manager는 애플리케이션 측으로 돌아가는 과정 중에 Systembuffer에 들어있는 내용을 Outputbuffer로 복사를 합니다. 즉, 순서를 정해서 먼저 Inputbuffer를 처리하고 Outputbuffer를 처리하는 순서로 Systembuffer를 사용하게 됩니다.

  • METHOD_NEITHER

    #define IOCTL    (ULONG) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
    ...
    inputbuffer = irpstack->Parameters.DeviceIoControl.Type3InputBuffer;
    outputbuffer = pIrp->UserBuffer;
    ...
    RtlZeroMemory(outputbuffer, inputbufferlength);
    RtlCopyMemory(outputbuffer, H, strlen(H));
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = strlen(H);

    METHOD_NEITHER 방식은 METHOD_BUFFERED 방식과는 달리 Type3InputBuffer로 Inputbuffer를 받아오고 UserBuffer를 통해 Outputbuffer를 반환합니다.

드라이버 코딩을 할 때 가장 애먹었던 부분이 Outputbuffer가 출력이 안되었던 문제였습니다.

Outputbuffer를 출력하기 위해서는 pIrp->IoStatus.Information의 값을 Outputbuffer의 길이만큼을 주어야 반환이 됩니다.

다시 돌아와서 user는 IRP 통신을 하기 위해서는 DispatchDeviceControl 루틴을 타야하는데 위에서 잠깐 나왔던 DeviceIoControl을 통해 루틴을 탈 수 있고 이로 인해 커널과 통신이 가능하게 됩니다.

BOOL DeviceIoControl(
  HANDLE       hDevice,
  DWORD        dwIoControlCode,
  LPVOID       lpInBuffer,
  DWORD        nInBufferSize,
  LPVOID       lpOutBuffer,
  DWORD        nOutBufferSize,
  LPDWORD      lpBytesReturned,
  LPOVERLAPPED lpOverlapped
);
Comments