
스냅샷 기반 커버리지 가이드 Windows 커널 퍼저
Rewind는 Windows 커널 구성 요소를 대상으로 하는 스냅샷 기반 커버리지 가이드 퍼저입니다.
아이디어는 실행 중인 라이브 시스템의 스냅샷에서 시작하는 것입니다. 이 스냅샷은 CPU 상태와 함께 물리적 메모리 페이지들로 구성됩니다.
이 상태는 가상 CPU의 초기 상태를 설정하는 데 사용됩니다. 온디맨드 페이징을 활용하여 대상 함수 실행에 필요한 페이지만 스냅샷에서 읽어옵니다.
대상 함수 실행에 유용한 물리적 메모리 페이지만을 포함하는 전용 가상 머신을 사용하므로 스냅샷 복원이 빠릅니다.
현재 사용 가능한 백엔드는 2가지입니다:
WHVP 백엔드는 WHVP(Windows Hypervisor Platform) API를 활용하여
Hyper-V 파티션에 대한 액세스를 제공합니다. 자세한 내용은
https://docs.microsoft.com/en-us/virtualization/api/hypervisor-platform/hypervisor-platform
를 참조하세요.Bochs 백엔드는 Bochs 에뮬레이터를 활용합니다.
(https://bochs.sourceforge.io/)KVM 백엔드가 개발 중이며 곧 제공될 예정입니다.
Rewind는 두 가지 주요 기능을 제공합니다:
또한 퍼징 관련 유용한 정보를 보고하는 기본 TUI(터미널 사용자 인터페이스)를 제공합니다.
Windows와 Linux에서 테스트되었습니다(현재 Linux에서는 bochs 백엔드만 지원).
저는 항상 특히 Windows 커널을 대상으로 한 커널 취약점 연구를 즐겨 했습니다. 그 과정에는 항상 정적 분석과 동적 분석이 혼합되어 있습니다. 동적 분석을 수행하는 일은 금방 지루해질 수 있습니다. 디버그 / 크래시 / 재부팅 / 모든 중단점 재설정의 사이클은 느리고 고통스럽습니다. 퍼징을 하고 싶을 때는 하나 또는 여러 대의 가상 머신과 커널 디버거를 설정하고, 크래시 탐지를 처리하기 위한 임시방편 스크립트를 만들어야 하는 경우가 많습니다...
가상 머신으로 스냅샷을 만드는 것이 도움이 되지만 느립니다.
2018년에 Microsoft는 Windows Hypervisor Platform(WHVP)이라는 새로운 API 세트를 도입했습니다. 이 API를 사용하면 일부 가상 프로세서로 파티션(Hyper-V 용어로 VM)을 설정하고 가상 머신에서 발생하는 VM exit을 제어할 수 있습니다. 이는 유저랜드에서 자체 VM-exit 핸들러를 갖는 것과 거의 같습니다. Simpleator나 applepie 같은 유용한 작업을 하기에 매우 편리합니다.
그래서 저는 WHVP를 가지고 놀기 시작했고 Hyper-V 파티션에서 셸코드를 실행할 수 있는 첫 PoC를 만들었습니다. Python으로 작성되었고 꽤 느렸습니다. 이 첫 PoC는 빠르게 일종의 스냅샷 기반 트레이서로 발전했습니다. 저는 가상 CPU를 부트스트랩하고 설정하기 쉬운 무언가를 원했습니다. 이미 대상으로 삼은 함수를 가지고 놀기 위해 커널 디버거를 사용하고 있었기 때문에 WinDbg로 만든 커널 덤프를 스냅샷으로 사용하기로 결정했습니다. 이를 통해 가상 CPU가 있는 파티션만 설정하면 되었습니다. 가상 CPU 컨텍스트는 덤프에서 가져온 컨텍스트로 설정됩니다. 가상 CPU가 물리적 페이지를 필요로 할 때마다 덤프의 페이지를 사용합니다.
이를 통해 덤프의 상태를 파티션으로 포크한 다음 실행을 재개할 수 있었습니다. 이를 통해 대상 함수의 실행을 쉽게 추적할 수 있었습니다. 인수를 수정하고 파티션의 메모리 상태를 되돌리는 것만으로도 대상을 퍼징하는 것이 정말 쉬웠습니다.
이 작업은 2020 SSTIC 컨퍼런스에서 발표되었고 github에 공개되었습니다.
이 도구는 커버리지를 얻기 위한 두 가지 방법을 구현합니다. 첫 번째는 고전적인 TF(트랩 플래그)를 활용하여 모든 명령어에서 INT1 인터럽트를 발생시키는 방식입니다. 대상 코드를 수정해야 하고 느립니다. 저는 MONITOR 트랩 플래그를 사용하고 싶었지만 WHVP는 이 기능을 제공하지 않습니다.
적절한 성능(퍼징에 필요한)을 얻기 위해 커버리지의 정밀도를 낮추고, 명령어가 처음 실행되는 시점만 알 수 있는 모드를 추가하기로 결정했습니다.
이를 위해 스냅샷에서 가져온 페이지를 0xcc 바이트로 패치합니다(실행 가능한 페이지만). CPU가 패치된 명령어를 실행하면 하이퍼바이저가 예외를 트랩하고 원래 코드로 명령어를 다시 씁니다.
모든 명령어에 소프트웨어 중단점이 하나씩 설정된 것과 같습니다. 95%의 경우에는 작동하지만 특정 코드(예: 점프 테이블이 있는 코드)에서는 데이터가 대체되기 때문에 실패합니다.
이를 극복하기 위한 한 가지 방법은 코드를 매핑하기 전에 디스어셈블한 다음 필요한 부분만 패치하는 것입니다(아마 다음에).
실험 중 WHVP를 사용할 때 몇 가지 제한 사항을 발견했습니다. 느립니다, 정말 느립니다. VirtualBox 소스 코드에 재미있는 주석이 몇 개 있습니다 :)
따라서 적절한 성능을 얻으려면 VM exit을 제한해야 하며, Hyper-V를 추적 하이퍼바이저로 사용하려는 경우 이는 호환되지 않습니다(많은 VM exit이 필요하기 때문입니다).
같은 시기에 저는 도구로 얻은 트레이스가 올바른지 확인하기 위해 bochs(특히 instrumentation 부분)를 사용하기 시작했습니다. Bochs는 트레이스가 분기되는지 확인하는 일종의 오라클이었습니다.
Bochs는 전체 트레이스를 수행할 때 WHVP보다 빠르며, 메모리 액세스 및 기타 유용한 기능을 함께 제공합니다.
저는 bochs를 또 다른 백엔드로 추가하기로 결정했습니다. whvp는 더 이상 적절한 이름이 아니었고 rewind로 정했습니다.

rewind는 Windows 플랫폼에서 커널 드라이버에 대한 보안 평가를 수행할 때 제 작업 흐름을 중심으로 설계되었습니다.
첫 번째 단계는 가상 머신 안에 대상 소프트웨어를 설치하는 것입니다. 정적 분석과 동적 분석을 혼합하여 사용하므로 커널 디버거도 설정할 것입니다.
IDA에서 몇 가지 임의의 드라이버를 연 후, 빠르게 특정 함수들을 대상으로 삼기 시작할 것입니다. 그러기 위해 보통 windbg로 중단점을 설정하고 ret-sync와 결합하여 작업을 시작할 수 있습니다.
바로 여기서 rewind가 등장합니다. 메모리의 임의 버퍼를 편집하고 단일 스텝을 실행하며 무슨 일이 일어나고 있는지 대략적으로 파악하기 위해 IDB에 주석을 다는 대신, windbg로 스냅샷을 찍고 rewind를 사용할 것입니다.
이렇게 하면 프로세스가 훨씬 수월해집니다. 스냅샷을 보유하면 많은 이점이 있습니다. 모든 것이 결정적입니다. 함수 호출을 지겹도록 반복 재생할 수 있습니다. 대상 함수가 흥미로워 보이면 퍼저를 실행할 수 있습니다. 더 이상 필요하지 않으므로 VM을 닫을 수도 있습니다.
당연히 Rust가 필요합니다(Windows 및 Linux에서 Rust 1.50으로 설치를 테스트했습니다). 일부 종속성에는 CMake도 필요합니다.
먼저 저장소를 클론합니다:
$ git clone [email protected]:quarkslab/rewind.git
다음으로 bochs 백엔드 설치를 계속합니다.
vendor 디렉터리에 bochscpu (https://github.com/yrp604/bochscpu) 저장소를 클론합니다:
$ cd vendor
$ git clone https://github.com/yrp604/bochscpu
bochscpu-build (https://github.com/yrp604/bochscpu-build)에서 사전 빌드된 bochs 아티팩트를 다운로드합니다.
$ curl.exe -L --output bochs-x64-win.zip [artifact_url]
lib 및 bochs 폴더를 bochscpu 체크아웃에 추출합니다.
$ Expand-Archive -Path .\bochs-x64-win.zip -DestinationPath .\
$ copy -Recurse .\bochs-x64-win\msvc\* .\bochscpu\
Windows에서는 WHVP도 백엔드로 빌드됩니다.
관리자 권한 PowerShell 세션에서 다음 명령을 사용하여 WHVP가 활성화되어 있는지 확인합니다:
Get-WindowsOptionalFeature -FeatureName HypervisorPlatform -Online
FeatureName : HypervisorPlatform
DisplayName : Windows Hypervisor Platform
Description : Enables virtualization software to run on the Windows hypervisor
RestartRequired : Possible
State : Enabled
CustomProperties :
활성화되어 있지 않으면 Set-WindowsOptionalFeature cmdlet을 사용하여 활성화할 수 있습니다. 또한 Hyper-V도 활성화해야 합니다.
또한 Windows SDK(10.0.19041.0)가 설치되어 있어야 합니다. https://developer.microsoft.com/fr-fr/windows/downloads/windows-10-sdk/ 에서 다운로드할 수 있습니다.
LLVM을 설치하고 LIBCLANG_PATH 환경 변수를 설정해야 합니다(bindgen에 필요). 자세한 설명은 https://rust-lang.github.io/rust-bindgen/requirements.html 을 참조하세요.
$ $env:LIBCLANG_PATH="C:\Program Files\LLVM\bin"
그런 다음 rewind를 빌드할 수 있습니다(bochscpu 크레이트의 unwind_attributes 때문에 nightly가 필요합니다):
$ cd rewind_cli
$ cargo +nightly build --release
rewind 바이너리는 target/release 디렉터리에서 사용할 수 있습니다.
cargo를 사용하여 로컬에 설치할 수도 있습니다:
$ cd rewind_cli
$ cargo +nightly install --path .
> error: failed to run custom build command for `zydis v3.1.1`
whvp-sys 빌드에 실패합니다CVE-2020-17087을 활용하는 기본 튜토리얼이 examples 디렉터리에 제공됩니다.
TODO.md를 참조하세요.
hit 커버리지 모드를 사용하면 일부 함수에서 트레이서가 오작동합니다(일부 switch 테이블의 경우가 그렇습니다). 그 이유는 각 바이트가 소프트웨어 중단점으로 대체되기 때문입니다(실행 가능한 페이지에 있는 데이터도 포함). 이를 개선하는 방법은 예를 들어 디스어셈블러에서 모든 기본 블록 목록을 얻는 것입니다.이 도구는 현재 Quarkslab이 Apache 2.0 라이선스 하에 개발 및 후원하고 있습니다.
@yrp604, @0vercl0k, Alexandre Gazet에게 도움과 피드백, 의견에 감사를 드립니다. Quarkslab의 모든 동료들에게도 감사합니다!