Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
NullGate — Библиотека, упрощающая использование indirect syscalls. Довольно интересный обход AV/EDR в качестве PoC. | Kitploit
Инструменты/GitHubGitHub/0xsch1zo/nullgate
Инструменты шифрования/дешифрованияШелл-кодПост-эксплуатацияRed TeamingРазработка Полезной НагрузкиСостязательная Атака
GitHub0xsch1zo/nullgate

NullGate

Библиотека, упрощающая использование indirect syscalls. Довольно интересный обход AV/EDR в качестве PoC.

Репозиторий
168191 год назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

NullGate

Этот проект реализует удобный и современный способ использования функций NTAPI через непрямые системные вызовы (indirect syscalls) в сочетании с методом FreshyCalls с небольшой доработкой для динамического получения номеров системных вызовов. Также здесь используется техника, о которой я не видел упоминаний, для обхода сканирования памяти Windows Defender. Проект реализует классический PoC-инжектор процессов, использующий эту библиотеку. Доступны и удобные функции для шифрования.

Демонстрация

Демонстрация работы примера

Сборка

Чтобы собрать пример, используйте -DNULLGATE_BUILD_SAMPLE=ON. Если вы собрали nullgate напрямую, он будет доступен по пути <build_dir>/sample.exe; если вы собрали его как зависимость, то по пути <build_dir>/_deps/nullgate-build/sample.exe. В Windows из-за странных путей сборки он, скорее всего, будет находиться в тех же базовых каталогах, где и примеры, но, вероятно, вложен на несколько уровней глубже. В качестве аргумента он принимает PID процесса, в который вы хотите внедрить шелл-код.

[!WARNING] Если вы используете Linux, вам необходимо установить кросс-компилятор mingw. Например, на Arch можно выполнить pacman -S mingw-w64-gcc. Затем используйте опцию -DNULLGATE_CROSSCOMPILE=ON, чтобы установить mingw в качестве компилятора по умолчанию.

[!TIP] Также рекомендуется выполнить strip результирующего бинарника, чтобы снизить вероятность обнаружения.

root@kitploit:~
git clone https://github.com/0xsch1zo/NullGate
cd NullGate
cmake . -B build -DNULLGATE_BUILD_SAMPLE=ON
cmake --build build/

Устаревший hashser (версии 1.1.3 и ниже)

Его можно собрать с помощью флага -DNULLGATE_DEPRECATED_HASHER.

Использование

Добавление nullgate в ваш проект

Поддерживается CMake FetchContent. Вот пример простого CMakeLists.txt:

root@kitploit:~
cmake_minimum_required(VERSION 3.25)

include(FetchContent)

FetchContent_Declare(nullgate
    GIT_REPOSITORY https://github.com/0xsch1zo/NullGate
    GIT_TAG 1.2.0 
)

FetchContent_MakeAvailable(nullgate)

project(test)

add_executable(test
    main.cpp
)

target_link_libraries(test
    PRIVATE nullgate
)

Линковка выполняется статически, поэтому вам не нужно беспокоиться о видимости символов.

[!NOTE] В следующих примерах будет использоваться namespace ng = nullgate

Системные вызовы

Использование довольно простое. Вот фрагмент, демонстрирующий основную функциональность:

root@kitploit:~
ng::syscalls syscalls;
typedef NTSTATUS NTAPI NtAllocateVirtualMemory(
    _In_ HANDLE ProcessHandle,
    _Inout_ _At_(*BaseAddress,
                 _Readable_bytes_(*RegionSize) _Writable_bytes_(*RegionSize)
                     _Post_readable_byte_size_(*RegionSize)) PVOID *BaseAddress,
    _In_ ULONG_PTR ZeroBits, _Inout_ PSIZE_T RegionSize,
    _In_ ULONG AllocationType, _In_ ULONG PageProtection);

NTSTATUS status = syscalls.SCall<NtAllocateVirtualMemory>(
      ng::obfuscation::fnv1Const("NtAllocateVirtualMemory"), processHandle,
      &buf, 0, &regionSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

Встроена типобезопасность. Вам просто нужно предоставить определение NT-функции, которую вы хотите вызвать! Его легко получить с ntdoc. Это рекомендуемый способ использования библиотеки.

Предыдущий (не устаревший) интерфейс

Для тех, кому не нравится чёрная магия шаблонов C++ или что-то подобное, предыдущий интерфейс всё ещё доступен:

root@kitploit:~
NTSTATUS status = syscalls.Call(ng::obfuscation::fnv1Const("NtAllocateVirtualMemory"),
                         processHandle, (PVOID)&buf, (ULONG_PTR)0, &regionSize,
                         (ULONG)(MEM_RESERVE | MEM_COMMIT), (ULONG)PAGE_EXECUTE_READWRITE);

При использовании этого интерфейса вам нужно приводить аргументы к правильному типу; если этого не делать, могут возникнуть проблемы.

Шифрование/хеширование

Хеширование вызовов NTAPI

root@kitploit:~
constexpr uint64_t hash = ng::obfuscation::fnv1Const("NtAllocateVirtualMemory");

Продемонстрированный ранее метод fnv1Const приносит радости современного C++ в мир разработки вредоносного ПО. Это consteval-функция, поэтому гарантируется, что она будет вычислена на этапе компиляции, заменяя читаемое имя функции на FNV1-хеш.

Существует также runtime-эквивалент под названием fnv1Runtime, но, конечно, он не даёт преимущества запутывания имён наших функций. Он используется реализацией, чтобы определить, для какой функции внутри ntdll получать номер системного вызова.

Общее XOR-шифрование

Есть три процедуры для XOR-«шифрования»:

xorConst
root@kitploit:~
constexpr ng::obfuscation::ConstData xored = ng::obfuscation::xorConst("some string");
std::cout << xored.string();

Возможный вывод:

root@kitploit:~
<Y:-E&X0

Эта процедура, как и функция fnv1Const, вычисляется на этапе компиляции, поэтому "some string" никогда не появится в бинарнике, поскольку строка будет храниться в зашифрованном виде. Но, эй, нам ведь нужно реально использовать эти данные! ConstData — это тонкая обёртка над сырыми байтами, имеющая постоянный размер. У неё есть два метода: raw() и string(). raw() возвращает std::vector<unsigned char>, а string, как следует из названия, возвращает std::string.

[!NOTE] Если вам нужно сконструировать ConstData напрямую из строкового литерала, используйте std::to_array для создания промежуточного массива, передаваемого в ConstData. Однако учтите, что при таком подходе ConstData будет хранить дополнительный нулевой символ литерала.

Конечно, нам нужен способ расшифровать это и использовать — это следующая функция, которую мы рассмотрим.

xorRuntime

xorRuntime — вторая доступная процедура. Как следует из названия, это runtime-эквивалент xorConst. Обратите внимание, что его можно использовать не только для расшифровки — он работает в обе стороны, хотя использование его для шифрования не даёт преимуществ запутывания строки на этапе компиляции.

root@kitploit:~
ng::obfuscation::ConstData xored = ng::obfuscation::xorConst("some string");
ng::obfuscation::ConstData data = ng::obfuscation::xorRuntime(xored);
assert(data.string() == "some string");

std::string dynamicString = "some data of dynamic size";
auto dynamicData = ng::obfuscation::DynamicData(dynamicString);
auto xoredDynamic = ng::obfuscation::xorRuntime(dynamicData);
auto unxoredDynamic = ng::obfuscation::xorRuntime(xoredDynamic);
assert(unxoredDynamic.string() == dynamicString);

DynamicData имеет те же методы, что и ConstData, с добавленными конструкторами для совместимости и, как следует из названия, может иметь размер, неизвестный на этапе компиляции. xorRuntime принимает как DynamicData, так и ConstData.

xorRuntimeDecrypted

Хотя мы всегда могли бы расшифровывать данные в рантайме, сначала вызывая xorConst и передавая результат в xorRuntime, это довольно утомительно. И вот решение этой проблемы — вишенка на торте: xorRuntimeDecrypted. Это удобная функция для строковых литералов, с которыми не нужно работать, пока они находятся в зашифрованном состоянии. Она шифрует строковый литерал на этапе компиляции, а затем расшифровывает его в рантайме одним вызовом. Разве не круто!.

root@kitploit:~
ng::obfuscation::ConstData text = ng::obfuscation::xorRuntimeDecrypted<"some string">();
assert(data.string() == "some string");
Ключ

Кто-то может сказать: всё здорово, но где ключ? В nullgate 1.2 ключ генерируется случайным образом при каждой новой сборке (то есть при каждом запуске команды cmake). Это ещё сильнее снижает шанс быть обнаруженным по сигнатурам.

Обход сканирования памяти Windows Defender

Суть проблемы в том, что при вызове NtCreateRemoteThreadEx или NtCreateProcess запускается сканирование памяти, и наш до чёртиков сигнатурный msfvenom-пейлоад обнаруживается.

Как это обойти?

Известное решение — сначала при вызове NtAllocateVirtualMemory установить права страницы как PAGE_NOACCESS, а затем создать поток в приостановленном состоянии. Когда Windows Defender попытается просканировать память нашего процесса, у него это не получится. Затем мы можем возобновить выполнение потока с помощью NtResumeThread. Это работает, но что если используется более компетентное защитное решение? Что бы оно сделало? Оно, конечно, просто использовало бы VirtualProtect, чтобы изменить права нашей страницы, и обнаружило бы msfvenom. Чтобы обойти это, я немного изменил стратегию. Вместо установки страницы как PAGE_NOACCESS при первой записи в память процесса мы можем просто записать в процесс какие-то мусорные данные (Да, это обязательно, или же я просто слишком глуп, чтобы найти способ обойтись без этого). Затем мы создаём поток в приостановленном состоянии. После этого мы записываем в процесс нужный шелл-код и, наконец, возобновляем поток с помощью NtResumeThread. Благодаря этому методу нам не нужно беспокоиться о том, что к нашей памяти обратятся после вызова NtCreateThreadEx, потому что в ней ничего нет. Только после этого записывается расшифрованный шелл-код, и выполнение возобновляется.

Благодарности:

  • Спасибо @ElephantSe4l и @MarioBartolome за отличный метод динамического получения номеров системных вызовов и в целом за весь проект, из которого я почерпнул много вдохновения.
  • Спасибо @cr-0w за потрясающие статью в блоге и видео, в которых обсуждаются прямые и косвенные системные вызовы.
  • Спасибо bordergate за статью, в которой описан первоначальный метод обхода.

Отказ от ответственности

Эта библиотека создана исключительно в академических целях. Авторы не несут ответственности за то, что делается с помощью этой библиотеки, и поэтому мы освобождаемся от любой ответственности, возникающей в результате её неправомерного использования.

Скачать инструмент