本文作者:G1ace@四叶草安全实验室

1、漏洞简介

漏洞公告相关链接:

ZDI-16-453 CVE-2016-3308

CloverSec Labs成员bear13oy在七月中旬也发现该内核漏洞,由于当时比较忙,本想在八月微软的补丁周过后再对其进行分析和利用,不巧的是微软八月份的补丁修复了该漏洞。再次撞洞!

附上当时精简后的poc代码截图:

内核崩溃后栈回溯信息截图:

2、漏洞原理

由于函数win32k!xxxInsertMenuItem在处理新增菜单插入时,获得了错误的插入偏移,导致函数在计算MenuItem内核数据长度时发生错误,进而在调用memmove移动MenuItem数据时,发生越界操作。

win32k!xxxInsertMenuItem函数在调用memmove移动MenuItem内核数据前,两次调用函数win32k!MNLookUpItem来获取相关Item数据的内存地址。在此漏洞中,第一次调用win32k!MNLookUpItem时,由于传入的wIndex不存在,函数会返回NULL,于是win32k!xxxInsertMenuItem会默认把新Item数据放到原Item数组的末尾,并把wIndex更新为新Item数据在原Item数组中的偏移。相关代码如下所示:

    if (w == MFMWFP_NOITEM)
        w = pMenu->cItems;
    w--;
    pItemWalk = pMenu->rgItems + w;
#ifdef MEMPHIS_MENUS
    while (w && (pItemWalk->hbmp) && (pItemWalk->hbmp < (HBITMAP)MENUHBM_MAX))
#else // MEMPHIS_MENUS
    while (w && (pItemWalk->fType & MFT_BITMAP) && (pItemWalk->hTypeData < (HANDLE)MENUHBM_MAX))
#endif // MEMPHIS_MENUS
    {
        wIndex = w--;
        pItemWalk--;
    }

在调用AppendMenuA函数后,Menu中的Item数据刚好达到了8个,而由下面的代码可以看出,内核中Item数据的分配刚好是以8个Item为分配粒度的。于是如下代码中的pMenu->cItems >= pMenu->cAlloced的条件正好成立,于是win32k!MNLookUpItem会被再次调用,在新分配的Item数据中通过wIndex偏移找到需要插入的内存地址并保存在pItem中。

#define CMENUITEMALLOC 8
...
...
if (pMenu->cItems >= pMenu->cAlloced) {  
    if (pMenu->rgItems) {
        pNewItems = (PITEM)DesktopAlloc(pMenu->head.rpdesk->hheapDesktop,
        (pMenu->cAlloced + CMENUITEMALLOC) * sizeof(ITEM));
    ...
    } else {
        pNewItems = (PITEM)DesktopAlloc(pMenu->head.rpdesk->hheapDesktop,
                sizeof(ITEM) * CMENUITEMALLOC);
    }
    ...
    pMenu->cAlloced += CMENUITEMALLOC;
    pMenu->rgItems = pNewItems;
    ...
    /*
     * Now look up the item again since it probably moved when we realloced the
     * memory.
     */
    if (wIndex != MFMWFP_NOITEM)
        pItem = MNLookUpItem(pMenu, wIndex, fByPosition, &pMenuItemIsOn);
}

然而win32k!MNLookUpItem函数会在所有的Item以及子菜单的Item中递归寻找,由于之前已经有7个Item数据存在,在调用InsertMenuA函数后wIndex被改写为7,而此时由于notepad的第一个File的子菜单中的最后一个Item(Exit)的id正好是7,于是win32k!MNLookUpItem函数遍会把此Item的内核地址返回。之后在计算memmove移动长度时便会出现不可预料的情况,并在移动数据时发生访问异常。

memmove(pItem + 1, pItem, (pMenu->cItems - 1) *  
        sizeof(ITEM) - ((char *)pItem - (char *)pMenu->rgItems));
typedef struct tagITEM  
{
    UINT fType;
    UINT fState;
    UINT wID;
    struct tagMENU* spSubMenu; /* Pop-up menu. */
    HANDLE hbmpChecked;
    HANDLE hbmpUnchecked;
    USHORT* lpstr; /* Item text pointer. */
    ULONG cch;
    DWORD_PTR dwItemData;
    ULONG xItem;   /* Item position. left */
    ULONG yItem;   /*     "          top */
    ULONG cxItem;  /* Item Size Width */
    ULONG cyItem;  /*     "     Height */
    ULONG dxTab;   /* X position of text after Tab */
    ULONG ulX;     /* underline.. start position */
    ULONG ulWidth; /* underline.. width */
    HBITMAP hbmp;  /* bitmap */
    INT cxBmp;     /* Width Maximum size of the bitmap items in MIIM_BITMAP state */
    INT cyBmp;     /* Height " */
} ITEM, *PITEM;

从上面代码可以看出,win32k!xxxInsertMenuItem中触发漏洞的memmove所用到的pItem地址正好是之前win32k!MNLookUpItem函数返回的内存地址。结合内存数据和PITEM数据结构可以看出,此时的pItem地址所指向的是notepad中File的子菜单中的最后一个Item(Exit)(数据结构如上图红色所示),而pMenu->rgItems为DesktopAlloc新分配的内存地址指针,这两个地址分别属于不同的Item数组中,所以相减的值是不可预料的,精确操作内核堆分配可控制memmove长度,从而控制特定的内存区域。

3、漏洞利用

漏洞利用主要用到两个数据结构如下:

typedef struct tagPROPLIST {  
        UINT cEntries;
        UINT iFirstFree;
        tagPROP aprop[1];
} PROPLIST, *PPROPLIST;
typedef struct tagMENU  
{
    ...
    UINT cItems;              /* Number of items in the menu \*/
    ...
    PITEM rgItems;            /* Array of menu items \*/
    ...
} MENU, *PMENU;

通过构造数据覆盖tagPROPLIST中的cEntries和iFirstFree便可以实现对tagPROPLIST结构之后的一段数据进行控制。

漏洞触发过程中tagPROPLIST在内存中的变化:

漏洞触发前
fce2f240  00000002 00000002 fb10afe0 0001c033   tagPROPLIST  
fce2f250  00000000 0002c04a 00010002 08000004  
fce2f260  00410041 00000041 00010002 08000002  
fce2f270  00410041 00000041 00010002 08000002  
fce2f280  00410041 00000041 00010002 08000002  
fce2f290  00410041 00000041 00010002 08000002  
fce2f2a0  00410041 00000041 00010002 08000002  
fce2f2b0  00410041 00000041 00010002 08000002

漏洞触发后
fce2f240  08000002 88888888 88888888 88888888   tagPROPLIST  
fce2f250  88888888 88888888 88888888 88888888  
fce2f260  88888888 88888888 88888888 88888888  
fce2f270  88888888 88888888 88888888 88888888  
fce2f280  88888888 88888888 88888888 88888888  
fce2f290  88888888 88888888 88888888 88888888  
fce2f2a0  00008888 00010004 0800000d 00000002  
fce2f2b0  00000002 fb10afe0 0001c033 00000000  

漏洞利用过程大概可以分为如下步骤:

1.  构造数据覆盖tagPROPLIST中的cEntries和iFirstFree  
2.  通过SetProp函数对tagPROPLIST对分布在其后的tagMENU结构中的cItems和rgItems字段进行控制  
3.  通过SetMenuItemInfo实现对任意地址的写操作  
4.  改写内核HalDispatchTable+4的数据实现EIP控制  

4、漏洞演示

测试环境: Windows 7 SP1 x86 (更新于2016-07-16)

来源链接:CVE-2016-3308 / ZDI-16-453 Microsoft Windows内核提权漏洞原理分析与利用