Programming/Kernel / Driver2008. 12. 5. 13:11

작업 관리자를 보면 현재 실행되고 있는 프로세스들이 있습니다.

이 프로세스들은 아주 간단한 손터치로 인해 강제 종료를 손쉽게 할 수 있습니다.

 

 

자 그럼 작업 관리자를 통해서 프로세스 강제 종료 보호하는 방법은 어떤 것 들이 있을까요?

 

제가 설명드리고자 하는 방법은 ZwOpenProcess()함수를 후킹하는 방법에 소개를 하겠습니다.

(SSDT 후킹 방법은 제 블로그에 올린 강좌나 다른 인터넷 자료를 참고하세요...^^;)

 

ZwOpenProcess()함수를 후킹하여 해당 함수의 리턴값을 "STATUS_INVALID_CID"로

리턴하면 됩니다.

 

아주 쉽죠? 이렇게 하면 다음 화면처럼 해당 프로세스를 강제 종료하지 못합니다.

 

 

참고) ZwOpenProcess()함수를 후킹하면 나쁜 목적으로 해당 프로세스에 접근하여 메모리를

        참조하는 것 또한  방지가 됩니다.

Posted by skensita
Programming/Kernel / Driver2008. 12. 5. 13:00
WDK 란 Windows Driver Kit 의 약자로서, WDM 을 개발하기 위한 도구입니다.
즉, WDK 를 설치해야 windows device driver 를 개발할 수 있습니다.
WDK 이전 버전으로는 DDK (Device Driver Kit) 라는게 있습니다.
DDK 는 아래 사이트에서 간단히 다운로드 받을 수 있습니다.
http://www.microsoft.com/whdc/DevTools/ddk/default.mspx

우리는 WDM 을 개발할 것이므로 WDK 를 설치하겠습니다.
WDK 다운로드는 아래 과정을 참고하세요.

1. https://connect.microsoft.com/directory/ 에 접속합니다.



2. 화면 우측 상단의 '로그인' 을 클릭한 후, Microsoft Live ID 를 이용하여 로그인 합니다.
보통은 MSN 아이디로 로그인 하면 되나, 아이디가 없는 경우 '지금 등록' 버튼을 눌러서 등록한 후 로그인하면 됩니다.



3. 로그인을 한 후, 연결 디렉터리 ( https://connect.microsoft.com/directory/ ) 페이지에서
ctrl + f 를 눌러 wdk 를 입력합니다. 즉, 해당 웹페이지에서 wdk 가 있는 부분을 찾습니다.



4. Windows Driver Kit (WDK), Windows Logo Kit (WLK) and Windows Driver Framework (WDF) 에 있는 '지금 신청' 을 클릭합니다.

5. '등록 정보 입력' 화면에서 '계속' 을 클릭합니다.

6. '사용 약관' 화면에서 '동의함' 을 클릭합니다.

7. '등록' 화면에 정보를 입력합니다.
내 이름, 위치/국가, 내 전자 메일 주소 등을 입력한 후, '계속' 을 클릭합니다.
위치/국가는 '대한민국'을 선택합니다.

8. '전자 메일 주소 소유자 확인' 화면에서 '계속' 을 누릅니다.

9. 'Connect에 오신 것을 환영합니다!' 화면에서 닉네임을 입력하고 '계속' 을 클릭합니다.

10. 그럼 아래와 같이 'Windows Driver Kit (WDK), Windows Logo Kit (WLK) and Windows Driver Framework (WDF) 홈' 페이지가 나옵니다.



11. 왼쪽 메뉴에서 '다운로드'를 클릭합니다.

12. 'WDK SP1 for Windows Server 2008 SP1' 을 클릭합니다.

13. '단일 파일 다운로드' 아래에 있는 '다운로드'를 클릭해서 다운로드 받으세요.



보시다 시피 WDK 를 다운로드 받는 과정이 상당히 번거롭습니다.


출처 : http://seoz.egloos.com/1833736
Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 15:24

드라이버를 개발할 때 필요한 유틸리티 중에 하나가 바로 InstDrv.exe 라는 녀석입니다. 개발된 드라이버를 테스트 해볼 때 INI파일 없이 간단하게 드라이버를 설치하고 실행해볼수 있도록 해주는 유틸입니다.

  

사용자 삽입 이미지

위의 경로에 전체경로를 적어주고 Install 버튼을 누르면 드라이버가 설치가 되고 Start를 누르면 드라이버가 로딩이 됩니다. (DriverEntry 호출되는 시점이 여기입니다. ^^)

참 편리한 프로그램인데 한가지 불편한 점은 전체경로를 적는 것입니다. 요즘 같은 환경에 누가 전체경로를 타이핑하고 있나~ 라는 생각에 이 프로그램에 파일다이얼로그를 추가해서 마우스 클릭으로 파일을 선택할수 있도록 해보자는 생각으로 아래처럼 만들어 봤습니다.
 

사용자 삽입 이미지

 

기능은 똑같구요 에디트 옆에 버튼을 하나 두어서 파일다이얼로그를 띄어서 드라이버 파일을 선택할수 있도록 하였습니다. ^^  원래 유틸은 파일 크기가 30KB인데 파일다이얼로그와 about 창이 추가되어서 44KB로 크기가 조금 늘었습니다. 이정도야 조금 편리해 지는데 따른 coast 치고는 싼편이죠.. 이것말고도 프로그램 개발하는데 들어간 시간이 있었지만요.. ㅋ

앞으로 파일 전체 경로를 쓰는 번거로움이 조금 사라지면 개발시간은 충분히 보상되리라 생각됩니다. (반나절 정도 걸렸네요 ^^)

 

드라이버 개발시에 유용한 Device Tree라는 도구도 있습니다. 현재 컴퓨터에 로딩되어 있는 드라이버들의 목록을 보여주는 것인데, DDK를 설치하면 그안에 포함되어 있습니다.


사용자 삽입 이미지
 

 

새로 만든 InstDriver.exe를 가지고 드라이버 로딩 테스트를 하니까 위의 Device Tree에서 잘 보입니다.

 

참고로 드라이버를 설치하고 실행시키는 방법을 코드로 작성시에는 SCManager(스컴매니져)를 이용하여 서비스로 드라이버를 실행시켜주는 방법을 사용하였습니다. OpenSCManager(), OpenService(), StartService(), ControlService(), DeleteService() 등의 함수를 사용하였습니다.

 

두개의 파일을 모두 올려봅니다.


Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 15:19
제가 자주 방문하는 사이트에서 WDF에 대해서 좋은 글이 있어서 올려 놓습니다.

사이트 주소는 www.insidekernel.net 입니다. 참고로 이 사이트 주인장 레벨이 초고수 이십니다. ^^


첫번째 순서로 아키텍쳐나 WDF 드라이버를 실제로 제작해보기에 앞서 WDF가 무엇인지 간략히 살펴보는 시간을 갖도록 합니다. 참고로 이 글은 whitepaper 중 ‘WDF Introduction’이란 문서를 번역한 것입니다. 인터넷의 다른 WDF 소개글과 중복되는 내용이 있으나, 이후의 강좌글에 대한 이해와 일관성을 위하여 우선적으로 첫번째 강좌글로서 소개합니다.
현재의 기술 - WDM 이란?

윈도우는 현재 디바이스 드라이버 개발을 위해 ‘WDM(Windows Driver Model)’이라는 모델을 지원하고 있습니다. 그리고 작성하려는 특정한 디바이스 종류에 따라서 device-class specific한 드라이버 모델을 따로 가지고 있기도 합니다. 구체적으로 나열하자면, WDM은 비동기 I/O(asynchronous I/O), 드라이버 레이어링, plug and play, power management, WMI 같은 것들을 포함하고 있습니다.

device-class specific driver model은 크게 두 종류로 구분할 수 있습니다.

클래스 드라이버 = 포트 드라이버(MS가 제작) + 미니포트 드라이버(하드웨어 벤더가 제작)

  • 포트 드라이버는 디바이스 클래스에 필요한 많은 작업을 함.
    미니포트 드라이버는 드라이버 specific한 것을 지원.

예를 들어보자면, MS가 제공하는 SCSI 포트 드라이버는 SCSI 디바이스를 위한 기본적인 요구사항들을 처리하고, SCSI 미니포트 드라이버는 디바이스 specific한 작업들을 지원합니다.

윈도우가 새로운 디바이스 클래스들을 추가로 지원하면서, 미니포트 모델도 늘어났고, 현재 윈도우는 열개가 넘는 미니포트 모델을 지원하고 있습니다.
WDM의 한계
1. WDM 드라이버를 작성하는 것은 로우레벨 작업이고 또한 매우 복잡하다.

예를 들어서, 현재의 WDM 모델에서 드라이버를 작성할 때 plug and play와 power management를 지원하려면 최소한 2000 라인이 필요합니다. 또한 power management, pnp, 동기화, 비동기 i/o 를 쓰기위한 DDI(device driver interface)역시 무척 쓰기가 어렵습니다. 완벽한 pnp 와 power management state 엔진을 구현하기 위해선 최소 100가지 state를 지원해야 합니다.
2. DDI가 구식이다

DDI가 처음 디자인 될 때에는 지금과는 상황이 많이 달랐기 때문에, 현재의 요구사항과는 좀 차이가 있습니다. 몇년전 처음으로 DDI를 디자인 할 때에는, MS는 써드파티 드라이버 개발 업체를 참여시키지 않았다고 합니다. 그래서 DDI는 커널로부터 곧바로 export 되었으며, 오직 퍼포먼스 만을 위해 디자인되었고, 보안이나 사용의 편의성 같은건 고려되지 않았습니다. 그리하여 DDI는 드라이버들에게 운영체제의 핵심적인 데이터 구조들을 곧바로 노출하고, 결과적으로 MS는 이런 구조체들의 변경이 제한될 수 밖에 없었고, 드라이버가 시스템을 크래쉬 시킬 확률도 높아진 것입니다.

또한 Versioning 지원이 없어서, 윈도우의 버전이 달라지면 드라이버도 다른 바이너리를 사용해야 하거나, 가장 오래된 윈도우 버전에서 사용가능한 기능만을 써서 만들어야 했습니다.

3. 미니포트 모델이 너무 많다.

윈도우는 현재 열개 이상의 미니포트 모델을 지원하고 있습니다. 미니포트 모델마다 pnp와 power management를 다루는 방법이 다릅니다. 미니포트 인터페이스는 운영체제에 새로운 기능이 추가될 때라던가, 새로운 하드웨어 기술이 나올때마다 새로 업데이트 되어야 합니다. 결과적으로, 미니포트 인터페이스는 자주 옛날 기술이 됩니다.

4. 멀티펑션 디바이스를 지원하는 작업은 너무 복잡하다.

두가지의 다른 미니포트 모델에 적용되는 멀티펑션 디바이스를 지원하기 위해서, 개발자는 WDM 버스 드라이버와 두개의 미니포트 드라이버 작성이 필요합니다.

5. 현재 드라이버 모델은 커널 레벨에서만 동작해야 한다.

WDM과 미니포트 드라이버 모델은 커널모드에서 실행되고, 커널 모드 드라이버는 운영체제의 일부로 대접받게 되므로, 시스템의 가상 메모리 영역에 접근권한을 가지고 있습니다. 그래서 커널 모드 드라이버에서 에러가 발생하면 바로 시스템을 크래쉬 시키는 BSOD를 겪게 되지요.

오늘날의 많은 디바이스들은 인터럽트나, 엄격하고 긴급한 타이밍 관리나, 하드웨어 레지스터를 직접 건드릴 일도 없습니다. 이런 드라이버들은 유저모드에서 동작해도 충분합니다.

6. 테스트 툴들이 쓰기 어렵다

새로운 정적 분석 툴인 PREfast와 static drive verifier는 드라이버 퀄리티를 아주 크게 향상시킬 수 있습니다. 이런 툴들은 룰집합에 따라 코드를 검사합니다.


차세대 윈도우즈 드라이버 모델의 디자인 목적

1. 드라이버 모델은 심플하고 유연성(flexible)이 있어야 한다

심플하고 플렉서블한 드라이버 모델은 쉽게 빠르게 견고하고 믿을 수 있는 드라이버를 생산하는데 도움을 줍니다.

2. 드라이버 모델은 코어 운영 체제 컴포넌트와 분리되어야만 한다.

드라이버 모델은 운영 체제의 세세한 부분과는 별도로 구분되어 별도의 프레임웍으로 구성되어야 합니다. 이렇게 함으로써 MS는 드라이버와의 호환성에 대한 걱정없이 운영체제를 업데이트 할 수 있을 것이고, 운영 체제의 새 버전이 나와도 드라이버 개발자들이나 하드웨어 벤더들이 이에 영향없이 일을 할 수 있을 것입니다.

3. 드라이버 모델은 일관성이 있어야 한다.

드라이버 모델은 모든 디바이스 클래스 들에 대해서 일관성이 있어야 합니다. 모든 디바이스들이 반드시 지원해야 하는 PnP나 Power Management, 동기화, I/O 큐, 레지스트리 엑세스, DMA 지원같은 운영 체제를 위한 핵심 기능들이 모든 디바이스 종류에 구애받지 않고 일관성 있게 개발자들에게 제공되어야 합니다. 그렇게 되면 개발자가 새로운 디바이스에 대한 드라이버를 작성할 때에도 진입장벽을 낮게 가져갈 수 있을 것입니다.

4. 드라이버 모델은 확장성이 있어야 한다.
5. 드라이버 모델은 유저모드를 지원해야 한다.

유저모드 드라이버는 시스템의 안정성을 크게 높일 것입니다.

6. 드라이버 모델은 고급 레벨의 언어를 지원해야 한다.

객체 지향과 type-safe 를 지원하는 언어로 작성하면 코드 분석 툴에 큰 도움이 됩니다.

WDF (Windows Driver Framework)

WDF는 다음 컴포넌트들을 포함합니다.

  • 커널모드 드라이버 프레임웍.
    유저모드 드라이버 프레임웍
    드라이버 검증 툴

WDF는 객체 지향, 이벤트 드리븐(event-driven) 모델을 제공합니다!

WDF의 Object들은 블럭 쌓기처럼 동작합니다. 드라이버는 이들 object들을 잘 정의된 인터페이스를 통해서 수정합니다.

특정 이벤트들은 지정된 object의 타입에만 영향을 미칠 수 있습니다. 프레임웍은 각 이벤트에 대한 기본 동작을 정의합니다. Device specific 한 행동을 정의하기 위해서, 드라이버는 기본 핸들러를 오버라이딩 하는 콜백 루틴을 포함합니다.  (즉, 프레임웍에 의해서 디바이스 드라이버에 필요한 수많은 기본 핸들러들이 기본으로 제공이 되며, 개발자들은 자신에게 필요한 이벤트만 C++에서의 상속과 같이 오버라이딩을 하여 구현하면 됩니다)

MS는 WDM과 NDIS와 같이 기존의 미니포트 모델 드라이버 지원을 계속할 것입니다.

1. 커널 모드 드라이버 프레임웍(Kernel-Mode Driver Framework)

커널모드 드라이버 프레임웍(KMDF)은 커널모드 드라이버를 만드는데 필요한 기본적인 기능인 PnP, Power Management, I/O 큐, DMA, WMI 그리고 synchronization 같은 기본적인 기능들을 구현하고, IEEE 1394, USB 같은 주요 버스들에 대한 지원도 합니다.

커널모드 드라이버 프레임웍은 운영체제 커널의 일부분으로 포함된 것이 아니라, 별도로 제공되는 독립된 라이브러리입니다.

커널모드 드라이버 프레임웍을 사용하면 멀티펑션 디바이스를 지원하는 버스 드라이버를 작성하는 것이 현재의 WDM 모델을 사용해서 하는 것보다 훨씬 간단해집니다. 위와 같은 상황에서 드라이버는 그 자신의 자식 디바이스들을 enum하여 각 자식 디바이스들의 프로퍼티들을 리스트 합니다. 이때 KMDF의 프레임웍은 device object 셋팅, 자식 디바이스들을 위한 드라이버에게 IRP 보내기, 등과 같은 기본적이고 공통된 요구사항들을 대신 처리합니다. 이와 관련된 내용은 KMDF는 샘플 토스터 버스 드라이버를 참고하시면 됩니다.

커널모드 드라이버 프레임웍에서 인터페이스는 미래에도 가능한 한 드라이버의 독립성을 추구하도록 디자인 되었습니다. 만약 드라이버가 크래쉬되면, 운영체제는 시스템을 멈추거나 전체를 크래쉬하지 않고 복구하거나 드라이버에 의해 할당된 리소스들을 클린업 합니다. 이러한 드라이버의 독립성을 구현하기 위해서는 인터페이스가 잘 디자인 되어야 합니다.

그리고 WinUSB, ultrawide 밴드, HD Audio, 그리고 다른 여러 다른 디바이스 타입들이 비스타를 위한 KMDF 드라이버로 계획되어 있습니다.
2. 유저 모드 드라이버 프레임웍 (User-Mode Driver Framework)

유저모드 드라이버 프레임웍(UMDF)은 PnP, Power Management, 비동기 I/O 지원을 포함하는 커널모드 프레임웍 기능의 일부분을 구현하고 있습니다. DMA 수행, 인터럽트 지원, nonpaged 풀 같은 커널모드 리소르를 사용하지 않는 드라이버들이라면 유저모드에서 실행될 수 있습니다.

유저모드 프레임웍을 사용하면 개발자들은 네트워크와 연결된 디바이스들(network-connected devices)을 위한 드라이버와 PMP, 카메라, 셀폰 같은 USB 디바이스들을 위한 드라이버를 만들 수 있습니다. 비록 이들 드라이버들이 유저모드에서 실행되지만, 그것들은 커널모드 PnP 드라이버들과 동일한 방법으로 시스템이 찾고 설치하는 표준 PnP 드라이버입니다.

유저모드 드라이버 프레임웍은 비스타에서 지원될 것입니다. XP에서의 UMDF지원은 정책결정 중입니다.

3. 드라이버 검증 툴들(Driver Verification Tools)

두개의 룰 기반 정적 분석 툴인 PREfast와 Static Driver Verifier(SDV)가 커널모드 드라이버/유저모드 드라이버 프레임웍 모두를 지원할 것입니다.

PREfast는 드라이버가 지켜야만 하는 규칙들을 검사합니다.

PREfast는 드라이버의 코드가 어떻게 어떤 함수들을 호출하는지를 룰에 따라 분석합니다. 예를 들어서, 함수A가 스핀락을 얻고나서 PASSIVE_LEVEL에서 실행되어야만 하는 Z 함수를 호출하려고 했을때, PREfast는 스핀락을 얻어서 IRQL이 DISPATCH_LEVEL로 올라갔다고 이 에러를 표시해줄겁니다. 그러나, 만약 함수 A가 스핀락을 얻고, DISPATCH_LEVEL에서 호출되도 되는 함수 M을 호출했는데 함수 M이 함수 Z를 호출하려고 했다면, PREfast는 이런 에러는 못 잡아냅니다. PREfast는 Windows Server 2003 SP1 DDK에서 얻을 수 있고, 이후 버전은 WHDC 웹사이트에 올아올 겁니다.

SDV
SDV는 룰 기반의 툴로서, 윈도우 내부와 어떻게 드라이버가 운영 체제 내부의 인터페이스들을 사용해야 하는지에 대해 지식을 가지고 있습니다. DriverEntry를 호출하고 차례로 AddDevice를 호출하는 것처럼 운영체제의 호출을 에뮬레이팅 합니다.


출처 : www.insidekernel.net

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 12:03

1. 드라이버의 작동 방식.

 운영체제는 하드웨어와 관련된 많은 동작을 수행하기 위해서 관련 서브 루틴 집합을 구현해 놓고있는데.
쉽게 말하면 드라이버는 이 서브루틴 집합을 담아두는 컨테이너라고 생각하면 된다.
이 컨테이너의 내용물은  DriverEntry, AddDevice, IRP(I/O request packet<- 앞으로 자주 나올놈이니 꼭 기억해두두록하자)처리를 위한 디스 패치 함수 등이 들어있다. 대부분의 드라이버는 몇 가지 종류의 IRP를 처리하는 디스패치 함수를 갖고있따.

WDM 드라이버 개발자는 이렇든 자신의 컨테이너에 포함시킬 기능들을 적절히 선택해서 조합하는 역활을 해야하는것이다.

                                    *컨테이너*

기본드라이버루틴          I/O컨트롤루틴            디스패치루틴

   DriverEntry               AdapterControl            DispatchPnp      
   AddDevice                    StartIO                      DispacthPower  
                                        Onlnterrupt                DispatchWmi
                                         DpcForlsr                  DispatchRead
                                                 ..                          DispatchWrite
                                                                                         ..

2. 어플리케이션의 작동 방식

"서브루틴들의 패키지(pacage of subroutines)" 모델인 드라이버를
"주프로그램과 조력자(main program and helper)" 모델인 일반 어플리케이션과 대비해서 생각해 보자.

ex>

 int main(int argc, char* argv[])
{
    printf("HELLO, WORLD");

    return 0;
}

이 프로그램은 main이라는 이름의 주프로그램과 보조 루틴들의 라이브러리로 구성되어있다.
보조 루틴중 하나인 printf는 표준 출력 파일에 메시지를 프린트한다.
주 프로그램이 들어있는 소스 모듈을 컴파일 하고, 이를 printf와 같은 보조 루틴들이 들어있는 런타임 라이버리와 링킹하고 나면, HELLO.EXE라는 실행 파일이 완성 될것이다.


* 다음은 어플리케이션에 대한 몇 가지 공통사항!

어플리케이션이 사용하는 특정 보조 루틴들은 정적 라이브러리에 포함되어 있고 linkage 에디터는 빌드 과정에서 이것들을 추출하게 된다. 일반적으로 printf같은것들이지. 일부 보조 루틴들은 시스템 DLL로부터 동적으로 링크되기도 한다. 이런 루틴들에 대해 linkage에디터는 실행 파일에 특별한 improt 레퍼런스를 삽입하여, 나중에 런타임 로더에서 이 레퍼런스가 실제 시스템 코드를 가리키도록 한다.
사실, 어플리케이션 프로그램에서 사용되는 모든 Win32 API는 동적으로 링크되어 잇기 때문에, Windows 프로그래밍에서 동적링크는 매우 중요한 개념임!!!


3. 디바이스 드라이버

Hello.EXE와 같이 드라이버도 역시 일종의 실행파일이란것을 알아두자!
파일 확장자는 ****.SYS이지만, 이 파일 구조는 다른 32비트 Windows나 콘솔 모드 어플리케이션과 동일함!!!!!<-
또한 HELLO.EXE와 마찬가지로 드라이버도 많은 보조 루틴을 사용하는데, 운영체제 커널이나 class driver나 다른 지원 라이브러리 에서 동적으로 링크된다. [ 또, 드라이버 파일도 심볼릭 디버깅 정보와 리소스데이터를 가지고있다. ]<- 심볼릭 디버깅등검색해보자!

  *드라이버에 대한 시스템의 역활

HELLO.EXE와는 달리, 드라이버는 main 프로그램을 포함하고 있지 않으며 대신!! 시스템의 판단에 따라 호출할 수 있는 서브 루틴들의 집합을 가지고있다.
서브루틴들은 드라이버나 정적 라이브러리나 운영체제 내에있는 여러 보조 루틴들을 사용할수 있지만. 드라이버는 자신의 하드웨어 외 다른것에는 관여하지 않는다.  그외 다른 부분은 모두 시스템이 담당하고있다.(예를들어 드라이버의 실행 시점 판단이라던가...)


 * 운영체제가 드라이버 내의 서브루틴을 호출하는 과정을 간단히 예를 들어보자!!

1) 사용자가 디바이스를 꽂으면, 시스템은 드라이버의 실행 파일을 가상 메모리에 올리고, DriverEntry 루틴을 호출!!  DriverEntry는 몇가지 작업을 하고 리턴~

2) PNP(Plug and Play) Manger는 드라이버의 AddDevice 루틴을 호출!! 역시 작업후 리턴~

3) PNP Manger가 드라이버에 IRP를 전송하면 디스패치 함수가 이를 처리하고 리턴!

4) 어플리케이션이 디바이스의 핸들을 열면 시스템이 또 다른 IRP를 전송하고, 디스패치 함수가 처리후 리턴!

5) 어플리케이션이 데이터를 읽으려고 시도하면, 시스템이 해당 IRP를 전송! 이에 대해 디스패치 루틴은 IRP를 큐에 저장하고 리턴!!!

6) 드라이버에 연결되어 있는 하드웨어 인터럽트에 신호를 보내서 이전의 I/O 오퍼레이션을 완료시킴.
   인터럽트 루틴에서는 DPC 스케줄링 등의 작업을 한후에 리턴!!!

7) DPC 루틴이 실행되어 5번작업에서 큐에 넣었던 IRP를 삭제하고 , 데이터를 읽도록 하드웨어를 작동시킨후 시스템으로 리턴~~~~~~~~~~

8) 시스템이 드라이버의 서브루틴들을 사용중. -ㅈ- 시간이 흐름.

9) 마침내, 유저가 디바이스를 제거. PNP Manger는 IRP를 드라이버로 전달하고 , 드라이버는 이를 처리한다음 리턴.
 운영체제는 드라이버의 Driver UNload 루틴을 호출해서 몇가지 일을 처리하고 리턴.
 최종적으로시스템은 가상메모리에서 드라이버 코드를 제거~~~~~~~~~~~~~~~~

 

4. 쓰레드와 드라이버 코드.

 드라이버가 어플리케이션과 다른점!
드라이버는 코드를 실행시키기 위해 시스템이 특별히 쓰레드를 생성시키지는 않는다는것!
대신 시스템이 드라이버의 서브루틴을 호출하기로 결정하면 그 순간 실행되고 있던 쓰레드 컨텍스트에서 드라이버 코드를 실행시킴!!!!

하드웨어 인터럽트가 발생하는 순간 모든 쓰레드들 중에서 어떤 것이 우연히 실행되고 있을지 알순 없지.
이것을 임의의(arbitrary) 쓰레드라고 부르며 "임의의 쓰레드 컨텍스트에서 실행된다" 라고 말함!

즉, 드라이버의 서브루틴을 호출하려 할 때, 대부분 임의의 쓰레드 컨텍스트에서 돌아가고 있을것이다.

예를들어, 자신의 드라이버의 ISR(Interrupt Service Routine)이 통제권을 가지는 순간 어떤 컨텍스트가 올지 모른다는거~

흔히 storage 드라이버의 경우에서 볼수있는데, 파일시스템 드라이버가 읽기/쓰기에 최종적인 책임을 지고 연관된 드라이버를 컨트롤 하는 형태로 되어있음!

하지만 항상 임의의 쓰레드 컨텍스트에서 드라이버 코드를 실행시키는것은 아님!

PsCreateSystemThread를 사용해서 자신의 시스템 쓰레드를 생성하거나 work item을 스케줄링해서 특정쓰레드를 요청할수도있음.

쓰레드가 임의적인지 아닌지가 왜중요하냐!


첫번째로, 드라이버는 임의의 쓰레드를 "블록킹" 해서는 안된다는거.

다른 쓰레드에 도움이 되는 작업을 수행한다고 해서 또다른 쓰레드를 중지시키는건 공평치 못하겠지?


두번째로, 드라이버가 IRP를 생성해서 다른 드라이버에게 이를 전달하는 경우인데
...

임의의 쓰레드에서는 비동기적 IRP만생성가능. 그러나 비임의 쓰레드에서는 동기적IRP도 생성가능하기때문이지.

동기적 방식에서는 I/O Manager는  IRP를 생성한 그 쓰레드의 컨텍스트에 IRP를 속하게 하고, 쓰레드가 종료되면 IRP를 자동으로 취소함.

 그러나 비동기적 IRP인경우,  I/O Manager는 이를 특정한 쓰레드에속하게 하지는 않음. 따라서 비동기적 IRP를 생성한 쓰레드는 드라이버에서 수행하는 I/O 오퍼레이션에 대해 직접적인 관계가 없으며, 이쓰레드가 종료됬다고 IRP를 취소시키지도 않는다는거지.



5. 시스템의 드라이버 로딩 방식

하드웨어가 플러그 앤 플레이(PNP) 를 지원하는지 여부에 따라 두 가지 조금 다른 방법을 사용한다.

1) 플러그 앤 플레이 디바이스는 시스템이 감지할 수 있는 전자 서명을 가지고 있음.
시스템 bus driver는 하드웨어의 존재 유무를 파악한 후, 어떤 종류의 하드웨어 인지 판단하기 위해 서명을 읽어옴
그 후 시스템은 레지스트리와 INF 파일을 이용해 자동으로 가장 적당한 드라이버를 로드하게 됨!!

2) Legacy 디바이스는 전자서명이 없다!!!  따라서 시스템이 자동으로 디바이스를 감지할수 없뜸.

그래서 사용자가 새 하드웨어 추가 마법사를 사용해서 검색해야함. 그러고나면 시스템은 레지스트리와 INF를 이용해 자동설치를한다!

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:58

WINDOWS XP 운영체제.

--------------------------------------------------------------------

어플리케이션     ->

                              WIN32 API 호출

WIN32서브시스템<-                                                                          유저모드

--------------------------------------------------------------------

                        ->                                                                         커널모드

                              시스템 서비스 인터페이스

                        <-

I/O MANAGE(디바이스 드라이버) -> HAL 호출

                            HAL(하드웨어 추상화 계층)

                                       -하드웨어-

---------------------------------------------------------------------



*WINDOWS 98/ME 하고 WINDOS 2000/XP 는 내부 동작이 전혀다름. 따라서 디바이스 개발도 다름.


*FILE SYSTEM DRIVER(파일시스템드라이버) : 명명된 파일(NAMED FILE)이 포함된 계층적 디렉토리 구조의 개념을 가진

표준 PC파일 시스템 모델을 로컬 하드디스크나 네트워크 연결에 대해 구현한다. 커널 모드 드라이버이다.


*LEGACY DEVICE DRIVER(레거시 디바이스 드라이버)  : 다른 드라이버의 도움 없이 하드웨어 디바이스를 직접적으로 컨트롤 하는 드라이버이다. 이 븐류에는 XP와 호환되는 NT 드라이버가 포함된다.


*WDM 드라이버 (윈도우즈 드라이버 모델)의 하부 드라이버 .


CLASS DRIVER - MICROSOFT에서 작성. 건드릴 필요없음.


MINI DRIVER - CLASS DRIVER와 함께 동작. 디바이스를 책임지고 있지만 클래스 드라이버의 서브루틴을 이용하며

클래스 드라이버는 하드웨어 관리를 맡고 있지만 많은 종속적 디바이스를 처리하기위해 MINIDRIVER를 역호출하게된다.

사용예-

(USB가 아닌 휴먼입력장치(HUMAN INPUT DEVICE:HID). 마으스, 키보드, 조이스틱, 게임 휠.

 스캐너와 카메라 같은 WINDOWS 이미지 수집 (WINDOWS IMAGE QCQUISITION:WIA) 디바이스.

 오디오,DVD,비디오 디바이스와 같은 스트리밍 디바이스와 멀티미디어 데이터 스트림

 비디오카드. 프린터. 배터리

 USB와 1394등 전통적이지 않은 버스를 사용하는 네트워크 인터페이스 디바이스. 이러한 디바이스에 대해서는 NDIS(NETWORK DRIVER INTERFACE SPECIFICATION)미니포트 드라이버를 만들어야한다. DDK 문서에서는 이를 "WDM LOWER EDGE"로 작성하라 권하고 있다. 이런 드라이버는 운영체제간에 호환성을 가지기가 어려우므로, 플랫폼 의존성을 해결하기 위해서는 작은 차이점을 가진 몇 가지 버전으로 만들도록 처음부터 계획해야한다. )



FILTER DRIVER - 경우에따라 마이크로 소프트가 제공하는 범용 드라이버가 완전한 요구사항과 맞아떨어지지 않을경우 필터드라이버를 작성해서 범용 드라이버의 작업을 수정하는것.



단일 WDM FUNCTION DRIVER - 이 드라이버는 기본적으로 독립적인 개체이고, 하드웨어를 제어하는 모든 세세한 부분을 처리.

사용예-

(스마트카드 리더, 디지털-아날로그 컨버터, 소유권확인태그 등등)


*SCSI(SMALL COMPUTER SYSTEM INTERFACE) 어댑터 : "SCSI MINIPORT" 드라이버를 사용하며, 다른 표준 커널 지원 함수를 전혀 사용하지 않는다. 이드라이버는 대신 SCSI-PORT.SYS 나 SCSIPORT.VXD에 의해 EXPORT된 특별한 API를 사용.


*네트워크 인터페이스 카드는 "NDIS MINIPORT 드라이버"를 사용. NDIS.SYS나 NDIS.VXD에의해 EXPORT된 특별한 API를 사용

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:57

WDM방식이란, 윈도우환경에서 디바이스드라이버를 작성하는 방법을 획일화 한 것이다. 이 방법으로 디바이스드라이버를 작성하면, 윈도우의 확장 버젼이 나오더라도, 쉽게 이 디바이스드라이버코드를 그대로 사용할 수 있는 장점을 가지고 있다.
그렇다고 모든 디바이스드라이버를 WDM방식으로 작성할 수 있는 것은 아니라는 것을 명심해야 한다. WDM방식의 디바이스드라이버를 작성하기 위해서는 윈도우가 어느 정도만큼 이런 모델을 지원하고 있는가를 알아야 한다
.

예를 들어, 윈도우 9x에서 사용하는 USB이동식디스크를 생각해보자. USB이동식디스크를 윈도우 9x에서 사용하려면 디바이스드라이버가 필요한데, 이때 WDM디바이스드라이버가 사용될 수 있을까? 정답은 그렇다 이다.

USB버스를 사용하는 디바이스를 구동하는 드라이버이기 때문에 WDM디바이스드라이버를 작성해야 한다. 결국, 윈도우에서는 USB버스를 사용하려면 WDM디바이스드라이버모델을 사용해야 한다는 것이 된다. 그런데, 윈도우 9x에서는 디스크드라이버로서는 WDM방식을 사용할 수 없다는 것도 알아야 한다. 이상하다. USB디스크드라이버는 그렇다면 어떤 드라이버모델을 사용해야 한다는 것 일까요?


결국 2가지를 사용해야 한다는 것이다. 하나는 USB버스를 사용하기 위한 WDM디바이스드라이버이고, 또 다른 하나는 윈도우9x에서 디스크드라이버로 사용되기 위해서 VxD라는 모델을 사용하는 디바이스드라이버가 된다.
만일, 윈도우9x가 디스크드라이버로서 WDM을 지원하고 있었다면, USB이동식디스크를 위한 디바이스드라이버는 달랑 WDM드라이버 하나만 잘 만들면 된다.
윈도우2000은 하나의 WDM드라이버만으로 이것들이 해결된다. 윈도우2000에서는 디스크드라이버의 역할을 WDM디바이스드라이버가 수행할 수 있거든. 윈도우가 지원하는 것이다.
그러니까, 윈도우2000에서는 USB버스를 사용하는 WDM디바이스드라이버의 코드 안에다 디스크역할을 수행하는 코드를 포함하면 간단히 USB이동식디스크드라이버가 만들어지는 거가 되다.

이렇게 WDM방식으로 디바이스드라이버를 만들게 되면, 이 드라이버가 어디까지 현재 사용되는 윈도우에서 지원되고 있는가를 분명히 알아야 한다.

만일, USB이동식디스크드라이버를 윈도우9x에서 만들려고 할 때, 실컷 WDM방식만을 생각하고 만들었다면, 나중에 땅을 치고 후회하게 된다. VxD를 생각해야 하니까..

WDM
방식을 조금 더 구체적인 부분까지 설명해 보도록 하자.

 

 

1. WDM디바이스드라이버의 의미


WDM
디바이스드라이버에서 사용되는 WDM이란 용어를 풀어서 보면, Win32 Driver Model이 된다. 물론, 어떤 책에서는 Windows Driver Model이라고도 설명하지만, 보통은 이보다 Win32 Driver Model이란 표현이 더 정확할 것이다.

Win32 Driver Model
이란 표현이 암시하는 것이 무엇일까? 그것은 Win32 Subset을 통해서만 디바이스드라이버에 접근한다는 것을 말한다. 이 의미는 응용프로그램에서 사용하는 SDK(API)중에서 Win32 API를 사용하여 디바이스드라이버에 접근할 수 있다는 것이다. 그것들이 어떤 함수이냐 하면, CreateFile, ReadFile, WriteFile, DeviceIoControl, CloseHandle 이다.

이런 함수의 용도를 보면, 원래는 실제 파일을 읽고 쓸 때 사용하는 거지만, 윈도우에서는 디바이스드라이버와 통신하는 모양을 마치 파일을 다루는 것과 동일하게 간주하려 한다는 것을 암시하고 있다.


파일을 여는 간단한 함수를 예를 들면,

  Handle = CreateFile( “C:\\TEST.DAT”, FILE_READ_DATA|FILE_WRITE_DATA…)

이렇게 함수를 사용하면 무엇을 기대하지? 그것은 아마도 C:\TEST.DAT라는 파일이 열릴 것으로 생각이 든다. 그런데, 이 모양을 조금 더 자세히 살펴보자.


C:\TEST.DAT
라는 문장은 크게 3가지로 구분 되어질 수 있다.
C:
이것은 볼륨을 나타내는 심볼 이름이고

\
이것은 볼륨과 그 다음을 구분하는 구분자 이고
..
TEST.DAT
는 볼륨아래에서 찾게 되는 파일이름이 된다
.

이렇게 3가지로 구분되어서 사용되는 이런 파일열기에서는 다음과 같은 일이 예상된다
.
응용프로그램 -> 파일열기(C:\TEST.DAT) -> 운영체제 -> C: 볼륨을 처리하는 디바이스드라이버(볼륨파일시스템) -> \ 단어를 구분하여, 그 아래에 존재하는 TEST.DAT라는 문장을 찾는다. -> 결국 TEST.DAT라는 파일을 찾기 위해 실제 디스크드라이버를 호출한다
.

이렇게 파일을 여는 단순한 명령을 보아도, 우리는 이곳에서 C:라는 볼륨을 처리하는 디바이스드라이버가 사용된다는 것을 알 수 있다. 이처럼 파일열기명령으로만 알았던 CreateFile이 실제로는 디바이스드라이버를 여는 명령이란 것을 알 수 있다
.
이렇게 CreateFile함수를 통해서 디바이스드라이버를 열게 되면, C:볼륨을 처리하는 디바이스드라이버는 적절한 핸들 값을 리턴 하여, 응용프로그램이 차후에 이 핸들을 사용하여 ReadFile, WriteFile등의 함수를 호출하게 되며, 이런 함수를 통해서 호출하는 Read, Write의 요청을 윈도우운영체제는 핸들 값을 통해서 적절한 디바이스드라이버를 찾게 된다
.

파일을 읽는 명령을 예를 들면

응용프로그램 -> 파일읽기(ReadFile) -> 운영체제, 사용된 파일핸들을 분석하여 이것이 C:볼륨파일시스템이라는 것을 찾아낸다. 그런 다음, C:볼륨을 처리하는 디바이스드라이버(파일시스템)를 호출한다 -> C: 볼륨을 처리하는 디바이스드라이버 ..
이렇게 호출관계가 되어진다는 것을 명심하면, WDM디바이스드라이버가 어떤 방법으로 응용프로그램과 통신하게 되는지를 추측할 수 있게 된다

.
결국, WDM디바이스드라이버는 다음과 같은 호출관계에 놓이게 된다.
응용프로그램 -> API(File을 다루는 함수들) -> 운영체제 -> WDM디바이스드라이버
이럴 때 사용되던 API함수들이 Win32 API함수이기 때문에 우리는 Win32 Driver Model이란 표현을 사용하게 되다. 그 말은 이런 함수를 통하지 않고는 응용프로그램이 WDM디바이스드라이버와 통신할 수 없다는 것이 된다.
윈도우9x에서 사용되는 VxD는 상대적인 의미를 가지게 되다.
윈도우9x에서 단순히, 메모리를 읽거나, 쓴다든지, 아니면, 입출력포트를 접근해서 키보드 데이터를 읽어 오려 한다든지 하는 동작을 응용프로그램이 하려고 하면, 곧바로 응용프로그램 모르게 윈도우는 현재 제어를 VxD로 뺏어가 버린다. 이런 것과는 차원이 다르다.

 

 

2. WDM디바이스드라이버와 윈도우


윈도우 2000에서는 대부분의 디바이스드라이버를 WDM방식으로 작성할 수 있거든. 그러나, 윈도우9x에서는 그것이 불가능하다. 윈도우9x에서는 WDM보다는 VxD방식으로 작성된 디바이스드라이버를 대부분에 디바이스드라이버에 적용할 수 있도록 윈도우가 지원하고 있다.

윈도우2000이 되어야, VxD는 사라져버리고, WDM방식이 대부분의 디바이스드라이버를 지원할 수 있게 된다. 물론, 모든 디바이스드라이버를 다 WDM방식으로 사용할 수 있다는 것은 절대로 아니다. 예를 들어, 프린터드라이버, 디스플레이드라이버, 네트워크 드라이버 등은 WDM방식으로 만들 수 없다

 

 

3. WDM디바이스드라이버의 내부함수들


아까 1)에서 살펴보았던 내용 중에 응용프로그램 -> API(File을 다루는 함수들) -> 운영체제 -> WDM디바이스드라이버 라는 부분을 다시 한번 생각해보자.

이 내용대로라면, 운영체제 -> WDM디바이스드라이버가 된다는 것이다
.
이렇게 된다는 의미라면, 결국 운영체제가 직접 WDM디바이스드라이버를 호출하는 것이 된다. 이렇게 운영체제가 우리가 만든 WDM디바이스드라이버를 호출하려면 어떻게 WDM디바이스드라이버를 만들어야 할까? 운영체제가 알아서 여러분들을 호출할까? 그냥 호출하는 것이 아니라 어느 정도의 약속이 되어지는 부분이 필요하다.


그것이 호출약속이 된다. WDM디바이스드라이버는 최소한 하나의 함수를 외부에 Export해야 할 의무가 있다. 그래야, 운영체제는 이 함수를 통해서 WDM디바이스드라이버와 통신할 수 있다. 그 함수가 바로, DriverEntry()함수가 된다.


이 함수는 유일하게 Export되어지는 함수이기에, 반드시 C로 컴파일하는 것이 좋다. 만일, C++로 컴파일하게 되면 다 아는데로, 언어의 특징상 다형성이 가미되어 맹글링문제가 생겨서 실제로 DriverEntry()란 함수는 없어지고, 그 자리에 DriverEntry@Ad23dsd42ZSER 이런 이상한 이름의 함수가 만들어진다. 이런 함수가 만들어지면, 당연히 운영체제는 이 함수를 호출할 수 없다. 이름이 어려워진다.

이렇게 WDM 디바이스드라이버가 가진 DriverEntry()함수를 운영체제가 호출하면, 드라이버는 무엇을 하느냐? 그것은 바로, WDM디바이스드라이버가 가진 호출을 원하는 자신의 함수들을 등록하는 것이다. 결국, WDM디바이스드라이버는 10개정도의 함수를 가지고 있는 상태에서, 이 함수를 운영체제가 호출하지 못하니까 일단, DriverEntry()함수를 먼저 공개하고, 이 함수가 호출되었을 때, 나머지 자신의 함수들을 정해지 방법을 사용해서 운영체제에게 공개하는 것이다.

 

 

여기서는 함수들을 모두 이해하는 것은 어려울 것 같고.. 어떤 함수들이 존재하는 대강 알아두자..

 

. DriverEntry() : 함수이름이 반드시 유지되어져야 한다.
. SAMPLEAddDevice() :
함수이름자체는 무의미하다. 역할만 중요하다
.
. SAMPLEIRPDispatch() :
함수이름자체는 무의미하다. 역할만 중요하다
.
. SAMPLEDriverUnload() :
함수이름자체는 무의미하다. 역할만 중요하다
.
. SAMPLEIRPStartIo() :
함수이름자체는 무의미하다. 역할만 중요하다.

                                              

 

특별한 일을 수행하지 않는 간단한 WDM디바이스드라이버의 골격은 다음 4가지 엔트리로 구성된다.


. DriverEntry Routine
. AddDevice Routine
. IRP Dispatch Routine
. DriverUnload Routine


이 중에서도 DriverEntry Routine은 디바이스드라이버코드의 시작위치가 된다. 그리고, 이곳은 코딩상에서 함수명을 반드시 "DriverEntry"라고 가져야 하는 특징을 가진다. 그리고 다른 함수들은 실제 함수의 시작주소를 디바이스드라이버코드가 수행될 때 드라이버코드내에서 직접 구해서 IO MANAGER에게 알려주는 반면에 "DriverEntry"함수는 외부에 Export되는 유일한 이름이다. 따라서, "C++"언어를 사용하는 경우에는 특히, extern "C"명령을 사용해서 “DriverEntry"함수를 정의해야만 "C++"언어가 가지는 다형성을 위한 Mangling문제에서 벗어날 수 있다.


) extern  "C"  NTSTATUS  DriverEntry(..)

그리고, DriverEntry Routine AddDevice Routine은 윈도우즈가 제공하는 “SYSTEM" Process의 쓰래드아래서 호출된다. 쉽게 이해하자면, ”SYSTEM.EXE"라는 응용프로그램을 하나 만들어서, 이 프로그램이 DriverEntry, AddDevice Routine을 호출하는것이라고 생각해도 좋다. 윈도우에서는 특정 Process 아래에서 수행되는 Thread Task Switching을 허용한다. 그렇기 때문에 디바이스드라이버개발자들은 이곳에서 Event(이벤트)를 기다리는 행동을 한다든지, 아니면 이런 행동을 할것으로 기대되는 운영체제의 함수들을 호출하는 행동을 해도 좋다. 이렇게 Event를 두고 기다리는 행동은 자연히 태스크 스위칭을 일으키기 때문이다.

 

 

WDM디바이스드라이버가 가지는 함수 엔트리들은 이 밖에도 많이 있지만, 꼭 있어야 하는 중요한 엔트리 3가지만 살펴보도록 하자.

(1) DriverEntry Routine


WDM
디바이스드라이버코드에서는 다음과 같이 정의하여 사용한다.

 
extern  "C"
 NTSTATUS
 DriverEntry( PDRIVER_OBJECT  pDriverObject, PUNICODE_STRING  pRegistryPath )
 {
 ................
 }

이곳은 WDM디바이스드라이버가 메모리에 적재되어 운영체제로부터 호출되는 가장 처음의 함수 엔트리가 된다. 디바이스드라이버가 메모리에 적재되는 경우에 가장 먼저 호출된다는 특징을 가지기에, 디바이스드라이버개발자들은 이곳에서 몇 가지 코드를 구현하려 한다. 이때, 주의할 점은 “DriverEntry"가 호출되는 것은 단지, 디바이스드라이버가 메모리에 적재되어서 처음 실행된다는 점을 생각 해야 한다. 그 외에 이 시기가 디바이스드라이버에 입장에서 현재 자신의 하드웨어장치를 마음대로 초기화하는 등의 작업을 하도록 허용된 시기는 아니라는 것이다. 운영체제는 이처럼 하드웨어장치를 사용하라는 의미로 ”DriverEntry"를 호출하는 것이 아니라, 드라이버가 메모리에 처음 적재되는 경우에 필요로 하는 내부 변수 등을 초기화하는 작업을 디바이스 드라이버 측에서 먼저 수행하라는 의미에서 호출하는 것을 명심해야 한다.

WDM
디바이스드라이버에서는 똑같은 하드웨어장치를 같은 PC에 여러 대 설치한다 하더라도, 똑같은 디바이스드라이버를 여러 번 메모리에 적재하지 않는다. 예를 들어, 같은 회사에서 만든 USB이동식 디스크를 하나의 PC 2대를 연결 한다 하더라도, 디바이스드라이버는 단 한번 메모리에 로딩된다는 것을 의미한다. 결국, “DriverEntry"는 디바이스드라이버입장에서 DriverEntry, 자신이 사용할 하드웨어장치가 똑같은 종류의 것이 2개건, 3개건간에 단 한번만 호출된다.

 

 

WDM디바이스드라이버는 사실 IO MANAGER의 내부 루틴 혹은 내부모듈에 불가하다. 따라서, IO MANAGER가 원하는 방법대로 함수 엔트리를 준비하지 않으면 WDM디바이스드라이버로서 역할을 수행할 수 없다. IO MANAGER “DriverEntry" 함수를 호출하는 목적을 단지, WDM디바이스드라이버 자체가 사용할 내부변수를 초기화하는 목적에만 두지 않는다. DriverEntry함수를 호출함으로써, WDM디바이스드라이버가 가지고 있는 다양한 함수 엔트리들, 결국 IO MANAGER가 필요로 하는 형식을 가지는 함수 엔트리들에 대한 주소를 IO MANAGER에게 알려주는 목적을 달성하게 된다. WDM디바이스드라이버의 함수 엔트리를 얻기 원하는 IO MANAGER의 목적은 “DriverEntry"함수의 파라미터로 전달되는 ”pDriverObject“를 통해서 완성되게 된다. IO MANAGER는 각각의 WDM디바이스드라이버들에게 하나씩의 ”pDriverObject"를 공급하게 되며, 이렇게 공급된 “pDriverObject"의 내용에는 WDM디바이스드라이버가 가진 자신의 함수 엔트리를 등록하도록 되어 있어서, 이를 통해서 IO MANAGER WDM디바이스드라이버가 가지는 함수 엔트리를 원하는시기에 언제든지 호출할 수 있는 정보를 가지게 된다. 따라서, ”DriverEntry"함수가 호출되면, WDM디바이스드라이버가 수행하는 일은 무엇보다도 “pDriverObject"의 내용 중에서 함수 엔트리를 등록하는 부분으로 자신의 함수 엔트리를 등록하는 일이 된다. 이제 “DriverEntry"를 무사히 수행한 WDM디바이스드라이버는 IO MANAGER로부터 언제든지 호출될 수 있는 준비가 되었다. WDM디바이스드라이버에서 이 구조체에 기록하는 자신의 함수포인터들은 2가지 그룹으로 나뉠 수 있다.

- IRP
명령어 처리그룹
  IRP
명령어를 받아들이는 함수들
  : Create, Close, Pnp, Read, Write, IoControl, ..
명령처리함수들

- IRP
명령어 비처리그룹
  IRP
명령어를 받아들이지 않는 함수들
  : AddDevice, Unload, ..
함수들

 

 

다음은 대게의 WDM 디바이스드라이버에서 작성되는 “DriverEntry"함수에서 이 ”pDriverObject"구조체를 다루는 예이다.


 pDriverObject->MajorFunction[IRP_MJ_CREATE] = SAMPLECreateDispatch;
 pDriverObject->MajorFunction[IRP_MJ_CLOSE] = SAMPLECloseDispatch;
 pDriverObject->MajorFunction[IRP_MJ_READ] = SAMPLEReadDispatch;
 pDriverObject->DriverExtension->AddDevice = SAMPLEAddDevice;

예를 보면, SAMPLECreateDispatch, SAMPLECloseDispatch, SAMPLEReadDispatch 함수들은 모두 IRP명령어를 받아들이는 함수들이며, SAMPLEAddDevice 함수는 IRP명령을 받아들이지 않는 함수이다.

함수이름을 보면, "SAMPLECreateDispatch"라고 하는데, 짐작으로는 무엇인가 “Create-열기"작업과 관련이 있어 보인다는 것은 추측이 가능하나, 이 함수 엔트리가 실제로 어떤 역할을 수행하는가는 왼쪽 편에 등록되는 맴버 변수를 통해서 정해져 있게 된다
.
결국, pDriverObject->MajorFunction[] 배열에 등록되는 함수엔트리들의 역할이 이미 등록되는 시기부터 정해져 버린다는것이다.

 

역시 pDriverObject -> DriverExtension -> AddDevice 변수도 역시 마찬가지이다. 이곳에 등록되는 것만으로도 그 역할이 정해져 버린다. pDriverObject->MajorFunction[]배열에 등록되는 함수엔트리들을 “IRP Dispatch Routine"이라고 부른다. 이곳은 명령어(IRP)를 직접 받아들이는 곳이기도 하다. 그리고 pDriverObject->DriverExtension->AddDevice배열에 등록되는 함수엔트리를 ”AddDevice Routine"이라고 부른다.

pDriverObject->MajorFunction[]
변수가 배열구조를 가지고 있으므로, 결국 이 배열에는 첨자가 사용된다. 이 첨자는 다음과 같이 사용되고 있다.

 

 

첨자의 의미를 통해서, 해당하는 첨자를 사용한 pDriverObject->MajorFunction[]배열의 내용이 되는 함수포인터의 역할을 짐작할 수 있게 된다.


"DriverEntry"
가 호출될 당시에 “pDriverObject"내의 함수포인터를 유지하는 대부분의 변수들은 0으로 초기화되어있는 반면, pDriverObject->MajorFunction[]배열의 내용은 “0”으로 초기화되어있는 것이 아니라, 특정 주소 값이 이미 기록된 상태이다. 이는 WDM디바이스드라이버에서 pDriverObject->MajorFunction[]배열의 값들을 변경하지 않더라도, 그 변수의 값은 이미 IO MANAGER가 유지하는디폴트 핸들러의 주소를 가지기 위해서이다
.
디폴트 핸들러는 다음과 같은 형태로 메모리 내에 이미 정의되어 있다
.

또 하나의 “DriverEntry"함수의 파라미터는 ”pRegistryPath"이다. 이곳은 디바이스드라이버를 설치하는 당시에 운영체제에 의해서 지정된 디바이스드라이버를 위한 소프트웨어 서비스키를 가리키는 위치정보를 담고 있다. “DriverEntry"시기에는 다른 레지스트리는 사용이 불가능할수도 있지만, 유독 이곳 ”pRegistryPath"가 가리키는 레지스트리키 만큼은 디바이스드라이버에서 항상 사용이 가능하다는 특징을 가진다. 윈도우즈시스템이 부팅되는 초반시기에 메모리에 적재되는 WDM디바이스드라이버들은 시스템레지스트리를 접근하는데에 있어서 제한을 받게 된다. 이때 “pRegistryPath"에서 가리키는 레지스트리키는 언제든지 접근이 가능하다는점에서 WDM디바이스드라이버들은 이곳을 정보보관장소로 사용하게 된다. 다음은 ”pRegistryPath"가 가리키는 값들의 예이다
.
 


   . “HKLM\System\CurrentControlSet001\Service\SAMPLE"
  . “HKLM\System\CurrentControlSet001\Service\USBSTOR"
  . “HKLM\System\CurrentControlSet001\Service\DISK"

 

[정리]


“DriverEntry Routine"
WDM 디바이스드라이버가 메모리에 적재되는 시기를 알려주는 곳이다. 이곳은 IO MANAGER가 현 WDM 디바이스드라이버가 메모리에 적재되는 것을 알리는 목적과 디바이스드라이버가 가지는 다양한 함수 엔트리들의 주소를 등록 받기 위한 목적을 가진다. 특히, WDM 디바이스드라이버에서는 이 시기에 자신이 사용하려는 하드웨어장치를 적절한 방법으로 초기화하는 것은 권장 하는 일이 못 된다. 하드웨어장치와는 전혀 관련이 없는 루틴이라는 사실을 명심해야 한다.

 

 

(2) AddDevice Routine


USB이동식디스크를 지원하는 디바이스드라이버가 하나 있다고 가정하자. 똑같은 장치 2개를 PC에 연결했을 때, 디바이스드라이버가 2번 메모리에 적재되는 것은 메모리낭비를 초래하는 비 능률적인 운용이 될 수 있다. 결국, 윈도우즈는 이렇게 같은 종류의 여려 개의 장치가 한대의 PC에 연결 되는 경우에 하나의 디바이스드라이버만으로도 이 장치들을 지원할 수 있도록 한다. 이렇게 하나의 디바이스드라이버에서 여러 대의 같은 하드웨어장치를 지원하기 위해서는 디바이스 문맥(Device Context) - “DeviceObject라는 용어를 사용해야 옳은 표현이지만, 지금 시기에는 이해하는 목적을 위해서 디바이스문맥이라는 용어를 임시로 사용한다유지라는 중요한 구현상 특징을 외면할 수 없다. 결국, 디바이스드라이버는 하나의 장치마다 하나의 문맥을 유지할 수만 있다면, 2대 이상의 장치가 연결되더라도 문맥을 통해서 이 장치를 관리할 수 있다. 디바이스문맥이란 용어는 디바이스근거라는 용어와 비슷한 의미로 이곳에서는 보여 질 수 있다. 예를 들어, 하나의 드라이버가 2가지 이상의 자신의 하드웨어장치를 지원하는 모습을 위해서 각각의 하드웨어장치에 대한 자신의 관리를 운영체제에게 알려주는 근거이기도 하기 때문이다.


여기서 디바이스 문맥이라는 것은 구체적으로 “DEVICE_OBJECT"라는 구조체로 표현되어 사용된다. 이 구조체는 뒤에서 자세히 배우지만, 이 곳에서는 ”AddDevice Routine"이 하는 역할을 이해하기 위해서 “DEVICE_OBJECT”구조체의 의미중 일부분에 대해서만 잠시 배우도록 하겠다.

똑같은 USB이동식디스크장치2대를 PC에 연결했다고 가정하자. 사용자는 탐색기를 통해서 2개의 드라이브명을 볼 수 있을 것이다. 이 드라이브명을 “G:", "H:"라고 가정한다. 이때, 사용자가 ”G:"를 접근하려 한다면, 사용자의 이런 접근은 USB이동식디스크장치를 지원하는 디바이스드라이버(이하 디스크드라이버)측으로 적절한 명령이 되어서 전달된다. “H:"드라이브를 사용자가 접근하는 경우에도 마찬가지로 해당하는 디스크드라이버로 명령이 전달된다.그렇다면, 디스크드라이버에서는 어떻게 이 2개의 물리적인 드라이브를 구분할 수 있을까? 앞에서 배운 대로 라면, "DriverEntry"에서 등록한 "MajorFunction[]"배열은 여러 개의 장치를 구분하는 방법을 제공하지 않았으며, 단지 외부로부터 디스크드라이버가 받게 되는 명령어(IRP)를 처리하는 함수 엔트리만을 등록하게 되어있다. 이대로라면, 디바이스드라이버는 자신이 등록한 함수 엔트리안에서 사용자가 접근한 ”G:", "H:"드라이브를 구분할 수 있어야 한다. 이럴 때 바로 문맥(Context)이 필요하다. 명령어(IRP)를 받는 “IRP Dispatch Routine"으로 전달되는 파라미터 중 첫 번째 파라미터 즉 "pDeviceObject"가 전달되는 목적이 바로 이것이다.

 

 

윈도우즈의 도움이 없는 황무지 같은 도스(MSDOS)같은 운영체제만 존재하는 컴퓨터에서 USB이동식디스크를 사용하는 것을 가정해보도록 하자. 윈도우즈에서 보는 탐색기와 비슷한 역할을 수행하는 프로그램이 있어야 한다. 이 프로그램 안에서는 USB이동식디스크를 마음대로 주무르는 코드를 가지고 있을 것이다. USB이동식디스크를 사용하는 사용자를 생각해보자. 이 사용자가 이 이동식디스크를 사용하는데 있어서 당신이 제공하는 탐색기와 같은 프로그램만을 사용하게 된다면 모르지만, 그렇지 않으면 당신은 여러 가지 탐색기 아류작을 만들어야 할 것이다. 사용자의 취향은 다양하기 때문이며, 목적도 다양하기 때문이다. 탐색기를 만들어서 사용자에게 제공하는 당신 입장에서는 앞으로 사용자가 원하는 그런 프로그램이 되기 위해서 계속된 업그래이드뿐만 아니라, 새로운 프로그램이 추가 작성될 수도 있다. 이렇게 사용자가 원하는 모양으로 프로그램이 추가되는 것 뿐 만아니라, 또 다른 기능을 가진 USB이동식디스크가 만들어지면, 이렇게 새롭게 만들어진 이동식디스크를 사용하기 위해서 역시 현재의 프로그램은 수정되어져야 한다.
이런식으로 수정 되어지는 부분은 크게 사용자가 보는 관점에서의 수정과 하드웨어(USB이동식디스크)의 변경으로 인한 수정으로 나뉠 수 있는데, 이렇게 수정되어지는 부분을 고려하기 위해서는 순수한 의미에서 응용프로그램과 하드웨어종속적인 코드를 담고 있는 디바이스드라이버라는 개념이 필요할 것이다.

응용프로그램과 디바이스드라이버를 나눈다는 것은 현실적으로는 하나의 프로그램이 아니라, 2개의 프로그램을 만든다는 것이다. 당신은 어느 쪽이 전문가이겠는가? 응용프로그램이겠는가? 아니면 디바이스드라이버이겠는가? 이 책을 읽는 독자라면, 아마 디바이스드라이버라고 대답할 것이다. 그렇다면 응용프로그램을 보다 더 전문가에게 맡기고, 당신은 디바이스드라이버만을 만들기 위해서는 일반인들이 자신이 만든 응용프로그램이 당신의 디바이스드라이버와 통신하는 환경을 만들어야 할 것이다. 이런 경우에 응용프로그램과 디바이스드라이버는 특정 인터패이스를 가지는 경우를 가정해보자. 인터패이스 계층이 존재하게 되면, 응용프로그램개발자는 이런 인터패이스에 맞추어서 자신의 프로그램을 만들게 되며, 디바이스드라이버 역시 인터패이스에 맞추어서 자신의 디바이스드라이버프로그램을 만들게 된다.
인터패이스를 가지게 됨으로써, 보다 확장성 있는 응용프로그램과 디바이스드라이버가 만들어질 수 있을 것이다.
이제, 조금 더 깊숙히 들어가보자. 이제 당신이 선택한 디바이스드라이버는 응용프로그램적인 요소는 완전히 제거되었다. 그런데, USB이동식디스크를 PC에 연결한다는 것은 사실 PC가 가진 USB HostController라는 장치가 제공하는 허브(HUB)에 이 이동식디스크를 연결한다는 의미이다. 그러므로, 디바이스드라이버가 이동식디스크를 관리하기 위해서라면, 필수적으로 PC에 내장된 USB HostController를 이해할 수 밖에 없다. PC에 내장된 USB HostController가 한가지 종류만 있는 것이 아니다. 크게 2가지의 형태로 시중에 나와 있기때문이다. 하나는 “Universal Host Controller"이며 또 하나는 ”Open Host Controller"이다. 이렇게 서로 다른 “USB HostController"에 대해서 여러분이 만든 USB이동식디스크드라이버는 모두를 고려하고 있어야 한다. , 이런 USB HostController가 사용하는 메인보드의 버스(BUS) ISA BUS인가? 아니면, PCMCIA BUS인가? 그것도 아니면 PCI BUS인가? 해당하는 버스에 연결되어 사용되려면, BUS에 대해서 고려하는 코드 역시 디바이스 드라이버내에서 구현 되어져야 한다. 예를 들어, PCI BUS를 사용하는 경우라면, PCI BUS가 요구하는 적절한 "PlugAndPlay Resource Enumeration"을 해야 한다. 또한, ”PCI Device Start"역시 제공해야 한다. “PCI Device Start"란 의미는 모든 PCI카드는 부팅과 PCIBIOS에 의해서 동작명령즉 Start명령을 받도록 설계되어 있기에, 이런 작업을 "PCI Device Start"라고 부른다. 과연, 당신은 USB이동식디스크를 위한 디바이스드라이버를 만들면서 이 모든 것들을 다 고려할 것인가? 해답은 그렇지 않는 것이 유리하다는 것이다. 꼭 그래야 한다면 할 수 없지만, 이 모든 것을 다 지원한다면 코드의 양은 무척 커지게 될 것이며, 코드의 신뢰도를 확인하는 작업도 만만치 않을 것 이다. 또한 코드의 확장성와 유연성에 있어서도 단점을 가지게 된다. 그런 이유로 인해서 당신이 만들게 되는 USB이동식디스크드라이버역시 몇 가지 부분으로 나뉘어 보다 전문성있는 개발자들이 손을 대는 부분을 염두하게 된다

 

 

결국, 디바이스드라이버라는 영역이 크게 3가지 영역으로 확장 되어진 결과가 되었다.
윈도우즈에서는 이렇게 구분되어서 작성된 디바이스드라이버를 많이 보게 된다
.
윈도우즈에서는 실제로 USB이동식디스크드라이버를 위한 드라이버코드는 크게 4부분으로 나뉘어디스크드라이버”, “USB HUB드라이버”, “USB HostController드라이버”, "PCI Bus드라이버등으로 나뉘게 된다. 이렇게 나뉘어지는 이유는 보다 전문적인 코드모듈을 사용하기 위해서이다. 이런 경우, 4가지의 드라이버들은 하나의 USB이동식디스크드라이버를 운용하기 위해서 함께 동작하는 하나의 모델을 그리게 되는데, 이런 모델을 그리기 위해서는 디바이스 스택(Device Stack)이란 개념이 필요하다.


윈도우즈에서 USB버스를 사용하는 장치는 이동식디스크만 있는 것이 아니라, USB Network카드, USB 카메라장치, USB 오디오 장치등등 많은 장치들이 있다. 따라서, USB이동식디스크드라이버에서 언급한 "USB HUB드라이버“, ”USB HostController드라이버그리고 ”PCI Bus드라이버등은 비단 USB이동식디스크에만 적용되지 않아야 한다. 다른 장치들도 이런 드라이버들이 필요하기 때문이다. 그런 이유에서 디바이스드라이버들은 자기자신이 개입해야 하는 장치들을 위해서 DeviceObject(Device Context)를 만들어서 사용하게 된다.

결국, 모든 드라이버는 자신이 포함 되어질 대상의 논리적인 하나의 디바이스를 위해서 하나의 DeviceObject를 생성하여 포함시키게 된다. 그렇기 때문에, 동일한 USB이동식디스크가 2개 사용되는 PC에서는 우리가 2개의 DeviceObject를 생성 해야 하는 이유가 여기에 있다고 할 수 있다.

이렇게 열거자와 주기능자로서의 역할을 모두 가지고 있는 드라이버들은 내부적으로 2가지의 DeviceObject를 생성하여 관리하고 있다. 이때, 열거자로서의 역할을 수행하는 DeviceObject Physical Device Object라고 부른다. 결국 하나의 논리적인 의미에서 PCI IDE CDROM디바이스를 구성하는 디바이스 스택은 크게는 하나의 DeviceStack이 사용되며, 세부적으로는 3가지의 DeviceStack으로 나뉠 수 있다.

 

 

“AddDevice Routine"함수가 호출되는 이유가 DeviceObject를 생성하라는데 있다고 보았다. 보다 더 정확한 의미에서 호출이유를 정의한다면, 그것은 바로 자신이 속할 DeviceStack을 위해서 필요로 하는 DeviceObject를 생성하라는 의미가 된다. , Physical Device Object(열거자로서의 역할) ”AddDevice Routine"에서 생성하는 것이 아니라는 점을 유념해야 한다. Physical Device Object가 생성되어진 이후에 DeviceStack이 생성되는 것 이기 때문이다. 결국 Physical Device Object가 없으면 디바이스스택자체도 생성될 수 있는 근거가 없는 것이다.

“AddDevice Routine"
함수의 프로토타입을 보자.


NTSTATUS  SAMPLEAddDevice(
         PDRIVER_OBJECT pDriverObject,
         PDEVICE_OBJECT pPhysicalDeviceObject );

파라미터에서 “pDriverObject" ”DriverEntry"함수에서 보았던 우리를 위해서 IOManager가 만들어 놓은 자료구조이다. “pPhysicalDeviceObject” DeviceObject로서, 현재 USB이동식디스크드라이버를 위해서 구축되고 있는 디바이스 스택의 가장 하위 DeviceObject, USB Bus드라이버가 생성한 DeviceObject를 의미한다. “AddDevice Routine"이 호출될 당시, 우리에게는 이처럼 우리가 속할 디바이스 스택 상의 버스드라이버가 생성한 가장하위 DeviceObject에 대한 주소를 넘겨받는다는 것을 알 수 있다.

현재 윈도우즈가 제공하는 디스크드라이버(DISK.SYS) USBHUB열거자(USBHUB.SYS)가 생성하는 PhysicalDeviceObject에 대해서 잘 어울리는(?) 성격을 가지고 있을까? 다시 말하면, 디스크드라이버(DISK.SYS)가 자신이 원할 때 어떤 일을 수행하는데 있어서, USBHUB열거자(USBHUB.SYS)드라이버를 호출하는 것이 자유스러운가 하는 이야기이다. 정답은 그렇지 않다는 것이다. USBHUB가 생성하는 열거자로서의 DeviceObject는 이런일을 하기에 너무 부족하다. USBHUB가 생성하는 열거자로서의 DeviceObject는 이런 USB이동식디스크장치에만 사용되는 것이 아니라, USB버스를 사용하는 거의 모든 USB디바이스에는 다 사용되는 성격을 가지고 있기 때문에 구태여 디스크드라이버를 위한 코드를 담고 있지는 않다는 것이다. 여기서, 우리는 필터드라이버의 역할을 배울수 있다. 지금같이 USBHUB드라이버와 DISK드라이버간의 매끄러운 연결을 지원하기 위해서 필터드라이버가 필요한 것이다
.

디바이스 스택 이란 의미는 여러 개의 DeviceObject들이 어떤 규칙을 가지고 서로 연결되어 있는데, 이런 연결된 DeviceObject가 생성되고 제거되는 순서가 마치 자료구조의 스택(Stack)과 같은 순서대로 이루어진다는 것이다
.

우리가 고려된 USB이동식디스크의 디스크 디바이스 스택이 형성되는 순서는 가장 하위의 USBHUB드라이버가 생성한 PhysicalDeviceObject가 가장 먼저 생성 되어 져야 하고, 그 다음에 우리가 개입하여 FilterDeviceObject를 생성해야 하고, 그 다음에 디스크드라이버가 개입하여 DeviceObject를 생성해야 하는 것이다. 그들간에는 서로 "AttachedDevice"라는 DeviceObject내의 맴버 변수에 의해서 연결되어 있다. 이런 연결된 관계는 후에 해체시기가 되면 역순으로 이루어지는데, 순서는 가장 상위의 디스크드라이버의 DeviceObject가 먼저 제거되고, 그 다음 우리가 개입한 FilterDeviceObject가 제거되고, 마지막으로 USBHUB드라이버가 생성한 PhysicalDeviceObject가 제거된다.


“AddDevice Routine"
이 하는 일은 여기까지이다. 이곳이 호출되는 것은 우리가 앞으로 사용하게 될 하드웨어장치의 문맥(DeviceObject)에 대한 생성과 특정 pPhysicalDeviceObject 로부터 시작된 디바이스 스택 상에 우리의 DeviceObject를 올려놓는일이다. 그렇기 때문에 ”AddDevice Routine" "Add My DeviceObject(Context) To Current Device(Context) Stack"으로 풀어서 설명할 수 있는 것이다. 주의할 점은 이곳 “AddDevice Routine"은 디바이스 스택을 형성하는 루틴이지, 하드웨어장치를 사용하는 루틴이 아니라는 점이다. 결국, USB이동식디스크드라이버를 작성하는 우리 역시, 이곳에서 USB이동식디스크를 사용하는 행동을 해서는 않 된다는 것이다. 디바이스 스택에 대한 더 자세한 내용은 3장에서 배우도록 하자.

[
정리]
AddDevice Routine
은 우리가 지원할 하드웨어장치가 PC에 연결되어 운영체제가 이를 인식하는 시기에 호출되는 루틴으로서, “DriverEntry"와 달리, 같은 종류의 하드웨어장치가 연결되는 횟수에 따라서 그와 같은 횟수로 호출되는 성질을 가진다.
이 곳에서는 우리가 앞으로 사용할 DeviceObject를 생성하여, DeviceObject DeviceStack상에 올려놓는 역할만 수행해야지, 이곳에서 역시 자신의 하드웨어장치를 사용하려는 행동을 해서는 안 된다. 그 이유는 아직 디바이스 스택이 완전히 완성된 것이 아니라, 완성하고 있는 과정 이기 때문이다.
 

 

 

(3) "IRP Dispatch Routine"


이곳은 외부로부터 명령어(IRP)를 받는 곳이다. 명령어(IRP)는 특별한 형식으로 되어 있는데, 이것은 단순한 명령어라고 하기 보다도, Device Stack에 전달되기에 적당한 형식으로 작성되어져 있다는 것이다. 명령어란 대게, 보내는 자와 받는 자와의 일대일 관계를 서술하는 것이 일반적이지만, IRP명령어는 보내는 자는 하나지만, 받는 자가 여러 개의 Device Object로 구성된 Device Stack이라는 점이 특이한 것이다.

일단, 이함수의 프로토타입을 보자.


NTSTATUS SAMPLEPnpDispatch(
       PDEVICE_OBJECT pDeviceObject, PIRP pIrp );

 첫 번째 파라미터인 pDeviceObject는 앞부분에서 서술했던, 우리가 생성한 DeviceObject를 의미한다. 이것을 통해서 2가지 이상의 같은 종류의 하드웨어를 구분하여 프로그래밍할 수 있는 문맥을 삼을 수 있다. 두 번째 파라미터는 명령어, IRP이다.
이 함수는 외부로부터 IRP를 받아드린다는 점에서 중요한 역할을 수행한다. WDM디바이스드라이버가 이런 IRP Dispatch함수를 두지 않으면, 외부로부터 완전히 고립된다는 의미를 가지기도 하기 때문이다. WDM디바이스드라이버가 IRP를 받지 않는다는 것은 현실적으로는 어떤 서비스로 수행하지 못한다는 것을 의미하며, 따라서 그런 드라이버는 실제로 존재가치가 무의미하다
.
따라서, WDM디바이스드라이버가 어떤 DeviceStack에 동참하고 있는 한, 외부로부터 명령어(IRP)를 받는 것은 당연하다고 할 수 있다
.
우리가 형성하고 있는 DeviceStack으로 보내지는 명령어들은 크게 몇 가지로 구분된다
.

IRP
DeviceStack을 사용하려는 외부의 Client는 항상 준비해야 하는 구조체이기도 하다. IRP구조체역시 뒷장에서 자세히 배우므로, 이곳에서는 DeviceStack해체를 수행하는 명령어인 IRP_MJ_PNP(IRP_MN_REMOVE_DEVICE)에 대해서만 살펴보도록 한다.


case  IRP_MN_REMOVE_DEVICE:
 
     IoDetachDevice(ptargetDevice);
      IoDeleteDevice(pDeviceObject);

 

문장을 볼수있는데, 이 문장은 현재 우리가 참여하고 있는 DeviceStack에서 우리를 제거한다는 의미를 가진다.

 

IoDetachDevice()함수는 앞 부분에서 배운 IoAttachDeviceToDeviceStack()함수의 반대역할을 수행하는 함수이다. 이 함수를 사용함으로써 현재 우리와 우리아래에 연결된 DeviceObject와의 연결관계가 끊어진다.

IoDeleteDevice()
함수는 앞 부분에서 배운 IoCreateDevice()함수의 반대역할을 수행한다. 이 함수를 사용함으로써 현재 우리가 생성했던 DeviceObject를 제거한다.

출처 : http://blog.naver.com/netrabbit?Redirect=Log&logNo=20055180390

 

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:55

WDM의 한계


1. WDM 드라이버를 작성하는 것은 로우레벨 작업이고, 복잡하다.

ex> 현재의 WDM 모델에서 드라이버 작성할 때  plug & play, power management 지원하려면,

     최소한 2000라인이 필요

     DDI(Device Driver Interface) 쓰기 어려움

2. DDI가 구식이다.

- 오직 퍼포먼스만을 위해 디자인되었고, 보안이나 사용 편의성이 고려되지 않아있다.

  그래서 DDI는 드라이버들에게 OS의 핵심적인 데이터 구조들을 곧바로 노출!
  →  MS는 이런 구조체들의 변경이 제한될 수 밖에 없었고, 드라이버가 시스템을 크래쉬 시킬 확률 증가!

 

- Versioning 지원이 없다.

3. 미니포트 모델이 너무 많다.

- Windows는 현재 10개 이상의 miniport  Model을 지원.

- 미니포트 모델마다 pnpdhk power management 다루는 방법이 다르다.

4. 멀티펑션 디바이스를 지원하는 작업이 너무 복잡하다.

두가지의 다른 미니포트 모델에 적용되는 멀티펑션 디바이스 지우너위해, 개발자는 WDM 버스 드라이버와 두개의 미니포트 드라이버 작성이 필요

5. 현재 드라이버 모델은 커널 레벨에서만 동작해야 한다.

- WDM과 미니포트 드라이버 모델 → 커널모드에서 실행

커널모드 드라이버는 운영체제의 일부로 대접받게 되므로, 시스템의 가상 메모리 영역 접근권한을 가짐.

이 때문에 커널모드 드라이버에서 에러가 발생하면 바로 시스템을 크래쉬 시키는 BSOD!!

6. 테스트 툴들이 쓰기 어렵다.

 

 

차세대 윈도우즈 드라이버 모델의 디자인 목적

1. 드라이버 모델은 심플하고 유연성(flexible)이 있어야 한다.

 

2. 드라이버 모델은 코어 운영체제 컴퓨넌트와 분리되어야만 한다.

 

3. 드라이버 모델은 일관성이 있어야 한다.

 

4. 드라이버 모델은 확장성이 있어야 한다.

 

5. 드라이버 모��유저모드를 지원해야 한다.

 

6.드라이버 모델은 고급 레벨의 언어를 지원해야 한다.

 

 

WDF(Windows Driver Framework)

* 프레임워크

- 커널모드 드라이버 프레임워크 (KMDF)

 

- 유저모드 드라이버 프레임워크 (UMDF)

- WDF는 Object-oriented, event-driven model을 제공

: Object는 드라이버를 위한 블록을 만드는 것으로 작업을 한다. 드라이버는 이 objects를 미리 정의된 인터페이스들을 통해 수정할 수 있다.

Event 집합은 object의 각각의 종류에 영향을 줄 수 있다. Framework는 각각의 event를 위한 default behavior가 정의되어 있다.
Device-specifice behavior를 위해, 드라이버는 defaults를 재정의할 수 있는 callback routine를 포함하고 있다.

 

- 커널모드 드라이버 프레임워크(Kernel-Mode Driver Framework; KMDF)

커널모드 드라이버를 만드는데 필요한 기본적 기능인 PnP, Power Management, I/O queue, DMA, WMI, synchronization 등 기본적인 기능을 구현, IEEE 1394, USB 같은 주요 버스들에 대한 지원도 한다.

 

운영체제 커널의 일부분으로 포함된 것이 아니라, 별도로 제공되는 독립된 라이브러리이다.

 

커널모드 드라이버 프레임워크에서 인터페이스는 미래에도 가능한 한 드라이버의 독립성을 추구하도록 디자인되어 있다. 만약 드라이버가 크래쉬되면, OS는 시스템을 멈추거나 전체를 크래쉬하지 않고 복구하거나 드라이버엥 의해 할당된 리소스들을 클린업한다.

- 유저모드 드라이버 프레임워크(User-Mode Driver Framework)

Pnp, Power management, 비동기 I/O 지원을 포함하는 커널모드 프레임워크 기능의 일부분을 구현한다.

DMA 수행, 인터럽트 지원, nonpaged 풀 등의 커널모드 리소스를 사용하지 않는 드라이버들은 유저모드에서 실행될 수 있다.

 

네트워크와 연결된 디바이스들(network-connected devices)을 위한 드라이버와 PMP, 카메라, 핸드폰 같은 USB 디바이스들을 위한 드라이버를 만들 수 있다.

 

UMDF는 비스테이서 지원된다. XP에서 지원은 정책결정중이라던데..이부분은 찾아봐야하겠다.

[출처] WDM과 WDF

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:54

WDM 드라이버 기본 구조

 

디바이스 드라이버 : 운영체제가 하드웨어 디바이스와 관련된 다양한 동작을 하기 위해 호출할 수 있는 서브루틴의 집합을 담을 수 있는 컨테이너의 개념.

 

디바이스 드라이버 계층화

 

WDM은 일정한 규격에 맞게 드라이버의 레이어가 생성되도록 한다.

DO(Device Object)들은 시스템이 소프트웨어의 하드웨어 관리를 돕기 위해 만들어 낸 자료 구조.

 

스택의 가장 낮은 레벨의 디바이스 객체는 PDO(Physical device object)라고 한다.

디바이스 객체의 가운데쯤 있는 것이 FDO(Functional device Object)라고 불리우는 객체이다.

 

FDO의 위쪽과 아래쪽에는 filter device object들의 모임이 존재할 것이다. FDO의 위에 위치한 객체는 상위필터(upper filter)라고 하고, 아래에 위치한 객체(PDO보다는 위에 있지만..)는 하위필터(lower filter)라고 한다.

 

Filter device object FiDO라는 약어로 사용하는 것을 볼 수 있지만 공식적인 것이 아니다. 하지만 FiDO라고 해도 알아듣도록 하자.

 

운영체제의 PnP 매니저 컴포넌트는 디바이스 드라이버의 요청에 의해 디바이스 객체의 스택을 형성한다.

 

Bus에 대한 드라이버의 역할 중에 하나는 bus에 부착된 디바이스를 나열하고, 그들 각각에 대한 PDO를 생성한다. PDO를 생성한 다음 PnP 매니저는 filter driver FDO를 찾기 위해 레지스트리 데이터베이스를 참고한다. 레지스트리 엔트리는 스택에 나타날 드라이버의 순서를 정한다. 그 결과 가장 낮은 레벨의 filter driver를 로딩하고 그것의 AddDevice 함수를 호출 함으로써 PnP가 시작된다.

AddDevice filter driver를 만들고 filter driver와 드라이버 사이의 수평 링크를 생성한다. 그리고 나서 PDO filter driver에 연결한다.

 

계층화의 목적.

디바이스에 영향을 주는 오퍼레이션에 대한 요청 각각은 IRP(I/O Request Packet)를 사용한다.

IRP는 디바이스에 대해 최상위에 위치한 드라이버로 정상적으로 보내지고, 다른 드라이버로 스택을 타고 내려올 수 있다. 각 레벨에서 드라이버는 IRP로 무엇을 할 것인가를 결정한다.

 

하나의 하드웨어를 위해 스택을 점유하고 있는 다양한 드라이버들은 각기 다른 역할을 수행한다.

FDO는 디바이스를 관리하고, bus driver(PDO)는 디바이스와 컴퓨터 사이의 연결을 관리한다.

Filter driver IRP의 스트림을 감시하거나 수정하는 역할을 한다.

 

PDO FDO와 완전히 다른 작업을 수행한다. FDO IRP PDO에게 내려보냄으로써 PDO에게 어떤 작업을 위임한다”.

 

시스템이 드라이버를 로드하는 방법

 

재귀적 계산(enumeration)

 

PnP 매니저는 실제로 존재하지 않는 루트(root)” 버스에 대한 빌트-(built-in, 내장)드라이버를 가지고 있다. PCI 같은 Primary hardware bus를 포함해서 루트 버스 드라이버는 레지스트리로부터 컴퓨터에 대한 정보를 얻는다.

 

Primary bus에 대한 function driver는 자신의 하드웨어를 전기적으로 나열(enumerate)한다.

PCI 버스는 각각의 부착된 디바이스에 대한 특별한 구성 공간(디바이스에 대한 디스크립션과 리소스 요구사항이 포함되어 있다)을 액세스하는 방법을 제공한다.

Bus driver가 하드웨어를 나열할 때 버스 드라이버는 일반적인 function driver로 가장하여 작동하다가 하드웨어를 감지한 후에는 드라이버의 역할이 바뀌게된다(감지된 하드웨어에 대한 새로운 PDO)를 생성하게 된다.

 

레지스트리의 역할

 

레지스트리 데이터베이스가 드라이버를 로드하고 디바이스를 구성하는 과정에서 결정적인 역할을 한다. 그러므로 어떤 레지스트리 키들이 관련되어 있고 그것들이 무엇을 담고 있는지 설명한다.

 

세가지 다른 레지스트리 키(하드웨어 키, 클래스 키, 서비스 키)들은 구성(configuration)과 관계가 있다. 저 세가지의 이름은 적절한 이름이 아니다.

하드웨어 키는 단일 디바이스에 대한 정보를 포함한다. (인스턴스 키라는 이름을 사용하기도 한다.)

클래스 키는 같은 타입의 모든 디바이스들과 관련이 된다.

서비스 키는 드라이버들에 대한 정보를 포함한다. (소프트웨어 키라는 이름을 사용하기도 한다.)

 

하드웨어 키 :

레지스트리의 HKEY_LOCAL_MACHINE\System\CurrentSet\Enum 서브키에서 나타난다.

Enum키 아래에 있는 첫번째 레벨에 위치한 서브키들은 시스템에 있는 다른 버스 나열자와 대응된다. 모든(과거 또는 현재) USB 디바이스들의 디스크립션이 …\Enum\USB 서브키안에 존재한다.

ClassGUID는 디바이스 클래스를 식별케 해주는 globally unique indentifier(GUID)의 고유 ASCII표현방식이며 디바이스의 클래스키에 대한 포인터이다.

Service서비스 키에 대한 포인터이다.

 

클래스 키:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Class에서 나타난다.

클래스 키들의 키 이름은 마이크로소프트에서 지정한 GUID이다.

각 디바이스는 클래스 키 아래에 자신의 서브키(0000 같은)를 가지고 있다. 이 키의 이름은 디바이스 하드웨어 키에 있는 드라이버 값이다.

 

서비스 키:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services에 나타난다.

이것은 드라이버의 실행 가능한 파일이 디스크에 놓인 위치를 나타내고 드라이버가 로드되는 방식을 제어하는 다른 매개 변수들을 포함한다.

서비스키에 대한 참고링크 : http://support.microsoft.com/kb/103000

 

 

드라이버 로딩의 순서

 

PnP 매니저가 새로운 디바이스를 접하게 되면 하드웨어와 클래스 키를 열고, 드라이버를 로드하기 위해 다음과 같은 순서로 작동한다.

1.     디바이스에 대한 하드웨어 키에서 지정된 하위 필터 드라이버. LowerFilters 값은 REG_MULTI_SZ 타입이기 때문에 하나 이상의 드라이버를 지정할 수 있다.

2.     클래스 키에서 지정된 하위 필터 드라이버, 또다시 하위 필터 드라이버, 이것들이 LowerFilter 값의 데이터 스트링에 나타난 순서대로 로드된다.

3.     하드웨어 키에서 Service 값에 의해 지정된 드라이버

4.     하드웨어 키에서 지정된 상위 필터 드라이버 그리고 UpperFilters 데이터 스트링에 나타난 순서

5.     마찬가지로 또다시 클래스 키에서 지정된 상위 필터 드라이버 그리고 UpperFilters 데이터 스트링에 나타난 순서대로 로딩한다.

 

시스템이 드라이버를 로드한다고 말할 때 이것은

드라이버의 이미지를 가상 메모리로 맵핑하고,

재배치 가능한 레퍼런스들을 수정하며,

드라이버의 메인 엔트리 포인트를 호출하는 것

을 의미한다. 메인 엔트리는 보통 DriverEntry라고 부른다.

 

디바이스 객체가 상호 관계를 가지는 방법

 

디바이스 객체 스택의 트리에서 IRP가 반드시 PDO에서 꼭대기의 filter driver object로 흘러가야 한다는 것을 의미하지 않는다. 간단하게 말하면 트리의 구조에 있는대로 다음의 드라이버객체로 IRP를 보낼 수도 있고 보내지 않을 수도 있다는 이야기이다.

 

이외에 설명이 더 있는데 이 부분은 잘 이해가 안된다.. L

 

 

드라이버 객체

 

I/O 매니저는 각각의 디바이스 드라이버를 나타내기 위해 드라이버 객체 자료 구조를 사용한다.

이 드라이버 객체 자료 구조는 부분적으로 불투명(opaque)하다. _DRIVER_OBJECT에서 강조되지 않은 부분이 그에 해당한다.

 

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;

 

헤더는 구조체를 DRIVER_OBJECT라는 타입명으로 정의한다. 또한 포인터 타입(PDRIVER_OBJECT)을 선언하고 구조체 태그(_DRIVER_OBJECT)를 할당한다.

 

드라이버 객체 구조체의 액세스 가능한 필드들에 대해 간단하게 알아본다.

DeviceObject(PDEVICE_OBJECT) : 드라이버에 의해 관리되는 각각의 디바이스에 대한 디바이스 객체 자료 구조의 리스트를 가리킨다.

DriverExtension(PDRIVER_EXTENSION) : 오직 AddDevice(PDRIVER_ADD_DEVICE) 멤버만을 액세스 할 수 있는 작은 하위 구조체를 가리킨다. 그 안에서 드라이버가 드라이버 객체를 만들어 내는 함수에 대한 포인터이다.

HardwareDatabase(PUNICODE_STRING) : 디바이스에 대한 하드웨어 데이터베이스 레지스트리 키의 이름이 들어 있는 문자열을 나타낸다. \Registry\Machine\Hardware\Description\System과 같은 이름이다.

FastIoDispatch(PFAST_IO_DISPATCH) : 파일 시스템과 네트워크 드라이버가 익스포트(export)하는 함수 포인터들의 테이블을 가리킨다.

DriverStartIo(PDRIVER_STARTIO) : 직렬화(serialize) I/O 요청을 처리하는 드라이버 함수를 가리킨다.

DriverUnload(PDRIVER_UNLOAD) : 드라이버의 클린업 함수를 가리킨다.

MajorFunction(PDRIVER_DISPATCH의 배열) : I/O 요청 타입의 각각을 처리하는 드라이버의 함수에 대한 포인터들의 테이블이다.

 

디바이스 객체

 

다음은 DEVICE_OBJECT의 자료 구조이다.

 

typedef struct _DEVICE_OBJECT {
  CSHORT  Type;
  USHORT  Size;
  LONG  ReferenceCount;
  PDRIVER_OBJECT  DriverObject;
  PDEVICE_OBJECT  NextDevice;  PDEVICE_OBJECT  AttachedDevice;
  PIRP  CurrentIrp;
  PIO_TIMER  Timer;
  ULONG  Flags;
  ULONG  Characteristics;

  __volatile PVPB  Vpb;
 
PVOID  DeviceExtension;
  DEVICE_TYPE  DeviceType;
  CCHAR  StackSize;  union {
    LIST_ENTRY  ListEntry;
    WAIT_CONTEXT_BLOCK  Wcb;
  } Queue;
  ULONG  AlignmentRequirement;
  KDEVICE_QUEUE  DeviceQueue;
  KDPC  Dpc;
  ULONG  ActiveThreadCount;
  PSECURITY_DESCRIPTOR  SecurityDescriptor;
  KEVENT  DeviceLock;
  USHORT  SectorSize;
  USHORT  Spare1;
  PDEVOBJ_EXTENSION  DeviceObjectExtension;
  PVOID  Reserved;
} DEVICE_OBJECT, *PDEVICE_OBJECT;

 

DEVICE_OBJECT에 대한 자세한 내용 : http://msdn.microsoft.com/en-us/library/aa491677.aspx

 

출처 : http://wert.egloos.com/4502911

 

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:38

음......

흔히들 디바이스 드라이버를 이용한 커널 레벨 후킹을 할 때에는,

모든 동일한 프로세스가 Kernel32.dll의 API를 호출할 때,

API가 실질적으로 호출하는 내부함수의 주소를

NTOSKRNL의 KeServiceDescriptorTable에서 참조하는 것을 이용하여

KeServiceDescriptorTable.ServiceTableBase[함수 인덱스 번호]

를 수정하여 자신의 함수주소로 바꾸어 놓습니다.


예를 들어, ZwOpenProcess()를 후킹한다 가정하면

그 코드는 아래와 같습니다.


중요한 부분만 올려놓았습니다.


#include <ntddk.h>


#define PROTECT_PROCESS_ID                   456

typedef struct _SERVICE_DESCRIPTOR_TABLE {
         unsigned int *ServiceTableBase;
         unsigned int *ServiceCounterTableBase;
         unsigned int NumberOfServices;
         unsigned char *ParamTableBase;
} SERVICE_DESCRIPTOR_TABLE;


//import 선언

NTKERNELAPI

SERVICE_DESCRIPTOR_TABLE

KeServiceDescriptorTable;


NTKERNELAPI

HANDLE

NTAPI

PsGetProcessId(

         IN PEPROCESS Process);


//함수에 해당하는 주소를 KeServiceDescriptorTable에서 쉽게 알수 있게 해주는 매크로

#define SYSTEMSERVICE(_function)  KeServiceDescriptorTable.ServiceTableBase[*(PULONG)((PUCHAR)_function+1)]

typedef NTSTATUS (NTAPI *ZWOPENPROCESS)(

        OUT PHANDLE ProcessHandle,

        IN ACCESS_MASK DesiredAccess,

        IN POBJECT_ATTRIBUTES ObjectAttributes,

        IN PCLIENT_ID ClientId OPTIONAL);


ZWOPENPROCESS PrevZwOpenProcess; //원래의 함수주소를 담기위한 것


NTSTATUS ZwOpenProcessHandler(

        OUT PHANDLE ProcessHandle,

        IN ACCESS_MASK DesiredAccess,

        IN POBJECT_ATTRIBUTES ObjectAttributes,

        IN PCLIENT_ID ClientId OPTIONAL)

{

         NTSTATUS Status=PrevZwOpenProcess(ProcessHandle, DesiredAccess,

               ObjectAttributes, ClientId);   //원래 함수를 호출

         if(NT_SUCCESS(Status))   //리턴값이 성공을 나타내면 Process Object를 얻음

         {//Process Object=EPROCESS 구조체

                  PEPROCESS Process;   //Process Object

                  NTSTATUS ObjectStatus=ObReferenceObjectByHandle(

                           *ProcessHandle, PROCESS_ALL_ACCESS, NULL, KernelMode,

                           &Process, NULL);  //프로세스 핸들로 EPROCESS의 포인터를 얻음

                  if(NT_SUCCESS(ObjectStatus))  //성공적으로 EPROCESS를 얻었다면

                  {  //EPROCESS의 구조체 내부의 UniqueProcessId를 조사해서

                      //그 프로세스가 보호하려는 프로세스인지 알아낸다.

                      //이러한 일을 해주는 함수가 PsGetProcessId이다.

                      //이 함수는 ddk 헤더파일에 선언되어있지 않으므로 직접 선언해주자.

                      //EPROCESS 구조체 역시 ddk 헤더파일에 선언되어있지 않다.

                      //직접 인터넷을 뒤지면 나올지도 모르나 OS마다 구조체가 다를 수 있으므로

                      //PsGetProcessId()를 호출하는 것이 훨~씬 안정적이다.

                      //PsGetProcessId()를 사용할 수 없다면 직접 프로세스아이디 오프셋을

                      //구해 와야 한다.

                      //참고 : *(ULONG *)((ULONG *)Process+프로세스아이디 오프셋)=

                      //Process->UniqueProcessId

                           if(PsGetProcessId(Process)==(HANDLE)PROTECT_PROCESS_ID)

                           {

                                    //보호하려는 프로세스가 맞으면

                                    //프로세스 핸들을 닫은 후 NULL로 세팅해준다.

                                    ZwClose(*ProcessHandle);

                                    *ProcessHandle=NULL;

                                    //엑세스 거부를 return.

                                    //STATUS_ACCESS_DENIED 값은 LsaNtstatusToWinError()로

                             //ERROR_ACCESS_DENIED로 바뀌어서 SetLastError()의 인자가 된다.

                                    return STATUS_ACCESS_DENIED;

                           }

                  }

         }

         return Status;

}


void SetupHookZwOpenProcess()

{

         //원래의 함수 주소를 저장

         PrevZwOpenProcess=(ZWOPENPROCESS)(SYSTEMSERVICE(ZwOpenProcess));

         //Cr0 Register 연산으로 보호를 해제시킨다.

         __asm
         {
                  cli
                  mov eax, cr0
                  and eax, not 10000h
                  mov cr0, eax
         }

         //SDT 수정부분

         (ZWOPENPROCESS)(SYSTEMSERVICE(ZwOpenProcess))

                  =ZwOpenProcessHandler;

         //Cr0 Register 연산으로 원래대로 보호시킨다.

         __asm
         {
                  mov eax, cr0
                  or eax, 10000h
                  mov cr0, eax
                  sti
         }

}


지금부터 이러한 방법에 대한 장/단점에 대해 알아봅시다.

장점은 쉽다는 것입니다.

그 때문에 루트킷에서도 널리 쓰이고 있으며, 백신 또한 각각의 프로세스나 파일의 보호/감시를 위해서 쓰고 있습니다.

하지만, 이렇게 쉬운만큼 역시나 단점도 존재합니다.

여러가지 방법으로 인해서 무력화 될 수 있다는 것이죠.

1. \Device\PhysicalMemory에 엑세스하여 ntoskrnl의 이미지에서 주소를 구해와 SDT를 원래대로 돌려놓거나

2. 서비스 테이블을 덮어쓰는 것이므로 커널모드 드라이버를 로드하여 ntoskrnl의 이미지에서 주소를 구해와서 원래 주소로 덮어쓰기한다.

3. KeServiceDescriptorTable.ServiceTableBase를 Relocate시킨다.

이 외에도 몇몇가지 방법이 더 있고 대처방법 역시 있습니다.


우선 첫번째 방법에 대한 대처방법은

ZwMapViewOfSection()/ZwOpenSection()을 후킹하여 \Device\PhysicalMemory일 경우 막아버리면 됩니다.

혹은, Admin의 권한이 없을 때에는, 1번 방법은 통하지 않습니다.

두번째 방법에 대한 대처방법은

ZwLoadDriver()를 후킹하거나

3번 방법처럼 하면 됩니다.

세번째 방법은 실제 디스크 이미지로부터 서비스 함수들의 주소를 각각 구해 배열로 NewKiServiceTable을 만든 후,

이것을 DeviceIoControl로 드라이버에게 넘겨준 후, 드라이버로 하여금 NewKiServiceTable의 함수를 사용하게 하면 될 것입니다.

Posted by skensita