
Windows x64 수작업으로 제작된 토큰 탈취 커널 모드 셸코드
Windows x64 커널 모드에서 수작업으로 제작된 셸코드로, 실행 중인 프로세스의 기본 액세스 토큰을 SYSTEM 프로세스 토큰으로 교체하여 권한 상승(EoP)을 수행합니다.
Windows 7/Windows Server 2008 R2 Build 7601Windows 8/Windows Server 2012 Build 9200Windows 8.1/Windows Server 2012 R2 Build 9600Windows 10 1507/TS1 Build 10240Windows 10 1511/TS2 Build 10586Windows 10 1607/RS1/Windows Server 2016 Build 14393Windows 10 1703/RS2 Build 15063Windows 10 1709/RS3 Build 16299Windows 10 1803/RS4 Build 17134Windows 10 1809/RS5/Windows Server 2019 Build 17763Windows 10 1903/19H1 Build 18362Windows 10 1909/19H2 Build 18363Windows 10 2004/20H1 Build 19041Windows 10 2009/20H2 Build 19042Windows 10 2104/21H1 Build 19043Windows 10 2110/21H2 Build 19044이 프로젝트를 빌드하기 위한 사전 요구 사항은 다음과 같습니다:
Visual Studio 2019(어떤 에디션이든 괜찮음)Windows 10 SDK, version 2004Windows 10 WDK, version 2004Python3여기서 짚고 넘어갈 점은, 어셈블러만 있으면 된다는 것입니다(이 프로젝트는 MASM을 사용합니다). 기술적으로는 그것만 있으면 되기 때문입니다.
위 항목들을 설치한 후에는 Visual Studio로 솔루션을 열고 x64 대상으로 빌드하는 것만큼 간단합니다.
빌드에 성공하면 바이너리는 적절한 비트 수 하위 디렉터리 아래의 Bin 디렉터리에서 찾을 수 있습니다.
또는 배포 준비가 완료된 위치 독립 셸코드를 Releases에서 다운로드할 수 있습니다.
작동 방식을 확신하지 못한다면, 업무에 의존하는 머신에 페이로드를 배포하려고 절대 하지 마십시오.
추가 정보는 Microsoft 문서를 참조하십시오.
테스트 목적으로는 커널 모드 셸코드를 테스트 VM에 배포하기 위해 flare-kscldr를 사용하고, 전체 커널 디버깅을 지원하는 Hyper-V Guest VM을 설정하기 위해 CodeMachine 시스템 설정: 커널 개발 및 디버깅 가이드를 사용하는 것을 적극 권장합니다.
선택적으로, kdbg-driver-vagrant를 사용하여 Vagrant로 전체 커널 디버깅이 지원되는 테스트 VM을 빠르게 구성하는 프로세스를 자동화하는 방법도 고려할 수 있습니다.

Dmytro Oleksiuk(@d_olex)이 지적했듯이, 코드에는 특히 다음 사항과 관련된 결코 미묘하지 않은 경쟁 조건(race condition)이 있습니다:
nt!_EPROCESS 구조체를 수동으로 탐색하는 것현재 이 코드는 작업 중에 해당 구조체에 대한 변경으로부터 보호하는 기능이 전혀 없습니다.
이것이 문제인가요? 그렇습니다. 경쟁 조건은 항상 문제가 되며, 온갖 종류의 정의되지 않은 동작/버그체크(Bugcheck) 같은 불쾌한 상황을 유발할 수 있습니다.
이 페이로드를 사용하면 익스플로잇의 안정성에 영향을 미칠까요? 아마도 그럴 것입니다.
그렇다면 해결책은 무엇일까요? 해결책은 두 단계로 이루어집니다.
1단계는 프로세스 목록을 탐색하기 전에 nt!ExAcquirePushLockExclusive를 사용하여 Pushlock과 같은 대기 유형 잠금인 nt!PspActiveProcessLock(푸시락 포인터)에 대한 배타적 액세스를 획득하는 것입니다(일반 커널 APC 전달은 사전에 비활성화되어야 합니다). 그리고 목록 사용이 끝나면 nt!ExReleasePushLockExclusive로 잠금을 해제하며, 이 시점에 일반 커널 APC 전달을 다시 활성화해야 합니다.
하지만 이 전역 변수는 nt 커널에서 내보내지지 않기 때문에, 훨씬 더 합리적이고 안전한 접근 방식은 nt!ZwQuerySystemInformation API를 SYSTEM_INFORMATION_CLASS == SystemProcessInformation와 함께 사용하여 ImageName에서 PID를 찾고, nt!PsLookupProcessByProcessId를 사용하여 PID에서 nt!_EPROCESS VA를 얻는 것입니다.
그런데 커널이 전자의 작업을 어떻게 수행하는지 궁금하다면, 디스어셈블러에서 nt!PsGetNextProcess를 살펴보시기를 권합니다.
2단계는 nt!ObReferenceObject 계열의 API를 사용하여 프로세스 개체를 안전하게 참조하고 참조 횟수를 증가시키는 것입니다. 이를 통해 프로세스 개체를 사용한 뒤 마지막에 nt!ObDereferenceObject로 명시적으로 감소시킬 때까지 삭제되지 않도록 합니다.
참고로 nt!PsLookupProcessByProcessId 호출이 성공하면 참조 횟수가 자동으로 증가하므로, 수동으로 참조 횟수를 늘리는 것은 중복됩니다.
그러나 이러한 수정을 구현하려면 ntoskrnl.exe 기본 주소를 찾고, 일부 문자열 해싱 알고리즘을 사용하여 EAT를 탐색하여 함수 포인터를 찾는 방식으로 그 안의 심볼을 해석해야 하며, 이 모든 것이 페이로드 크기를 크게 증가시킬 것입니다.
언젠가는 그것을 구현하거나, 그냥 C로 작성하고 컴파일러 출력을 마구 쏟아낼지도 모르겠습니다 :)
오류를 지적하고 수정 방법도 제안해 준 Dmytro Oleksiuk(@d_olex)와 Paul L.(@am0nsec)에게 감사드립니다.