윈도우 커널 구조
Ring 0 - Kernel
Ring 1 - Device Driver
Ring 2 - Device Driver
Ring 3 - Applications
RING 0~2까지가 Kernel-Mode , RING3는 User-Mode로 동작.
- 응용 프로그램 (Apllication)이 하드웨어 장치에 접근하기 위해서는 드라이버가 필요함.
- 커널모드는 기본적으로 사용자가 접근 불가능. 현재 윈도우 커널모드에서 RING 1과 2는 사용하지 않고 RING0 만을 사용한다.
- 대부분의 응용 프로그램들이 RING3에서 유저모드로 동작함.
- NT 서비스 프로그램 : 윈도우 계정 로그온/로그아웃에 무관하게 항시 동작하게 만드는 프로그램 형태. 디바이스 드라이버는 NT 서비스 형태로 시스템에서 동작하여 항시 상주하거나 언제건 메모리에서 제거할 수 있다.
- 낮은 레벨의 RING 단계 프로그램은 보다 상위단계 RING 프로그램에 간섭과 접근이 불가능함.
- 허나 특정 조건에서 상위권한 RING에 접근을 허용할수도 있음.
환경 구축
윈도우 10 기준, windows10 SDK, Visual studio 2019, WDK 10 , 디버깅을 위한 dbgview 설치가 필요함. 현재는 윈 11이 나온 상태이기 때문에 MSDN에서는 11 기준으로 소개하므로, 별개 링크에서 구성요소를 준비한다.
윈도우는 하위호환성 ( win7에서 제작 → win10에서 실행) 은 지키지만 상위호환성(win10에서 제작 → win7에서 실행) 은 지켜지지 않으므로 되도록 구버전 프로그램을 이용해 구축하자.
- sdk10 : https://developer.microsoft.com/ko-kr/windows/downloads/windows-10-sdk/
- wdk10 : https://docs.microsoft.com/en-us/windows-hardware/drivers/other-wdk-downloads
wdk 설치 후 자동으로 VS2019용 확장프로그램까지 설치가 진행된다.
프로젝트 생성 및 드라이버 프로젝트 기본 구성
Visual Studio에서 프로젝트 새로 생성 - WDM Driver로 진행한다.
cpp 소스파일 생성후 코드를 작성한다.
우선 헤더파일을 include 해야하는데, 두가지 종류가 있다.
- ntddk.h : NT Legacy Style 드라이버
- 디바이스 스택을 사용하지 않아 장치와 통신하지 않는, 커널레벨에서 코드 동작을 위해 만드는 드라이버.
- 어느 디렉토리에 있더라도 실행이 가능하다.
- wdm.h : WDM style 드라이버
- 디바이스 스택을 사용하며 장치와 통신한다.
- Windows\System32\Drivers 디렉토리에 존재해야 한다.
*디바이스 스택?
- 하나의 하드웨어 디바이스 관리를 위해서는 해당 하드웨어를 관리하기 위해 여러개의 드라이버가 필요함
- 각 드라이버는 디바이스 관리를 위하여 각 디바이스에 자신(드라이버)의 디바이스 오브젝트를 생성한다.
- 하나의 디바이스 내에 여러 드라이버들이 만든 디바이스 오브젝트가 스택처럼 만들어지게 되는데, 이것이 디바이스 스택이다.
- window에 연결된 장치들을 Device Tree로 표기하고, 트리에 표현되는 각 장치들을 Device Node라고 한다.
- 디바이스 노드 내에 구성되는 Device Object 들이 모여 Device Stack이 된다.
나중에 별도로 정리한 문서 만들 필요 있어보임.
헤더파일 include 후 Driver Entry를 작성해준다.
일반적인 C 언어 파일의 main함수,DLL 파일로 치면 dllmain.cpp 에 있는 APIENTRY DLLMain 함수와 동일하게 가장 먼저 실행되는 함수이다.
#include <ntddk.h>
extern "C" NTSTATUS DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "Hello World!\n");
return STATUS_UNSUCCESSFUL;
}
extern C는 컴파일 시 네임 맹글링을 C언어 방식으로 사용하겠다는 의미로, C++방식 네임 맹글링이 이뤄질 시 외부 프로그램에서 이 모듈을 참조할 수 없기 때문에 타 모듈과의 호환성을 위해 C 네임 맹글링을 이용하게 된다.
위와같이 코드 작성 후 빌드를 해야하는데, 하위호환성 옵션을 먼저 해야한다.
프로젝트 속성 - Driver Settings에 들어가보면 Target OS, 즉 목표로 하는 OS 버전을 선택할 수 있는데, 윈도우의 경우 상위호환성이 지켜지지 않기 때문에 윈7 기준으로 빌드해야 그 하위 OS들에서 실행할 수 있다.
NT legacy style은 드라이버 설치를 지원하지 않기 때문에 설치파일인 .inf 파일은 솔루션 목록에서 삭제해줘야한다.
빌드시 "이 프로젝트에는 스펙터 완화된 라이브러리가 필요합니다 " 라는 에러가 발생한다면, 속성 → C/C++ → CodeGeneration(코드 생성) → Spectre Mitigration(스펙터 완화) 를 Disable 해야한다.
( https://docs.microsoft.com/ko-kr/visualstudio/msbuild/errors/msb8040?view=vs-2019)
또한 미 사용 파라미터는 오류로 처리되기 때문에 예외처리가 필요하다.
#include <ntddk.h>
extern "C" NTSTATUS DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath) {
//미 사용 파라미터를 에러처리 하지 않으면 오류 발생
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
//디버그 메시지 출력
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "Hello World!\n");
return STATUS_UNSUCCESSFUL;
}
드라이버 서비스 등록 및 실행
빌드가 왼료되면 드라이버를 서비스에 등록해서 실행해봐야한다.
sc.exe [<servername>] command [<servicename>] option
윈도우에서 지원하는 sc를 이용해 드라이버를 서비스에 등록하고 실행해보자.
- create : 서비스 생성
- query : 서비스 상태 확인
- start : 서비스 실행
- stop : 서비스 중단
- delete : 서비스 제거
리눅스의 systemctl이나 service와 비슷한 역할을 수행한다고 생각해도 될듯 .
아래 명령을 관리자권한 cmd에서 실행해주자.
sc create driver1 binpath="C:\Users\synod2\source\repos\MyDriver1\Debug\MyDriver1.sys" displayname=driver1 start=demand type=kernel
- binpath : 바이너리 위치
- displayname : 사용자에게 보여질 이름
- start : 구동 방법
- type : 드라이버 서비스 유형
SC 옵션 관련해서 나중에 일괄정리 필요할듯
서비스 생성이 완료되면 디버거를 켜두고 실행까지 진행
디버그 로그 분석 도구 Debugview ++
유저모드와 커널모드 디버깅 로그를 볼 수 있는 프로그램인 debugview의 개량판인 debugview++을 이용해서 로그를 찍어보자.
https://github.com/CobaltFusion/DebugViewPP
디버그뷰를 관리자권한으로 실행 후, cmd에서 아까 등록한 드라이버를 실행해본다.
위 에러는 시스템 아키텍쳐가 맞지 않으면 발생할 수 있는 에러다.
혹은 64비트 빌드시 이런 에러가 날수도 있다.
이는 64비트 윈도우10 환경이기 때문. 빌드된 드라이버가 32비트건 64비트건 서명이 되어있지 않으면 차단되는걸로 보인다.
윈도우에서 서명되지 않은 드라이버를 설치할 수 있도록 설정 변경이 필요하다.
bcdedit.exe -set loadoptions DISABLE_INTEGRITY_CHECKS
bcdedit.exe -set TESTSIGNING ON
혹은
bcdedit /set nointegritychecks on
bcdedit /set testsigning on
cmd에서 위 명령어 실행 후 재부팅 진행
이걸로도 안되면 고급 시작옵션을 통해 설정해야한다.
고급 시작옵션 - 문제해결 - 고급옵션 - 시작 설정 선택하여 재부팅하면
이렇게 선택하는 창이 나오는데, F7을 입력하여 드라이버 서명 적용 안함 을 선택한다.
우하단에 저런 메시지가 뜨면 TESTSIGNING이 적용된거다.
허나 계속 오류가 발생하고, DBGview에도 디버그 메시지가 나오지 않더라.
실패 원인 분석
확인해보니 각기 다른 원인이 있었다.
- STATUS_UNSUCCESSFUL 을 반환시에 저렇게 실패 메시지가 나온다
- 디버그 메시지가 나오지 않는건 win7 이상부터는 dbgprint가 그냥은 나오지 않기때문
몇가지 방법을 시도해볼건데
- 디버그 메시지가 나오게끔 수정 + 환경 구성
- 1이 안된다면 STATUS_UNSUCCESSFUL을 반환하지 않게 수정
dbgview 대신 어차피 VM에 올라가있으니 그냥 커널 디버거를 붙여버리는걸로
https://github.com/4d61726b/VirtualKD-Redux
재시도
아래 순서로 다시 시작해봐야겠다.
- 개발환경에서 장치 드라이버 빌드
- VM으로 옮겨서 실행, 유저모드 우회 체크
- VirtualKD 걸고 디버깅
그 전에 먼저, 드라이버 로드하는 프로그램을 사용하는 대신 로더도 직접 빌드해서 사용해보자.
참고 문서
https://docs.microsoft.com/ko-kr/windows-hardware/drivers/download-the-wdk - WDK 다운로드 MSDN페이지 (win11)
https://developer.microsoft.com/ko-kr/windows/downloads/windows-10-sdk/- MSDN 페이지(구버전 윈도우)
https://nroses-taek.tistory.com/276 - 윈도우 디바이스 드라이버 제작
https://richong.tistory.com/268 - 디바이스 스택
https://docs.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/device-nodes-and-device-stacks -디바이스 스택 + 디바이스 노드
https://richong.tistory.com/89 - 디바이스 오브젝트 , 디바이스 노드
https://jungmonster.tistory.com/17 - 디바이스 스택
https://oasess.tistory.com/12 -윈도우7 이상에서 디버그 메시지 출력
https://hack.kr/87 드라이버 로더 만들기