Принудительное завершение сторонних процессов Windows

принудительно завершить сторонний процесс Windows

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

Еще по теме: Как убить процесс системы обнаружения атак (EDR)

В качестве «подопытных кроликов» возьмем браузер Firefox, антивирусный комплекс ESET NOD32 Smart Security и программа защиты от 0day-угроз HitmanPro.Alert, которые будут работать в Windows 10 LTSB 1809. Все приложения последних версий, скачаны с официальных сайтов и трудятся на полную мощность — хоть некоторые и в пробных режимах. Разрядность как ОС, так и приложений будет x64.

Подготовка

Работать мы будем с процессами и потоками, поэтому сначала нужно написать необходимые вспомогательные функции. Кроме того, нам понадобится функция, повышающая наши привилегии в системе до отладочных (SE_DEBUG_NAME). Получать мы их будем стандартным образом, используя функции OpenProcessToken и LookupPrivilegeValue.

Во всех экспериментах я использовал свою собственную библиотеку для работы с WinAPI по хешам имен API-функций, так что, вероятно, это повлияло на взаимодействие с защитными решениями.
Для получения отладочных привилегий вызовем эту функцию таким образом:
Для своего личного удобства работу с процессами я разделил на две функции: одна будет получать PID по имени процесса, другая — получать хендл процесса по его PID. Конечно, можно было бы сделать большую функцию, которая сразу бы давала хендл процесса по имени, но это не всегда удобно, потому что порой требуется просто получить только PID.

PID (process identifier) — это идентификатор процесса, который выступает контейнером для потоков. В свою очередь, у потоков тоже есть идентификатор, который называется TID (thread identifier). Зная PID и TID, можно получить их хендлы, чтобы потом работать с потоками и процессами.

Идентификатор процесса мы получим при помощи функций CreateToolhelp32Snapshot (создадим снимок активных процессов в системе), далее будем перебирать и сравнивать процессы с нужным именем, функциями Process32First и Process32Next.

Процессы можно перечислять и другими методами, например использовать для этого функцию Process Status Helper (PSAPI) K32EnumProcesses или недокументированную функцию ZwQuerySystemInformation. Чтобы прокачать свои навыки работы с Windows, вы можете самостоятельно реализовать эти методы и посмотреть, как они работают.

Чтобы получить PID процесса firefox.exe, функцию надо вызвать таким образом:

Осталась маленькая функция получения хендла. Обратите внимание: она позволяет задать права доступа к нужному процессу.

Если функция отрабатывает успешно, она возвращает хендл процесса, если нет — FALSE. Вызывается она таким образом:

В примере выше мы получаем хендл с правами PROCESS_ALL_ACCESS.

Способы завершения процессов

Сначала поработаем с процессами, а потом с потоками. Я буду писать маленькие функции, которые демонстрируют применение различных методов для завершения процессов и потоков. Обратите внимание — использовать будем только необходимые права доступа для процессов, потому что не каждый процесс позволит открыть себя с правами PROCESS_ALL_ACCESS, особенно это касается защитных решений.

Думаю, первое, что приходит в голову, — это применить функцию NtTerminateProcess.

Разумеется, ESET NOD32 Smart Security и HitmanPro.Alert легко противостоят такому простому трюку и выводят сообщение ERROR_ACCESS_DENIED при попытке их завершения. Зато браузер Firefox с удовольствием закрывается.

Следующий способ закрыть процесс — создать поток в интересующем нас процессе при помощи функции CreateRemoteThread и запустить этим потоком функцию ExitProcess. Вот код функции:

Как видно из кода, вначале мы получаем PID процесса с правами PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION (лишние права не берем), далее получаем адрес функции ExitProcess из библиотеки kernel32.dll и, наконец, передаем его в функцию CreateRemoteThread. Firefox закрывается, а защитные решения показывают стойкость к этому приему.

Следующий способ будет манипулировать с заданиями (job) при помощи функций CreateJobObject → AssignProcessToJobObject → TerminateJobObject. Сначала код, потом я расскажу, что он делает.

Итак, сначала мы создаем объект задания функцией CreateJobObjectA. Объект задания — это такой объект ядра, который позволяет работать с группой процессов. Ну а в данном случае группа процессов будет состоять из одного процесса. Далее функцией AssignProcessToJobObject мы связываем наш процесс с созданным объектом задания.

Функцией TerminateJobObject мы можем завершить все процессы, которые связаны с объектом задания (в нашем случае один процесс). Результат выполнения этой подпрограммы таков: NOD32 успешно выдержал эту атаку, браузер Firefox закрылся, и также закрылся процесс HitmanPro.Alert.

Переходим к следующему способу завершения процессов: в этот раз мы притворимся отладчиком!

Здесь мы создаем объект отладки, используя функцию NtCreateDebugObject. Чтобы понимать, что происходит, остановимся на ней немного подробнее. Вот ее прототип:

Параметр DebugObjectHandle — это хендл объекта отладки, который мы передаем по ссылке. Далее идет маска доступов, которую мы выставляем в 0x2, что значит DEBUG_OBJECT_PROCESSASSIGN, третье поле атрибутов оставляем пустым, а четвертое ставим в 0x1 — это значит KillProcessOnExit.

Теперь присоединяем созданный объект отладки к процессу функцией NtDebugActiveProcess. Если после этого закрыть хендл, процесс должен быть завершен операционной системой. Хендл закрываем как всегда — CloseHandle. После этого подопытный Firefox закрывается без проблем, как и HitmanPro.Alert. Но NOD32 по-прежнему выдерживает наш натиск.

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

Тут все просто: при помощи функции VirtualAllocEx мы пытаемся занять всю доступную память в приложении с флагом PAGE_READONLY, то есть доступной только для чтения. От этих действий Firefox зависает и падает операционная система, а защитные программы продолжают работать и не позволяют разрушить себя таким образом.

Еще по теме: Внедрение кода в чужое приложение с помощью Frida

Следующий способ похож на предыдущий. Изменим атрибуты доступа в памяти приложения на PAGE_NOACCESS при помощи функции VirtualQueryEx → VirtualProtectEx. Код:

Здесь мы сначала в цикле получаем нужную информацию функцией VirtualQueryEx, а потом меняем атрибут защиты региона памяти приложения на PAGE_NOACCESS функцией VirtualProtectEx. Несмотря на схожесть с предыдущим методом, этот подход завершает одно из защитных решений — HitmanPro.Alert и браузер. NOD32 остается непоколебим.

Следующий метод будет использовать функцию DuplicateHandle с параметром DUPLICATE_CLOSE_SOURCE, чтобы закрыть все хендлы процесса и вызвать в нем ошибки.

После того как мы пройдемся функцией DuplicateHandle с параметром DUPLICATE_CLOSE_SOURCE по 10 000 хендлов, Firefox упадет, а защитные программы не пострадают.

Итак, мы рассмотрели способы воздействия на сами процессы по их PID. Теперь перейдем непосредственно к потокам.

Способы завершения потоков

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

При помощи этой функции мы будем взаимодействовать с потоками необходимых нам процессов.

Итак, первый способ завершения потоков очень похож на тот, который мы использовали с процессами. Это открытие тредов при помощи функции OpenThread с параметром THREAD_SET_CONTEXT. Далее идет получение адреса ExitProcess и передача его в функцию QueueUserAPC, чтобы она попала в очередь потока.

Похожий способ был с процессами, только использовалась функция CreateRemoteThread. Функция QueueUserAPC позволяет выполнять код в адресном пространстве нужного процесса, в контексте его потока. Код реализации простой:

Я уже думал, что NOD32 SS нам не удастся сломить ничем, но здесь он дрогнул. У нас все-таки получилось разрушить его потоки, вызвать зависание и дальнейшее аварийное завершение. Что интересно, HitmanPro.Alert выдержал эту атаку, ну а Firefox, конечно, рухнул.

Переходим к следующему способу. Он проще: будем просто открывать треды процессов и пытаться завершить их при помощи TerminateThread:

Способ простой и не очень эффективный, особенно против серьезных программ: таким образом удалось убить только Firefox, остальные приложения выдержали атаку.

И последний способ, который мы рассмотрим, — это попытка сменить контекст потока (функция SetThreadContext) с прыжком в нулевые данные. Это должно вызвать ошибку и аварийное завершение приложения.

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

Заключение

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

Еще по теме: Лучшие программы для реверс-инжиниринга

ВКонтакте
OK
Telegram
WhatsApp
Viber

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *