📜 ⬆️ ⬇️

How “Russian hackers” tried to learn about new sanctions against Russia: we study CVE-2015-1701

This zero-day vulnerability in Windows became known on April 20, when FireEye and Bloomberg reported an unsuccessful cyber attack on a government department of a foreign country, which had discussed sanctions against Russia with the United States. He was accused of “Russian hackers” from the APT28 grouping in FireEye of involvement in the deed, as well as attempts at hacking by the secret services of NATO, the state bodies of Georgia, Poland, and Hungary.

The attack was implemented using previously unknown vulnerabilities CVE-2015-3043 in Adobe Flash and CVE-2015-1701 in Windows. The user was sent via a link to an infected site, where a JavaScript script, using a Flash vulnerability, loaded an executable file into a computer, which, through the CVE-2015-1701 hole in Windows, raised privileges and stole encryption keys.

The Adobe company in a matter of hours eliminated the vulnerability in Flash, but Microsoft was in no hurry and released a patch just the day before. In this article we will tell about the main features of this bug.
')

Valuable gSharedInfo


First, describe some of the structures and mechanisms used to exploit CVE-2015-1701 vulnerabilities. This time, the infamous win32k.sys not without cost, so the first thing to do is to look at the win32k!tagSHAREDINFO , which is answered by the symbol win32k!gSharedInfo , and also on the data type HWND , which is very closely associated with it.



Our gSharedInfo stores pointers to various window-related structures and, best of all, many of these structures are mapped into user space (put into the user-mode, in our opinion), and the corresponding user32!gSharedInfo symbol has user32!gSharedInfo for some time (or whist, either from the seven) became exported.



We are interested in two fields here:


So, the lower 16 bits of the HWND window handle are in fact the index in the gSharedInfo->aheList . For example, if we have a window variable that contains the HWND handle:



and the same in the core:



The wUniq field of the win32k!_HANDLEENTRY contains the top 16 bits of the HWND descriptor and, apparently, serves the simple purpose of separating objects that occupy the same address in a given array at different time intervals. Thus, if an object is freed and later its place is taken, for example, by a new window with wUniq = 0x12 , then according to the old descriptor 0x0011024c it will be impossible to access it.

The bFlags and bType contain various flags and the type of object addressed by the phead field, respectively. In more detail possible values ​​accepted by them can be looked in ReactOS .

We are only interested in one possible bType value bType :

TYPE_WINDOW = 1

means that the object is a window, and the phead field addresses the win32k!tagWND .



Here you can note that the user user32!gSharedInfo->aheList[…].phead stores the address belonging to the kernel. However, if you wish, you can get the address of its user mapping, but this is another story, so for details I refer you to the HWND window handle and the user32!ValidateHWND procedure that returns the tagWND* , or rather to the user32!HMValidateHandle .

The last pOwner field of the win32k!_HANDLEENTRY pOwner structure not considered earlier contains a pointer to win32k!_W32THREAD stream to which the object belongs. Each thread stores this pointer in win32k!_KTHREAD->Win32Thread (why not in _ETHREAD ), and also, which is much more important in our case, in TEB!Win32ThreadInfo .



Using all this information, we can search for windows belonging to the thread of our process and restore their descriptors. To do this, find the element user32!gSharedInfo->aheList[…] , which:


The index of such a structure will be equal to the wUniq 16 bits of the descriptor, and the upper 16 bits will be contained in the wUniq field.

Why not just use user32!FindWindow ? At the moment when we need it, neither the name nor the class will be filled in by the window.

KernelCallbackTable


Another concept to be explained is closely related to the PEB!KernelCallbackTable .



As you can see, there are various callbacks here, but they, of course, not the kernel , but received their name because their client is usually win32k.sys , accessing them when it is necessary to perform an operation in the user space. The call is made via ntdll!KiUserCallbackDispatcher similar to exception scheduling .

In the kernel, the mechanism for calling these callbacks is implemented in the function nt!KeUserModeCallback . The call is made on the kollbek index. Address resolving by index is done already in ntdll!KiUserCallbackDispatcher .

SetWindowLongPtr


Next in line is user32!SetWindowLongPtr , and in fact - its execution in the form of win32k!xxxSetWindowData . We confine ourselves to only one case of interest - with the GWLP_WNDPROC parameter.

win32k!xxxSetWindowData first performs various checks. For example, is the window owned by the process whose thread is trying to set the WndProc , and also whether this window is already destroyed ( FNID_DELETED_BIT bit).

Then the optimization is very important for us.



On the passed WndProc parameter ( value_ in the screenshot), MapClientToServerPfn is MapClientToServerPfn . This simple and extremely useful function maps functions from win32k!gpsi->apfnClientW and win32k!gpsi->apfnClientA to the corresponding functions from win32k!gpsi-> aStoCidPfn :







If such a mapping for the transmitted WndProc possible, then the procedure call can be optimized by referring directly to the implementation of the function in the kernel, for example, win32k!xxxDefWindowProc , without wasting time switching to user mode to call the wrapper, for example, ntdll!NtdllDefWindowProc_A , which user32!DefWindowProcA is an end-to-end export.

As you can see from the screenshot, if the mapping is successful, then the WFSERVERSIDEPROC flag is raised at the window, after which the mapped value is entered in its win32k!tagWND->lpfnWndProc .

Thus, if one of the standard procedures is set up through user32!SetWindowLongPtr , then the corresponding procedure from win32k.sys in kernel mode will actually be executed.

xxxCreateWindowEx


Now consider creating a window. Roughly speaking, the win32k!xxxCreateWindowEx procedure is fully responsible for win32k!xxxCreateWindowEx . First, by calling win32k!HMAllocObject tagWND object is tagWND and information about it is entered into the gSharedInfo->aheList :



Then the window attributes are populated. The whole procedure, even in hex-rays takes a couple of thousand lines, so there is no point or opportunity to dwell on all the actions performed.

Operating option


The question arises: what happens if you call SetWindowLongPtr(hwnd, GWLP_WNDPROC, DefWindowProc) on the window at the moment when it is already created, but it still has the lpfnWndProc field. After all, this field is filled from the class field, in which it is probably already stored mapped by MapClientToServerPfn , if such mapping is possible.

Indeed, there is a possibility of calling SetWindowLongPtr to raise the WFSERVERSIDEPROC flag before the WndProc address is filled with the value from the class field. At the same time, this flag is not reset when the WndProc field is set, as the developers did not anticipate the possibility that it can be installed. There is only the logic of setting the flag for the window, if the corresponding class flag is raised.



However, the probability of setting a flag from a neighboring thread by calling SetWindowLongPtr at run time CreateWindowEx negligible, because you must first find the HWND windows in the user32!gSharedInfo->aheList , then the user32!SetWindowLongPtr -> … -> win32k!xxxSetWindowData chain user32!SetWindowLongPtr -> … -> win32k!xxxSetWindowData should work out the call chain what happens initialization of the tagWND fields in win32k!xxxCreateWindowEx . You can, of course, play with processor affinity and thread priorities. However, for Windows 7 and earlier versions, there is a simple way.

Option for Windows 7


Despite the enormous size of the win32k!xxxCreateWindowEx , all the information we are interested in fits into several hex-rays lines:



If during the registration of a window class, it had a picture for the normal hIcon icon, but was not specified for the small hIconSm icon, then win32k!xxxCreateWindowEx copies the window, or rather, scales it, the window for filling the win32k!tagCLS->spicnSm field win32k!tagCLS->spicnSm . This action is performed by the win32k!xxxCreateClassSmIcon , which delegates the task to one of the above-described user so-called kernel callbacks :



Under the 0x36 number in the table goes user32!_ClientCopyImage . He performs the task.



After copying the icon in win32k!xxxCreateWindowEx , the WndProc window is immediately filled from the WndProc class. Then, apparently, if the WFSERVERSIDEPROC flag WFSERVERSIDEPROC raised in the class, it is also raised for the window.

results


As a result, we get the following. First you need to register a class with the usual icon, but without a small one:





You also need to hook user32!_ClientCopyImage :



It will call SetWindowLongPtr for the newly created window:



After that, at the time of creating the window, the previously installed hook is called.



The window at this moment is already listed in the table, but not yet initialized.





The hook calls SetWindowLongPtr , which raises the bServerSideWindowProc flag in the appropriate window structure.



And on returning from the callback, win32k!xxxCreateWindowEx overwrites lpfnWndProc value from the class field.



Thus, the window function specified during class registration will be executed in the kernel:



Obviously, the simplest thing for which it can be used is the theft of the system token with the subsequent launch of the system shell.





PS A quick inspection of Windows 8.1 showed that in win32k!xxxCreateWindowEx installation of tagWND->lpfnWndProc and the call to win32k!xxxCreateClassSmIcon are in reverse order compared to earlier versions. Thus, the user32!_ClientCopyImage hook user32!_ClientCopyImage will not help.

There is a possibility that the “race condition” still exists and may, with some probability, be exploited in the manner described above with two streams. On this score, nothing more accurate to say now will not work.

Source: https://habr.com/ru/post/257879/


All Articles