Wie verstecken sich Malware-Programme in Windows?

Einige der schwierigsten Angriffstechniken zielen nicht darauf ab, die Verteidigung zu durchbrechen. Sie nutzen legitime Windows-Funktionen, um ihre Absichten vor Sicherheitstools zu verbergen – und machen die Erkennung zu einem Analyseproblem statt zu einer Frage automatischer Detection.

In der folgenden Analyse beschreiben wir, wie moderne Malware Techniken implementiert, die in einigen Fällen Endpoint-Security-Lösungen (EDR) zu umgehen, ermöglichen – und was konkret nötig ist, um sie abzufangen. Vor der detaillierten Analyse der einzelnen Techniken einige Grundbegriffe, die das Verständnis des restlichen Artikels erleichtern.

Malware Development: Grundbegriffe

Payload – das Herzstück jeder Malware

Das erste Konzept, das es zu klären gilt, ist der Payload – der Teil, der bestimmt, was eine malware wirklich tut. Wörtlich übersetzt bedeutet es „Nutzlast“; der Begriff stammt aus der Raumfahrt, wo er bezeichnet, was ein Träger zum Ziel transportiert. Im Malware-Kontext ist der Sinn identisch: der Payload ist die Komponente, die die schädliche Aktion auf dem Zielsystem ausführt, zu unterscheiden vom Träger, der ihn ausliefert (ein E-Mail-Anhang, ein Exploit, ein Dropper).

Konkret kann ein Payload viele Formen annehmen:

  • eine ausführbare Datei
  • ein in den Speicher injizierter Shellcode
  • ein Skript (PowerShell, VBS, Python, JavaScript)
  • ein Befehl oder eine Befehlssequenz

Wir sprechen bewusst von „schädlicher Aktion“ und nicht von „Schaden“, weil ein Payload nicht zwingend etwas zerstört. In vielen Fällen ist er schlicht ein PowerShell-Befehl oder ein VBS-Skript, das etwas anderes herunterlädt und ausführt – zum Beispiel AnyDesk oder eine andere legitime Fernzugriffssoftware, die als Backdoor genutzt wird. Für das Opfer gibt es keine verdächtige „Virus-Datei“ zu finden, und der eigentliche Payload ist eine vollständig legitime Software, die illegal eingesetzt wird.

Shellcode – reiner Maschinencode

Shellcode ist ein kleiner Block Maschinencode (Instruktionen, die direkt von der CPU ausgeführt werden können), der dafür konzipiert ist, direkt in den Speicher eines Prozesses injiziert und dort ausgeführt zu werden.

Der Name leitet sich vom ursprünglichen Zweck ab: eine Shell (eine Eingabeaufforderung) über eine Schwachstelle zu öffnen. Heute bezeichnet der Begriff allgemein jeden Code dieser Art, unabhängig von seiner Funktion.

Der Unterschied zu einer normalen ausführbaren Datei: Shellcode ist keine .exe-Datei mit der üblichen Struktur – er ist eine rohe Bytesequenz, zum Beispiel:

Er wird nicht vom Betriebssystem gestartet, sondern in einen Speicherbereich geschrieben, auf den die Ausführung dann „umgeleitet“ wird. In der Praxis ist Shellcode:

  • Klein – oft nur wenige Hundert Byte, weil er in begrenzten Speicherbereichen Platz finden muss.
  • Eigenständig – weil er sich nicht auf das normale Laden von Bibliotheken stützen kann.
  • Oft „verschleiert“ (encoded), um Bytes zu vermeiden, die ihn blockieren würden – wie das Null-Byte „\x00“.

Im Verhältnis zum Payload ist Shellcode sehr häufig selbst der Payload – oder sein erster Teil: das initiale Fragment, das nach der Ausführung den Rest herunterlädt oder entschlüsselt.

Syscall – die Brücke zwischen Programm und Kernel

Eine Syscall (oder System Call) ist der Mechanismus, über den ein Programm den Kernel bittet, eine Operation in seinem Namen auszuführen. Eine Datei öffnen, Speicher reservieren, einen Prozess erstellen, auf die Festplatte schreiben: all das kann ein Programm nicht selbst erledigen – es muss den Kernel über eine Syscall darum bitten.

Der Grund: Windows (wie jedes moderne Betriebssystem) unterteilt die Ausführung in zwei „Welten“: User mode, in dem normale Programme mit eingeschränkten Rechten laufen, und den Kernel mode, in dem der Kern des Betriebssystems mit vollem Zugriff auf Hardware und Speicher läuft. Die Syscall ist die Brücke, die den kontrollierten Übergang von der ersten in die zweite ermöglicht.

Unter Windows wird eine Syscall fast nie direkt aufgerufen. Der typische Weg ist mehrschichtig; hier ist zu sehen, was eine einfache Dateierstellung unter Windows auslöst:

Das Programm ruft eine „freundliche“ Funktion wie CreateFile auf, die ihrerseits eine Nt*-Funktion in „ntdll.dll“ aufruft – und erst diese führt die eigentliche Syscall-Instruktion aus, die in den Kernel mode führt.
Dieses Detail ist wichtig, weil viele Antivirenprogramme und EDR-Lösungen genau auf diese Zwischenschichten setzen: Sie platzieren ihre „Haken“ (Hooks) in „ntdll.dll“, um zu überwachen, was ein Programm tut, bevor die Syscall den Kernel erreicht. Daraus entstehen offensive Techniken wie die Direct Syscalls, die die Zwischenschichten überspringen und die Instruktion „syscall“ direkt aufrufen – und den EDR vor einem Eingang stehen lassen, durch den niemand mehr geht.

API Hooking – die Technik zum Abfangen von Aufrufen

API Hooking ist die Technik, mit der man einen Funktionsaufruf abfängt, zum eigenen Code umleitet und dann (optional) weiter zur ursprünglichen Zieladresse laufen lässt.

Es ist wie ein Privatdetektiv mitten in einem Chat: Der Absender schickt eine Nachricht, der Detektiv hört zu (und ändert sie vielleicht), leitet den Aufruf dann weiter. Keiner der beiden weiß, was dazwischen passiert ist.

Im Windows-Kontext ist der häufigste Ansatzpunkt genau „ntdll.dll“. Ein EDR oder Antivirenprogramm verändert die ersten Instruktionen einer Funktion wie „NtCreateFile“ oder „NtAllocateVirtualMemory“ und fügt einen Sprung („JMP“) zum eigenen Code ein.

Wirkt der Aufruf verdächtig, kann der EDR ihn blockieren, protokollieren oder den Prozess beenden. Wirkt er legitim, lässt er ihn passieren.

Dieser Mechanismus wird jedoch auch offensiv eingesetzt:

  • Malware kann Funktionen hooken, um sich zu verstecken – etwa indem er Aufrufe abfängt, die laufende Prozesse auflisten, und sich selbst aus der Liste entfernt.
  • Malware kann Funktionen hooken, um Daten zu stehlen – etwa indem er Leseaufrufe eines Browsers abfängt und Passwörter oder Tokens vor der Verschlüsselung abgreift.

Die Kehrseite aus defensiver Perspektive: Hooks sind erkennbar. Ein Programm kann überprüfen, ob die ersten Instruktionen einer Funktion in „ntdll.dll“ gegenüber dem, was auf der Festplatte liegt, verändert wurden. Diese Technik nennt sich Unhooking – und schließt den Kreis mit den Direct Syscalls: Beide entstammen derselben Anforderung, nämlich die Kontrollen in den Zwischenschichten zu umgehen.

Evasionstechniken

Die im Folgenden beschriebenen Techniken werden häufig in Kette eingesetzt (Multi-Stage-Ansatz): Jede Phase zielt darauf ab, die Sichtbarkeit zu verringern, die Analyseresistenz zu erhöhen und das Erstellen wirksamer Detection/Prevention-Regeln zu erschweren. Sie werden selten einzeln eingesetzt: Das prägende Merkmal moderner Malware ist genau die Multi-Stage-Komplexität, bei der eine Reihe von Techniken und Schritten ausgeführt wird, um die Analyse zu erschweren und damit eine wirksame Erkennung durch Regeln zu verhindern.

IAT Hiding

Die IAT (Import Address Table) ist ein Bereich in jeder ausführbaren Windows-Datei, der alle externen Funktionen auflistet, die das Programm verwendet – Funktionen, die nicht im Programm selbst geschrieben sind, sondern zu Systembibliotheken gehören wie „kernel32.dll” oder “user32.dll“ ausgeführt.

Ein konkretes Beispiel: Will eine Software einen Pop-up auf dem Desktop anzeigen, erfindet sie das Rad nicht neu – sie ruft schlicht „MessageBox“ auf, eine Funktion der Windows API. Damit wird diese Funktion als Abhängigkeit in der IAT erfasst – und ist dort für jeden lesbar, der sie sucht.

Das Problem aus Sicht eines Malware-Entwicklers: Einige WinAPI-Funktionen sind sofort verdächtig. Nehmen Sie „CreateRemoteThread“: Schon beim Namen ist es schwer, sie in legitimer Software zu rechtfertigen. Diese Funktion erstellt einen Thread innerhalb eines Prozesses, der nicht dem Aufrufer gehört – ein klassisches Werkzeug zur Code-Injektion. Taucht sie in der IAT eines unbekannten Programms auf, ist das für jeden Analysten und jede Sicherheitslösung sofort eine rote Flagge.

Deshalb versuchen Malware-Entwickler, ihre Imports zu verstecken – damit verdächtige Funktionen nicht im Klartext in der IAT erscheinen. Das ist IAT Hiding. Nachfolgend ein Beispielschema der IAT von „chrome.exe“:

Die Struktur ist typisch für eine ausführbare Datei: Jeder Eintrag entspricht einer deklarierten externen Funktion, die jedes Analysetool lesen kann.

Dynamic Function Name Resolution

Eine der verbreitetsten Implementierungen ist die Dynamic Function Name Resolution: die Funktion nicht zur Compile-Zeit zu importieren, sondern sie stattdessen dynamisch zur Laufzeit aufzulösen. Nachfolgend ein minimales Beispiel, wie eine bestimmte Funktion vor der IAT verborgen werden kann:

Die logischen Schritte sind folgende:

  • Die Signatur der Funktion definieren und einen eigenen Typ erstellen („fnMessageBoxW“)
  • Die DLL laden, die sie enthält, mit „LoadLibraryA”
  • Die Adresse der Funktion auflösen mit „GetProcAddress”
  • Die Funktion über den Zeiger aufrufen

Dieses Beispiel zeigt, wie die Funktion „MessageBoxW“ vor der IAT verborgen werden kann. Verwendet man das Microsoft-Tool „dumpbin.exe“ auf die kompilierte ausführbare Datei, erhält man eine Import Address Table ähnlich der folgenden:

Wie das Schema zeigt, verrät kein Eintrag der IAT, dass die Funktion „MessageBoxW“ aufgerufen wurde. Nimmt man eine Änderung am Code vor, lässt sich sehen, wie die IAT aussehen würde, wenn die Funktion nicht dynamisch aufgelöst wird.

Zum Vergleich: So sieht die IAT-Struktur aus, wenn die Funktion auf herkömmliche Weise importiert wird:

Auf diese Weise erscheint die aufgerufene Funktion nicht in der IAT der kompilierten ausführbaren Datei. Analysten und Sicherheitssysteme finden bei der statischen Analyse keine Spur des verdächtigen Imports.

IAT Hiding in seiner Grundform zielt darauf ab, möglichst viele Funktionen vor den Augen von Analysten und Abwehrsystemen zu verbergen. Es handelt sich um eine überwiegend passive Technik, die sichtbare Signale reduziert, noch bevor die Malware eine Aktion ausführt.

Schutz vor IAT Hiding

Die statische Analyse der Imports reicht nicht aus: Eine ausführbare Datei mit sauberer IAT kann Dutzende sensibler Funktionen verbergen. Wirksame Abwehr verlagert sich auf das Laufzeitverhalten: Aufrufe von LoadLibrary und GetProcAddress überwachen, die dynamische Auflösung verdächtiger Funktionen mit den nachfolgenden Aktionen des Prozesses korrelieren und ein Gesamtbild erstellen, das das Einzelereignis nicht erkennen lässt.

Direct Syscall und Indirect Syscall

Anders als IAT Hiding, das eine überwiegend passive Technik ist, sind Direct und Indirect Syscalls proaktiv und kontinuierlich: Sie kommen jedes Mal zum Einsatz, wenn die Malware eine sensible Operation durchführen muss – während der gesamten Ausführungsdauer.

Ausgangspunkt ist das, was wir im Abschnitt zu Syscalls gesehen haben: EDR-Lösungen platzieren ihre Hooks in „ntdll.dll“, um Aufrufe abzufangen, bevor sie den Kernel erreichen. Die Frage eines Malware-Entwicklers ist simpel: Was wäre, wenn wir „ntdll.dll“ vollständig überspringen würden?

Genau das ist die Idee hinter Direct Syscalls: Statt durch „ntdll.dll“ und seine Hooks zu gehen, führt die Malware die Instruktion „syscall“ direkt aus und gibt manuell die Kennnummer der aufzurufenden Funktion an (den sogenannten SSN, Syscall Service Number). Der EDR sieht nichts, weil sein Hook nie durchquert wurde.

Indirect Syscalls entstanden als Weiterentwicklung derselben Idee, mit einem feinen, aber wichtigen Unterschied: Statt die Instruktion „syscall“ im eigenen Code auszuführen (was manche EDR erkennen können, indem sie analysieren, woher sie aufgerufen wird), springt die Malware direkt zur Instruktion „syscall“, die bereits in „ntdll.dll“ vorhanden ist, hinter dem Punkt, an dem der Hook installiert ist. Das Ergebnis ist dasselbe, aber die Ausführung wirkt für Überwachungssysteme legitimer.

Direct Syscall

Der von uns geschriebene Code wird vom Compiler in Assembly umgewandelt, und auch ein System Call hat seine Definition in Assembly. Grundlegend hat ein System Call eine ähnliche Struktur wie diese:

Die Zahl 6 ist als SSN (System Call Service Number) definiert: Sie gibt an, welcher System Call ausgeführt wird, wenn die Instruktion „syscall” aufgerufen wird.

Die folgende Tabelle zeigt die Entsprechung zwischen einigen Nt*-Funktionen und den jeweiligen SSNs für eine bestimmte Windows-Version, als Beispiel:

Es gibt jedoch ein Problem: Selbst eine einzelne Minor-Version von Windows kann die SSNs verändern. Es gibt zwei Optionen: die Malware auf eine bestimmte Windows-Version ausrichten oder den SSN-Wert dynamisch durch Lesen von „ntdll.dll“ zur Laufzeit auflösen. Die zweite Option wird häufiger verwendet, weil sie den Code auf verschiedene Betriebssystemversionen portierbar macht.

Komplikationen: Auch wenn diese Methode narrensicher erscheint, hat sie einen bedeutenden Schwachpunkt. Eine „normale“ Software ruft diese Operationen über die APIs auf (und damit über den Kontext von „ntdll.dll“). Bei Direct Syscalls hingegen führt unsere ausführbare Datei die Instruktionen direkt aus – was bei fortgeschritteneren EDR-Lösungen Alarm auslösen kann.

Indirect Syscall

Die zweite Methode ähnelt der ersten, aber statt die Instruktion „syscall“ aus unserer ausführbaren Datei auszuführen, lassen wir sie von „ntdll.dll“ ausführen. Wie? Per Sprung (über „JMP“) direkt zur Instruktion „syscall“, die bereits in „ntdll.dll“ vorhanden ist, hinter dem Punkt, an dem der Hook installiert wurde.

Dies ermöglicht:

  • Jeden API-Hook zu umgehen, den der EDR möglicherweise installiert hat.
  • Die Instruktion so erscheinen zu lassen, als würde sie aus dem Kontext von „ntdll.dll” ausgeführt.

Das folgende Schema veranschaulicht den vollständigen Ablauf der Technik mit dem direkten Sprung zur Syscall-Instruktion innerhalb von ntdll.dll:

Der Sprung beschränkt sich nicht auf „ntdll.dll“: Man kann auf jede Instruktion „syscall“ in jeder System-DLL abzielen.

Die Technik bereitet einfachen EDR-Lösungen keine Probleme, aber fortgeschrittenere EDR-Lösungen verfügen über Kernel-Level-Module oder überwachen die vom ETW (Event Tracing for Windows) bereitgestellten Ereignisse – wie etwa Cortex XDR und SentinelOne XDR.

Schutz vor Direct und Indirect Syscalls

Der kritische Punkt ist die Verlagerung des Monitorings auf Kernel-Ebene, wo Syscalls im User-Mode nicht umgangen werden können. ETW-Telemetrie erzeugt jedoch enorme Ereignisvolumen: Legitime Aktivität von einer ausgeklügelten Evasion zu unterscheiden erfordert die Fähigkeit, Verhaltenssequenzen über die Zeit zu lesen – nicht das Einzelereignis. Zu überprüfen, ob die Instruktionen in „ntdll.dll“ im Speicher, mit denen auf der Festplatte übereinstimmen – die sogenannte Unhooking Detection – ist ein weiterer Kontrollpunkt, der zu einer eingehenden Infrastrukturanalyse gehört.

GPU-Entschlüsselung: den Payload dort verstecken, wo niemand hinschaut

Die meisten Payload-Verschlüsselungstechniken laufen vollständig im RAM des Prozesses ab: Der verschlüsselte Code wird geladen, entschlüsselt und am selben Ort ausgeführt. Moderne EDR-Lösungen wissen das – und haben genau dort ihre Augen.

Die Idee hier ist eine andere: die Entschlüsselungsphase auf die GPU zu verlagern, so dass der verschlüsselte Payload im Binär verbleibt und derKlartext nie im Heap oder in vom Userland-Code verwalteten Puffern erscheint.

Wie die GPU-Entschlüsselung funktioniert

Der Ablauf besteht aus drei klar getrennten Phasen.

  1. Der verschlüsselte Payload verbleibt unbewegt im Binär. Ciphertext und Schlüssel liegen im Abschnitt „.data“ der ausführbaren Datei, verschlüsselt. Eine statische Dateianalyse findet nichts Lesbares.
  2. Die GPU entschlüsselt im Stillen. Über DirectX 11 werden Ciphertext und Schlüssel in den VRAM geladen. Ein Compute-Shader führt die Entschlüsselung vollständig auf der GPU durch – jenseits der Reichweite der User-Mode-Hooks, mit denen EDR-Lösungen die Standard-Krypto-APIs von Windows überwachen („BCryptDecrypt“, „CryptUnprotectData” usw.).
  3. Das Ergebnis kehrt erst zum Ausführungszeitpunkt in den RAM zurück. Der entschlüsselte Klartext wird aus dem VRAM in eine einzige Speicherseite kopiert, die nur in diesem Moment als ausführbar markiert und sofort nach der Ausführung gelöscht wird.

Warum die Technik relevant ist

Die Technik hat zwei spezifische Merkmale, die sie aus defensiver Forschungsperspektive interessant machen: Die Entschlüsselung findet in einem Kontext statt, den herkömmliche Tools nicht aktiv überwachen (VRAM und Grafiktreiber), und der Klartext des Payloads passiert nie den Heap des Prozesses – was die dem Arbeitsspeicher-Scanner ausgesetzte Fläche reduziert.

Die Haupteinschränkung ist, dass das Readback vom VRAM in den RAM unvermeidlich ist: Früher oder später muss der Code in den Systemspeicher zurückkehren, um ausgeführt zu werden. Das Ziel ist nicht, diesen Schritt zu eliminieren, sondern die Zeit zu minimieren, in der der Payload im Klartext exponiert ist.

Was das für Verteidiger bedeutet

Techniken wie diese treiben zu einem tieferen Monitoring voran – auf Ebene des Grafiktreibers und DXGI/D3D-Aufrufe –, was heute noch eine von der Standard-EDR-Telemetrie kaum abgedeckte Fläche darstellt. Sie zu kennen ist der erste Schritt zur Entwicklung wirksamer Gegenmaßnahmen.

Schutz vor GPU-Entschlüsselung

Der Payload muss vor der Ausführung in den RAM zurückkehren: Das ist der unvermeidliche Schritt, auf den sich die Abwehr konzentriert. Speicherseiten zu erkennen, die zur Laufzeit über VirtualProtect als ausführbar markiert werden, unerwartete Transfers zwischen VRAM und Systemspeicher zu überwachen und DirectX-API-Aufrufe in anomalen Prozesskontexten im Blick zu behalten – das sind die wirksamsten Kontrollen. Dies ist ein Bereich, der tiefe Sichtbarkeit auf die Infrastruktur und Analysekompetenz erfordert, die über die Standard-Telemetrie hinausgeht.

Der gemeinsame Faden: Warum kontinuierliches Monitoring notwendig ist

Die drei in diesem Artikel beschriebenen Techniken haben eines gemeinsam: Sie sind darauf ausgelegt, normal zu wirken. Sie nutzen legitime Windows-Funktionen, operieren in den Schichten, die automatische Tools am schwersten überwachen können, und erzeugen Signale, die isoliert betrachtet keinen Alarm auslösen.

Die Antwort ist kein besseres Tool, sondern kontinuierliche menschliche Analyse der Telemetrie, die Fähigkeit, zeitlich und infrastrukturell weit entfernte Ereignisse zu korrelieren, und die Kompetenz, Muster zu erkennen, bevor sie zu Vorfällen werden. Diese Kompetenz wird auch durch kontinuierliche Cyber Threat Intelligence-Aktivität aufgebaut, die offensive Forschung – wie die in diesem Artikel beschriebene – in operative Indikatoren übersetzt, die das tägliche Monitoring speisen. Ein Security Operations Center (SOC) aktiv rund um die Uhr mit Kernel-Level-Sichtbarkeit und spezialisierten Analysten ist heute die einzige operative Antwort, die die gesamte Angriffsfläche abdecken kann, die diese Techniken ausnutzen.

Referenzimplementierung

Eine vollständige Implementierung der in diesem Artikel beschriebenen Technik, einschließlich des Host-Codes in C und des HLSL-Compute-Shaders, ist auf GitHub verfügbar: dovelus/GPU-RC4-Payload-Decryption