Это исследование и инструмент предназначены для того, чтобы по-новому взглянуть на использование косвенных системных вызовов или других методов, таких как обфускация, которые требуют легитимного стека для работы без обнаружения.
Конструирование стека в программе может быть сложным и привести к повреждению, если не делать это осторожно. Этот инструмент позволяет операционной системе генерировать необходимый стек вызовов без особых проблем, при этом потенциально можно использовать любой API Windows. Однако это не означает, что метод обхода будет работать для каждого EDR, так как требуется более тщательное тестирование с различными EDR и методами обнаружения.
Ссылка на инструмент
Далее описание самого исследования.
Если спросить любого исследователя в области наступательной безопасности, как можно обойти системы обнаружения и реагирования на инциденты (EDR), можно получить множество ответов: удаление хуков, прямые системные вызовы, косвенные системные вызовы и т.д.
В этом посте мы рассмотрим другой подход, используя обработчики векторных исключений (VEH) для создания легитимного стека вызовов потока и применения косвенных системных вызовов для обхода пользовательских хуков EDR.
Отказ от ответственности: Данное исследование предназначено только для этических целей. Используйте его ответственно и не применяйте в незаконных целях. Это исключительно для образовательных целей.
Некоторые популярные методы обхода хуков включают:
Общая идея заключается в создании легитимного стека вызовов перед выполнением косвенного системного вызова при переключении режимов на режим ядра с поддержкой до 12 аргументов. VEH используется для управления контекстом ЦП без вызова тревог. Исключения не часто считаются вредоносными, что дает доступ к аппаратным точкам останова, которые используются как хуки.
Генерация стека вызовов выполняется системой без необходимости ручных операций или выделения памяти, что позволяет изменять стек вызовов вызовом другого API Windows.
После получения адреса функции Nt*, мы выполняем сканирование памяти, чтобы найти смещение, где находятся операторы syscall и ret (сразу после оператора syscall). Мы можем сделать это, проверяя, что операторы 0x0F и 0x05 расположены рядом друг с другом, как показано в приведенном ниже коде.
Системные вызовы в Windows, как видно на следующем скриншоте, создаются с использованием операторов 0x0F и 0x05. Два байта после начала системного вызова можно найти оператор ret, 0xC3.
Аппаратные точки останова устанавливаются с использованием регистров Dr0, Dr1, Dr2 и Dr3, где Dr6 и Dr7 используются для изменения необходимых флагов соответствующего регистра. Обработчик использует Dr0 и Dr1 для установки точки останова на смещении syscall и ret. Как показано в коде ниже, мы активируем их, получая доступ к ExceptionInfo->ContextRecord->Dr0 или Dr1. Мы также устанавливаем последний и второй бит регистра Dr7, чтобы сообщить процессору, что точка останова включена.
Как видно на изображении ниже, исключение возникает из-за попытки чтения адреса нулевого указателя.
Как только исключение возникает, обработчик берет на себя управление и устанавливает точки останова.
Учтите, что после возникновения исключения необходимо сдвинуть регистр RIP на количество байтов, достаточное для прохождения оператора, вызвавшего исключение. В данном случае это было 2 байта.
После этого процессор продолжит обработку исключения, и это будет работать как наши хуки. Мы увидим это во втором обработчике ниже.
Мы сохраняем контекст ЦП в момент возникновения исключения. Это позволяет нам получить доступ к аргументам, хранящимся в регистрах RCX, RDX, R8 и R9 согласно соглашению о вызовах Microsoft. Также это позволяет использовать регистр RSP для доступа к остальным аргументам, что будет объяснено далее.
После сохранения контекста мы можем изменить регистр RIP, чтобы указать на нашу демонстрационную функцию; в данном случае, мы используем простую функцию MessageBox().
Демонстрационная функция ниже отвечает за создание легитимного стека вызовов, который нам нужен. Пользователь может изменить эту функцию по своему усмотрению.
Общая идея заключается в перенаправлении выполнения к безвредному вызову API Windows, затем создании легитимного стека вызовов и перенаправлении на выполнение косвенного системного вызова. Хотя у нас есть хуки на инструкциях syscall и ret, возникает проблема, когда нужно знать, где остановить выполнение для перенаправления на косвенный системный вызов.
Мы используем флаг ловушки (TF), который используется отладчиками для пошагового выполнения. Включить флаг ловушки можно с помощью регистра EFlags. Поскольку у нас уже есть доступ к контексту, мы можем включить его с помощью следующего фрагмента кода.
Для генерации легитимного стека вызовов необходимо дождаться выполнения определенного условия системой (например, вызовы должны достичь адресного пространства ntdll.dll, так как большинство системных вызовов Nt* обычно перенаправляются из ntdll.dll). Это гарантирует, что стек вызовов будет выглядеть максимально легитимным для наблюдателя.
Это можно проверить различными способами, но для упрощения можно получить дескриптор ntdll.dll и использовать функцию GetModuleInformation() для получения базового и конечного адресов библиотеки. После этого можно проверить, находится ли адрес исключения, сгенерированный из-за флага ловушки, в пределах этого адресного пространства.
Мы используем простую структуру для хранения информации, которая инициализируется в начале работы инструмента.
Если условия выполнены, мы можем перейти к перенаправлению выполнения на нужный системный вызов. Сначала нам нужно получить сохраненный контекст, который был установлен при прерывании на операторе системного вызова, и настроить системный вызов.
Системные вызовы в Windows настраиваются следующим образом:
Нам нужно получить сохраненный контекст, но перед этим необходимо сохранить текущий указатель стека (RSP) во временную переменную для последующего восстановления. Так как перезапись указателя стека сохраненным значением полностью изменит стек вызовов, что противоречит нашей цели, мы должны сохранить и восстановить текущий указатель стека сразу после копирования.
Это сохраняет стек вызовов неизменным и одновременно имеет исходное состояние аргументов из предполагаемого системного вызова.
Хуки EDR обычно размещаются в виде инструкций jmp в начале или через несколько инструкций после начального адреса системного вызова Nt*.
Таким образом, если мы эмулируем функциональность системного вызова в нашем обработчике, а затем изменим регистр RIP на адрес оператора системного вызова, мы сможем эффективно обойти хук EDR без необходимости его трогать.
Мы можем продолжить эмуляцию системного вызова перед изменением регистра RIP на оператор системного вызова.
Это позволяет избежать использования встроенного ассемблерного кода или доступа к контексту с помощью winapis.
Однако есть нюанс. Некоторые системные вызовы поддерживают менее 4 аргументов, но для поддержки большинства вызовов необходимо поддерживать до 12 аргументов.
Поддержка более 4 аргументов
При создании стека вызовов с использованием API Windows нужно учитывать размер стека, выделяемого каждым из этих API. Это важно, так как согласно соглашению о вызовах Windows, аргументы, превышающие 4, сохраняются в пространстве стека.
Соглашение о вызовах Windows работает следующим образом:
- Первые 4 аргумента сохраняются в регистрах RCX, RDX, R8 и R9
- Выделяются 8 байт для возвратного адреса
- Выделяются еще 4 x 8 байт для сохранения первых 4 аргументов
- Выделяются байты для переменных и других данных.
Для дальнейшей информации, посмотрите:
Это означает, что сначала нужно найти подходящую функцию, поддерживающую размер стека до 12 аргументов, что больше 0x58 байт. После нахождения подходящей функции нужно дождаться выполнения этой функции вызова другой функции. Этот вызов будет перехвачен в момент обращения к внутренней функции, чтобы обеспечить достаточно места в стеке и легитимный возвратный адрес. Для этого можно снова использовать сканирование памяти, хотя с некоторыми оговорками.
Как показано на скриншоте ниже, в некоторых рамках функций недостаточно места в стеке для хранения более 4 аргументов без его повреждения.
Большинство кадров функции выделяют стек в начале функции, используя инструкцию sub rsp, #size.
Мы можем найти соответствие этой инструкции, проверив оператор кода, 0xEC8348, и извлечь наибольший байт, что в большинстве случаев даст размер стека.
Одной из главных сложностей является то, что иногда размеры кадров функции могут быть меньше ожидаемого, и в таких случаях легко достичь конца кадра, который обычно заканчивается инструкцией ret. Поэтому необходимо прервать цикл, если обнаружена инструкция ret до нахождения размера стека. Это можно проверить, добавив следующий фрагмент кода:
Мы используем глобальный флаг IsSubRsp, чтобы узнать, выполнили ли мы первый шаг, который приводит нас ко второму шагу: ждем, пока в той же рамке функции, которую мы хотим, произойдет вызов инструкции call.
Снова, это можно сделать, проверяя адрес исключения на наличие оператора вызова, 0xE8.
Еще одно важное замечание: необходимо убедиться, что функция не завершилась, иначе нужно сбросить наш счетчик на 0, чтобы указать, что подходящая функция еще не найдена.
Предположим, что мы находим подходящую рамку функции, которая содержит необходимый размер стека и выполняет инструкцию вызова. В этом случае можно сохранить остальные аргументы из сохраненного контекста в найденную рамку стека, начиная с 5 x 8 байт после начального значения RSP.
Таким образом, это позволяет обеспечить чистоту стека, не повреждая его путем перезаписи значений возврата из-за недостатка места в стеке. Целостность стека вызовов сохраняется.
Итак, наши условия изменились на:
Поскольку текущая рамка стека указывает на легитимный стек вызовов API Windows, после выполнения ret выполнение немедленно вернется к нормальному выполнению. Вместо этого можно указать на RSP из сохраненного контекста, что позволит ret извлечь адрес из стека и вернуться к функции, вызвавшей системный вызов Nt*, обходя необходимость выполнения дальнейших вызовов API Windows.
Мы также очищаем регистры от установленных аппаратных точек останова, чтобы их можно было повторно использовать для нескольких системных вызовов.
Парсируя прототипы
Поскольку SSN системных вызовов меняются с каждой версией Windows, нам также нужно поддерживать динамическое получение SSN для версии Windows, работающей на системе. Поэтому мы включили функцию GetSsnByName(), предоставленную MDSec. Есть различные методы получения SSN, такие как Halo’s gate, инструмент Syswhispers и другие.
Использование
Ниже приведен пример кода, показывающий, как могут использоваться обертки функций. В заголовочный файл в инструменте включены часто используемые функции системных вызовов из ntdll.dll.
Теперь, когда наш инструмент работает, мы можем увидеть стек вызовов, сгенерированный во время выполнения системного вызова.
Мы выбрали старую технику Process Hollowing в качестве вредоносного метода для тестирования. Поскольку это широко обнаруживаемая техника, это был хороший выбор для сравнения до и после применения нашей техники.
Наш первоначальный метод Process Hollowing был немедленно обнаружен EDR.
Теперь давайте используем наш инструмент для обертывания всех функций системных вызовов и проведем тест снова.
Результаты
Как показано на скриншоте выше, исполняемый файл успешно внедряет пример полезной нагрузки MessageBox без предупреждений от EDR. (Показанное предупреждение относится к предыдущему тесту).
Заключение
Это исследование и инструмент предназначены для того, чтобы по-новому взглянуть на использование косвенных системных вызовов или других методов, таких как обфускация, которые требуют легитимного стека для работы без обнаружения. Конструирование стека в программе может быть сложным и привести к повреждению, если не делать это осторожно. Этот инструмент позволяет операционной системе генерировать необходимый стек вызовов без особых проблем, при этом потенциально можно использовать любой API Windows. Однако это не означает, что метод обхода будет работать для каждого EDR, так как требуется более тщательное тестирование с различными EDR и методами обнаружения.
Ссылка на инструмент
Потенциальные методы обнаружения
В настоящее время для обнаружения этой техники необходимо проверять вредоносно зарегистрированные обработчики исключений в конкретной программе. Другие методы могут включать флаги аномального поведения стека, реализуя эвристику против известных стеков вызовов, созданных API Windows.
Конструирование стека в программе может быть сложным и привести к повреждению, если не делать это осторожно. Этот инструмент позволяет операционной системе генерировать необходимый стек вызовов без особых проблем, при этом потенциально можно использовать любой API Windows. Однако это не означает, что метод обхода будет работать для каждого EDR, так как требуется более тщательное тестирование с различными EDR и методами обнаружения.
Ссылка на инструмент
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
Далее описание самого исследования.
Если спросить любого исследователя в области наступательной безопасности, как можно обойти системы обнаружения и реагирования на инциденты (EDR), можно получить множество ответов: удаление хуков, прямые системные вызовы, косвенные системные вызовы и т.д.
В этом посте мы рассмотрим другой подход, используя обработчики векторных исключений (VEH) для создания легитимного стека вызовов потока и применения косвенных системных вызовов для обхода пользовательских хуков EDR.
Отказ от ответственности: Данное исследование предназначено только для этических целей. Используйте его ответственно и не применяйте в незаконных целях. Это исключительно для образовательных целей.
Введение
EDR используют хуки на уровне пользовательского режима, которые обычно размещаются в ntdll.dll или иногда в kernel32.dll, загружаемом в каждый процесс операционной системы Windows. Хуки внедряются двумя основными способами:- Патчинг первых байтов функции для перенаправления (похож на библиотеку Microsoft Detours).
- Перезапись адреса функции в таблице IAT библиотеки, использующей эту функцию.
Некоторые популярные методы обхода хуков включают:
- Перемаппинг ntdll.dll: Доступ к свежей копии ntdll с диска или KnownDll кэша и замена хуковой версии на свежую копию.
- Прямые системные вызовы: Эмуляция оберток системных вызовов Nt* в своей программе, используя соответствующий SSN и операцию системного вызова.
- Косвенные системные вызовы: Настройка параметров системного вызова в своей программе и перенаправление выполнения с помощью инструкции jmp к адресу в ntdll.dll, где находится операция системного вызова.
Стратегии обнаружения
Для обнаружения и предотвращения вышеупомянутых техник обхода могут использоваться следующие стратегии:- Обнаружение перемаппинга ntdll.dll: Если в памяти процесса находятся две копии ntdll.dll, это обычно признак подозрительного поведения.
- Обнаружение прямых системных вызовов: EDR может зарегистрировать callback для инструментирования и проверки, откуда возобновляется выполнение пользовательского кода. Если оно возвращается в процесс, а не в адресное пространство ntdll.dll, это явный признак прямого системного вызова.
- Обнаружение косвенных системных вызовов: Так как эта техника включает переход в адресное пространство ntdll.dll для выполнения системного вызова, предыдущее обнаружение не сработает. Однако анализ стека вызовов потока покажет аномальное поведение, так как нет легитимных вызовов через различные API Windows, а только процесс к ntdll.dll.
LayeredSyscall – Обзор
Общая идея заключается в создании легитимного стека вызовов перед выполнением косвенного системного вызова при переключении режимов на режим ядра с поддержкой до 12 аргументов. VEH используется для управления контекстом ЦП без вызова тревог. Исключения не часто считаются вредоносными, что дает доступ к аппаратным точкам останова, которые используются как хуки.
Генерация стека вызовов выполняется системой без необходимости ручных операций или выделения памяти, что позволяет изменять стек вызовов вызовом другого API Windows.
Обработчик VEH #1 – AddHwBp
Первый обработчик устанавливает аппаратные точки останова на операцию syscall и операцию ret в обертках системных вызовов Nt* в ntdll.dll. Обработчик обрабатывает EXCEPTION_ACCESS_VIOLATION, генерируемое инструментом перед вызовом системного вызова, путем чтения нулевого указателя для создания исключения. Обработчик может извлечь адрес функции Nt* из регистра RCX, который хранит первый аргумент, переданный обертке функции.После получения адреса функции Nt*, мы выполняем сканирование памяти, чтобы найти смещение, где находятся операторы syscall и ret (сразу после оператора syscall). Мы можем сделать это, проверяя, что операторы 0x0F и 0x05 расположены рядом друг с другом, как показано в приведенном ниже коде.
Системные вызовы в Windows, как видно на следующем скриншоте, создаются с использованием операторов 0x0F и 0x05. Два байта после начала системного вызова можно найти оператор ret, 0xC3.
Аппаратные точки останова устанавливаются с использованием регистров Dr0, Dr1, Dr2 и Dr3, где Dr6 и Dr7 используются для изменения необходимых флагов соответствующего регистра. Обработчик использует Dr0 и Dr1 для установки точки останова на смещении syscall и ret. Как показано в коде ниже, мы активируем их, получая доступ к ExceptionInfo->ContextRecord->Dr0 или Dr1. Мы также устанавливаем последний и второй бит регистра Dr7, чтобы сообщить процессору, что точка останова включена.
Как видно на изображении ниже, исключение возникает из-за попытки чтения адреса нулевого указателя.
Как только исключение возникает, обработчик берет на себя управление и устанавливает точки останова.
Учтите, что после возникновения исключения необходимо сдвинуть регистр RIP на количество байтов, достаточное для прохождения оператора, вызвавшего исключение. В данном случае это было 2 байта.
После этого процессор продолжит обработку исключения, и это будет работать как наши хуки. Мы увидим это во втором обработчике ниже.
Обработчик VEH #2 – HandlerHwBp
Этот обработчик состоит из трех основных частей:- Сохранение контекста и инициация генерации выбранного пользователем стека вызовов.
- Корректное возвращение к процессу без сбоев.
- Поиск подходящего места для перенаправления выполнения и обхода хука, выполняя косвенный системный вызов.
Часть 1 – Обработка точки останова системного вызова
Аппаратные точки останова генерируют исключение с кодом EXCEPTION_SINGLE_STEP, которое проверяется для обработки наших точек останова. Сначала проверяется, было ли исключение сгенерировано в начале системного вызова Nt* с помощью члена ExceptionInfo->ExceptionRecord->ExceptionAddress, указывающего на адрес, где было сгенерировано исключение.Мы сохраняем контекст ЦП в момент возникновения исключения. Это позволяет нам получить доступ к аргументам, хранящимся в регистрах RCX, RDX, R8 и R9 согласно соглашению о вызовах Microsoft. Также это позволяет использовать регистр RSP для доступа к остальным аргументам, что будет объяснено далее.
После сохранения контекста мы можем изменить регистр RIP, чтобы указать на нашу демонстрационную функцию; в данном случае, мы используем простую функцию MessageBox().
Демонстрационная функция ниже отвечает за создание легитимного стека вызовов, который нам нужен. Пользователь может изменить эту функцию по своему усмотрению.
Общая идея заключается в перенаправлении выполнения к безвредному вызову API Windows, затем создании легитимного стека вызовов и перенаправлении на выполнение косвенного системного вызова. Хотя у нас есть хуки на инструкциях syscall и ret, возникает проблема, когда нужно знать, где остановить выполнение для перенаправления на косвенный системный вызов.
Мы используем флаг ловушки (TF), который используется отладчиками для пошагового выполнения. Включить флаг ловушки можно с помощью регистра EFlags. Поскольку у нас уже есть доступ к контексту, мы можем включить его с помощью следующего фрагмента кода.
Для генерации легитимного стека вызовов необходимо дождаться выполнения определенного условия системой (например, вызовы должны достичь адресного пространства ntdll.dll, так как большинство системных вызовов Nt* обычно перенаправляются из ntdll.dll). Это гарантирует, что стек вызовов будет выглядеть максимально легитимным для наблюдателя.
Это можно проверить различными способами, но для упрощения можно получить дескриптор ntdll.dll и использовать функцию GetModuleInformation() для получения базового и конечного адресов библиотеки. После этого можно проверить, находится ли адрес исключения, сгенерированный из-за флага ловушки, в пределах этого адресного пространства.
Мы используем простую структуру для хранения информации, которая инициализируется в начале работы инструмента.
Если условия выполнены, мы можем перейти к перенаправлению выполнения на нужный системный вызов. Сначала нам нужно получить сохраненный контекст, который был установлен при прерывании на операторе системного вызова, и настроить системный вызов.
Системные вызовы в Windows настраиваются следующим образом:
Нам нужно получить сохраненный контекст, но перед этим необходимо сохранить текущий указатель стека (RSP) во временную переменную для последующего восстановления. Так как перезапись указателя стека сохраненным значением полностью изменит стек вызовов, что противоречит нашей цели, мы должны сохранить и восстановить текущий указатель стека сразу после копирования.
Это сохраняет стек вызовов неизменным и одновременно имеет исходное состояние аргументов из предполагаемого системного вызова.
Хуки EDR обычно размещаются в виде инструкций jmp в начале или через несколько инструкций после начального адреса системного вызова Nt*.
Таким образом, если мы эмулируем функциональность системного вызова в нашем обработчике, а затем изменим регистр RIP на адрес оператора системного вызова, мы сможем эффективно обойти хук EDR без необходимости его трогать.
Мы можем продолжить эмуляцию системного вызова перед изменением регистра RIP на оператор системного вызова.
Это позволяет избежать использования встроенного ассемблерного кода или доступа к контексту с помощью winapis.
Однако есть нюанс. Некоторые системные вызовы поддерживают менее 4 аргументов, но для поддержки большинства вызовов необходимо поддерживать до 12 аргументов.
Поддержка более 4 аргументов
При создании стека вызовов с использованием API Windows нужно учитывать размер стека, выделяемого каждым из этих API. Это важно, так как согласно соглашению о вызовах Windows, аргументы, превышающие 4, сохраняются в пространстве стека.
Соглашение о вызовах Windows работает следующим образом:
- Первые 4 аргумента сохраняются в регистрах RCX, RDX, R8 и R9
- Выделяются 8 байт для возвратного адреса
- Выделяются еще 4 x 8 байт для сохранения первых 4 аргументов
- Выделяются байты для переменных и других данных.
Для дальнейшей информации, посмотрите:
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
.Это означает, что сначала нужно найти подходящую функцию, поддерживающую размер стека до 12 аргументов, что больше 0x58 байт. После нахождения подходящей функции нужно дождаться выполнения этой функции вызова другой функции. Этот вызов будет перехвачен в момент обращения к внутренней функции, чтобы обеспечить достаточно места в стеке и легитимный возвратный адрес. Для этого можно снова использовать сканирование памяти, хотя с некоторыми оговорками.
Как показано на скриншоте ниже, в некоторых рамках функций недостаточно места в стеке для хранения более 4 аргументов без его повреждения.
Большинство кадров функции выделяют стек в начале функции, используя инструкцию sub rsp, #size.
Мы можем найти соответствие этой инструкции, проверив оператор кода, 0xEC8348, и извлечь наибольший байт, что в большинстве случаев даст размер стека.
Одной из главных сложностей является то, что иногда размеры кадров функции могут быть меньше ожидаемого, и в таких случаях легко достичь конца кадра, который обычно заканчивается инструкцией ret. Поэтому необходимо прервать цикл, если обнаружена инструкция ret до нахождения размера стека. Это можно проверить, добавив следующий фрагмент кода:
Мы используем глобальный флаг IsSubRsp, чтобы узнать, выполнили ли мы первый шаг, который приводит нас ко второму шагу: ждем, пока в той же рамке функции, которую мы хотим, произойдет вызов инструкции call.
Снова, это можно сделать, проверяя адрес исключения на наличие оператора вызова, 0xE8.
Еще одно важное замечание: необходимо убедиться, что функция не завершилась, иначе нужно сбросить наш счетчик на 0, чтобы указать, что подходящая функция еще не найдена.
Предположим, что мы находим подходящую рамку функции, которая содержит необходимый размер стека и выполняет инструкцию вызова. В этом случае можно сохранить остальные аргументы из сохраненного контекста в найденную рамку стека, начиная с 5 x 8 байт после начального значения RSP.
Таким образом, это позволяет обеспечить чистоту стека, не повреждая его путем перезаписи значений возврата из-за недостатка места в стеке. Целостность стека вызовов сохраняется.
Итак, наши условия изменились на:
- Вызовы должны достигать адресного пространства ntdll.dll.
- Вызов должен поддерживать соответствующий размер стека.
- Вызов должен поддерживать вызов другой функции внутри себя.
Часть 3 – Обработка точки останова ret
После настройки стека и выполнения системного вызова выполнение дойдет до оператора ret, где уже установлена аппаратная точка останова. Заключительный шаг – обеспечить безопасное возвращение к исходной вызывающей функции, а не к функции API Windows, использованной для генерации стека вызовов.Поскольку текущая рамка стека указывает на легитимный стек вызовов API Windows, после выполнения ret выполнение немедленно вернется к нормальному выполнению. Вместо этого можно указать на RSP из сохраненного контекста, что позволит ret извлечь адрес из стека и вернуться к функции, вызвавшей системный вызов Nt*, обходя необходимость выполнения дальнейших вызовов API Windows.
Мы также очищаем регистры от установленных аппаратных точек останова, чтобы их можно было повторно использовать для нескольких системных вызовов.
Тестирование инструмента
Мы предоставили заголовочный файл в нашем инструменте, который необходимо включить для использования оберток функций системных вызовов Nt*. Это было вдохновлено работой
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
, которую можно посмотреть здесь:
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
.Парсируя прототипы
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
, мы можем сгенерировать заголовочный файл для нужного нам системного вызова.Поскольку SSN системных вызовов меняются с каждой версией Windows, нам также нужно поддерживать динамическое получение SSN для версии Windows, работающей на системе. Поэтому мы включили функцию GetSsnByName(), предоставленную MDSec. Есть различные методы получения SSN, такие как Halo’s gate, инструмент Syswhispers и другие.
Использование
Ниже приведен пример кода, показывающий, как могут использоваться обертки функций. В заголовочный файл в инструменте включены часто используемые функции системных вызовов из ntdll.dll.
Результаты
Анализ стека вызовов
До выполнения нашего инструмента косвенный системный вызов создаст стек вызовов. Это явный признак подозрительного поведения, так как до достижения ntdll.dll не происходит никаких легитимных вызовов функций.Теперь, когда наш инструмент работает, мы можем увидеть стек вызовов, сгенерированный во время выполнения системного вызова.
Тестирование на EDR
Мы решили продемонстрировать эффективность этого инструмента, протестировав его на существующей системе обнаружения и реагирования на инциденты (EDR). В качестве тестовой среды был выбран Sophos Intercept X.Мы выбрали старую технику Process Hollowing в качестве вредоносного метода для тестирования. Поскольку это широко обнаруживаемая техника, это был хороший выбор для сравнения до и после применения нашей техники.
Наш первоначальный метод Process Hollowing был немедленно обнаружен EDR.
Теперь давайте используем наш инструмент для обертывания всех функций системных вызовов и проведем тест снова.
Результаты
Как показано на скриншоте выше, исполняемый файл успешно внедряет пример полезной нагрузки MessageBox без предупреждений от EDR. (Показанное предупреждение относится к предыдущему тесту).
Заключение
Это исследование и инструмент предназначены для того, чтобы по-новому взглянуть на использование косвенных системных вызовов или других методов, таких как обфускация, которые требуют легитимного стека для работы без обнаружения. Конструирование стека в программе может быть сложным и привести к повреждению, если не делать это осторожно. Этот инструмент позволяет операционной системе генерировать необходимый стек вызовов без особых проблем, при этом потенциально можно использовать любой API Windows. Однако это не означает, что метод обхода будет работать для каждого EDR, так как требуется более тщательное тестирование с различными EDR и методами обнаружения.
Ссылка на инструмент
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
Потенциальные методы обнаружения
В настоящее время для обнаружения этой техники необходимо проверять вредоносно зарегистрированные обработчики исключений в конкретной программе. Другие методы могут включать флаги аномального поведения стека, реализуя эвристику против известных стеков вызовов, созданных API Windows.