
POC для затруднения/обхода аналитиков вредоносного ПО
Последующая работа является PoC, позволяющим вредоносному ПО «привязать» себя к конкретной жертве, чтобы затруднить усилия аналитиков вредоносных программ.
Я не несу ответственности за злонамеренное использование любых идей или кода, содержащихся в этом проекте. Я предоставляю это исследование для дальнейшего обучения специалистов по информационной безопасности, а также для дополнительного обучения/пищи для размышлений аналитиков вредоносных программ, специалистов по обратной разработке и Blue Team в целом.
При первом запуске вредоносной программы на жертве она шифрует AES фактическую полезную нагрузку (RDLL), используя данные окружения этой жертвы. При каждом последующем запуске вредоносная программа собирает те же данные окружения, расшифровывает AES полезную нагрузку, хранящуюся в виде массива байтов внутри программы, и запускает её. Если расшифровка не удаётся или полезная нагрузка не запускается, вредоносная программа удаляет себя сама. Защита от специалистов по обратной разработке и аналитиков вредоносных программ.


Я решил, что проект ещё не завершён, поэтому вернулся и сделал довольно существенную переработку. Оригинальное исследование и методы можно найти здесь.
Основные изменения следующие:
Есть довольно много разных вещей, которые можно взять из исходного кода этого проекта для использования в других местах. Надеюсь, кому-то это будет полезно.
Было несколько недостатков в оригинальном релизе BeatRev, которые я решил попытаться исправить.
Ранее Stage2 был отдельным исполняемым файлом, хранящимся в альтернативном потоке данных (ADS) Stage1. Для достижения AES-шифрования жертвой и последующей расшифровки и выполнения каждый раз при запуске Stage1 он читал ADS, расшифровывал его, записывал обратно в ADS, вызывал CreateProcess, а затем снова шифровал Stage2 и записывал его обратно на диск в ADS. Это было много операций ввода-вывода, и вызов CreateProcess, конечно, был не очень хорош.
Я наткнулся на исследование Стивена Фьюера о Reflective DLL, и оно показалось подходящим. Теперь Stage2 — это RDLL; наше вредоносное ПО/запускатель шелл-кода/что угодно, что мы хотим защитить, можно портировать в формат RDLL и хранить в виде массива байтов внутри Stage1, который затем расшифровывается во время выполнения и выполняется Stage1. Это устраняет все операции ввода-вывода и вызов CreateProcess из версии 1, что является приятным изменением.
Stage1 не имел никаких реальных мер обхода AV; это было сделано намеренно, так как это дополнительная работа и не было основной целью этого исследования. Во время переработки я принял это как дополнительный вызов и добавил хэширование API для удаления функций из таблицы импорта Stage1. Это помогло с обнаружением, и Stage1 имеет показатель обнаружения 4/66 на VirusTotal. Мне было комфортно загружать Stage1, поскольку он уже привязан к исходной машине, на которой он был запущен, и файловая подпись постоянно меняется из-за происходящего AES-шифрования.
Недавно я начал обращать внимание на энтропию как на средство обнаружения вредоносных ПО; чтобы попытаться снизить иначе очень высокую энтропию, которую огромный зашифрованный AES двоичный блок придаёт исполняемому файлу, я изучил интеграцию шелл-кода, хранящегося в виде UUID. Поскольку двоичный файл хранится в строковом представлении, общая энтропия в исполняемом файле ниже. Используя этот метод, энтропия Stage0 теперь составляет ~6,8, а Stage1 ~4,5 (по максимальной шкале 8).
Наконец, интегрировать и создать полный Stage0 — огромная работа из-за всех компонентов, которыми нужно манипулировать. Чтобы упростить это, я создал приложение-конструктор, которое принимает файл шаблона Stage0.c, заглушку Stage1, заглушку Stage2 и файл сырого шелл-кода (это было построено вокруг Stage2 как запускателя шелл-кода, содержащего шелл-код CobaltStrike) и создаёт скомпилированную полезную нагрузку Stage0 для использования на цели.
Код Reflective DLL от Стивена Фьюера содержит некоторые инструкции, специфичные для компилятора Visual Studio; я уверен, что можно портировать эту технику на MingW, но у меня нет навыков для этого. Основная проблема здесь в том, что шелл-код CobaltStrike (без состояния ~265К) должен быть помещён внутрь RDLL и скомпилирован. Чтобы обойти это и красиво интегрировать с остальным процессом, я написал свой Stage2 RDLL, содержащий глобальную переменную — блок памяти размером с шелл-код CS; этот блок памяти размером ~265К содержит небольшой заполнитель, который можно найти в скомпилированном двоичном файле. Код в src/Stage2 уже содержит это.
После компиляции эта заглушка Stage2stub переносится на kali, где может быть выполнено двоичное патчирование, чтобы вставить реальный шелл-код CS в то место памяти, где он должен быть. Это даёт полный Stage2.
Чтобы избежать ранее описанной катастрофы с вводом-выводом и CreateProcess, полный Stage2 также должен быть пропатчен в скомпилированный Stage1 с помощью Stage0; это необходимо для того, чтобы Stage2 мог быть зашифрован один раз на цели, а также чтобы предотвратить хранение Stage2 отдельно на диске. Та же концепция, описанная ранее для Stage2, выполняется Stage0 на цели для сборки конечной полезной нагрузки Stage1. Следует отметить, что функция memmem используется для поиска заполнителя внутри каждой заглушки; эта функция недоступна в Windows, поэтому была использована пользовательская реализация. Спасибо Foxik384 за его код.
Для выполнения двоичного патча мы должны заранее выделить необходимую память; это оказывает эффект накопления, так как Stage1 теперь должен быть достаточно большим, чтобы также содержать Stage2. С добавленным шагом преобразования Stage2 в строку UUID размер Stage2 и Stage1 увеличивается, чтобы вместить его. RDLL Stage2 скомпилированного размера ~290К приводит к полезной нагрузке Stage0 ~1.38М и полезной нагрузке Stage1 ~700К.
Приложение-конструктор поддерживает только создание x64 EXE. Однако при небольшой дополнительной работе теоретически можно сделать Stage0 DLL, а также Stage1, и сделать весь жизненный цикл реализованным как угон DLL вместо автономного исполняемого файла.
Эти инструкции помогут вам начать использовать этот PoC.

Около 6 месяцев назад мне пришло в голову, что хотя я многому научился и много сделал в области вредоносных программ в части обхода AV/EDR, я уделял очень мало времени попыткам обойти или победить обратную разработку/анализ вредоносных программ. На это было несколько веских причин:
Тем не менее это был интересный мысленный эксперимент, и у меня было несколько коллег, которые действительно разбираются в анализе вредоносных программ, с которыми я мог обсудить идеи. Это показалось вызовом совершенно другого масштаба по сравнению с обходом AV/EDR, и я решил попробовать.
Моя первоначальная предпосылка заключалась в том, что вредоносная программа при первом запуске будет каким-то образом «привязывать» себя к этой машине-жертве; любые последующие попытки запуска будут оценивать что-то в целевой среде и сравнивать с совпадением в программе. Если эти два фактора совпадают, она выполняется как ожидалось. Если нет (как в случае, когда образец был передан в песочницу аналитика), вредоносная программа удаляет себя (снова опираясь на работу LloydLabs и его delete-self-poc).
Этот «ключ» должен быть чем-то «уникальным» для компьютера жертвы. В идеале это будет комбинация нескольких фрагментов информации, а затем дальнейшая обфускация. Например, мы можем собрать имя хоста компьютера и объём установленной оперативной памяти; эти два значения можно объединить (например, Client018192MB), а затем хэшировать с помощью пользовательской функции, чтобы получить число (например, 5343823956).
Есть масса вариантов, какую информацию собирать, но следует подумать о том, какие значения Blue Teamer может легко подделать; например, MAC-адрес может показаться привлекательным «уникальным» идентификатором жертвы, однако MAC-адреса можно легко установить вручную, чтобы специалист по обратной разработке мог сопоставить свою песочницу с оригинальной жертвой. В идеале выбранные и перечисленные значения должны быть такими, которые трудно воспроизвести в своей среде.
С помощью магии самоудаления вредоносная программа могла бы прочитать себя в буфер, найти переменную-заполнитель и заменить её этим числом, удалить себя, а затем записать модифицированную программу обратно на диск в то же место. В сочетании с оператором if/else в Main при следующем запуске программа обнаружит, что она уже запускалась ранее, и снова соберёт имя хоста и объём RAM, чтобы получить хэшированное число. Затем оно будет сравниваться с числом, сохранённым в программе во время первого запуска (5343823956). Если совпадёт (как в случае, если программа работает на той же машине, что и изначально), она выполняется как ожидалось, но если возвращается другое значение, она снова вызывает функцию самоудаления, чтобы удалить себя с диска и защитить автора от аналитика.
Это казалось отличной идеей в теории, пока я не поговорил с коллегой, имеющим реальный опыт анализа вредоносных программ и обратной разработки. Мне сказали, что специалист по обратной разработке сможет увидеть условный оператор в программе (if ValueFromFirstRun != GetHostnameAndRAM()), и, поскольку ожидаемое значение жёстко закодировано на одной стороне условного оператора, просто изменить регистры, чтобы они содержали ожидаемое значение, тем самым полностью обойдя весь механизм защиты.
Это новое знание полностью разрушило мысленный эксперимент, и, поскольку у меня изначально не было реальной потребности в такой возможности, на этом проект остановился примерно на 6 месяцев.
Этот проект всплывал несколько раз за прошедшие 6 месяцев, но каждый раз это был не более чем мимолётный мысль, так как я не получил новых знаний о реверсе/анализе вредоносных программ и снова не имел потребности в такой возможности. Несколько дней назад идея снова возникла, и хотя ни один из этих факторов не изменился, думаю, у меня было немного больше знаний, и на этот раз я не смог отпустить эту идею.
Имея в виду вышеупомянутую проблему с жёстко закодированными значениями, я в итоге решил использовать многоступенчатую архитектуру. Я буду называть их Stage0, Stage1 и Stage2.
Stage0: Настройка. Запускается при первоначальном заражении и затем удаляется. Stage1: Запускатель. Запускается при каждом последующем выполнении вредоносной программы. Stage2: Полезная нагрузка. Вредоносная программа, которую вы хотите защитить. Создаёт процесс и внедряет шелл-код для получения Beacon.
Stage0 — это свежий исполняемый файл, доставленный атакующим на цель. Он содержит Stage1 и Stage2 в виде зашифрованных AES-массивов байтов (используется библиотека tiny-AES-c); это сделано для защиты программы при передаче или на случай, если защитник каким-то образом получит копию Stage0 (чего не должно происходить). Ключ AES и IV содержатся внутри Stage0, поэтому в реальности это не защитит Stage1 или Stage2 от компетентного Blue Teamer.
Stage0 выполняет следующие действия:
По завершении этой последовательности действий Stage0 завершается. Поскольку он был удалён с диска на шаге 2 и больше не выполняется в памяти, Stage0 фактически исчезает; без предварительного знания этой техники остальная часть жизненного цикла вредоносной программы будет гораздо более запутанной, чем она уже есть.
На шаге 4 собираются имя процессора и Microsoft ProductID; ProductID извлекается из реестра, и это значение можно изменить вручную, что даёт Blue Teamer лёгкую возможность сопоставить свою песочницу с целевой средой. В зависимости от того, какая информация окружения собирается, это может стать проще или сложнее.
Stage1 был сброшен Stage0 и находится в том же самом месте, что и Stage0 (включая имя). Stage2 хранится в виде ADS Stage1. Когда атакующий/механизм персистентности впоследствии запускает вредоносную программу, они выполняют Stage1.
Stage1 выполняет следующие действия:
Обратите внимание, что Stage2 ДОЛЖЕН завершиться, чтобы его можно было перезаписать; трюк с самоудалением, похоже, не работает для файлов, которые уже являются ADS, поскольку техника самоудаления основана на переименовании основного потока данных исполняемого файла. В идеале Stage2 будет исполняемым файлом для внедрения или создания+внедрения.
Есть два момента, когда Stage1 может обнаружить, что он запущен не с той же жертвы, и удалить себя/ Stage2, чтобы защитить субъекта угрозы. Первый — это проверка заголовка исполняемого файла после расшифровки Stage2 с использованием собранной информации окружения; теоретически этот шаг может быть обойдён специалистом по обратной разработке, но это хорошая первая проверка. Вторая точка защиты — результат вызова CreateProcess: если он не удаётся из-за того, что Stage2 не был правильно расшифрован, вредоносная программа аналогичным образом удаляется. Результат этого вызова также может быть изменён, чтобы предотвратить удаление специалистом по обратной разработке, однако это не меняет того факта, что Stage2 зашифрован и недоступен.
Stage2 — это сердце и основа цепочки вредоносного ПО; это полноценный запускатель шелл-кода / сама вредоносная программа. Шифруя и защищая его таким образом, мы значительно лучше скрываем действия конечной вредоносной программы и защищаем её от специалистов по обратной разработке и аналитиков. При разработке я использовал один из своих существующих запускателей шелл-кода, содержащих шелл-код CobaltStrike, но это может быть что угодно, что атакующий хочет запустить и защитить.
Итак, что же на самом деле достигается с помощью такого жизненного цикла вредоносного ПО? Есть несколько интересных особенностей, о которых стоит поговорить.
Альтернативные потоки данных — это функция, уникальная для файловых систем NTFS; это означает, что большинство способов передачи вредоносной программы после первоначального заражения удалят и потеряют Stage2, поскольку он является ADS Stage1. При передаче образца необходимо проявлять особую осторожность, чтобы сохранить Stage2, так как без него многие специалисты по обратной разработке и аналитики будут очень сбиты с толку относительно того, что происходит. RAR-архивы способны сохранять ADS, а такие инструменты, как 7Z и PeaZip, могут извлекать файлы и их ADS.
Как упоминалось ранее, к тому времени, когда вредоносная программа, использующая этот жизненный цикл, попадает к Blue Teamer, она должна быть на этапе Stage1; Stage0 уже прошёл, а Stage2 уже зашифрован информацией окружения, собранной Stage0. Незнание того, что Stage0 вообще существовал, добавит значительную неопределённость в понимание жизненного цикла и расшифровку Stage2.
Теоретически (потому что, опять же, у меня нет опыта в реверсе) Stage1 можно будет подвергнуть обратной разработке (после того, как Blue Teamer пройдёт через несколько его копий, поскольку он постоянно удаляет себя), и информацию, которую Stage1 собирает из целевой системы, можно будет идентифицировать. При хорошо организованном ответе Blue Team сможет определить жертву, с которой пришло вредоносное ПО, собрать с неё эту информацию и передать её в программу, чтобы она могла быть соответствующим образом преобразована в ключ/IV AES для расшифровки Stage2. Однако здесь много «если», связанных с относительным мастерством специалиста по обратной разработке, а также с доступностью машины жертвы для восстановления этой информации.
Белые списки приложений (Application Whitelisting) значительно затруднят этот жизненный цикл. Stage0/Stage1, возможно, можно будет загрузить как DLL, но я подозреваю, что Stage2 как ADS будет создавать некоторые проблемы. У меня нет среды для тестирования вредоносных программ против AWL, и я не заморачивался переносом всего этого в формат DLL, поэтому не могу сказать. Я уверен, что есть творческие способы обойти эти проблемы.
Я также вполне уверен, что есть более умные способы запуска Stage2, чем сброс на диск и вызов CreateProcess; либо ручное отображение исполняемого файла, либо использование такого инструмента, как Donut, для преобразования его в шелл-код, кажутся разумными идеями.
В процессе разработки я создал приложение-конструктор, которому можно скормить Stage1 и Stage2, чтобы получить работающий Stage0; однако предоставлено оно не будет, но я предоставлю большую часть исходного кода для stage1, так как это та часть, которая будет наиболее видна Blue Teamer. Stage0 будет исключён как упражнение для читателя, а stage2 — это любой автономный исполняемый файл, который вы хотите запустить и защитить. Этот PoC может быть дополнительно исследован в рамках усилий и на усмотрение способных читателей.Я предоставлю скомпилированную копию этого вредоносного ПО как Dropper64.exe. Dropper64.exe скомпилирован для x64. Dropper64.exe — это Stage0; он содержит Stage1 и Stage2. При запуске Stage1 и Stage2 будут сброшены на диск, но НЕ будут выполнены автоматически; вам нужно снова запустить Dropper64.exe (теперь Stage1). Stage2 — это x64-версия calc.exe. Я включаю это для тех, кто занимается защитой (Blue Teamers) и хочет взглянуть на это, но имейте в виду, что в сценарии реагирования на инциденты в 99% случаев вы будете получать Stage1/Stage2, Stage0 уже будет удалён.
Это был интересный побочный проект, который съел целые выходные. Уверен, он был бы гораздо более продвинутым/завершённым, если бы у меня был опыт работы с отладчиком и дизассемблером, но используешь то, что есть. Мне не терпится услышать мнение как специалистов по защите (Blue Teamers), так и других разработчиков вредоносного ПО. Уверен, что я изобрёл велосипед с излишней сложностью, учитывая то, что делают настоящие APT-группировки, но я кое-чему научился. Спасибо за чтение!