Малварь как искусство LayeredSyscall - Обход EDR при помощи аппаратных точек останова


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 170
Репутация
8 303
Это исследование и инструмент предназначены для того, чтобы по-новому взглянуть на использование косвенных системных вызовов или других методов, таких как обфускация, которые требуют легитимного стека для работы без обнаружения.

Конструирование стека в программе может быть сложным и привести к повреждению, если не делать это осторожно. Этот инструмент позволяет операционной системе генерировать необходимый стек вызовов без особых проблем, при этом потенциально можно использовать любой API Windows. Однако это не означает, что метод обхода будет работать для каждого EDR, так как требуется более тщательное тестирование с различными EDR и методами обнаружения.

Ссылка на инструмент



Далее описание самого исследования.

Если спросить любого исследователя в области наступательной безопасности, как можно обойти системы обнаружения и реагирования на инциденты (EDR), можно получить множество ответов: удаление хуков, прямые системные вызовы, косвенные системные вызовы и т.д.

В этом посте мы рассмотрим другой подход, используя обработчики векторных исключений (VEH) для создания легитимного стека вызовов потока и применения косвенных системных вызовов для обхода пользовательских хуков EDR.

Отказ от ответственности: Данное исследование предназначено только для этических целей. Используйте его ответственно и не применяйте в незаконных целях. Это исключительно для образовательных целей.

Введение​

EDR используют хуки на уровне пользовательского режима, которые обычно размещаются в ntdll.dll или иногда в kernel32.dll, загружаемом в каждый процесс операционной системы Windows. Хуки внедряются двумя основными способами:
  1. Патчинг первых байтов функции для перенаправления (похож на библиотеку Microsoft Detours).
  2. Перезапись адреса функции в таблице IAT библиотеки, использующей эту функцию.
Хуки не ставятся на каждую функцию в целевой библиотеке. В ntdll.dll большинство хуков размещаются в обертках системных вызовов Nt*. Эти хуки часто используются для безопасного перенаправления выполнения в библиотеку EDR, чтобы исследовать параметры и определить, выполняет ли процесс какие-либо вредоносные действия.

Некоторые популярные методы обхода хуков включают:
  • Перемаппинг ntdll.dll: Доступ к свежей копии ntdll с диска или KnownDll кэша и замена хуковой версии на свежую копию.
  • Прямые системные вызовы: Эмуляция оберток системных вызовов Nt* в своей программе, используя соответствующий SSN и операцию системного вызова.
  • Косвенные системные вызовы: Настройка параметров системного вызова в своей программе и перенаправление выполнения с помощью инструкции jmp к адресу в ntdll.dll, где находится операция системного вызова.
Существуют и другие методы обхода, такие как блокировка загрузки неподписанных dll, блокировка загрузки библиотеки EDR путем мониторинга LdrLoadDll и т.д.

Стратегии обнаружения​

Для обнаружения и предотвращения вышеупомянутых техник обхода могут использоваться следующие стратегии:
  • Обнаружение перемаппинга ntdll.dll: Если в памяти процесса находятся две копии ntdll.dll, это обычно признак подозрительного поведения.
  • Обнаружение прямых системных вызовов: EDR может зарегистрировать callback для инструментирования и проверки, откуда возобновляется выполнение пользовательского кода. Если оно возвращается в процесс, а не в адресное пространство ntdll.dll, это явный признак прямого системного вызова.
  • Обнаружение косвенных системных вызовов: Так как эта техника включает переход в адресное пространство ntdll.dll для выполнения системного вызова, предыдущее обнаружение не сработает. Однако анализ стека вызовов потока покажет аномальное поведение, так как нет легитимных вызовов через различные API Windows, а только процесс к ntdll.dll.
Данное исследование направлено на решение вышеупомянутых стратегий обнаружения.

LayeredSyscall – Обзор​


1722607957045.png


Общая идея заключается в создании легитимного стека вызовов перед выполнением косвенного системного вызова при переключении режимов на режим ядра с поддержкой до 12 аргументов. VEH используется для управления контекстом ЦП без вызова тревог. Исключения не часто считаются вредоносными, что дает доступ к аппаратным точкам останова, которые используются как хуки.

Генерация стека вызовов выполняется системой без необходимости ручных операций или выделения памяти, что позволяет изменять стек вызовов вызовом другого API Windows.

Обработчик VEH #1 – AddHwBp​

Первый обработчик устанавливает аппаратные точки останова на операцию syscall и операцию ret в обертках системных вызовов Nt* в ntdll.dll. Обработчик обрабатывает EXCEPTION_ACCESS_VIOLATION, генерируемое инструментом перед вызовом системного вызова, путем чтения нулевого указателя для создания исключения. Обработчик может извлечь адрес функции Nt* из регистра RCX, который хранит первый аргумент, переданный обертке функции.

1722608111417.png


После получения адреса функции Nt*, мы выполняем сканирование памяти, чтобы найти смещение, где находятся операторы syscall и ret (сразу после оператора syscall). Мы можем сделать это, проверяя, что операторы 0x0F и 0x05 расположены рядом друг с другом, как показано в приведенном ниже коде.

1722608161689.png


Системные вызовы в Windows, как видно на следующем скриншоте, создаются с использованием операторов 0x0F и 0x05. Два байта после начала системного вызова можно найти оператор ret, 0xC3.

1722608202406.png


Аппаратные точки останова устанавливаются с использованием регистров Dr0, Dr1, Dr2 и Dr3, где Dr6 и Dr7 используются для изменения необходимых флагов соответствующего регистра. Обработчик использует Dr0 и Dr1 для установки точки останова на смещении syscall и ret. Как показано в коде ниже, мы активируем их, получая доступ к ExceptionInfo->ContextRecord->Dr0 или Dr1. Мы также устанавливаем последний и второй бит регистра Dr7, чтобы сообщить процессору, что точка останова включена.

1722608254189.png


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

1722608299479.png


Как только исключение возникает, обработчик берет на себя управление и устанавливает точки останова.

1722608334672.png


Учтите, что после возникновения исключения необходимо сдвинуть регистр RIP на количество байтов, достаточное для прохождения оператора, вызвавшего исключение. В данном случае это было 2 байта.

После этого процессор продолжит обработку исключения, и это будет работать как наши хуки. Мы увидим это во втором обработчике ниже.

Обработчик VEH #2 – HandlerHwBp​

Этот обработчик состоит из трех основных частей:
  1. Сохранение контекста и инициация генерации выбранного пользователем стека вызовов.
  2. Корректное возвращение к процессу без сбоев.
  3. Поиск подходящего места для перенаправления выполнения и обхода хука, выполняя косвенный системный вызов.

Часть 1 – Обработка точки останова системного вызова​

Аппаратные точки останова генерируют исключение с кодом EXCEPTION_SINGLE_STEP, которое проверяется для обработки наших точек останова. Сначала проверяется, было ли исключение сгенерировано в начале системного вызова Nt* с помощью члена ExceptionInfo->ExceptionRecord->ExceptionAddress, указывающего на адрес, где было сгенерировано исключение.

1722608427478.png


Мы сохраняем контекст ЦП в момент возникновения исключения. Это позволяет нам получить доступ к аргументам, хранящимся в регистрах RCX, RDX, R8 и R9 согласно соглашению о вызовах Microsoft. Также это позволяет использовать регистр RSP для доступа к остальным аргументам, что будет объяснено далее.

1722608469504.png


После сохранения контекста мы можем изменить регистр RIP, чтобы указать на нашу демонстрационную функцию; в данном случае, мы используем простую функцию MessageBox().

1722608509642.png


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

1722608545926.png


Общая идея заключается в перенаправлении выполнения к безвредному вызову API Windows, затем создании легитимного стека вызовов и перенаправлении на выполнение косвенного системного вызова. Хотя у нас есть хуки на инструкциях syscall и ret, возникает проблема, когда нужно знать, где остановить выполнение для перенаправления на косвенный системный вызов.

Мы используем флаг ловушки (TF), который используется отладчиками для пошагового выполнения. Включить флаг ловушки можно с помощью регистра EFlags. Поскольку у нас уже есть доступ к контексту, мы можем включить его с помощью следующего фрагмента кода.

1722608658785.png


Для генерации легитимного стека вызовов необходимо дождаться выполнения определенного условия системой (например, вызовы должны достичь адресного пространства ntdll.dll, так как большинство системных вызовов Nt* обычно перенаправляются из ntdll.dll). Это гарантирует, что стек вызовов будет выглядеть максимально легитимным для наблюдателя.

Это можно проверить различными способами, но для упрощения можно получить дескриптор ntdll.dll и использовать функцию GetModuleInformation() для получения базового и конечного адресов библиотеки. После этого можно проверить, находится ли адрес исключения, сгенерированный из-за флага ловушки, в пределах этого адресного пространства.

1722608775216.png


Мы используем простую структуру для хранения информации, которая инициализируется в начале работы инструмента.

1722608809101.png


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

Системные вызовы в Windows настраиваются следующим образом:

1722608858390.png


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

1722608907518.png


Это сохраняет стек вызовов неизменным и одновременно имеет исходное состояние аргументов из предполагаемого системного вызова.

Хуки EDR обычно размещаются в виде инструкций jmp в начале или через несколько инструкций после начального адреса системного вызова Nt*.

1722609019981.png


Таким образом, если мы эмулируем функциональность системного вызова в нашем обработчике, а затем изменим регистр RIP на адрес оператора системного вызова, мы сможем эффективно обойти хук EDR без необходимости его трогать.

1722609108608.png


Мы можем продолжить эмуляцию системного вызова перед изменением регистра RIP на оператор системного вызова.

1722609139934.png


Это позволяет избежать использования встроенного ассемблерного кода или доступа к контексту с помощью winapis.

Однако есть нюанс. Некоторые системные вызовы поддерживают менее 4 аргументов, но для поддержки большинства вызовов необходимо поддерживать до 12 аргументов.

Поддержка более 4 аргументов

При создании стека вызовов с использованием API Windows нужно учитывать размер стека, выделяемого каждым из этих API. Это важно, так как согласно соглашению о вызовах Windows, аргументы, превышающие 4, сохраняются в пространстве стека.

Соглашение о вызовах Windows работает следующим образом:

- Первые 4 аргумента сохраняются в регистрах RCX, RDX, R8 и R9
- Выделяются 8 байт для возвратного адреса
- Выделяются еще 4 x 8 байт для сохранения первых 4 аргументов
- Выделяются байты для переменных и других данных.

1722609289895.png


Для дальнейшей информации, посмотрите: .

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

Как показано на скриншоте ниже, в некоторых рамках функций недостаточно места в стеке для хранения более 4 аргументов без его повреждения.

1722609369312.png


Большинство кадров функции выделяют стек в начале функции, используя инструкцию sub rsp, #size.

1722609398844.png


Мы можем найти соответствие этой инструкции, проверив оператор кода, 0xEC8348, и извлечь наибольший байт, что в большинстве случаев даст размер стека.

1722609444588.png


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

1722609482549.png

Мы используем глобальный флаг IsSubRsp, чтобы узнать, выполнили ли мы первый шаг, который приводит нас ко второму шагу: ждем, пока в той же рамке функции, которую мы хотим, произойдет вызов инструкции call.

1722609516390.png


Снова, это можно сделать, проверяя адрес исключения на наличие оператора вызова, 0xE8.

1722609547554.png


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

Предположим, что мы находим подходящую рамку функции, которая содержит необходимый размер стека и выполняет инструкцию вызова. В этом случае можно сохранить остальные аргументы из сохраненного контекста в найденную рамку стека, начиная с 5 x 8 байт после начального значения RSP.

1722609598295.png


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

1722609630682.png


Итак, наши условия изменились на:
  1. Вызовы должны достигать адресного пространства ntdll.dll.
  2. Вызов должен поддерживать соответствующий размер стека.
  3. Вызов должен поддерживать вызов другой функции внутри себя.

Часть 3 – Обработка точки останова ret​

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

Поскольку текущая рамка стека указывает на легитимный стек вызовов API Windows, после выполнения ret выполнение немедленно вернется к нормальному выполнению. Вместо этого можно указать на RSP из сохраненного контекста, что позволит ret извлечь адрес из стека и вернуться к функции, вызвавшей системный вызов Nt*, обходя необходимость выполнения дальнейших вызовов API Windows.

1722609701844.png


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

1722609758535.png


Тестирование инструмента​

Мы предоставили заголовочный файл в нашем инструменте, который необходимо включить для использования оберток функций системных вызовов Nt*. Это было вдохновлено работой , которую можно посмотреть здесь: .

Парсируя прототипы , мы можем сгенерировать заголовочный файл для нужного нам системного вызова.

1722609942331.png


Поскольку SSN системных вызовов меняются с каждой версией Windows, нам также нужно поддерживать динамическое получение SSN для версии Windows, работающей на системе. Поэтому мы включили функцию GetSsnByName(), предоставленную MDSec. Есть различные методы получения SSN, такие как Halo’s gate, инструмент Syswhispers и другие.

Использование

Ниже приведен пример кода, показывающий, как могут использоваться обертки функций. В заголовочный файл в инструменте включены часто используемые функции системных вызовов из ntdll.dll.

1722610092259.png


Результаты​

Анализ стека вызовов​

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

1722610136132.png


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

1722610168546.png


Тестирование на EDR​

Мы решили продемонстрировать эффективность этого инструмента, протестировав его на существующей системе обнаружения и реагирования на инциденты (EDR). В качестве тестовой среды был выбран Sophos Intercept X.

Мы выбрали старую технику Process Hollowing в качестве вредоносного метода для тестирования. Поскольку это широко обнаруживаемая техника, это был хороший выбор для сравнения до и после применения нашей техники.

Наш первоначальный метод Process Hollowing был немедленно обнаружен EDR.

1722610222099.png


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

1722610247341.png



Результаты

Как показано на скриншоте выше, исполняемый файл успешно внедряет пример полезной нагрузки MessageBox без предупреждений от EDR. (Показанное предупреждение относится к предыдущему тесту).

Заключение

Это исследование и инструмент предназначены для того, чтобы по-новому взглянуть на использование косвенных системных вызовов или других методов, таких как обфускация, которые требуют легитимного стека для работы без обнаружения. Конструирование стека в программе может быть сложным и привести к повреждению, если не делать это осторожно. Этот инструмент позволяет операционной системе генерировать необходимый стек вызовов без особых проблем, при этом потенциально можно использовать любой API Windows. Однако это не означает, что метод обхода будет работать для каждого EDR, так как требуется более тщательное тестирование с различными EDR и методами обнаружения.

Ссылка на инструмент



Потенциальные методы обнаружения

В настоящее время для обнаружения этой техники необходимо проверять вредоносно зарегистрированные обработчики исключений в конкретной программе. Другие методы могут включать флаги аномального поведения стека, реализуя эвристику против известных стеков вызовов, созданных API Windows.
 
Автор темы Похожие темы Форум Ответы Дата
X-Shar Технологии создания невидимой малвари 0
X-Shar Технологии создания невидимой малвари 6
X-Shar Технологии создания невидимой малвари 2
X-Shar Технологии создания невидимой малвари 2
X-Shar Технологии создания невидимой малвари 0
X-Shar Технологии создания невидимой малвари 3
X-Shar Технологии создания невидимой малвари 0
D Технологии создания невидимой малвари 0
virt Технологии создания невидимой малвари 2
Верх Низ