[part 1] from patch diff to exp

Posted by tetley at 2020-04-06

Click on the arrow "blue words", pay attention to us!!

This is the 21st article of A-Team

Author: ze0r @ 360a-team

In this article, we will conduct an in-depth analysis of cve-2018-8453 (windows win32kfull.sys kernel privilege raising vulnerability).

Due to various subjective and objective reasons of major security companies and platforms at home and abroad, the technical analysis of the vulnerability has been unclear, even with intentional errors. In order to restore the truth, we mainly analyze the vulnerability, supplemented by Kaspersky's analysis article. Now we will analyze the process and use it to share with others for reference.

Statement: This article was originally written by ze0r @ 360a-team. It is only used for technical research. Improper use will cause harm. Illegal use is strictly prohibited, or the consequences will be borne by yourself.


Cve-2018-8453 vulnerability is a Windows Kernel right offering vulnerability, which was officially discovered by Kaspersky in the field to attack countries in the Middle East in apt. After Microsoft released the update patch, Kaspersky also released a more detailed analysis of the vulnerability the next day, but it was still very secretive, and there were many intentional errors (probably because Kaspersky was worried that the vulnerability might be exploited as nday). In addition, the description of the vulnerability on the two major domestic security platforms is also translated directly for Google. Intentional errors plus translation errors make it impossible to clearly understand the principle and utilization of the vulnerability.

In order to restore the truth, we analyze and learn the vulnerability by facing the vulnerability directly and supplemented by Kabbah's analysis article.

Related links:

The official patch and vulnerability profile of Microsoft can be found at the link:

Kaspersky's analysis article link:

Download a separate patch of the vulnerability and check it with bindiff and historical patches. The main changes are as follows (the old and new changes are mainly compared with Win32k.sys of the previous patch date, the same below):

At the bottom, you can see that a function called ntusersetwindowfnid has a large color difference. Compare it:

It can be seen that there is an iswindowbeingdestroyed function call in the judgment process:

In other words, there is an additional check when setting a member of the change window. This means that the reason for this vulnerability is that when setting a member, it is not judged that a member is caused by it. In terms of name, this member is fnid.

So, the question is, what is the impact of not checking by fnid? Let's check the function of this member. In some source codes of Win2000, we can search fnid to find out what it means.

This fnid member is used to identify what kind of window this window is, such as a button or an edit box, which can also be verified from the article. From the newly added function name iswindowbeingdestroyed after the patch modification, this is to determine whether this window is ready to be deleted. Looking at the ReactOS code mentioned in the article, we can see that the mark to be deleted is to add the mark of fnid_free (0x8000). The key is, how does the window after free trigger the vulnerability?

Through Kabbah's article, we sort out the general idea of utilization: code first hook Kernelcallbacktable - > generate a main window - > find and cancel sysshadow window in user32!? create a scroll bar window with the main window as the parent window - > send WM? Lbuttondown message - > user32!? DWORD callback will occur when the system processes the message, and destroy the main window in user32!? DWORD callback - > this will cause the main window to be destroyed and thus produce a result USER32! __fnNCDESTROY callback ->USER32! __fnNCDESTROY callback calls NtUserSetWindowFNID to change FNID->. So far, the article begins with a vague statement. The article says that sysShadow is reused, but we simply can not understand how to reuse it. So we need to do it ourselves.

First of all, we have to call the vulnerability function

As you can see, in order to successfully change fnid, several conditions need to be met. We can't just set it to 0x4000 (this is just a mark, and it doesn't work). As for the value of the new fnid, we can directly set it to 0x2a1 according to the article. For the later conditions, the window we need to set cannot have fnid (except for 0x4000 and 0x8000, which are useless). After many tests, it is found that fnid is empty in three cases: one is when any type of window is just established, the system calls ntusersetwindowfnid in user mode to set fnid (automatically implemented in user32. DLL). At this time, if fnid is not set, the window has not set message processing function, and has no ability to process messages. The WM? Lbuttondown message mentioned in the article is definitely after the scrollbar window is completely created. So this is not the case. The other is the window generated by the window class registered by the user. Fnid has not been set until the window is destroyed. The third is the sysshadow window mentioned in the article. The function of this window is only to produce shadow effect, but fnid is empty. Because of this characteristic, I have been misled for a long time. Later, I asked leeqwind to know that it is not the reused sysshadow at all, but the sbtrack structure. In addition, you can see the screenshot of the article:

Because I pay attention to the triggering of the article, I haven't paid attention to the use, and I haven't paid attention to the following content. In fact, the truth has been revealed here (deep review and reflection!). As you can see from the red box of the screenshot, the tag is UST, and the allocator is win32k! Xxxsbtrackinit. So it's clear that sbtrack is to be reused.

The article shows that it is necessary to call vulnerability function to change fnid after fnid is set to 0x8000. We know that the last message received by the user state of a window destruction is WM ﹣ ncdestory. In win32k, this message is sent to the window in the function of xxxfreewindow:

You can see that the message 0x82 (WM ﹣ ncdestory) is sent in line 106, so we need to find a way to return to the user state after line 106. But there is another problem at the same time, that is, pay attention to line 134. This line marks fnid with 0x4000. In the article, 0x8000 is directly changed into 0x82a1, and there is no 0x4000. So if we change fnid in WM ﹣ ncdestory message, we can change fnid immediately. But at this time, the window has not been marked with 0x8000 (it will be marked in line 136) , which is obviously inconsistent with the article. So it is a deliberate error to call ntusersetwindowfnid to change fnid in user32! (fnncdeploy).

After my dynamic test with pykd, it is found that the window handle will not exist after the 426 line call of the window, and the validatehwnd function of ntusersetwindowfnid function will return 0 to skip the fnid setting directly. That is to say, if we want to directly set 0x8000 to 0x82a1 in the article, we need to find a call back to user state between lines 134 and 426.

Here's a little digression. I first did the analysis in Win32k.sys of Windows 7, and the result search failed to find it successfully for a long time, until someone mentioned that the vulnerability could be used after win8.1 on twitter one day. After watching win32kfull.sys of windows 10, I suddenly realized that the lesson was profound!

Back to the main topic, we can see here:

The above screenshot is the IDA analysis result of win32kfull.sys under win10. We can see that after 256 lines are changed to 0x8000, there is a function call of xxxclientfreewindowclassextrabytes in 266 lines:

This function directly calls keusermodecallback! It returns to the user state unconditionally. So we just need to meet the conditions of entering the function of xxxclientfreewindowclassextrabytes. Check the code carefully, and find out that the judgment of 258 lines is mainly to determine whether the window has extended bytes (as indicated by the name of the xxxclientfreewindowclassextrabytes function). In some cases, call the xxxclientfreewindowclassextrabytes function to release. Because the extended bytes are allocated in the user space, the function returns to the user state to let the user state code interpret Let go (at least inform). So as long as we register the window class, the cbwndextra member is not 0. When the window is destroyed, it will return to the user state after 0x8000 is set. When the window returns to user state with 0x8000, we change fnid to 0x82a1. After returning to kernel state, xxxfreewindow will continue to execute later.

Back to the xxxfreewindow function:

Here, you can see that the code judges the value of fnid, so as to decide whether to call user32! (fndword). We know that when a window is referenced by multiple other windows and structures, even if the window has been destroyed by the user's call destorywindow, the window object must continue to exist in memory, so as to wait for all the places that reference it to no longer refer to it to really release the object memory. Then, if we call xxxfreewindow after destroying a window and releasing its last reference, we can use fnid to control whether the process will return to user32! \. So the attack chain is complete.

In combination with the above, the article mentioned the use of xxxsbtrackinit. This function is mainly used to scroll with the mouse. When the user presses the left button on a scroll bar, it means that the user wants to drag the scroll bar. At this time, it needs to start to process the mouse movement, so that the scroll bar moves accordingly. In the system, sbtrack structure is generated to mark the current position of the user's mouse. Finally, when the user releases the left mouse button, it means that The user has finished dragging and needs to release the corresponding sbtrack structure.

In the source code of Windows 2000, part of the code of xxxsbtrackinit is as follows:

The general process is to initialize sbtrack after calling userallocpoolwithquota to apply for memory, put the pointers of scroll bar window and notification window in this structure, and then set the current window as capture window in line 2425. Then call xxxsbtrackloop to start the loop to process the user's mouse message:

As you can see, the xxxsbtrackloop loops get messages, judge messages, and distribute messages. When the user releases the mouse, the tracking processing message shall be stopped. After exiting the xxxsbtrackloop and returning to the xxxsbtrackinit, the memory occupied by sbtrack shall be released:

In the previous two lines, you can see that the spwndsbnotify window will be dereferenced once before sbtrack is released. Combined with the above analysis, we can return to the user state when dereferencing this time. If the sbtrack is released in user mode, when the process returns to the kernel again, the user freepool that follows will cause the problem of repeated release.

So how can we release sbtrack in user mode? It is found that one way to release sbtrack is that the user normally releases the left mouse button, and the other way is the xxxendscroll function:

void xxxEndScroll(

    PWND pwnd,

    BOOL fCancel)


    UINT oldcmd;

    PSBTRACK pSBTrack;




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

... . omit some codes .

        pSBTrack->xxxpfnSB = NULL;


         * Unlock structure members so they are no longer holding down windows.






        PWNDTOPSBTRACK(pwnd) = NULL;



The xxxendscroll function judges the sbtrack and PQ - > sqpwndcapture() stored in the window's thread information.

Our program is single thread. Because each thread information belongs to thread, all windows created by thread also point to the same thread information structure. Therefore, even if the scrollbar window to which sbtrack belongs has been released, psbtrack will remain the same as long as it is a new window created by the same thread. How can QP - > spwndcapture = = pWnd be bypassed? If we create a new window, send messages and operations to the new window, pWnd will be the new window, which is obviously not equal to the capture window set in xxxsbtrackinit - the old window.

Through the test, it is found that the settings of this capture window can be set directly by simply calling the SetCapture API in the user state. So we just need to call the API directly to let the judgment in xxxendscroll pass completely.

After searching, it is found that the xxxendscroll function can be called through the following path:

Send WM cancellmode - > xxxdefwindowproc judgment message to a window - > call xxxdwp > docancemode - > xxxdwp > docancemode to judge psbtrack - > xxxendscroll in the current thread information. As we know above, all windows are created in the same thread, so the judgment here can also be passed!

Sort out the following process:

Hook kernelcallbacktable - > register window class, set wndclassexw.cbwndextra to 4 - > generate main window ->Generate a scroll bar window with the main window as the parent window srollbar - > send WM ﹤ lbuttondown message - > the system processes the message to initialize the sbtrack structure and start the cycle - > fndword callback occurs, in the fndword callback, destroy the main window - > destroy the main window, release the extended byte xxxclientfreewindowclassextrabytes - > xxxclientfreewindowclassextrabytes system call callback fnclientfreewindowc lassExtraBytesCallBack->fnClientFreeWindowClassExtraBytesCallBack HOOK calls NtUserSetWindowFNID, changes window FNID->, creates new window, and calls SetCapture to set up new window to capture window. After ->xxxSBLoop returns, the main window reference is lifted. > because this is the only reference of the main window, this release causes a complete release of the main window object, and the xxxFreeWindow function executes > > because the FNID of the main window object has been changed, xxxFreeWindow function is executed. Line process will return to user state again - > user state sends WM cancellmode message to new window - > system processes WM cancellmode message, releases sbtrack - > process returns to kernel to continue executing xxxsbtrackinit function, and finally releases sbtrack - > repeatedly releases sbtrack!

It's worth noting that in the above process, it has nothing to do with sysshadow window, and naturally it doesn't need hook fninlpcreatetruecallback.

Let's see the specific code implementation:

First of all, let's set the call back to OK. Here we use fs to get PEB directly:

Create main window and scrollbar:


    wcex.cbSize = sizeof(WNDCLASSEX); = CS_HREDRAW | CS_VREDRAW;

    wcex.lpfnWndProc = DefWindowProc;

    wcex.cbClsExtra = 0;

    wcex.cbWndExtra = 4;

    wcex.hInstance = hInstance;

    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CVE8453));

    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);

    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    wcex.lpszMenuName = NULL;

    wcex.lpszClassName = L"WNDCLASSMAIN";

    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));


    hMainWND = CreateWindowW(L"WNDCLASSMAIN", L"CVE", WS_DISABLED , 2, 2, 4, 3,nullptr, nullptr, hInstance, nullptr);

    hSBWND = CreateWindowEx(0, L"ScrollBar", L"SB", WS_CHILD | WS_VISIBLE | SBS_HORZ, 0, 0, 3, 3, hMainWND, NULL, hInstance, NULL);

Then send the WM? Lbuttondown message:


    SendMessage(hSBWND, WM_LBUTTONDOWN, 0, 0x00020002);

This causes the system to initialize sbtrack and start the loop. This causes the system to call back fndword:

void fnDWORDCallBack(PDWORD msg) {

    if (*msg) {

        HWND hCurrentDestroyWND = (HWND)*((DWORD*)(*msg));

        memset(ClassName, 0, 0x10);

        GetClassNameA(hCurrentDestroyWND, ClassName, 0xF);

        if (!strcmp(ClassName, "ScrollBar")) {

             if (bMSGSENT) {

                 bMSGSENT = FALSE;







Since the DWORD callback will be executed many times during the operation, we add a global variable bmsgsent to control it. When the system executes destroywindow, it will call back to the user hook because the extended bytes have been reserved:

void fnClientFreeWindowClassExtraBytesCallBack(PDWORD msg) {

    if ((HWND)*(msg + 3) == hMainWND) {

        hSBWNDnew = CreateWindowEx(0, L"ScrollBar", L"SB", SB_HORZ, 0,0, 0, 0, NULL, NULL, NULL, NULL);

        SetWindowFNID(hMainWND, 0x2A1);





We set fnid directly in the fnclientfreewindowclassextrabytes callback. Since there is also a capture window check later, we create the window together and set it as a capture window. When the process returns to the system, it finds that the capture window has changed, exits the xxxsbtrackloop function and starts to release the sbtrack memory space. When the reference to the main window is released, it will cause the call to xxxfreewindow to release the main window memory object. Since we have changed fnid, we will return to the user state again. At this time, the message is 0x70:

So in fndword, judge the message:

if ((*(msg + 1) == 0x70) && (hCurrentDestroyWND == hMainWND)) {

        SendMessage(hSBWNDnew, WM_CANCELMODE, 0, 0);



WM ﹣ cancellmode will cause sbtrack to be released. After returning from user status, xxxsbtrack will continue to release sbtrack, which will cause repeated release!

Finally: Thank you very much for the help of leeqwind! In the analysis process to a great help! Thanks again! Highly recommended blog:

Appendix 1: 360 safety monitoring and response center

360 security monitoring and response center is a network security service platform established by 360 to serve the government and enterprise organizations. It aims to provide the government and enterprise organizations with early warning, notification, disposal suggestions, technical analysis and 360 security product solutions for emergencies. Emergency network security incidents include but are not limited to: security vulnerabilities, trojan virus, information disclosure, hacker activities, attack organizations, etc.

360 security monitoring and response center has both security monitoring and response capabilities: combining 360 security big data monitoring capabilities and massive threat intelligence analysis capabilities, the center can monitor and capture all kinds of unexpected network security events around the clock and in all directions; at the same time, based on more than 10 years of experience in providing security services and emergency response for tens of thousands of large-scale government and enterprise institutions, the center can In the first time, it can provide effective measures and emergency response plan for the government and enterprise to deal with network security emergencies.

In the attack event of wannacry in May 2017, 360 security monitoring and response center issued 9 security early warning notices, 7 Security restoration guidelines and 6 professional technical tools within 72 hours to help and guide more than 100000 domestic enterprises to deal with the crisis.

A-team is a professional technical research team subordinate to 360 security monitoring and response center. It mainly focuses on the research of Web penetration and apt attack and defense technology, and continues to carry out the pre research of forward-looking attack and defense tools to detect more unknown threats and emerging threats in advance. The technical research of A-Team starts with the underlying principle and protocol implementation, which can deeply restore the technical essence of attack and defense.

360 A-Team is a pure technical research team affiliated to 360 safety monitoring and response center under 360 enterprise security group.