진행해볼 순서
- 드라이버 로더 제작
- 올라갈 드라이버 제작
- 환경에 디버거 (VirtualKD) 연결 + 로컬에 winDBG 세팅
실습 환경
가상 OS : Windows10 20H2 - 19042.1237
로컬 OS : Windwos10 21H1 - 19043.1348
VS : Microsoft Visual Studio Community 2019 - 16.10.3
.NET : Microsoft .NET Framework 4.8.04084
CPP : Visual C++ 2019 00435-60000-00000-AA581
DBG : Debugging Tools for Windows 10.0.19030.1000
driver kit : Windows Driver Kit 10.0.19030.1000
1. 드라이버 로더 제작
C언어로 작성된 코드 예제. 코드를 따라가면서 분석해보자.
#include <windows.h>
#include <stdio.h>
BOOL LoadDriver(void);
BOOL UnloadDriver(void);
const char MY_DRIVER[] = "empier";
const char MY_PATH[] = "C:\\path\\driver.sys";
int main(void)
{
if(LoadDriver()){
~~~~~~~~~~~~~~~~~~~~~~~~
}
}
//드라이버 로드하는 함수
BOOL LoadDriver(void){
SC_HANDLE hSCM;
SC_HANDLE hSService;
//서비스 매니저 핸들 오픈
hSCM = OpenSCManager(NULL,NULL,SC_MAnAGER_ALL_ACCESS);
if(!hSCM){
printf("SCM open ERROR");
return FALSE;
}
// 서비스 생성하고 핸들 반환
hService = CreateService(hSCM,
MY_DRIVER,
MY_DRIVER,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
MY_PATH,
NULL,
NULL,
NULL,
NULL,
NULL);
//서비스 생성하지 못한경우 - OpenService 함수 실행하여 서비스열고 핸들 받아옴.
if(!hService){
hService = OpenService(hSCM,MY_DRIVER,SERVICE_ALL_ACCESS);
if(!hService){
~~~~~
CloseServiceHandle(hSCM);
return FALSE;
}
}
//받아온 서비스 핸들을 인자로 서비스 시작
if(!StartService(hService,0,NULL) &&
(GetLastError() != ERROR_SERVICE_ALREADY_RUNNING)){
~~~~~
CloseServiceHandle(hSCM);
CloseServiceHandle(hService);
return FALSE;
}
)
CloseServiceHandle(hSCM);
CloseServiceHandle(hService);
return TRUE;
}
//드라이버 언로드하는 함수
BOOL UnloadDriver(void){
SC_HANDLE hSCM;
SC_HANDLE hService;
SERVICE_STATUS svcStatus;
//SC 매니저를 열고 핸들 반환
hSCM = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(!HSCM){
~~~~~~
}
//서비스 열고 핸들 반환
hService = OpenService(hSCM,MY_DRIVER,SERVICE_ALL_ACCESS);
if(!hService){
~~~~~~~
CloseServiceHandle(hSCM);
}
if(!ControlService(hService,SERVICE_CONTROL_STOP,&svcStatus)){
~~~~~
CloseServiceHandle(hSCM);
CloseServiceHandle(hService);
}
return TRUE;
}
꼭 필요한 부분들만 남겨봄.
드라이버 로드를 위한 큰 단계는
- 서비스 매니저 핸들 오픈
- 드라이버를 위한 서비스 생성
- 인자로 서비스 매니저 핸들, 드라이버 이름, 경로가 전달됨
- 서비스 시작
- 인자로 생성한 서비스 핸들 전달
로 요약이 가능한데, 각 과정에서 동작을 수행하기 위해 SCM(Service Control Manager) 핸들과 서비스 핸들을 반환하여 인자로 사용한다. SCM이란?
Service Control Manager - 서비스 제어 관리자
- SCM은 시스템 부팅시에 시작됨
- 서비스를 구성하고 제어하는 프로그램들이 서비스를 조작할 수 있게하는 RPC 서버 역할을 한다.
- 따라서 SCM은 윈도우 서비스 프로세스의 제어를 돕는 역할을 한다.
- 각 프로그램들과 API 함수를 통해 상호작용할 수 있다.
필요한 함수
- OpenSCManager() - SCM 생성 및 핸들 오픈
- CreateService or OpenService() - 서비스 생성 혹은 이미 생성된 서비스에 접근해 핸들 획득
- StartService() - 서비스 시작
드라이버를 위한 서비스를 생성하고 제어하기 위해서 SCM 핸들이 우선적으로 필요하고, 이를 생성하는것.
위 코드를 C++로 포팅해서 드라이버 로더를 만들어보자.
사용할 win32 API 함수
- OpenSCManager() - SCM 생성 및 핸들 오픈
- 실행할 동작에 따라 권한을 다르게 부여함.
- OpenService() - 등록되어있는 서비스 열고 서비스 핸들 반환
- 이때 실행할 동작에 따라 옵션을 각기 다르게 줘야함.
- StartService() - 서비스 시작
- DeleteService() - 서비스 삭제
- CloseServiceHandle() - 서비스 핸들 닫기
- ControlService() - 실행중인 서비스에 제어코드 전송.
- 서비스 종료시에 사용.
- 정의된 코드 목록 : https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-controlservice
드라이버를 설치하고 서비스를 등록하는 함수와 등록된 서비스를 실행하는 함수를 각각 선언하고 실행한다.
드라이버 제작
드라이버는 디버깅 메시지를 띄우는 간단한 동작을 수행한다.
#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;
}
이번 실습의 경우는 64비트 윈도우 환경에 맞춰 진행할 것 이므로, 드라이버와 로더 모두 release - x64 옵션으로 빌드해주어야 하는데, 이에 더불어 각종 최적화 옵션과 보호기법들을 전부 꺼줘 분석에 좀 더 용이하게 해보자.
- 프로젝트 - 속성 - 구성속성 - C++ - 최적화 - 최적화 : 사용안함
- 코드 생성 - 보안 검사 : 보안검사 사용 안함(/GS-) ⇒ 스택 쿠키 옵션을 제외한다
- 링커 - 고급 - 임의 기준 주소, DEP 를 모두 아니오로 설정
- 컴파일시 /DRIVER 사양과 호환되지 않는다고 나오는 경우 아니오 옵션을 빼준다.
- 디버깅 중 "이 프로젝트에는 스펙터 완화된 라이브러리가 필요합니다" 오류가 발생하는 경우 C/C++ - 코드 생성 - Spectre Mitigation - Disabled 로 설정해줘야한다.
빌드 완료된 로더와 드라이버 파일을 가상환경으로 복사해주자.
디버거 세팅
로더와 드라이버 제작이 완료되었으니 가상환경에 VirtualKD를 설치하여 커널 디버깅 환경을 구성한다. 나같은 경우는 virtualKD Redux를 설치하여 사용함.
시스템 아키텍쳐에 맞는 target 폴더를 VM환경으로 복사하고 install을 실행한다.
원하는대로 세팅한 다음 install 진행
그러면 위와같은 경고창이 뜨는데, 윈도우 시작 옵션에서 드라이버 서명 적용 안함 옵션을 수동으로 적용해주어야 한다는 뜻. 설치가 완료되면 안내창에 따라 재부팅하거나 수동으로 재부팅을 진행한다.
부팅 옵션에서 F8을 입력해 고급 옵션을 지정한다.
"드라이버 서명 적용 사용 안함" 을 선택하여 부팅해주면 된다.
재부팅이 완료되면 호스트 PC에서 vmmon64.exe를 실행해준다
가상 환경과 제대로 연결이 되었다면 위와같은 화면이 보일것이다. windbg와 자동으로 연결되게끔 "WinDBG/KD debugger path" 를 클릭해 Windbg가 설정된 위치로 경로를 지정해주자.
경로가 제대로 잡히면 위와 같이 보일것.
설정이 완료되면 가상머신을 클릭하고 "RUN Debugger" 버튼을 눌러 디버거를 실행할 수 있다.
디버깅
처음 디버거가 열리면 위 상태에서 대기하게 되는데, ctrl+break 키를 입력하면 가상환경의 동작이 멈추고 디버기가 연결된다.
이 상태에서 명령창에 g 를 입력하면 다시 가상환경이 제대로 동작하며 디버깅 메시지를 전달할 것이다.
로더를 실행해 드라이버를 설치하고 서비스를 생성했는데,
서비스 시작이 안되는 에러가 발생한다. sc 명령어를 활용해 하나씩 확인해보자
일단 서비스는 성공적으로 등록이 되었고, 수동 시작을 진행하여 왜 시작이 안되는지 체크하면
지정된 경로에 드라이버가 없어 시작이 안된다는듯.
레지스트리 값을 확인하여 파일 경로가 어디로 세팅되어있는지를 확인해보면
HKEY_LOCAL_MACHINE\SYSTEM\CurrnetControlSet\Services
코드에서 지정한 값과 완전 다른 이상한곳으로 되어있는게 보인다.
서비스 삭제후 다시 등록해봐도 지정한 경로와는 다른곳으로 드라이버 경로가 잡힌다.
일단 수동으로 ImagePath 값을 고쳐서 다시 서비스를 실행해보자.
그럼 이번에는 이런 에러가 나온다 . 왜 그런가 보니, 기본적으로 ImagePath에 지정되는 경로값은 C:\Windows가 최 상위 경로로 지정되는데 무턱대고 절대경로를 넣어두니 제대로 읽어오지 못하는 것.
코드상에서 드라이버 경로를 다시 제대로 잡아주고 빌드한 다음 재시도.
- 경로를 하드코딩해서 지정할 때 맨 앞에 역슬래시가 들어가면 자동으로 최상위 경로로 잡아주는 모양.
- 역슬래시 기호를 사용할 때는 두번씩 적어줘야 제대로 인식된다.
다시 서비스를 시작하면 이번엔 31에러가 뜨지만(STATUS_UNSUCCESSFUL 반환), 연결된 디버거 상에는 드라이버에서 지정한 디버깅 메시지가 제대로 올라오는게 확인된다.
반환값도 고쳐서 다시 실행하면
정상 동작 확인된다.
드라이버 정지, 서비스 삭제 동작
서비스를 생성하고 드라이버를 올리는건 제대로 테스트가 되었으니, 이제 드라이버를 정지하고 서비스를 삭제하는 동작까지 구현을 해봐야한다.
이때, 테스트용으로 만든 드라이버에 unload 루틴이 반드시 들어가 있어야 한다. 이건 드라이버가 Windoes Driver Model(WDM) 기반인지 Windows Driver Frameworks (WDF) 기반인지에 따라 달라진다.
언로드 루틴이 필요한 이유는, 드라이버가 시작되면서 함께 실행 된 DriverEntry 함수 내부에서 정의하거나 동작하여 생성된 내용들은 드라이버가 종료될 때 반드시 삭제되어야 하기 때문이다.
일반 유저레벨 프로세스와 다르게, 드라이버에서 생성된 데이터들은 커널레벨에서 동작하기 때문에 제대로 정리되지 않아 읽고 쓰는 동작이 잘못 수행되면 커널에 손상을 입힐 우려가 있다.
그러므로 드라이버 언로드 루틴이 반드시 필요한 것.
#include <ntddk.h>
DRIVER_UNLOAD UnloadMyDriver;
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");
DriverObject->DriverUnload = UnloadMyDriver;
return 0;
}
void UnloadMyDriver(PDRIVER_OBJECT DriverObject) {
UNREFERENCED_PARAMETER(DriverObject);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "Driver Unloaded!\n");
return ;
}
드라이버 코드에 언로드 루틴을 추가하고 나면 제대로 sc stop 명령도 먹히고, 드라이버 로더에서 서비스를 중단하는 동작도 잘 실행된다.
Github
https://github.com/synod2/Kernel_Hooking/tree/main/Window%20Device%20Driver/MyDriver1
참고자료
https://hack.kr/87 -C언어 드라이버 로더
https://ko.wikipedia.org/wiki/서비스_제어_관리자 -SCM
https://docs.microsoft.com/ko-kr/windows/win32/services/service-control-manager -SCM
https://www.sysnet.pe.kr/2/0/12106 - C++ 드라이버 로더
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-openservicea
https://sysprogs.com/legacy/virtualkd/download/ - VirtualKD
https://github.com/4d61726b/VirtualKD-Redux/releases/tag/2021.3 - VirtualKD Redux
https://docs.microsoft.com/en-us/windows-hardware/drivers/network/specifying-an-unload-function - Driver Model