Итак, давай вспомним, как происходит стандартная инъекция шелл‑кода с последующим его выполнением.
- Получение дескриптора процесса (
OpenProcess\
).NtOpenProcess - Выделение памяти для полезной нагрузки (
VirtualAllocEx\
).NtMapViewOfSection - Запись полезной нагрузки в эту память (
WriteProcessMemory\
).Ghost Writing - Выполнение шелл‑кода (
CreateRemoteThread\
).NtQueueApcThread
Эта последовательность хорошо известна всем средствам EDR, и если какое‑то ПО ее реализует, то сразу будет красный флаг и завершение процесса.
Нельзя ли все это написать таким образом, чтобы действия выполнялись те же, но без прямого использования перечисленных функций WinAPI? С первыми шагами такое проделать можно, но с выполнением шелл‑кода все не так просто. Прямой вызов функций CreateRemoteThread\
даст алерт EDR с вероятностью 100%.
Словом, чтобы обвести защиту вокруг пальца, нам надо сломать эту последовательность. Например, почему бы не перехватить какие‑нибудь вызовы API в стороннем приложении, в экспортируемой функции DLL и потом заставить эту функцию работать на нас?
warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Например, можно пропатчить функции работы с сетью какого‑нибудь легитимного ПО, которое и так работает с сетью, и использовать их для связи со своими сетевыми ресурсами! В этом и состоит смысл техники Threadless Injection — пропатчить экспортные функции используемой процессом динамической библиотеки, чтобы при их вызове запускался наш код. По шагам это выглядит примерно вот так:
- Найти область памяти code cave, которая сможет вместить наш шелл‑код и трамплин.
- Записать шелл‑код и трамплин в эту память.
- Пропатчить экспортируемую функцию DLL, настроив ее на запуск нашего кода.
- Подождать вызова этой функции, чтобы шелл‑код выполнился.
Но в динамических библиотеках могут быть сотни и тысячи функций, и не факт, что рандомно выбранная нам подойдет. Ведь нет никаких гарантий, что она будет вызвана в разумное для нас время или вообще будет вызвана.
Чтобы решить этот вопрос, нужно провести небольшое исследование ПО, в котором мы собираемся реализовывать такой перехват экспортной функции. В идеальном случае нужно найти приложение, которое вызывает какие‑то функции DLL регулярно: например, обращается к своему временному файлу на диске и записывает в него промежуточные результаты своей работы или проверяет доступность своих серверов в сети, вызывая соответствующие API с определенным промежутком времени. Если найти такие функции, то можно быть уверенным, что вызов точно произойдет.
Но не следует злоупотреблять этим правилом: если приложение вызывает API слишком часто (например, несколько раз в секунду) и ты захочешь перехватить вызов, то неизбежны разные глюки.
Чтобы провести подобное исследование, воспользуемся программой API Monitor. В этой же программе мы сможем увидеть, как в реальном времени происходит вызов WinAPI, какие действия в интересующей нас программе на это влияют. Кроме того, можно увидеть, какие DLL прицеплены к процессу и какие API они реализуют (то есть это не просто список WinAPI непонятно откуда). Исходя из данных мониторинга, мы должны решить для себя, какую функцию из экспорта используемой библиотеки перехватывать.
После того как подопытная программа исследована и нужные WinAPI выявлены, можно начинать кодить.
Кодим
В начале статьи мы обозначили шаги, которые нужно сделать для реализации Threadless Injection, теперь пришло время реализовать каждый шаг в коде.