구리의 창고

키보드, 마우스 디바이스 드라이버 동적 로딩하기 본문

Window Driver

키보드, 마우스 디바이스 드라이버 동적 로딩하기

구리z 2010. 3. 14. 10:43



디바이스 드라이버를 로딩하는 방법에는 두가지가 있다.

(1) 정적 로딩 :레지스트리에 드라이버 정보를 등록한 후 로딩한다. 레지스트리를 수정하기 때문에 반드시 재부팅이 필요하다.

 

(2) 동적 로딩 : 레지스트리를 쓰지않고, 서비스 메니져를 통해서 동적으로 로딩하는 방법. 재부팅이 필요하지 않다.

 

먼저 정적으로 로딩하는 방법은 아래와 같다.

 

1. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services 에 드라이버 정보를

등록한다.

 

 2. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class에서 내가 만든 필터 드라이버를 부착시킬 클래스 드라이버를 찾아낸 후, UpperFilters 부분에 내가 만든 필터 드라이버 클래스 이름을 추가한다.

 

 

 3. 재부팅을 실행하면 클래스 드라이버를 로딩하는 시점에 자동으로 내가 만든 필터

 드라이버도 따라서 로딩되게 된다.

 

 

 정적으로 로딩하는 방법은 레지스트리를 수정해야하고, 또 재부팅을 해야 하기 때문에

매우 불편하다.

 

다음은 우리가 사용한 동적 로딩 방법이다.

 

 1. 서비스 매니저의 핸들을 얻는다.

 2. 서비스를 하나 생성한다. 이때 드라이버의 바이너리 파일을 이용하여 서비스를 새로

생성한다.

 3. 2에서 생성시킨 서비스를 스타트 시킨다.

 4. 서비스 매니저의 핸들을 닫는다.

 

 코드는 아래와 같다.

 

   SC_HANDLE t;

   SC_HANDLE man;

   man = OpenSCManager(0,0,SC_MANAGER_ALL_ACCESS);//서비스 매니저를 연다.

   char driverPath[128];                         

   memset(driverPath,0,128);

   GetCurrentDirectory(128,driverPath);     //현재 디렉토리의 절대경로 패스를 알아낸다.

   strcat(driverPath,"\\drivers\\vkeyboard.sys");     

   t = CreateService(man, "vkbd.service" , "vkbd.service", SERVICE_ALL_ACCESS,

   SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,

   driverPath,0,0,0,0,0);             //서비스를 생성한다.

   

   IsStart = StartService(t,0,0);      //서비스를 시작한다.

   CloseServiceHandle(t);           //서비스의 핸들을 닫는다.

   CloseServiceHandle(man);        //서비스 매니저의 핸들을 닫는다.


   어플리케이션에서 CreateService를 실행하면, 드라이버에서는 DriverEntry 함수가

실행된다.

 

  이때 중요한 것은, 로딩되는 시점에 필터드라이버를 대상 클래스 드라이버에 붙여야

한다는 것이다.

 

 IoAttachDeviceToDeviceStack 함수를 사용하여 클래스 드라이버의 디바이스 오브젝트에

우리가 만든 필터 드라이버의 디바이스 오브젝트를 붙일 수 있다. 이 때 필요한 것이 클래스

 드라이버의 디바이스 오브젝트를 얻어오는 방법이다. 일반적으로 디바이스 드라이버의

심볼릭 링크를 통해서 얻어올 수 있다.

 

 IoGetDeviceObjectPointer() 함수를 이용하여 디바이스 오브젝트의 심볼릭 링크명을 이용

하여 디바이스 오브젝트를 얻어올 수있다. 예를 들어 마우스 디바이스 오브젝트를 얻어오기

위해서는 다음 단계를 거친다.

 1. "Device Tree" 프로그램을 이용하여 마우스 디바이스 오브젝트의 심볼릭 링크명을

알아온다.

 

 

  Mouse 의 클래스 드라이버는 Mouclass 이고, Mouclass의 디바이스 오브젝트의 심볼릭

링크 이름은  "PointerClass0","PointerClass1" 이라는 것을 알아냈다.

 

 2. 1에서 알아낸 심볼릭 링크를 이용하여 디바이스 오브젝트를 얻어온다. 코드는 아래와

같다.

 

PDEVICE_OBJECT pMouclassDeviceObject;

   UNICODE_STRING nameString;

   NTSTATUS tempStatus;

   NTSTATUS status;

   PFILE_OBJECT fileObject;

   RtlInitUnicodeString( &nameString, L"\\Device\\PointerClass0" );

   

   tempStatus = IoGetDeviceObjectPointer(

             &nameString,

             FILE_READ_ATTRIBUTES,

             &fileObject,

             &pMouclassDeviceObject );

 

   IoGetDeviceobjectPointer 를 이용하여 심볼릭 링크명 만으로 디바이스 오브젝트를 얻어

  올 수 있었다.

 

 

 3. 2에서 얻어진 클래스 드라이버의 디바이스 오브젝트에 필터 디바이스 오브젝트를 attach

한다.  코드는 아래와 같다.

      PDEVICE_OBJECT pDevObj;

      //mouclass 드라이버위에 부착시킬 필터 드라이버 디바이스 객체를 생성한다.

      //필터 드라이버이므로 심볼릭 링크가 필요하지 않다.

      status = IoCreateDevice(

                                         pDrvObj, sizeof(DEVEXT),

                                         NULL, FILE_DEVICE_MOUSE, 0, FALSE, &pDevObj);

      g_pDeviceObject2 = pDevObj;

      if( !NT_SUCCESS(status) ) return status;

      RtlZeroMemory( pDevObj->DeviceExtension, sizeof(DEVEXT) );

      

      DEVEXT *pDevExt = (DEVEXT *)pDevObj->DeviceExtension;

      //새롭게 생성한 필터 디바이스를 타겟 디바이스(mouclass)위로 쌓아 올린다.

      pDevExt->pNextDevObj = IoAttachDeviceToDeviceStack( pDevObj, pMouclassDeviceObject);//타겟      디바이스의 포인터를 획득한다.

      //타겟 드라이버와 동일한 버퍼링 정책을 가지도록 플래그를 셋팅한다.

      pDevObj->Flags |= (DO_BUFFERED_IO | DO_POWER_PAGABLE);

      pDevObj->Flags &= ~DO_DEVICE_INITIALIZING; 

 

      ObDereferenceObject( fileObject );

 

 

 이렇게 하면 필터를 클래스 드라이버에 적용시킬 수 있다. 하지만, 이 방식에는 결정적인

문제가 있다.

 

  P/S2 방식의 마우스나 키보드에서는 확실히  동작하나, USB 방식에서는 필터가 동작하지

않는다.

 

 그 이유는 아래 디바이스 트리 그림으로 설명할 수 있다.


 위의 디바이스 트리를 보면 키보드의 클래스 드라이버인 kbdclass 의 디바이스 오브젝트

들로  KeyboardClass0,1,2 가 생성된 것을 알 수있다. 뒤에 붙은 숫자가 클 수록 나중에 생성

되었다는 것을 의미한다.

  그리고 USB 키보드를 담당하는 kbdhid 클래스 드라이버를 보면 아래 이름없는 디바이스

오브젝트가 하나 생성되어 있고, 그 디바이스 오브젝트에 KeyboardClass2 디바이스 오브젝

트가 필터로 걸려 있는 것을 확인할 수 있다. 즉, 우리가 만든 필터를 USB 방식에도 적용하기

위해서는 KeyboardClass2에 필터를 적용해야한다는 결론을 얻게 된다. 하지만 키보드가

여러개 달려 있는 환경까지 고려한다면 모든 키보드 디바이스 오브젝트에 필터를 붙여야

한다. 물론 정적 로딩을 하게 되면 이런 문제는 발생하지 않는다. 부팅시 자동으로 모든 디바

이스 오브젝트에 필터로 붙여 주기 때문이다. 동적 로딩시 나타나는 이런 문제를 해결하기

위해서는 방식을 다음과  같이 바꿔야 한다.

 

 키보드 필터를 예로 들겠다.

 

 1. "Device Tree" 프로그램을 이용하여 키보드 클래스 드라이버의 심볼릭 링크명을 알아

온다.

"디바이스 오브젝트의 심볼릭 링크명" 이 아닌 "클래스 드라이버의 심볼릭 링크명" 임에

주의해야한다.

 

 우리는 이미 "kbdclass" 라는 심볼릭 링크명을 알고 있다.

 

 2. 드라이버 오브젝트의 심볼릭 링크명을 이용하여, 드라이버 오브젝트를 획득해야 한다.

 이 작업을 위해서는 아래와  같은 함수를 직접 제작해야 했다.

 

//심볼릭 링크에 대한 오브젝트를 구하는 함수

PVOID SearchObject(PUNICODE_STRING pUni)

{

   NTSTATUS st;

   HANDLE Handle;

   UNICODE_STRING Uni;

   OBJECT_ATTRIBUTES ObjectAttributes;

   PVOID Object;

 

   InitializeObjectAttributes

      (&ObjectAttributes,

      pUni,

      OBJ_CASE_INSENSITIVE,

      NULL,

      NULL);

 

   st = ObOpenObjectByName

      (&ObjectAttributes,

             0L,

             0L,

             0L,

             0L,

             0L,

             &Handle);

 

   if(st!= STATUS_SUCCESS)

   {

      DbgPrint(( "ObOpenObjectByName - ObOpenObjectByName failed \n"));

      return (PVOID)0;

   }

      

 

   st = ObReferenceObjectByHandle

      (Handle,

      0x80000000,

      NULL,

      0,

      &Object,

      NULL);//Handle을 object로 변환

 

   if(st != STATUS_SUCCESS)

   {

      DbgPrint(( "ObReferenceObjectByHandle - ObReferenceObjectByHandle failed \n"));

      ZwClose(Handle);

      return (PVOID)0;

   }

 

   ZwClose(Handle);

   ObDereferenceObject(Object);

   return Object;                                                    

}

      DbgPrint(( "ObReferenceObjectByHandle - ObReferenceObjectByHandle failed \n"));

      ZwClose(Handle);

      return (PVOID)0;

   }

 

   ZwClose(Handle);

   ObDereferenceObject(Object);

   return Object;                                                    

}

 

 

  특히 이 함수 내에서 쓰인 ObOpenObjectByName() 함수는 MS에서 공개하지 않은 함수

이다.

공개되지 않은 함수를 사용하기 위해서는 소스 맨 위 선언부에 다음과 같이 직접 선언을

해주어야 한다.

 

extern "C"{

#include <ntddk.h>

#include <ntddkbd.h>

#include "../common/def.h"

 

NTSTATUS

ObOpenObjectByName(

  IN POBJECT_ATTRIBUTES  ObjectAttributes,

  IN POBJECT_TYPE  ObjectType,

  IN OUT PVOID  ParseContext  OPTIONAL,

  IN KPROCESSOR_MODE  AccessMode,

  IN ACCESS_MASK  DesiredAccess,

  IN PACCESS_STATE  PassedAccessState,

  OUT PHANDLE  Handle);

 

}

 

 이제 만들어놓은 SearchObject 함수를 이용하여, 드라이버 오브젝트를 얻어온다.

 

 

   PDEVICE_OBJECT pKbclassDeviceObject;

   PDRIVER_OBJECT pKbclass;

   UNICODE_STRING nameString; 

   NTSTATUS status;

   

   RtlInitUnicodeString( &nameString, L"\\Driver\\Kbdclass" );//kbdclass의 심볼릭 링크명

   pKbclass = (PDRIVER_OBJECT)SearchObject(&nameString);     

 

 

 3. 2에서 구한 드라이버 오브젝트의 모든 디바이스 오브젝트를 찾아서 우리가 작성한 필터

를 붙인다.   소스는 아래와 같다.

      pKbclassDeviceObject = pKbclass->DeviceObject;//가장 최근에 생성된 디바이스 오브젝트를 얻어온다.

      for(int i=0;i<MAX_DEVICE_OBJECT_NUM;i++)//디바이스 객체가 있는 만큼 필터를 생성한다.

      {

             PDEVICE_OBJECT pDevObj;

             //mouclass 드라이버위에 부착시킬 필터 드라이버 디바이스 객체를 생성한다.

             //필터 드라이버이므로 심볼릭 링크가 필요하지 않다.

             status = IoCreateDevice(

                                                pDrvObj, sizeof(DEVEXT),

                                                NULL, FILE_DEVICE_MOUSE, 0, FALSE, &pDevObj);

             g_pFilterDeviceObject[i] = pDevObj;

             if( !NT_SUCCESS(status) ) return status;

             RtlZeroMemory( pDevObj->DeviceExtension, sizeof(DEVEXT) );

             

             DEVEXT *pDevExt = (DEVEXT *)pDevObj->DeviceExtension;

             //새롭게 생성한 필터 디바이스를 타겟 디바이스(mouclass)위로 쌓아 올린다.

            pDevExt->pNextDevObj = IoAttachDeviceToDeviceStack( pDevObj, pKbclassDeviceObject);//타겟 디바이스의 포인터를 획득한다.

            //타겟 드라이버와 동일한 버퍼링 정책을 가지도록 플래그를 셋팅한다.

             pDevObj->Flags |= (DO_BUFFERED_IO | DO_POWER_PAGABLE);

             pDevObj->Flags &= ~DO_DEVICE_INITIALIZING;

 

             if(pKbclassDeviceObject->NextDevice == NULL)//다른 디바이스 오브젝트가 없는 경우

             {

                    break;

             }

             pKbclassDeviceObject = pKbclassDeviceObject->NextDevice;

      }

             pDevObj->Flags |= (DO_BUFFERED_IO | DO_POWER_PAGABLE);

             pDevObj->Flags &= ~DO_DEVICE_INITIALIZING;

 

             if(pKbclassDeviceObject->NextDevice == NULL)//다른 디바이스 오브젝트가 없는 경우

             {

                    break;

             }

             pKbclassDeviceObject = pKbclassDeviceObject->NextDevice;

      }

 

 

 드라이버 오브젝트의 NextDevice를 계속해서 찾아내어 가장 최근에 생성된 것부터 가장

 먼저 생성 된 것까지 모든 디바이스 오브젝트를 찾아내어 필터를 붙인다. 필터를 붙인 결과

를 디바이스 트리에서 다시 확인 할 수 있다.

 

 

 kbdclass의 모든 디바이스 오브젝트들에 필터가 생성된 것을 볼 수있고, kbdhid 클래스

드라이버의 디바이스 오브젝트에 붙은 kbdclass의 디바이스 오브젝트에도 필터가 동작하고

있음을 알 수 있다. 이 방식을 사용하면 PS/2 나 USB 방식의 모든 장치에 필터를 붙일 수

있게 된다.



출처 : http://blog.naver.com/PostView.nhn?blogId=hinghihi&logNo=50008719760

Comments