
Кроссплатформенный имплант и C2 на основе системных вызовов — прямые системные вызовы (Windows), необработанные системные вызовы (Linux), каналы HTTPS/DNS/ICMP. Без слоя winapi.
English | 简体中文 | 조선어 | Русский
Ноль WinAPI. Каждый NT-примитив разрешается в рантайме из образа ntdll в памяти — инструкция SYSCALL выполняется напрямую через стубы на Plan9-ассемблере, без таблицы импорта и без касания пользовательских хуков ntdll. Канал C2 — HTTPS / DNS / ICMP, каждое сообщение зашифровано AES-256-GCM (AEAD) с собственным nonce. Нативная логика импланта для Windows, сборки под Linux/macOS сохранены.
Это не библиотека-обёртка над syscall-ами, а полный каркас импланта: от инжекта кода и чтения файлов до персистентности в реестре — всё идёт через сырые Nt*-сисколы, разрешённые при старте из таблицы экспорта текущего ntdll. В бинарнике нет ни одного импорта kernel32/advapi32/user32/win32u — пользовательским хукам (inline, IAT, callback'и) не за что зацепиться. Имена функций существуют только в виде констант djb2-хешей: на дизассемблере вы не найдёте строк, только 32-битные хеши и мнемонику SYSCALL.
ReadGSBase ──GS:[0x60]──▶ TEB ──+0x60──▶ PEB ──+0x18──▶ PEB_LDR_DATA
│
+0x10 InMemoryOrderModuleList ◀── список
│
обход по списку; DllBase → читаем PE → djb2 ищем ntdll
│
IMAGE_DOS_HEADER.e_lfanew ──▶ IMAGE_NT_HEADERS
│
DataDirectory[0].VirtualAddress ──▶ экспорт
│
AddressOfNames[] по одному → djb2 → ordinal → AddressOfFunctions[]
│
адрес функции ──▶ пролог ──▶ извлекаем SSN
h = 5381; h = h*33 + c. Хеши имён считаются на этапе компиляции (hash.go, строковый и UTF-16 варианты) — в бинарник попадают только uint32-константы.resolve.go) — в окне 64 байт перед функцией, с учётом jmp-трамплинов (EB xx, E9 xxxxxxxx — следование по цепочке переходов):
B8 xx xx 00 00 0F 05 (mov eax, SSN; syscall)B8 xx xx 00 00 C3 (mov eax, SSN; ret)4C 8B D1 B8 xx xx 00 00 0F 05 (mov r10, rdx; mov eax, SSN; syscall)B8, little-endian, младшие 16 бит — SSN. Кэш: map[uint32]uint16 (хеш → SSN).NtUser*/NtGdi*) идут через win32u.dll — их SSN-пространство отдельно от ntdll, разрешается и кэшируется независимо.asm_amd64.s:
| Стадия | Регистры |
|---|---|
| Индексы | 1–3 в CX/R8/R9, 4-й в R10 (обязательно: SYSCALL затирает RCX/R11) |
| SSN | movl $ssn, AX |
| Спуск | SYSCALL → KiSystemCall64 (через MSR_LSTAR) |
| Возврат | NTSTATUS в RAX; у NtDeviceIoControlFile и подобных выходной буфер в RDX |
Syscall (≤7 арг.) + новый Syscall9 (9 арг., фрейм $0-88) перекрывают «тяжёлые» виды вроде NtGdiBitBlt.
У прямого syscall в стеке виден только фрейм самого импланта — EDR со стековым трассировщиком замечает мгновенно. Косвенный режим ищет в .text ntdll гаджет 0F 05 C3 (syscall; ret) и прыгает через него:
syscall EDR часто делает неисполняемыми — скан пропускает первый байт и садится на соседний 0F 05.SSN — нестабильный интерфейс: между 10.0.19041 (20H1) и 10.0.26100 (24H2) номера плывут. fingerprint.go сортирует и хеширует все разрешённые SSN текущей сборки ntdll в 64-битный отпечаток:
EDR обычно делает .text ntdll RWX и вшивает трамплин 5–14 байт (jmp r11 и пр.). Процесс:
NtCreateFile(\SystemRoot\System32\ntdll.dll) → NtMapViewOfSection(SEC_IMAGE)
→ побайтовый diff нового .text и старого
→ NtProtectVirtualMemory(.text → RWX) → memcpy(чистые страницы)
→ защита(RX) → FlushInstructionCache → сброс кэша SSN
Каждый бикон безусловно вызывает CheckAndUnhook() в начале — даже повторный хукинг сносится до следующего beacon.
Ротация 4 методов на каждый бикон: два инжекта никогда не дают одинаковую раскладку памяти, точку входа и путь создания треда.
| Метод | Цепочка примитивов | Почему тихо |
|---|---|---|
| Section mapping | NtCreateSection → NtMapViewOfSection (запись локально → чтение удалённо) → NtCreateThreadEx | Section файло-подкреплён, в VAD нет PAGE_EXECUTE_READWRITE; удалённые приватные страницы видны как PAGE_EXECUTE_READ |
| Process hollowing | NtCreateUserProcess (suspend) → NtSuspendProcess → обнуление базы → NtWriteVirtualMemory (shellcode) → NtSetContextThread (RIP=payload) → NtResumeProcess | В диспетчере задач — легитимный svchost.exe: путь, PID, PPID настоящие |
| APC queueing | NtQuerySystemInformation (перечисление тредов) → NtOpenThread → NtQueueApcThread | Ноль новых тредов/TEB/стеков; срабатывание при alertable-ожидании |
| Module stomping | аллокация в цели → минимальный PE-заголовок + shellcode → NtCreateThreadEx на entry point | В списке модулей — правдоподобное имя DLL; геометрии PE-заголовка хватает, чтобы обмануть перечисление модулей |
Прогон всех проверок выводит балльный отчёт об угрозе; при Critical — автодеструкция (SelfDel + NtTerminateProcess).