- Windows 커널 드라이버도 Rust로 작성할 수 있음을 보이기 위해, 임의 스레드의 우선순위를 바꾸는 Booster WDM 드라이버를 구현함
- 빌드 환경은 WDK 또는 EWDK, LLVM/Clang,
windows-drivers-rs 기반 WDK crates, build.rs와 Cargo.toml 설정이 맞물려야 함
- 커널 공간에서는 표준 라이브러리를 쓸 수 없어
#![no_std], WDK allocator, panic handler, unsafe FFI 호출을 조합해야 함
- 드라이버는
\Device\Booster와 \??\Booster를 만들고, IRP_MJ_WRITE에서 받은 ThreadData로 스레드 우선순위를 1~31 범위에서 변경함
- Rust로 Windows 커널 드라이버 작성은 가능하지만, WDK crates가 0.3 단계라 더 안전한 래퍼와 적은
unsafe 코드가 필요함
Rust로 구현한 Booster WDM 드라이버
- 예제는 Windows 커널 프로그래밍에서 쓰는 “Booster” 드라이버를 Rust로 옮긴 것임
- 목적은 임의 스레드의 우선순위를 원하는 값으로 바꾸는 것임
- 드라이버 모델은 WDM임
- Rust 생태계는 컴파일 시점의 메모리 안전성, 동시성 안전성,
cargo 빌드 시스템, crates 생태계를 갖추고 있음
- C 타입을 Rust에서 직접 다루면 코드가 장황해질 수 있음
- 적절한 래퍼와 매크로를 쓰면 이 부담을 줄일 수 있음
빌드 준비와 Cargo 설정
- 드라이버 빌드를 준비하려면 Windows Drivers-rs를 참고하고, WDK 또는 EWDK를 설치해야 함
- LLVM 설치도 필요하며, Clang 컴파일러에 접근하기 위해 사용함
- 새 Rust 라이브러리 프로젝트를 만들며, 커널 드라이버는 기술적으로 커널 공간에 로드되는 DLL임
cargo new --lib booster
build.rs는 cargo가 CRT에 정적 링크하도록 설정하고 WDK 바이너리 빌드를 구성함
fn main() -> Result<(), wdk_build::ConfigError> {
std::env::set_var("CARGO_CFG_TARGET_FEATURE", "crt-static");
wdk_build::configure_wdk_binary_build()
}
Cargo.toml에는 WDM 드라이버 모델, cdylib crate 타입, WDK 관련 의존성을 넣음
- 주요 crates는
wdk, wdk-macros, wdk-alloc, wdk-panic, wdk-sys임
- dev/release 프로필에는
panic = "abort"와 lto = true를 설정함
wdk와 wdk-sys에는 선택적으로 nightly feature가 있음
표준 라이브러리 없이 커널 코드 작성
- 커널에는 Rust 표준 라이브러리가 없으므로
#![no_std]를 사용함
wdk_sys는 저수준 커널 함수와의 상호운용을 맡고, wdk는 더 높은 수준의 래퍼를 제공함
Vec와 String은 표준 라이브러리의 일부처럼 보이지만, 실제로는 alloc 모듈의 타입을 사용할 수 있음
- 이를 쓰려면 전역 할당자가 필요함
- WDK crates가 제공하는
WdkAllocator를 #[global_allocator]로 지정함
WdkAllocator는 ExAllocatePool2와 ExFreePool을 사용해 할당을 관리함
- 표준 라이브러리가 없기 때문에 allocator 지원과 panic handler를 위해
wdk_panic과 alloc 외부 crate를 추가함
DriverEntry와 장치 초기화
- Windows 커널 드라이버의 진입점은
DriverEntry임
- Rust 함수명은 관례에 맞춰
driver_entry로 두고, #[export_name = "DriverEntry"]로 링커가 찾는 이름을 지정함
println! 매크로는 DbgPrint 호출로 재구현되어 있어, C/C++에서 DbgPrint를 쓰는 것처럼 커널 디버그 출력에 사용할 수 있음
UNICODE_STRING은 println!에서 직접 지원되지 않으므로 unicode_to_string 함수로 Rust String으로 변환함
- 장치 객체는
IoCreateDevice로 \Device\Booster에 생성함
- 실패하면 오류 상태를 출력하고 해당
NTSTATUS를 반환함
- 성공 여부 판단에는 WDK의
NT_SUCCESS 매크로와 유사한 nt_success를 사용함
- 표준
CreateFile 호출로 장치를 열 수 있도록 IoCreateSymbolicLink로 \??\Booster 심볼릭 링크를 만듦
- 심볼릭 링크 생성에 실패하면 장치 객체를 삭제하고 실패 상태를 반환함
- 장치 객체는 Buffered I/O를 사용하도록 설정함
DriverUnload는 boost_unload로 설정함
IRP_MJ_CREATE와 IRP_MJ_CLOSE는 boost_create_close로 처리함
IRP_MJ_WRITE는 boost_write로 처리함
- 콜백 존재 여부는 Rust의
Option<> 타입으로 표현됨
요청 처리와 스레드 우선순위 변경
- 언로드 루틴은
IoDeleteSymbolicLink와 IoDeleteDevice를 호출해 심볼릭 링크와 장치 객체를 정리함
IRP_MJ_CREATE와 IRP_MJ_CLOSE 처리는 단순함
- IRP의
IoStatus.Status를 STATUS_SUCCESS로 설정함
IoStatus.Information을 0으로 설정함
IofCompleteRequest로 요청을 완료함
IoStatus는 IO_STATUS_BLOCK이며, Status 접근 시 자동 생성된 union 멤버를 거쳐야 해 코드가 보기 좋지 않음
- 실제 우선순위 변경은
IRP_MJ_WRITE 핸들러에서 수행함
- 클라이언트가 드라이버에 넘기는 구조체는 C/C++와 같은 메모리 배치를 위해
#[repr(C)]를 사용함
#[repr(C)]
struct ThreadData {
pub thread_id: u32,
pub priority: i32,
}
boost_write는 Buffered I/O로 전달된 SystemBuffer를 ThreadData 포인터로 해석함
- 오류 검사는 다음 조건을 포함함
- 데이터 포인터가 null이면
STATUS_INVALID_PARAMETER
- 우선순위가 1보다 작거나 31보다 크면
STATUS_INVALID_PARAMETER
PsLookupThreadByThreadId로 스레드 객체를 찾음
- 실패하면 해당 스레드 ID가 없을 가능성이 있으며 처리 루프를 빠져나감
- 스레드를 찾으면
KeSetPriorityThread로 우선순위를 설정하고 ObfDereferenceObject로 참조를 해제함
- 요청 완료 시 IRP 상태와 정보 필드를 설정한 뒤
IofCompleteRequest를 호출함
서명, 설치, 테스트
- INF 또는 INX 파일이 있으면 crates가 드라이버 서명을 지원하는 것으로 보이지만, 이 예제는 INF를 사용하지 않아 수동 서명이 필요함
- 프로젝트 루트에서 다음 명령으로 빌드 산출물을 서명할 수 있음
signtool sign /n wdk /fd sha256 target\debug\booster.dll
/n wdk는 Visual Studio가 드라이버 빌드 시 보통 자동 생성하는 WDK 테스트 인증서를 사용함
- 빌드 산출물 확장자는 DLL임
- 현재
cargo build 과정에서 자동으로 확장자를 바꾸는 방법은 없음
- INF/INX를 사용하면 확장자가 SYS로 바뀜
- 확장자는 직접 바꾸거나 DLL 그대로 둘 수 있음
- 테스트 서명이 켜진 머신에서 관리자 권한 명령 프롬프트로
sc.exe를 사용해 소프트웨어 드라이버처럼 설치할 수 있음
sc.exe sc create booster type= kernel binPath= c:\path_to_driver_file
sc.exe start booster
- 테스트 클라이언트는 기존 C++ 애플리케이션을 사용함
CreateFile(L"\\\\.\\Booster", GENERIC_WRITE, ...)로 장치를 엶
ThreadData에 스레드 ID와 우선순위를 넣고 WriteFile로 전달함
- 예시는 ID 9408 스레드의 우선순위를 26으로 바꾸는 테스트임
남은 과제와 참고 자료
- Rust로 커널 드라이버를 작성하는 것은 가능함
- WDK crates는 버전 0.3 단계라 아직 개선 여지가 있음
- Rust의 장점을 제대로 얻으려면 안전한 래퍼가 더 필요함
- 코드가 덜 장황해야 함
unsafe 블록이 줄어야 함
- Rust가 제공하는 안전성 이점을 더 잘 활용해야 함
- KMDF Rust 드라이버 샘플은 Windows-rust-driver-samples에 있음
- 예제 코드는 Booster 저장소에서 확인할 수 있음
- Rust 학습 자료는 https://trainsec.net에 있음