Как работает EDR. Подробно разбираем механизмы антивирусной защиты

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
В «Хакере» часто обсуждается скрытие процессов и системных вызовов, обфускация кода и прочие способы обхода AV/EDR. Но что, если ты хочешь не только следовать инструкциям, но и самостоятельно искать такие техники? Для этого нужно как минимум понимать общие принципы работы антивирусов и EDR. Эти принципы мы и обсудим.

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

СТАТИЧЕСКИЕ СИГНАТУРЫ​

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

Со временем сигнатурный метод обнаружения претерпел некоторые изменения: скажем, сегодня вполне могут использоваться байтовые последовательности, и не только статичные, но и плавающие. Например, вот так выглядит детектирующая последовательность YARA-скрипта для одного из творений группы Lazarus:
Код: Скопировать в буфер обмена
$scan_1 = { d1 ?? 33 ?? fc 81 ?? ff 00 00 00 c1 ?? 17 }
Здесь представлена как раз плавающая байтовая сигнатура: на месте знаков вопроса могут быть любые байты, но детект все равно сработает.

Сигнатуры могут включать в себя не только шестнадцатеричные байтовые последовательности, но и строки, встречающиеся в малвари:
Код: Скопировать в буфер обмена
$scan_2 = "%s\\~%d.tmp" wide ascii
Кроме того, под детект могут попасть контрольные суммы таблицы импорта или, например, Rich-сигнатуры. Словом, все, что может однозначно говорить, что перед нами именно искомый файл, а не какой‑то другой. Подобные сигнатуры таким же способом можно искать в памяти. Сегодня этот метод сканирования часто работает в связке с другими методами обнаружения малвари.

Что сбивает статические сигнатуры (и не только)​

Например, упаковщики или протекторы! Они могут:
  • менять набор импортов и байтовые последовательности;
  • вставлять в код малвари свои кодовые антиотладочные блоки, содержащие длинные циклы или вызовы функций WinAPI;
  • шифровать код и расшифровывать его только в памяти, в том числе кусками (расшифровали → выполнили кусок → зашифровали обратно → расшифровали следующий кусок и так далее);
  • превратить код в последовательность пи‑кода для своей виртуальной машины;
  • менять энтропию зашифрованных данных малвари;
  • делать многие другие изменения в файле.

СКАНИРОВАНИЕ ПАМЯТИ​

Сканирование памяти тоже часто используется средствами EDR, но работает оно не постоянно (из‑за оптимизаций производительности), а часто запускается из‑за компрометирующих последовательностей действий, например:
  • загрузка определенных библиотек;
  • выполнение определенной последовательности WinAPI;
  • запись данных в память (свою или чужую);
  • запись данных в определенные ключи реестра.
Другими словами, сканирование часто запускается по сигналу от поведенческого анализатора, который наблюдает достаточное количество негативных флагов в поведении процесса.

В свою очередь, вредоносное ПО часто использует шифрование в качестве противоядия от сканирования памяти: шифрование может быть как целого образа, так и фрагментами с последующим исполнением. Кроме того, шифроваться может не только сам исполняемый файл или шелл‑код целиком, но и строки, которые могут вызвать подозрение сканера. Например, некоторые вирмейкеры просто указывают в своем коде прямым текстом что‑то такое:
Код: Скопировать в буфер обмена
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
А потом удивляются, почему что‑то идет не так!

При этом нельзя полагаться на шифрование методом XOR: когда‑то это было популярно и модно (работает быстро и реализуется на раз), но сейчас подобные вещи легко детектятся теми же правилами YARA. Зато работе EDR по‑прежнему мешает шифрование Heap.

Кроме стандартных байтовых последовательностей, характерных, к примеру, для шелл‑кодов, EDR могут смотреть и на атрибуты страниц памяти. Как известно, память может быть читаемой (read, R), записываемой (write, W) или исполняемой (execute, X). И если память выделяется с атрибутами RWX, то это автоматически обратит на себя внимание EDR, потому что при нормальном выполнении такие атрибуты выдаются очень редко и только специфическому софту.

ТРАССИРОВКА СОБЫТИЙ WINDOWS ETW​

Часто средства EDR опираются на трассировку событий ETW, которая позволяет получать весьма детальную информацию о запуске функций WinAPI, процессах, драйверах и подобных вещах. Сам механизм трассировки располагается как в режиме ядра, так и в режиме пользователя в файле ntdll.dll (функции EtwEventWrite и EtwEventWriteFull).

Так как часть механизма — в пользовательском режиме, мы можем на него воздействовать, например пропатчив функции EtwEventWrite и EtwEventWriteFull. Конечно, EDR может контролировать целостность WinAPI и обломать малвари всю малину. Но в силах вирусописателя испортить функцию NtTraceEvent (в которую упираются EtwEventWrite и EtwEventWriteFull) непосредственно в момент вызова ядра. Впрочем, это уже отдельная история. Вот пример патчинга EtwEventWrite:
Код: Скопировать в буфер обмена
Код:
  // Заглушка
  unsigned char eew_patch[] = { 0x48, 0x33, 0xc0, 0xc3};
  unsigned char sEEW[] = { 'E','t','w','E','v','e','n','t','W','r','i','t','e', 0x0 };
  int size = sizeof(eew_patch);
  ULONG OldAccessProtection = 0;
  HANDLE hProcess = GetCurrentProcess();
  void *pEew = GetProcAddress(GetModuleHandle((LPCSTR) L'ntdll.dll'), (LPCSTR) sEEW);
  if(!pEew) return 1;
  // Разрешаем записывать память
  NtProtectVirtualMemory(hProcess,
          &pEventWrite,
          (PSIZE_T) &size,
          PAGE_READWRITE,
          &OldAccessProtection);
  // Патчим
  memcpy(pEew, eew_patch, size / sizeof(eew_patch[0]));
  // Меняем права памяти обратно
  NtProtectVirtualMemory(hProcess, &pEew, &size, OldAccessProtection, &OldAccessProtection);
}
На крайний случай можно использовать недокументированную функцию EtwpDisableStackWalkApc, которая применяется для отключения процесса Stack Walking (получения стека вызовов) в рамках механизма APC.

ПЕРЕХВАТ API​

Еще один способ детекта основан на перехвате функций WinAPI и NTAPI. Это позволяет системам EDR и AV отслеживать вызовы и смотреть параметры, с которыми вызываются функции. Естественно, эта информация передается поведенческому анализатору.

В первом, самом простом приближении механизм перехвата я описывал в статье «Волшебные хуки», да и в других тоже затрагивал. Суть метода заключается в том, что производится инжект мониторящей DLL в процесс и дальнейшая обработка событий вызова нужных WinAPI. Кроме этого, защитное средство может патчить начальный код в ntdll.dll, чтобы мониторить NTAPI.

Для противодействия этому существует много методов обхода. Например, в статье «Врата ада» описаны прямые и непрямые (indirect) вызовы функций API. Кроме того, можно попробовать снять непосредственно сами хуки в ntdll.dll. Если смотреть по общей эффективности, то, на мой взгляд, перспективнее всего техника непрямых вызовов. Среди ее плюсов:
  • В коде нет захардкоженных номеров системных вызовов, они получаются динамически. Это снимает вопрос с тем, что номера вызовов в системной таблице сервисов могут непредсказуемо меняться (что они и делают, если сравнить их в разных версиях Windows).
  • Точка вызова ядра будет не в нашем процессе, а, как ей и положено, в файле ntdll.dll.
  • Будет отсутствовать сам опкод инструкции syscall в коде. Ведь в обычных приложениях такой опкод не встречается практически никогда.

ПОВЕДЕНЧЕСКИЙ АНАЛИЗ​

Этот вид детекта вредоносного кода считается одним из самых эффективных, хотя он и более рискованный. Если антивирус решит, что перед ним вредоносное ПО, он сразу же завершит процесс. Но если это было ошибкой, то пользователь может потерять данные или результаты работы. Если такое будет повторяться часто, то юзер либо отключит, либо снесет антивирус, ну или будет мучить админов организации, и они сделают что‑нибудь нехорошее с EDR — ослабят политики или (о ужас!) перейдут к другому вендору.

Суть поведенческого детекта, как ты можешь догадаться, заключается в том, что антивирус анализирует поведение программы. Часто можно выделить определенный шаблон характерных вредоносных активностей. Например, вот действия, которые нужно предпринять для реализации Process Doppelgänging:
  1. Создать транзакцию NTFS при помощи функции CreateTransaction.
  2. В контексте транзакции создать временный файл для вредоносного кода при помощи функции CreateFileTransacted.
  3. Создать в оперативной памяти буферы для временного файла (объект «секция», функция NtCreateSection).
  4. Настроить PEB для запуска кода свежесозданной секции.
  5. Запустить процесс при помощи NtCreateProcessEx->ResumeThread.
В этом примере внимание привлекает вызов редких WinAPI для работы с транзакциями и функция NtCreateProcessEx, которая вообще стала красной тряпкой для всех систем EDR. Да, можно какие‑то шаги переписать при помощи других функций с нужными возможностями, но общая логика будет именно такая. И таких «шаблонов» EDR знает много, например:
  • один процесс запускает другой из файла в определенной директории;
  • что‑то записывается в автозагрузку;
  • один процесс пишет в память другого;
  • с диска читаются «интересные» DLL, например ntdll.dll;
  • у файла, который выполняет эти действия, нет цифровой подписи.
Идея обхода подобного анализа сводится либо к максимальному скрытию своих действий (те же непрямые сисколы), либо к мимикрии под «легальные» последовательности действий, чтобы вызвать максимальное количество ложных срабатываний и заставить пользователя отключить защиту. Либо можно стараться разрывать цепочку действий (хотя бы во времени), используя техники сна.

Например, шифрование данных можно реализовать при помощи стандартных криптофункций WinAPI, и это сразу же привлечет внимание. А можно использовать недокументированную функцию SystemFunction032 из Advapi32.dll, которая реализует RC4, и юзать для шифрования уже ее, что будет весьма внезапно для EDR. А если еще и код будет зашифрован при помощи Heap, то детект точно получится обойти.

ТРАССИРОВКА СТЕКА​

Достаточно сложная и могучая техника — реконструкция последовательности вызовов функций (стек вызовов). Для каждого потока создается свой стек вызовов. При вызове функции для нее создается стековый фрейм, содержащий в себе аргументы функции, локальные переменные, необходимые для последующего восстановления регистры и обратный адрес — тот, куда передастся управление, когда функция отработает. Другими словами, стек представляет собой упорядоченный набор адресов возврата функций, которые вызывались при выполнении программы. Посмотрим, как это устроено:
Код: Скопировать в буфер обмена
Код:
void func2(int a, int b) {
    int c = a + b;
}
void func1() {
    func2(10, 20);
}
int main() {
    func1();
    return 0;
}
По шагам:
  • main() вызывает func1(), и адрес возврата func1() добавляется в стек (ведь стек имеет структуру LIFO — первым вошел, последним вышел);
  • func1() вызывает func2(), адрес возврата func2() тоже добавляется в стек;
  • func2() — это верхняя функция в стеке вызовов;
  • когда func2() завершится, из стека будет удален адрес возврата, и выполнение перейдет к func1();
  • после завершения func1() из стека точно так же удалится адрес возврата, и управление вернется к main().
Системы EDR могут мониторить стек вызовов, чтобы анализировать последовательность работы функций. Но в нашем примере только наш код, а EDR умеют точно так же реконструировать последовательность вызовов WinAPI.

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

Вот пара примеров использования этой техники. Защитное средство может перехватить выделяющие память функции WinAPI и искать адрес возврата, указывающий на вредоносный код. Как вариант, антивирус может проследить стек до таких функций, как RtlUserThreadStart или BaseThreadInitThunk (точки входа для пользовательских потоков после их создания), чтобы убедиться, что поток создан «нормальным» образом.

Конечно, вредоносный код может противостоять и этим техникам. Например, существует такая штука, как Spoofing Call Stacks — подделка стека вызовов с изменением обратных адресов. Это позволяет запутать след и сбить с толку функции трассировки стека EDR.

ВЫВОДЫ​

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

Автор NIK Zerof
источник xakep.ru
 
Сверху Снизу