Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
NullGate — Librería que facilita el uso de syscalls indirectas. Bypass de AV/EDR bastante interesante como PoC. | Kitploit
Herramientas/GitHubGitHub/0xsch1zo/nullgate
Herramientas de Cifrado/DescifradoShellcodePost-ExplotaciónRed TeamingDesarrollo de PayloadsAtaque Adversario
GitHub0xsch1zo/nullgate

NullGate

Librería que facilita el uso de syscalls indirectas. Bypass de AV/EDR bastante interesante como PoC.

Ver Repositorio
16819hace 1 añoRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

NullGate

Este proyecto implementa una forma cómoda y moderna de usar las funciones NTAPI mediante syscalls indirectos, combinado con el método FreshyCalls con un pequeño giro para la recuperación dinámica de números de syscall. También utiliza una técnica que no he visto mencionada para evadir el escaneo de memoria de Windows Defender. El proyecto implementa un inyector de procesos PoC clásico usando la librería. También hay funciones convenientes para cifrado.

Demo

Demostración de la muestra

Compilación

Para compilar el ejemplo usa -DNULLGATE_BUILD_SAMPLE=ON. Si compilaste nullgate directamente será accesible en <build_dir>/sample.exe, si lo compilaste como dependencia estará en <build_dir>/_deps/nullgate-build/sample.exe. En Windows, debido a que los destinos de compilación son extraños, probablemente estará en los mismos directorios base de las ubicaciones de los ejemplos, pero probablemente anidado un poco más. Toma como argumento un PID en el que quieras inyectar shellcode.

[!WARNING] Si estás usando Linux necesitas tener instalado el compilador cruzado mingw. En Arch, por ejemplo, puedes hacer pacman -S mingw-w64-gcc. Luego usa la opción -DNULLGATE_CROSSCOMPILE=ON para establecer mingw como compilador predeterminado.

[!TIP] También se recomienda hacer strip del binario resultante para disminuir la posibilidad de detección.

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

hashser obsoleto (versiones 1.1.3 y anteriores)

Se puede compilar usando el flag -DNULLGATE_DEPRECATED_HASHER.

Uso

Añadir nullgate a tu proyecto

CMake FetchContent está soportado. Aquí hay un ejemplo de un CMakeLists.txt sencillo:

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
)

El enlazado se realiza estáticamente, por lo que no tienes que preocuparte de que los símbolos sean visibles.

[!NOTE] Los siguientes ejemplos usarán namespace ng = nullgate

Syscalls

El uso es bastante sencillo, aquí hay un fragmento que demuestra la funcionalidad principal:

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);

Hay seguridad de tipos integrada. Solo necesitas proporcionar la definición de la función NT que quieras llamar. Puedes obtenerla fácilmente desde ntdoc. Esta es la forma recomendada de usar la librería.

La interfaz anterior no obsoleta

Para las personas a las que no les gusta la magia negra de las plantillas de C++ o algo así, la interfaz anterior sigue disponible:

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);

Usando esta interfaz necesitas hacer cast de los argumentos al tipo correcto; no hacerlo puede causar problemas.

Cifrado/hashing

Hashing de llamadas NTAPI

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

El método fnv1Const demostrado anteriormente trae las alegrías del C++ moderno al mundo del maldev. Es una función consteval, por lo que se garantiza que se evaluará en tiempo de compilación, reemplazando el nombre de función legible por un hash fnv1.

También existe un equivalente en tiempo de ejecución llamado fnv1Runtime, pero por supuesto no añade el beneficio de tener nuestros nombres de función ofuscados. Es usado por la implementación para comprobar qué función dentro de ntdll obtener el número de syscall.

Cifrado xor general

Hay tres rutinas para el "cifrado" xor:

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

Posible salida:

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

Esta rutina, de manera similar a la función fnv1Const, se evalúa en tiempo de compilación, por lo que "some string" nunca aparecerá en el binario, porque la cadena se almacenará en su forma cifrada. Pero bueno, ¡necesitamos hacer uso real de esos datos! ConstData es un envoltorio delgado alrededor de bytes crudos que tiene un tamaño constante. Tiene dos métodos: raw() y string(). raw() devuelve un std::vector<unsigned char> y string, como su nombre indica, devuelve un std::string.

[!NOTE] Si necesitas construir ConstData directamente con un literal de cadena, usa std::to_array para construir un array intermedio que se pasa a ConstData. Ten en cuenta, sin embargo, que con este enfoque ConstData almacenará el carácter nulo adicional del literal.

Por supuesto, necesitamos una forma de descifrar esto y usarlo, que es la siguiente función que cubriremos.

xorRuntime

xorRuntime es la segunda rutina disponible. Como su nombre indica, es el equivalente en tiempo de ejecución de xorConst. Ten en cuenta que no solo tiene que usarse para descifrar; funciona en ambos sentidos, aunque usarlo para cifrar no tiene los beneficios de ofuscar la cadena en tiempo de compilación.

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 tiene los mismos métodos que ConstData con algunos constructores añadidos para interoperabilidad y, como su nombre indica, puede tener un tamaño no conocido en tiempo de compilación. xorRuntime acepta tanto DynamicData como ConstData.

xorRuntimeDecrypted

Aunque siempre podríamos descifrar en tiempo de ejecución llamando primero a xorConst y pasando el resultado a xorRuntime, eso es algo tedioso. Aquí viene la solución a este problema, la guinda del pastel: xorRuntimeDecrypted. Es una función práctica para literales de cadena que no necesitan ser manipulados mientras están en estado cifrado. Cifra el literal de cadena en tiempo de compilación y luego lo descifra en tiempo de ejecución, todo en una sola llamada. ¿No es genial?

root@kitploit:~
ng::obfuscation::ConstData text = ng::obfuscation::xorRuntimeDecrypted<"some string">();
assert(data.string() == "some string");
La clave

Ahora alguien podría decir que todo es genial, pero ¿dónde está la clave? En nullgate 1.2 la clave se genera aleatoriamente en cada compilación nueva (es decir, cada vez que se ejecuta el comando cmake). Esto reduce aún más la probabilidad de ser detectado por firmas.

Evasión del escaneo de memoria de Windows Defender

El núcleo del problema es que cuando llamamos a NtCreateRemoteThreadEx o NtCreateProcess, se activa un escaneo de memoria y nuestro payload de msfvenom, lleno de firmas hasta decir basta, es detectado.

¿Cómo evadir eso?

Una solución conocida es, primero, al llamar a NtAllocateVirtualMemory, establecer los permisos de la página como PAGE_NOACCESS y luego crear el hilo en estado suspendido. Cuando Windows Defender escanee la memoria de nuestro proceso, fallará al hacerlo. Entonces podemos reanudar la ejecución de nuestro hilo con NtResumeThread. Esto funciona, pero ¿qué pasa si se está usando una solución de seguridad más competente? ¿Qué haría? Por supuesto, usaría VirtualProtect para cambiar los permisos de nuestra página y detectar msfvenom. Para evadir eso cambié un poco la estrategia. En lugar de establecer la página como PAGE_NOACCESS, durante nuestra primera escritura en la memoria del proceso podemos simplemente poner algunos datos basura en el proceso (Sí, es necesario, o soy demasiado tonto para encontrar una forma de hacerlo funcionar sin esto). Luego creamos un hilo en estado suspendido. Después de eso, escribimos nuestro shellcode deseado en el proceso y finalmente reanudamos el hilo usando NtResumeThread. Con esta técnica no tenemos que preocuparnos de que nuestra memoria sea accedida después de la llamada a NtCreateThreadEx porque no hay nada ahí. Solo después de eso se escribe el shellcode descifrado y se reanuda la ejecución.

Agradecimientos:

  • A @ElephantSe4l y a @MarioBartolome por un gran método de recuperación dinámica de números de syscall y, en general, por todo el proyecto del que he tomado una gran inspiración.
  • A @cr-0w por el increíble artículo de blog y video que discuten syscalls directos e indirectos.
  • A bordergate por el artículo que describe el método inicial de evasión.

Descargo de responsabilidad

Esta librería fue creada únicamente con fines académicos. Los autores no son responsables de lo que se haga con esta librería y, por lo tanto, quedamos exentos de cualquier responsabilidad derivada de su mal uso.

Descargar herramienta