
CVE-2022-37969에 대한 개념 증명 익스플로잇으로, Windows 공용 로그 파일 시스템 드라이버의 로컬 권한 상승 취약점입니다. 힙 스프레이, 토큰 탈취, 임의 커널 쓰기를 시연하여 SYSTEM 권한을 획득합니다.
저자: Ricardo Narvaja & Daniel Kazimirow (Solid)
데모 목적으로만 사용하십시오. 완전한 익스플로잇은 취약한 Windows 11 21H2 시스템에서 작동합니다.
이전에 Zscaler가 게시한 정보를 기반으로 한 기능적 PoC입니다.
관련 분석 문서는 Understanding the CVE-2022-37969 Windows Common Log File System Driver Local Privilege Escalation에서 확인하세요.
익스플로잇 워크스루:
여기서 사용된 시나리오는 Windows 11 21H2 (OS 빌드 22000.918) clfs.sys v10.0.22000.918입니다.
첫 번째 단계는 CreateLogFile() 함수를 사용하여 공용 폴더(%public%)에 MyLog.blf라는 파일을 생성하는 것입니다:



그런 다음 루프를 사용하여 무작위 이름의 여러 로그 파일을 생성합니다.
루프 내에서 우리의 getBigPoolInfo() 함수를 호출합니다:

**NtQuerySystemInformation()**을 첫 번째 인수 0x42(10진수 66)로 호출하면, bigpool에서 이루어진 할당에 대한 정보가 v5에 반환되며, 해당 구조체는 SYSTEM_BIGPOOL_INFORMATION 유형입니다.

이 함수를 두 번 호출해야 합니다. 첫 번째 호출은 오류를 반환하지만, 두 번째 호출에서 원하는 정보를 얻기 위해 올바른 버퍼 크기를 알려줍니다.

v5는 SYSTEM_BIG_POOL_INFORMATION 구조체의 정보를 받습니다.

bigpool의 할당 수는 첫 번째 필드인 Count에 저장되고, 두 번째 필드에는 SYSTEM_BIGPOOL_ENTRY 구조체의 배열이 있습니다.

그런 다음 모든 구조체에서 "Clfs" 태그와 크기 0x7a00을 검색합니다.

CLFS 태그와 크기 0x7a00을 가진 각 구조체의 첫 번째 필드인 VirtualAddress를 kernelAddrArray라는 배열에 저장합니다. 이제부터 이 두 조건을 모두 만족하는 풀을 "올바른 풀" 이라고 부릅니다.

각 올바른 풀을 배열에 저장하는 것 외에도, 함수의 인수로 사용되는 a2 변수의 내용에 마지막으로 발견된 올바른 풀을 저장합니다.

이렇게 하면 a2는 항상 CLFS 태그와 크기 0x7a00을 가진 마지막으로 생성된 올바른 풀을 가리킵니다.
변수 v26은 **getBigPoolinfo()**를 호출하기 전에 v24와 같게 설정되므로(v26=v24) 항상 이전에 찾은 올바른 풀을 저장합니다. 그러나 이 호출을 종료할 때 v24는 마지막으로 찾은 올바른 풀로 업데이트되고, v26은 이전 올바른 풀을 유지합니다.

그런 다음 두 주소를 뺍니다. 결과가 음수이면 항상 양수가 되도록 피연산자를 반전시킵니다.

이렇게 하면 v32에 마지막 두 올바른 풀의 VirtualAddress 간 차이가 저장됩니다.
그런 다음 유사한 작업을 수행합니다. 이 경우 v23은 처음에 0이므로 첫 번째에는 v23=v32가 됩니다.

루프에서 다음 번에 v23은 여전히 동일한 값을 가지며 0이 아니므로 루프를 중단하고 여기로 이동합니다.

V32는 마지막 차이를 가지고 있고 v23은 이전 차이를 가지고 있습니다. 둘이 같으면 하나를 증가시키지만 카운터를 0으로 재설정합니다.
목표는 차이가 동일한 CLFS 태그 및 크기 0x7a00의 6번 연속 비교를 찾는 것이며, 그 차이는 0x11000이 됩니다. 실행 시 6번(0부터 시작) 연속 동일한 거리를 찾으면 그 차이 값이 제공되는 것을 볼 수 있습니다.


여기서 6번 연속을 찾고 로그 파일 생성 루프를 빠져나온 것을 볼 수 있습니다.
"public" 폴더에서 생성된 파일을 볼 수 있습니다.

craftFile() 함수는 원본 파일(MyLog.blf)을 열고 버그를 트리거하도록 수정합니다.

파일을 수정한 후에는 CRC32를 변경해야 합니다. 그렇지 않으면 손상된 파일 오류가 발생합니다.
이 값은 파일 오프셋 0x80C에 위치합니다.

다음으로, VirtualAlloc() 함수를 사용하여 HeapSpray를 수행합니다. 메모리를 임의 주소 0x10000 및 0x5000000에 각각 할당하고, 두 번째 할당(0x10000)에 값 0x5000000을 0x10바이트마다 저장합니다.

**CreatePipe()**를 사용하여 익명 파이프를 만들고, **NtFsControlFile()**을 인수 0x11003c로 호출하여 속성을 추가합니다. 나중에 동일한 함수를 인수 0x110038로 호출하여 읽을 수 있습니다.
이 방법에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

여기서 입력 버퍼는 추가 중인 속성입니다. **NtFsControlFile()**을 인수 0x11038로 다시 호출하면 출력에서 동일한 속성이 반환되어야 합니다.

생성된 속성의 태그(NpAt)에 대해 풀을 검색합니다.


찾으면 v30.Pointer에 이 풀의 VirtualAddress를 저장합니다.
V30.pointer+24는 커널 풀에서 AttributeValueSize를 가리키며, 이를 이전에 만든 HeapSpray 중 하나에 저장합니다.

목표는 해당 커널 주소+8에 써서 AttributeValue를 덮어쓰는 것입니다.
