CVE-2024-38080 Windows Hyper-V 권한 상승 취약성
#영향받는 시스템
- Windows 11 21H2
- < 10.0.22000.3079
- Windows 11 22H2
- < 10.0.22621.3880
- Windows 11 23H2
- < 10.0.22631.3880
diffing을 해서 취약점이 있는 부분을 특정.

diffing에 성공해서 VidExoBrokerIoctlReceive에서 취약점이 발생하였음을 알 수 있다.
#용어 정리
Non paged Pool: 가상화된 메모리를 사용하지 않고 항상 실제 메모리를 사용하는 메모리 풀
ioctl: input output control
하드웨어의제어와 상태 정보를 얻기 위해 제공되는 함수/ read(), write()를 이용해서 데이터를 읽고 쓰는 등의 기능은 가능하지만 하드웨어를 제어하거나 상태 정보를 확인하려면 ioctl을 이용해야한다.
SPI 통신 속도를 설정하는 등의 작업은 read, write만으로는 할 수 없으며 ioctl을 이용해야한다.
#취약점 존재 코드
__int64 __fastcall VidExoBrokerIoctlReceive(
__int64 VidExoObj,
struct _LIST_ENTRY *a2,
BrokerIrpDataHeader *Dest,
unsigned int OutputLen,
unsigned int *a5)
{
ReceivedIRP = (_IRP *)VidExoBrokerpFindAndDequeueSendIrpForFileObject(VidExoObj, a2);//[1] Sent by VidExoBrokerIoctlSend
v9 = ReceivedIRP;
if ( !ReceivedIRP )
{
...
}
Source = (BrokerIrpDataHeader *)ReceivedIRP->AssociatedIrp.SystemBuffer;
if ( OutputLen < 0x10 )
{
VidExoBrokerpQueueSendIrp(VidExoObj, (__int64)ReceivedIRP);
v10 = -1073741306;
VidTraceErrorInternal0("VidExoBrokerIoctlReceive", "onecore\\vm\\vid\\sys\\driver\\videxobroker.c", 198);
goto LABEL_16;
}
Dest->HeaderSize = 16;
Dest->NumHandles = Source->NumHandles;
v12 = 8 * Source->NumHandles + 16;//[2] Integer Overflow
Dest->DataOffset = v12;
Dest->DataLen = Source->DataLen;
v13 = Source->DataLen + v12;//[3] Integer Overflow
if ( OutputLen < v13 )//[4] Bypass length check
{
...
}
VidExoBrokerpFindAndDequeueSendIrpForFileObject(VidExoObj, a2);//[1] Sent by VidExoBrokerIoctlSend
VidExoBrokerIoctlSend 이 보낸 걸 처리(find dequeue sendirp)한 return값을 ReceivedIRP라고 함. 이 값은 공격자가 조작할 수 있는 값임(왜 인지 모르겠음)
그 IRP를 AssociatedIrp.SystemBuffer Source에 그 irp의 구조체(brokerirpdataheader) 포인터output이 0x10보다 작으면 에러
RequestorProcess = IoGetRequestorProcess(ReceivedIRP);
v26 = RequestorProcess;
CurrentProcess = PsGetCurrentProcess(v16, v15, v17, v18);
v27 = CurrentProcess;
TargetHandle = (HANDLE *)((char *)Dest + (unsigned int)Dest->HeaderSize);
SourceHandle = (HANDLE *)((char *)Source + (unsigned int)Source->HeaderSize);
v25 = SourceHandle;
Idx = 0i64;
while ( 1 )
{
if ( (unsigned int)Idx >= Source->NumHandles )
{
memmove(
(char *)Dest + (unsigned int)Dest->DataOffset,
(char *)Source + (unsigned int)Source->DataOffset,
(unsigned int)Source->DataLen);//[5] Non paged pool BOF
v10 = 0;
*a5 = v13;
goto LABEL_15;
}
memmove를 유심히 볼 것 같긴하다. memmove: src를 dest에 count바이트 복사. 그리고 dest 포인터 리턴
idx>= source->numhandles
source, 그 구조체의 numhandles가 idx보다 작으면 'dest+dest->dataoffset' 에 'src+src->datadffest'를 'source->datalen'만큼
이때 source->datalen
v12에서 numhandles가 0x1fffffffe가 넘어서 *8+16 했을 때 integer overflow가 나서 v12는 매우 작은 값이 됨 datalen에 v12를 더한게 v13

source->datalen은 엄청 작은 값인데 NumHandle의 값이 0x1ffffffe 이상이면 integer overflow로

이 조건문 bypass가능.
검증할 때 쓰이는 v12와 v13은 integer overflow를 일으켜 크기 검증 조건문을 우회하면서, 실제로 넣는 양은 엄청 크게해서 bof를 일으킬 수 있음

왜 저 Source->Numhandles, 즉 Source 구조체, 즉 ReceiveIRP->associatedIRP.systembuffer가 조작 가능한 값임?
(_IRP *)VidExoBrokerpFindAndDequeueSendIrpForFileObject(VidExoObj, a2);이게 ReceiveIRP인데


vidExoBrokerIoctlReceive는 VidExoBrokerIoctlSend가 IRP객체를 보냈을 때 받는 역할을 하는 함수라고 한다.
VidExoBrokerIoctlSend함수를 사용자가 보낼 수도 있어서 조작할 수 있다고 하는것일거다.
그러면 vid.sys로 데이터를 전송할 수 있는 드라이버의 인터페이스를 찾아야한다.
일반 사용자 프로그램으로는 접근할 수 없고 vmwp.exe에서만 쓰여서 루트계정으로 접근해야된다고 한다. vmwp.exe에서 vid.sys는 파티션 관리 메모리 생성 등을 할 때처럼 가상머신을 관리하는 용도로 쓰이는 거라고 한다.

그럼 vmwp.exe에서 어떻게 vid.sys로 데이터를 보내는가?는 이제부터 분석~.. receiver가 어디서 받아오는지 타고타고
아 vid가 video가 아니고 Virtualization Infrastructure Driver 였음... 와!
아까 봤듯이 vidExoBrokerIoctlReceive, send는 videxoIoControlPartition에서 호출이됨.
videxoIoControlPartition은 videxoiocontrolpreprocess에서 호출이됨
videxoiocontrolpreprocess는 VidDeviceAdd나 VidExopDeviceSetupInternal에서 호출이됨
타고타고 맨 위에 FxDriverEntry가 있다.
내가 궁금한건 가상머신의 저 vmwp.exe에 어떤 파일을 삽입하거나 설정파일에 대한 입력을 통해 vid.sys내에서 VidExoBrokerIoctlSend를 호출하게 할 수 있는지.
그걸 알려면 VidExoBrokerIoctlSend가 어떤 경우에 호출이 되는건지, 확실히 파티션에 관련된 것 같은데 파티션에서 입출력 제어를 어떻게 수행하는건지, vmwp.exe에 파일 혹은 코드 삽입이 가능한지 설정파일을 변조하거나 해서..?
상위레벨의 드라이버에서 하위레벨의 드라이버로 IO요청을 할 때 IRP I/O requestpacket이 사용된다
https://github.com/gerhart01/Hyper-V-Internals/blob/master/Hyper-V%20components.pdf
Hyper-V-Internals/Hyper-V components.pdf at master · gerhart01/Hyper-V-Internals
Internals information about Hyper-V. Contribute to gerhart01/Hyper-V-Internals development by creating an account on GitHub.
github.com
Hyper-V-Internals/vid_dll_evolution/vid.dll evolution - Windows 11.png at master · gerhart01/Hyper-V-Internals
Internals information about Hyper-V. Contribute to gerhart01/Hyper-V-Internals development by creating an account on GitHub.
github.com
아니 원데이분석글도 지금 이해하는데 오래 걸리는데
원데이분석글 분석글....푸하학~~
지금 모르는 게 너무 많아. 뭘 모르냐면
videxo의 핸들을 얻을 수가 있대. 이 문장 자체가 이해가 안됨
onecore\vm\vid\sys\driver\videxobroker.c 처럼

그 videxo가 뭔데.. 버추얼라이제이션 인프라스트럭쳐 드라이버 엑소. 외부. 외부와 연결.
디바이스의 드라이버의 핸들을 얻을 수가 있다고
그걸 어디서 확인한건데
아니 videxo 핸들러가 있으면 videxobrokerioctlsend 호출할 수 있다는 건 어떻게 안건데
아아악

일단은 타고타고타고 올라가면서 그냥 슥슥 보다가 videxopregkeynotificationhandler에서 Device\VidExo라는 문자열을 발견함 근데 내가 해석글을 안 읽었으면 보고도 몰랐을 듯.
1. \Device\VidExo를 찾습니다.
2. VidExopRegKeyNotificationHandler()란
(1) VidRegistryOpenParametersKey(&v5) :레지스트리 키를 열어서
(2) VidRegistryQueryUINT32WithDefault(v5, L"ExoDeviceEnabled", 0LL) : "ExoDeviceEnabled" 값을 가져옴
(3) VidRegistryQueryUINT32WithDefault(v5, L"ExoDeviceEnabledClient", 0LL) : "ExoDeviceEnabledClient" 값을 가져옴
(4) (2),(3) 중 하나라도 참이면 내부장치설정함수인 videxopdevicesetupinternal() 호출 여기서 vid exo 에 대한 장치를 설정
3. VidExopDeviceSetupInternal() 란
(1) *(_OWORD *)L"\\Device\\VidExo" : "Device\VidExo" 문자열을 qword로 저장
(2) v18 = (*(__int64 (__fastcall **)(__int64, _QWORD))(WdfFunctions_01015 + 312))( // 드라이버 객체 가져오기 WdfDriverGlobals, *(_QWORD *)(v15 + 184)); : 드라이버 객체 가져오기
(3) v21 = (*(__int64 (__fastcall **)(__int64, __int64, __int64 *))(WdfFunctions_01015 + 200))( // 장치 객체 생성 WdfDriverGlobals, v18, v23); : 장치 객체 생성
(4) v17 = (*(__int64 (__fastcall **)(__int64, __int64, __int64 *))(WdfFunctions_01015 + 536))( // 드라이버 등록 WdfDriverGlobals, v21, v24); : 장치 객체 생성 시 드라이버 등록
(5) 드라이버 등록 성공시 파일 생성( VidExopFileCreate ) 닫기( VidExopFileClose ) 정리( VidExopFileCleanup ) 함수 설정, I/O제어 처리기인 v17( v17 = (*(__int64 (__fastcall **)(__int64, __int64, void *, __int64, _QWORD, _DWORD))(WdfFunctions_01015 + 584))( WdfDriverGlobals, v21, &VidExopIoControlPreProcess, v19, 0LL, 0); )을 생성
(6) v17은 &VidExopIoControlPreProcess 를 포함
(7) 성공시 위의 VidExopRegKeyNotificationHandler() 예외처리없이 잘 돌아가게 됨
4. VidExopIoControlPreProcess()란 I/O제어 요청 처리중 조건 검사
(1) v15가 oxE가 아닌지 : v15가 vidExo obj를 뜻함 exo 타입인지 ptrn 타입인지 근데 왜인지 모름!
(2) 아니면 v15가 ntrp가 아닌지
(3) ntrp이고 v15 + 48, 22464 값을 검증한 다음 vidExoIoControlPartition( VidExoIoControlPartition(v15, a2, v10, *(unsigned int **)v22, v7, Dst, v8, v4, (unsigned int *)&v21); else ) 호출
(4) v15가 요청을 보낸 프로세스를 나타낸 값이라고 함 프로세스 검증 후 I/O제어함수 호출되겠지
5. vidExoIoControlPartition() 란
(1) case를 a8값에 따라 엄청 여러개 나눠둠
(2) case가 0x221288일 때 VidExoBrokerIoctlSend
(3) case가 0x22128C일 때 VidExoBrokerIoctlReceive
=> 결론은 \vid\exo 드라이버에 대한 핸들러를 만들고 제어하면서 저 send랑 receive를 실행하게 될 수도 있다 이말임
send가 어떤 구조체를 보내는지, 그 구조체의 어느 변수에서 오버플로우를 일으킬 수 있는지.
send에서는 에러가 안 나면 VidExoBrokerQueueSendIrp를 실행하는데 얘가 receive에서도 쓰임

__int64 __fastcall VidExoBrokerpQueueSendIrp(__int64 a1, __int64 a2)
{
__int64 v2; // rbp
char v5; // al
_QWORD *v6; // rdx
char v7; // di
char v8; // r9
_QWORD *v9; // rax
__int64 result; // rax
v2 = a1 + 24064;
*(_QWORD *)(a2 + 120) = a1;
v5 = KeAcquireSpinLockRaiseToDpc(a1 + 24064);
v7 = 0;
_InterlockedExchange64((volatile __int64 *)(a2 + 104), (__int64)VidExoBrokerpIrpCancelRoutine);
v8 = v5;
if ( *(_BYTE *)(a2 + 68) && _InterlockedExchange64((volatile __int64 *)(a2 + 104), 0LL) )
{
v7 = 1;
}
else
{
v6 = *(_QWORD **)(a1 + 24080);
v9 = (_QWORD *)(a2 + 168);
if ( *v6 != a1 + 24072 )
__fastfail(3u);
*v9 = a1 + 24072;
*(_QWORD *)(a2 + 176) = v6;
*v6 = v9;
*(_QWORD *)(a1 + 24080) = v9;
}
LOBYTE(v6) = v8;
result = KeReleaseSpinLock(v2, v6);
if ( v7 )
{
*(_DWORD *)(a2 + 48) = -1073741536;
return IofCompleteRequest(a2, 0LL);
}
return result;
}
( a1+24064, KeAcquireSpinLockRaiseToDpc(a1 + 24064), *(_QWORD **)(a1 + 24080), 0, v5 or 1, (_QWORD *)(a2 + 168), KeReleaseSpinLock(v2, v6))
# Proof of Concept
__int64 __fastcall VidExoIoControlPartition(
struct _KDPC *VidExoObj,
_IRP *irp,
struct _LIST_ENTRY *FileObj,
unsigned int *InputBuffer,
unsigned int InputLen,
unsigned int **OutputBuffer,
unsigned int OutputLen,
unsigned int IoControlCode,
unsigned int *a9)
{
# 참고자료
https://hackyboiz.github.io/2024/09/01/pwndorei/hyperv-1dayclass_CVE-2024-38080/#google_vignette
hackyboiz
hack & life
hackyboiz.github.io