Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
NullGate — Bibliothek, die die Verwendung indirekter Syscalls erleichtert. Ziemlich interessanter AV/EDR-Bypass als PoC. | Kitploit
Tools/GitHubGitHub/0xsch1zo/nullgate
Verschlüsselungs-/EntschlüsselungstoolsShellcodePost-ExploitationRed TeamingPayload-EntwicklungAdversarial-Angriff
GitHub0xsch1zo/nullgate

NullGate

Bibliothek, die die Verwendung indirekter Syscalls erleichtert. Ziemlich interessanter AV/EDR-Bypass als PoC.

Repository anzeigen
168192vor 1 JahrVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

NullGate

Dieses Projekt implementiert eine komfortable und moderne Möglichkeit, die NTAPI-Funktionen mithilfe indirekter Syscalls zu verwenden, gekoppelt mit der FreshyCalls-Methode mit einer kleinen Abwandlung zur dynamischen Syscall-Nummernabrufung. Es verwendet außerdem eine Technik, von der ich nicht gesehen habe, dass sie erwähnt wird, um den Speicherscan von Windows Defender zu umgehen. Das Projekt implementiert einen klassischen PoC-Prozess-Injektor, der die Bibliothek verwendet. Praktische Funktionen zur Verschlüsselung sind ebenfalls verfügbar.

Demo

Demonstration of the sample

Erstellen

Zum Erstellen des Beispiels verwende -DNULLGATE_BUILD_SAMPLE=ON. Wenn du nullgate direkt gebaut hast, ist es unter <build_dir>/sample.exe verfügbar; wenn du es als Abhängigkeit gebaut hast, unter <build_dir>/_deps/nullgate-build/sample.exe. Unter Windows sind die Build-Ziele seltsam, daher wird es sich wahrscheinlich in denselben Basisverzeichnissen wie die Speicherorte der Beispiele befinden, aber wahrscheinlich noch ein paar Ebenen tiefer verschachtelt. Es benötigt als Argument eine PID, in die du Shellcode injizieren möchtest.

[!WARNING] Wenn du Linux verwendest, musst du den MinGW-Cross-Compiler installiert haben. Auf Arch kannst du zum Beispiel pacman -S mingw-w64-gcc ausführen. Verwende dann die Option -DNULLGATE_CROSSCOMPILE=ON, um mingw als Standard-Compiler festzulegen.

[!TIP] Es wird außerdem empfohlen, die resultierende Binärdatei zu strippen, um die Erkennungswahrscheinlichkeit zu verringern.

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

Veralteter hashser (Versionen 1.1.3 und darunter)

Es kann mit dem Flag -DNULLGATE_DEPRECATED_HASHER gebaut werden.

Verwendung

Hinzufügen von nullgate zu deinem Projekt

CMake FetchContent wird unterstützt. Hier ist ein Beispiel für eine einfache 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
)

Die Verknüpfung erfolgt statisch, sodass du dir keine Sorgen um sichtbare Symbole machen musst.

[!NOTE] Die folgenden Beispiele verwenden namespace ng = nullgate

Syscalls

Die Verwendung ist ziemlich einfach. Hier ist ein Codeausschnitt, der die Hauptfunktionalität demonstriert:

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

Es gibt eine eingebaute Typsicherheit. Du musst nur die Definition der NT-Funktion bereitstellen, die du aufrufen möchtest! Du kannst sie leicht von ntdoc bekommen. Dies ist der empfohlene Weg, die Bibliothek zu verwenden.

Die bisherige, nicht veraltete Schnittstelle

Für Leute, die die C++-Template-Schwarze Magie oder so etwas nicht mögen, ist die bisherige Schnittstelle weiterhin verfügbar:

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

Bei dieser Schnittstelle musst du die Argumente in den richtigen Typ casten. Wenn du das nicht tust, kann das zu Problemen führen.

Verschlüsselung/Hashing

Hashing von NTAPI-Aufrufen

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

Die zuvor gezeigte fnv1Const-Methode bringt die Freuden des modernen C++ in die Maldev-Welt. Sie ist eine consteval-Funktion, sodass garantiert ist, dass sie zur Kompilierzeit ausgewertet wird und den lesbaren Funktionsnamen durch einen fnv1-Hash ersetzt.

Es gibt auch ein Laufzeit-Äquivalent namens fnv1Runtime, aber natürlich bietet es nicht den Vorteil, dass unsere Funktionsnamen verschleiert werden. Es wird von der Implementierung verwendet, um zu prüfen, welche Funktion innerhalb von ntdll die Syscall-Nummer liefert.

Allgemeine xor-Verschlüsselung

Es gibt drei Routinen für die xor-„Verschlüsselung“:

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

Mögliche Ausgabe:

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

Diese Routine wird ähnlich wie die fnv1Const-Funktion zur Kompilierzeit ausgewertet, sodass "some string" niemals in der Binärdatei erscheint, weil der String in seiner verschlüsselten Form gespeichert wird. Aber hey, wir müssen diese Daten tatsächlich nutzen! ConstData ist ein dünner Wrapper um rohe Bytes mit konstanter Größe. Es hat zwei Methoden: raw() und string(). raw() gibt ein std::vector<unsigned char> zurück und string gibt, wie der Name schon sagt, ein std::string zurück.

[!NOTE] Falls du ConstData direkt mit einem String-Literal konstruieren musst, verwende std::to_array, um ein Zwischenarray zu erstellen, das an ConstData übergeben wird. Beachte jedoch, dass ConstData bei diesem Ansatz das zusätzliche Nullzeichen des Literals speichert.

Natürlich brauchen wir eine Möglichkeit, dies zu entschlüsseln und zu verwenden, was die nächste Funktion ist, die wir behandeln werden.

xorRuntime

xorRuntime ist die zweite verfügbare Routine. Wie der Name schon sagt, ist es das Laufzeit-Äquivalent von xorConst. Bitte beachte, dass es nicht nur zur Entschlüsselung verwendet werden muss; es funktioniert in beide Richtungen, obwohl die Verwendung zur Verschlüsselung nicht den Vorteil bietet, den String zur Kompilierzeit zu verschleiern.

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 hat dieselben Methoden wie ConstData, mit einigen zusätzlichen Konstruktoren für die Interoperabilität, und kann, wie der Name schon sagt, eine Größe haben, die zur Kompilierzeit nicht bekannt ist. xorRuntime akzeptiert sowohl DynamicData als auch ConstData.

xorRuntimeDecrypted

Obwohl wir immer zur Laufzeit entschlüsseln könnten, indem wir zuerst xorConst aufrufen und das Ergebnis an xorRuntime übergeben, ist das ziemlich mühsam. Hier kommt die Lösung für dieses Problem, das i-Tüpfelchen, nämlich xorRuntimeDecrypted. Es ist eine praktische Funktion für String-Literale, die nicht bearbeitet werden müssen, solange sie sich im verschlüsselten Zustand befinden. Es verschlüsselt das String-Literal zur Kompilierzeit und entschlüsselt es dann zur Laufzeit – alles in einem Aufruf. Ist das nicht cool!

root@kitploit:~
ng::obfuscation::ConstData text = ng::obfuscation::xorRuntimeDecrypted<"some string">();
assert(data.string() == "some string");
Der Schlüssel

Jetzt könnte jemand sagen: Alles ist großartig, aber wo ist der Schlüssel? In nullgate 1.2 wird der Schlüssel bei jedem frischen Build zufällig generiert! (d. h. wenn der cmake-Befehl ausgeführt wird) Das verringert die Chance, per Signatur erkannt zu werden, noch weiter.

Umgehung des Windows-Defender-Speicherscans

Der Kern des Problems ist, dass beim Aufruf von NtCreateRemoteThreadEx oder NtCreateProcess ein Speicherscan ausgelöst wird und unser bis zum Anschlag signiertes msfvenom-Payload erkannt wird.

Wie kann man das umgehen?

Eine bekannte Lösung besteht darin, beim Aufruf von NtAllocateVirtualMemory zunächst die Seitenberechtigungen auf PAGE_NOACCESS zu setzen und den Thread dann in einem angehaltenen Zustand zu erstellen. Wenn Windows Defender den Speicher unseres Prozesses scannt, wird ihm das nicht gelingen. Wir können dann die Ausführung unseres Threads mit NtResumeThread fortsetzen. Das funktioniert, aber was, wenn eine kompetentere Sicherheitslösung verwendet wird? Was würde sie tun? Sie würde natürlich einfach VirtualProtect verwenden, um die Berechtigungen unserer Seite zu ändern und msfvenom zu erkennen. Um das zu umgehen, habe ich die Strategie etwas geändert. Anstatt die Seite auf PAGE_NOACCESS zu setzen, können wir beim ersten Schreiben in den Speicher des Prozesses einfach ein paar Junk-Daten in den Prozess schreiben (Ja, das ist erforderlich, oder ich bin einfach zu dumm, einen Weg zu finden, es ohne das zum Laufen zu bringen). Dann erstellen wir einen Thread im angehaltenen Zustand. Danach schreiben wir unseren gewünschten Shellcode in den Prozess und setzen schließlich den Thread mit NtResumeThread fort. Mit dieser Technik müssen wir uns keine Sorgen machen, dass nach dem Aufruf von NtCreateThreadEx auf unseren Speicher zugegriffen wird, weil dort nichts drin ist. Erst danach wird der entschlüsselte Shellcode geschrieben und die Ausführung fortgesetzt.

Danksagungen:

  • An @ElephantSe4l und @MarioBartolome für eine großartige Methode zur dynamischen Ermittlung von Syscall-Nummern und allgemein für das gesamte Projekt, von dem ich große Inspiration genommen habe.
  • An @cr-0w für den großartigen Blogbeitrag und das Video, die direkte und indirekte Syscalls behandeln.
  • An bordergate für den Artikel, der die ursprüngliche Methode des Bypasses beschreibt.

Haftungsausschluss

Diese Bibliothek wurde ausschließlich für akademische Zwecke erstellt. Die Autoren sind nicht verantwortlich für das, was mit dieser Bibliothek gemacht wird, und daher sind wir von jeglicher Haftung befreit, die aus dem Missbrauch entsteht.

Tool herunterladen