Не е тайна, че някои от процесите на операционната система Windows се считат за "критични" - ако прекратите един от тях, операционната система може да попадне в синия екран на смъртта и да се рестартира. Това свойство на операционната система често се използва от авторите на вируси: ако направите своя процес критичен, тогава няма да е възможно да го завършите просто така. В днешната статия ще ви кажа как да създавате такива вечни процеси и как правилно да ги убивате, ако е необходимо, и без да се срива Windows.
Факт е, че е почти невъзможно да се организира добра защита на процеса от прекратяване, когато потребителят работи под администратора. Има различни методи, но те изобщо не водят до истинска защита. Например, можете да използвате драйвери за режим на ядрото, но в 64-битовите операционни системи не е толкова лесно да преодолеете механизма за защита на корекцията на ядрото. Остава да прибегнем до шаманизма с тамбури, за тях ще говорим по-късно.
Съдържание
Как да създадете критичен процес на Windows
Нека видим как да създадете критичен процес в Windows, как да разберете, че даден процес е критичен и как да го убиете, без да напускате системата в BSOD. Строго погледнато, вие и аз няма да създадем процес, а критичен поток. В крайна сметка процесът в Windows е нещо като контейнер за нишки, в който се изпълнява самият код.
Целият код, който ще предоставя в статията, силно се препоръчва да се изпълнява само във виртуална машина, тъй като прекратяването на критичен процес ще доведе до повреда в цялата система и системата ще влезе в синия екран на смъртта с кода CRITICAL_PROCESS_DIED и възможна загуба на данни. Проведох всички тези експерименти виртуалноVirtualBox и използвайте Windows 10 LTSB x64 като хост.
Има няколко начина да ни помогнете да създадем критичен процес. Всички методи се основават на манипулирането на NTAPI (Native Windows API) извиквания, най-„ниското ниво“, което може да се извърши в потребителски режим. Тези функции се експортират от ntoskrnl.exe. Чрез обвивката на ntdll.dll ще можем да получим техните адреси и да ги извикаме.
RtlSetProcessIsCritical
Първата Native API функция, която ще ни помогне да маркираме процес като критичен, е RtlSetProcessIsCritical. Прототипът му изглежда така:
12345678 | NTSYSAPINTSTATUSSTDAPIVCALLTYPERtlsetprocessiscritical( IN BOOLEAN NewValue, OUT PBOOLEAN OldValue OPTIONAL, IN BOOLEAN CheckFlag); |
За да работи тази функция, привилегията SeDebugPrivilege трябва да бъде получена преди да я извикате. Това може да стане чрез "обикновени" WinAPI функции.
1234567891011121314151617181920212223 | BOOL setPrivileges(LPCTSTR szPrivName){ TOKEN_PRIVILEGES tp = { 0 }; HANDLE hToken = 0; tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) std::cout |
Извикването на функцията за получаване на SE_DEBUG_NAME ще бъде:
1 | setPrivileges(SE_DEBUG_NAME); |
Друг начин да го получите е собствената функция RtlAdjustPrivilege: трябва да му предадете стойността 20 (SE_DEBUG_NAME) като първи параметър. Разбира се, преди да извикате тази функция, нейният адрес трябва да бъде получен динамично от ntdll.dll. Обаждането ще изглежда така:
123 | PBOOLEAN pbEn; RtlAdjustPrivilege(20, TRUE, FALSE, pbEn); |
И така, привилегията е получена, нека започнем да изпълняваме основнатафункционалност.
123456789 | typedef NTSTATUS(NTAPI *pRtlSetProcessIsCritical)(BOOLEAN bNewValue, BOOLEAN *pbOldValue, BOOLEAN CheckFlag); bool set_proc_critical(){ pRtlSetProcessIsCritical RtlSetProcessIsCritical = (pRtlSetProcessIsCritical)GetProcAddress(извиквания към loadlibrary((“ntdll.dll”)), “RtlSetProcessIsCritical”); if (NT_SUCCESS(RtlSetProcessIsCritical(TRUE, 0, FALSE))) върне TRUE; иначе върне FALSE;} |
Тук всичко е ясно: получаваме функцията RtlSetProcessIsCritical чрез динамично свързване с помощта на GetProcAddress/извиквания към loadlibrary директно от ntdll.dll. Най-интересният ред код за нас е този
1 | ако (NT_SUCCESS(RtlSetProcessIsCritical(TRUE, 0, FALSE))) върне TRUE; |
Тук предаваме TRUE, за да кажем на функцията RtlSetProcessIsCritical да направи процеса критичен. В същото време проверяваме върнатата му стойност и ако успеем, нашата функция връща TRUE. Ако извикате RtlSetProcessIsCritical с параметъра FALSE, процесът вече няма да бъде критичен.
Нека копаем малко по-дълбоко и да разберем как точно работи функцията RtlSetProcessIsCritical. Той извиква функцията NtSetInformationProcess от Native API, която осъществява достъп до PEB на процеса и променя полето ThreadBreakOnTermination в него — тя е отговорна за това операционната система да счита нашия процес за критичен.
Блокът на средата на процеса (Process Environment Block, PEB) се запълва от зареждащия механизъм на операционната система, намира се в адресното пространство на процеса и може да бъде модифициран от потребителски режим. Той съдържа много полета — например от тук можете да намерите информация за текущия модул, за средата, за изтеглените модули. PEB структурата може да бъде получена, като се свържете директно с нея на fs:[30h] за x86 системи иgs:[60h] за x64.
Така преминаваме към втория метод, който ще ни покаже създаването на критични процеси.
NtSetInformationProcess
NtSetInformationProcess е собствена функция, която ще ни помогне да променим стойността на полето ThreadBreakOnTermination в PEB на тази, от която се нуждаем. Адресът на тази функция трябва да бъде получен динамично от ntdll.dll, както вече направихме с RtlSetProcessIsCritical. Този метод е универсален, защото с помощта на тази функция гарантираме съвместимост с по-стари версии на Windows: RtlSetProcessIsCritical се появи само в Windows 8.
Изпълнението ще изглежда по следния начин.
123456789101112131415 | typedef NTSTATUS(WINAPI *pNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); pNtSetInformationProcess NtSetInformationProcess = (pNtSetInformationProcess)GetProcAddress(извиква до loadlibrary((“ntdll.dll”)), “NtSetInformationProcess”); bool set_proc_critical(HANDLE hProc){ ULONG брой = 1; if (NT_SUCCESS(NtSetInformationProcess(hProc, 0x1D, // ThreadBreakOnTermination в структурата PROCESSINFOCLASS & count, sizeof(ULONG)))) return TRUE; иначе върне FALSE;} |
Във функцията NtSetInformationProcess предаваме манипулатора на процеса, който ще направим критичен, и числото 0x1D, което означава ThreadBreakOnTermination в структурата PROCESSINFOCLASS.
Проверка на критичността на процеса
Сега, като разберем как операционната система определя състоянието на нашите процеси и знаем точно кои WinAPI и NTAPI функции използва, можем лесно да определим дали даден процес е критичен или не. Както обикновено, ще използваме няколко метода наведнъж.
123456789101112 | BOOL check_critical(HANDLE hProc){ ULONG count = 0; if(NT_SUCCESS(NtQueryInformationProcess(hProc, 0x1D, // ThreadBreakOnTermination в структурата PROCESSINFOCLASS &count, sizeof(ULONG), NULL)) && count) връща TRUE; иначе върне FALSE;} |
Тук манипулаторът на процеса, който ни интересува, и полето в структурата PROCESSINFOCLASS, което трябва да се провери, се предават на функцията NtQueryInformationProcess. След като изпълним функцията NtQueryInformationProcess, проверяваме променливата count, която ще стане равна на единица, ако процесът е критичен.
В допълнение към функцията NtQueryInformationProcess има специална функция, създадена специално за нашите нужди, IsProcessCritical. Нейният прототип изглежда така.
1 | BOOL IsProcessCritical(HANDLE hProcess, PBOOL Critical); |
Пример за използване на тази функция:
1234567 | PBOOL тест = FALSE; if(IsProcessCritical(GetCurrentProcess(), test)){ if(test) std::cout |
Функцията IsProcessCritical ще промени тестовата променлива на TRUE, ако процесът е критичен, или на FALSE, ако не е.
Изводи
В тази статия се опитах да кажа какви са критичните процеси, как операционната система реагира на тяхното завършване. Научихме също как да премахнем флага за критичност от процес, за да го прекратим безболезнено, и как да проверим програмно дали е критичен. Надявам се, че няма да използвате получената информация за деструктивни цели. Е, с изключение на шеги с приятели!