Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
NullGate — Bibliothèque qui facilite l'utilisation des syscalls indirects. Contournement AV/EDR assez intéressant en tant que PoC. | Kitploit
Outils/GitHubGitHub/0xsch1zo/nullgate
Outils de Chiffrement/DéchiffrementShellcodePost-ExploitationRed TeamingDéveloppement de Charges UtilesAttaque Adversariale
GitHub0xsch1zo/nullgate

NullGate

Bibliothèque qui facilite l'utilisation des syscalls indirects. Contournement AV/EDR assez intéressant en tant que PoC.

Voir le dépôt
16819il y a 1 anVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

NullGate

Ce projet implémente une manière confortable et moderne d'utiliser les fonctions NTAPI via des appels système indirects, couplée à la méthode FreshyCalls avec une petite variante pour la récupération dynamique des numéros d'appels système. Il utilise également une technique que je n'ai pas vu être mentionnée pour contourner l'analyse mémoire de Windows Defender. Le projet implémente un injecteur de processus PoC classique utilisant la bibliothèque. Des fonctions pratiques pour le chiffrement sont également disponibles.

Démo

Démonstration de l'exemple

Compilation

Pour compiler l'exemple, utilisez -DNULLGATE_BUILD_SAMPLE=ON. Si vous avez compilé nullgate directement, il sera accessible à <build_dir>/sample.exe, si vous l'avez compilé en tant que dépendance à <build_dir>/_deps/nullgate-build/sample.exe. Sous Windows, comme les destinations de compilation sont étranges, il sera probablement dans les mêmes répertoires de base que les emplacements des exemples, mais probablement avec beaucoup plus de niveaux d'imbrication. Il prend en argument un PID dans lequel vous souhaitez injecter du shellcode.

[!WARNING] Si vous utilisez Linux, vous devez avoir le compilateur croisé mingw installé. Sur Arch par exemple, vous pouvez faire pacman -S mingw-w64-gcc. Utilisez ensuite l'option -DNULLGATE_CROSSCOMPILE=ON pour définir mingw comme compilateur par défaut.

[!TIP] Il est également recommandé de stripper le binaire résultant afin de réduire les possibilités de détection

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

hashser obsolète (versions 1.1.3 et inférieures)

Il peut être compilé à l'aide de l'option -DNULLGATE_DEPRECATED_HASHER.

Utilisation

Ajouter nullgate à votre projet

Le CMake FetchContent est pris en charge. Voici un exemple de CMakeLists.txt simple :

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
)

La liaison est effectuée statiquement, vous n'avez donc pas à vous soucier de la visibilité des symboles.

[!NOTE] Les exemples suivants utiliseront namespace ng = nullgate

Appels système

L'utilisation est assez simple, voici un extrait illustrant la fonctionnalité principale :

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

La sécurité de types est intégrée. Vous avez simplement besoin de fournir la définition de la fonction NT que vous souhaitez appeler ! Vous pouvez facilement l'obtenir depuis ntdoc. C'est la manière recommandée d'utiliser la bibliothèque.

L'interface précédente non obsolète

Pour ceux qui n'aiment pas la magie noire du templating C++ ou quelque chose du genre, l'interface précédente est toujours 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);

Avec cette interface, vous devez caster les arguments vers le bon type ; ne pas le faire peut causer des problèmes.

Chiffrement/hachage

Hachage des appels NTAPI

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

La méthode fnv1Const démontrée précédemment apporte les joies du C++ moderne au monde du maldev. C'est une fonction consteval, donc il est garanti qu'elle sera évaluée au moment de la compilation, remplaçant le nom de fonction lisible par un hash fnv1.

Il existe également un équivalent à l'exécution appelé fnv1Runtime, mais bien sûr il n'apporte pas l'avantage d'obfusquer nos noms de fonctions. Il est utilisé par l'implémentation pour vérifier quelle fonction dans ntdll doit être utilisée pour obtenir le numéro d'appel système.

Chiffrement xor général

Il existe trois routines pour le « chiffrement » xor :

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

Sortie possible :

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

Cette routine, comme la fonction fnv1Const, est évaluée au moment de la compilation, donc "some string" n'apparaîtra jamais dans le binaire, car la chaîne sera stockée sous sa forme chiffrée. Mais bon, nous devons réellement utiliser ces données ! ConstData est une fine enveloppe autour d'octets bruts, de taille constante. Elle a deux méthodes : raw() et string(). raw() retourne un std::vector<unsigned char> et string, comme son nom l'indique, retourne un std::string.

[!NOTE] Si vous devez construire ConstData directement avec une chaîne littérale, utilisez std::to_array pour construire un tableau intermédiaire passé à ConstData. Veuillez toutefois noter qu'avec cette approche, ConstData stockera le caractère nul supplémentaire de la chaîne littérale.

Bien sûr, nous avons besoin d'un moyen de déchiffrer ces données et de les utiliser, c'est ce que couvre la fonction suivante.

xorRuntime

xorRuntime est la deuxième routine disponible. Comme son nom l'indique, c'est l'équivalent à l'exécution de xorConst. Veuillez noter qu'elle ne doit pas être utilisée uniquement pour le déchiffrement : elle fonctionne dans les deux sens, bien que l'utiliser pour le chiffrement n'apporte pas les avantages d'obfusquer la chaîne au moment de la compilation.

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 possède les mêmes méthodes que ConstData, avec quelques constructeurs supplémentaires pour l'interopérabilité, et comme son nom l'indique, elle peut avoir une taille non connue au moment de la compilation. xorRuntime accepte à la fois DynamicData et ConstData.

xorRuntimeDecrypted

Bien que nous puissions toujours déchiffrer à l'exécution en appelant d'abord xorConst puis en passant le résultat à xorRuntime, c'est un peu fastidieux. Voici la solution à ce problème, la cerise sur le gâteau : xorRuntimeDecrypted. C'est une fonction pratique pour les chaînes littérales qui n'ont pas besoin d'être manipulées tant qu'elles sont dans l'état chiffré. Elle chiffre la chaîne littérale au moment de la compilation puis la déchiffre à l'exécution en un seul appel. N'est-ce pas cool !.

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

Quelqu'un pourrait dire : tout est super, mais où est la clé ? Dans nullgate 1.2, la clé est générée aléatoirement à chaque nouvelle compilation ! (c'est-à-dire à chaque exécution de la commande cmake) Cela réduit encore davantage les risques d'être détecté par signature.

Contournement de l'analyse mémoire de Windows Defender

Le cœur du problème est que lorsque nous appelons NtCreateRemoteThreadEx ou NtCreateProcess, une analyse mémoire est déclenchée et notre payload msfvenom, signaturé à mort, est détecté.

Comment contourner cela ?

Une solution connue consiste d'abord, lors de l'appel à NtAllocateVirtualMemory, à définir les permissions de la page comme PAGE_NOACCESS, puis à créer le thread dans un état suspendu. Quand Windows Defender analysera la mémoire de notre processus, il échouera. Nous pouvons ensuite reprendre l'exécution de notre thread avec NtResumeThread. Cela fonctionne, mais que se passerait-il si une solution de sécurité plus compétente était utilisée ? Que ferait-elle ? Elle utiliserait bien sûr simplement VirtualProtect pour modifier les permissions de notre page et détecter msfvenom. Pour contourner cela, j'ai un peu changé la stratégie. Au lieu de définir la page comme PAGE_NOACCESS, lors de notre première écriture dans la mémoire du processus, nous pouvons simplement mettre des données factices dans le processus (oui, c'est nécessaire, ou alors je suis trop bête pour trouver un moyen de le faire fonctionner sans cela). Ensuite, nous créons un thread dans un état suspendu. Après cela, nous écrivons dans le processus notre shellcode souhaité et enfin nous reprenons le thread avec NtResumeThread. Avec cette technique, nous n'avons pas à nous soucier de l'accès à notre mémoire après l'appel à NtCreateThreadEx, car il n'y a rien dedans. Ce n'est qu'après coup que le shellcode déchiffré est écrit et que l'exécution est reprise.

Remerciements :

  • À @ElephantSe4l et @MarioBartolome pour une excellente méthode de récupération dynamique des numéros d'appels système et globalement pour tout le projet dont je me suis fortement inspiré.
  • À @cr-0w pour l'incroyable article de blog et vidéo traitant des appels système directs et indirects.
  • À bordergate pour l'article qui décrit la méthode initiale de contournement.

Avertissement

Cette bibliothèque a été créée à des fins académiques uniquement. Les auteurs ne sont pas responsables de ce qui est confié à cette bibliothèque et sont donc exonérés de toute responsabilité découlant de son utilisation abusive.

Télécharger l’outil