Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
cve-2019-1458_POC — cve-2019-1458용 POC | Kitploit
도구/GitHubGitHub/piotrflorczyk/cve-2019-1458_poc
Vulnerability AnalysisExploitationReverse EngineeringLearning & EducationBinary Exploitation
GitHubpiotrflorczyk/cve-2019-1458_poc

cve-2019-1458_POC

cve-2019-1458용 POC

저장소 보기
1815394년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2019-1458: '인더와일드 보고서'에서 POC까지

소개

12월에 Kaspersky는 [야생에서 사용된 0day 익스플로잇][1]에 관한 블로그 포스트를 게시했습니다. 그들의 분석에는 익스플로잇이 어떻게 동작하는지 설명되어 있었지만 POC가 포함되어 있지 않았기 때문에 제 관심을 끌었습니다. 그래서 저는 Kaspersky의 블로그 포스트와 패치 분석을 기반으로 이 취약점에 대한 POC를 작성해 보기로 결정했습니다.
이 포스트는 그 과정을 다룹니다.

정보 수집:

가장 먼저 이 취약점에 대해 가능한 한 많은 정보를 수집했습니다. 언급된 블로그 포스트를 읽으면서 다음 정보를 추출했습니다:

  • 취약점은 창 전환 기능과 관련이 있습니다
  • 트리거하려면 ALT 키 누름을 시뮬레이션해야 합니다
  • 문서화되지 않은 NtUserMessageCall API에 대한 호출이 두 번 필요합니다
  • 특별한 전환 창을 생성해야 합니다
  • 커널 함수 win32k!DrawSwitchWndHilite에 대한 언급이 있었습니다

그 외에도 앞서 나열한 일부 내용을 보여주는 디컴파일된 코드의 멋진 스크린샷이 있습니다. 정확히는 전환 창 생성, toggle_alt_key라는 함수 호출, 그리고 여러 번의 NtUserMessageCall 호출을 보여줍니다.

디컴파일된 익스플로잇 코드의 일부 [이미지 출처][1]

유용한 정보가 많지만, 이 취약점이 정확히 어떻게 동작하고 어떻게 트리거하는지는 여전히 설명되지 않습니다.

패치 디핑

영향을 받은 모듈은 [win32k.sys][2]였습니다. 이 모듈의 패치 버전과 패치 전 버전을 모두 다운로드했습니다.
Win7 x64의 경우 다음과 같습니다:

  • 패치됨: KB4530692
  • 패치 전: KB4525233

이들은 [Microsoft Update Catalog][3]에서 다운로드할 수 있습니다

두 버전을 비교한 bindiff 결과는 다음과 같습니다

win32k 비교

DebugHook 기능과 관련된 함수들을 제외하고 나면 실제로 남는 것은 약간 변경된 함수 InitFunctionTables()뿐입니다.

InitFunctionTables 변경 사항

확실히 가장 큰 패치는 아닙니다.
이것만으로 이 취약점의 근본 원인을 즉시 식별하는 데는 도움이 되지 않습니다. 하지만 *(gpsi+0x14E), *(gpsi+0x154), *(gpsi+0x180) 변수에 대한 초기 값이 추가되었다는 점은 주목할 만합니다. 따라서 이는 초기화되지 않은 변수와 관련된 버그일 수 있습니다.

POC 빌드 - 단계별로

이 섹션에서는 이 취약점을 트리거하는 POC를 단계적으로 구축하면서 동시에 취약점의 정체를 파악해 나가는 과정을 설명하겠습니다.

시작 위치

패치 디핑은 처음에 그다지 유용한 정보를 주지 않았기 때문에 개발 초기 단계에서는 주로 Kaspersky의 블로그 포스트에 의존했습니다.
좋은 테스트 환경을 위해 마지막 취약 버전의 win32k가 실행되는 Win7 SP1 x64 VM을 준비했습니다. 그 위에 Windbg를 연결하여 커널 디버깅을 수행하고, 동시에 심볼 서버 경로도 설정했습니다.
블로그 포스트에서 언급된 win32k!DrawSwitchWndHilite를 살펴보면서 조사를 시작했습니다. 이 함수는 xxxMoveSwitchWndHilite와 xxxPaintSwitchWindow 두 곳에서 호출되는데, 후자는 원래 보고서에서 언급된 주변의 GetKeyState/GetAsyncKeyState 호출 때문에 즉시 관심을 끌었습니다. 게다가 그 호출들은 ALT 키가 눌렸는지 확인하고 있습니다.

DrawSwitchWndHilite로의 흥미로운 호출 지점
xxxPaintSwitchWindow에서 DrawSwitchWndHilite 호출

호출 교차 참조(xxxWrapSwitchWndProc->xxxSwitchWndProc->xxxPaintSwitchWindow->DrawSwitchWndHilite)를 더 따라가면서, 그 체인의 첫 번째 요소가 패치에서 수정된 InitFunctionTables에서 참조된다는 것을 발견했습니다.

다음으로 디컴파일된 코드의 스크린샷에서 NtUserMessageCall을 살펴보았습니다. 이 함수의 선언은 다음과 같습니다```cpp NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOLEAN bAnsi)

Exploit은 `msg = 0x14`와 `dwType = 0xE0`로 그것을 호출하고 있습니다. 그것이 무엇을 하는지 살펴보겠습니다.```cpp
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASSEX wcx;
ZeroMemory(&wcx, sizeof(wcx));
wcx.hInstance = hInstance;
wcx.cbSize = sizeof(wcx);
wcx.lpszClassName = L"SploitWnd";
wcx.lpfnWndProc = DefWindowProc;

printf("[*] Registering window\n");
ATOM wndAtom = RegisterClassEx(&wcx);
if (wndAtom == INVALID_ATOM) {
    printf("[-] Failed registering SploitWnd window class\n");
    exit(-1);
}

printf("[*] Creating instance of this window\n");
HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
if (sploitWnd == INVALID_HANDLE_VALUE) {
    printf("[-] Failed to create SploitWnd window\n");
    exit(-1);
}
NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0xE0, 1);

여기서 간단한 창 클래스를 등록하고 해당 클래스의 창을 생성했습니다. 그런 다음 익스플로잇과 동일한 매개변수로 NtUserMessageCall을 호출했습니다. 내부에서 어떤 일이 발생하는지 확인하기 위해 중단점 kd> ba e 1 win32k!NtUserMessageCall을 설정하고 코드를 실행했습니다. 이 함수에 대한 호출이 꽤 많았기 때문에 올바른 호출을 찾아야 했지만, 그리 어렵지 않았습니다. 콜스택이 매우 짧은 호출이 바로 그것이었습니다.

NtUserMessageCall
NtUserMessageCall

코드를 단계별로 살펴보면 gapfnMessageCall 배열의 함수를 호출하며, 인덱스는 msg 값을 기준으로 계산되고 0이므로 NtUserfnDWORD를 호출하게 됩니다.

NtUserfnDWORD
NtUserfnDWORD

다음 호출은 dwType 값을 사용하여 이루어지며, 이제 gpsi 오프셋은 0x40이고, 호출은 xxxWrapSwitchWndProc로 이어집니다 (이 함수는 DrawSwitchWndHilite 호출 체인을 확인할 때 이미 나타났습니다).
xxxWrapSwitchWndProc는 단순히 xxxSwitchWndProc를 호출합니다.

xxxSwitchWndProc
xxxSwitchWndProc

그리고 이것이 끝입니다. 코드는 여기서 실패하며, msg 값(0x14)에 따라 도달하려는 xxxPaintSwitchWindow로 더 이상 진행되지 않습니다. 왜 그런지 확인해 봅시다.

올바른 경로 트리거하기

이 단계에서 코드가 실패하는 이유는 이전 이미지에서 강조된 것처럼 창의 fnid가 0x2A0(FNID_SWITCH)와 같지 않고 보내는 메시지가 1이 아니기 때문입니다. 따라서 xxxDefWindowProc로 빠지게 됩니다. 이 상황을 피하려면 fnid를 FNID_SWITCH로 설정한 상태로 xxxSwitchWndProc를 호출해야 합니다. 그래야 switch 문으로 바로 이동하고 나중에 xxxPaintSwitchWindow에 도달할 수 있습니다.
올바른 fnid를 설정하는 방법은 무엇일까요? 실제로 같은 함수가 첫 번째 if 블록에서 이 작업을 수행하므로, fnid를 설정하는 명령에 도달하려면 그 안의 모든 검사를 실패시키기만 하면 됩니다.

다음은 세 개의 if 검사를 모두 실패시키기 위해 충족해야 하는 조건입니다.

  • fnid == 0 및 cbwndExtra + 0x128 >= *(gpsi + 0x154)
    새로 생성된 모든 사용자 창에서 fnid는 0입니다. 패치되지 않은 win32k!에서 *(gpsi+0x154)는 0입니다. 하지만 패치된 버전처럼 0x130으로 설정된 경우에도 cbwndExtra를 8 이상으로 설정하면 첫 번째 검사를 여전히 우회할 수 있습니다.
  • msg == 1
    NtUserMessageCall 호출에서 설정할 수 있습니다. msg를 1로 설정하면 제어 흐름은 NtUserfnDWORD 대신 NtUserfnINLPCREATESTRUCT를 거치지만 여전히 xxxSwitchWndProc에서 끝납니다.
  • extraData == 0
    ExtraData 크기는 앞서 언급한 cbwndExtra를 사용하여 창 클래스를 등록할 때 설정할 수 있습니다. ExtraData는 tagWND 구조 바로 뒤에 추가됩니다 (저는 디컴파일된 코드를 좀 더 보기 좋게 만들기 위해 이 필드를 IDA의 tagWND 구조에 sizeof(tagWND) 오프셋의 QWORD로 추가했습니다). ExtraData 값은 SetWindowLongPtr 호출로 설정할 수 있습니다.

이 모든 조건이 충족되면 창의 fnid가 FNID_SWITCH로 설정됩니다.
이제 원하는 fnid를 설정하기 위해 msg를 1로 하여 NtUserMessageCall을 한 번 호출하고, xxxPaintSwitchWindow에 도달하기 위해 두 번째로 호출해야 합니다.```cpp HINSTANCE hInstance = GetModuleHandle(NULL); WNDCLASSEX wcx; ZeroMemory(&wcx, sizeof(wcx)); wcx.hInstance = hInstance; wcx.cbSize = sizeof(wcx); wcx.lpszClassName = L"SploitWnd"; wcx.lpfnWndProc = DefWindowProc; wcx.cbWndExtra = 8; //to pass check in xxxSwitchWndProc

printf("[*] Registering window\n"); ATOM wndAtom = RegisterClassEx(&wcx); if (wndAtom == INVALID_ATOM) { printf("[-] Failed registering SploitWnd window class\n"); exit(-1); }

printf("[*] Creating instance of this window\n"); HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); if (sploitWnd == INVALID_HANDLE_VALUE) { printf("[-] Failed to create SploitWnd window\n"); exit(-1); }

printf("[] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n"); NtUserMessageCall(sploitWnd, WM_CREATE/ = 1*/, 0, 0, 0, 0x0, 1);

printf("[] Calling NtUserMessageCall second time"); NtUserMessageCall(sploitWnd, WM_ERASEBKGND/ = 0x14*/, 0, 0, 0, 0x0, 1);

창 클래스에 `extraData`를 추가하고 두 번째 `NtUserMessageCall` 호출을 추가했습니다. 이제 제어 흐름이 `xxxPaintSwitchWindow`에 도달할 수 있습니다.
(참고: `dwType`은 `0xE0`일 필요는 없습니다. 어차피 `NtUserfnDWORD`에서 `0x1F`와 AND 연산을 하기 때문에 `0`도 그냥 동작합니다.)

![xxxPaintSwitchWindow](https://assets.kitploit.com/production/public/readmes/30684/0a794f2d2af1e438ad43ce6f36921871ba04eeadeba67141388de7163ceaa0fa.png)
*`xxxPaintSwitchWindow`*

자세히 살펴보니 창 객체에서 가져온 `extraWndData` 값(25번째 줄)이 쓰기 대상 포인터로 사용되고 있음을 확인했습니다(46-52번째 줄)! 내가 제어하는 값으로 `extraWndData`를 설정하는 코드에 도달할 수 있다면 임의의 메모리를 손상시킬 수 있습니다!  
그 코드에 도달하려면 먼저 빨간색으로 표시된 몇 가지 검사를 더 통과해야 합니다
도구 다운로드