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
ThreadStackSpoofer — Thread Stack Spoofing – PoC für eine fortgeschrittene In-Memory-Verschleierungstechnik, die es ermöglicht, die Speicherallokation von eingeschleustem Shellcode besser vor Scannern und Analysten zu verbergen. | Kitploit
Tools/GitHubGitHub/mgeeky/threadstackspoofer
Penetrationstest-FrameworksExploit-FrameworksSpeicherforensikShellcodeMalware-AnalyseRed TeamingPayload-Entwicklung
GitHubmgeeky/threadstackspoofer

ThreadStackSpoofer

Thread Stack Spoofing – PoC für eine fortgeschrittene In-Memory-Verschleierungstechnik, die es ermöglicht, die Speicherallokation von eingeschleustem Shellcode besser vor Scannern und Analysten zu verbergen.

Repository anzeigen
1.2k1924vor 4 JahrenVon 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

Thread Stack Spoofing / Call Stack Spoofing PoC

Eine PoC-Implementierung einer fortschrittlichen In-Memory-Evasion-Technik, die den Thread Call Stack spoofed. Diese Technik ermöglicht es, thread-basierte Speicheruntersuchungsregeln zu umgehen und Shellcodes besser im Prozessspeicher zu verbergen.

Intro

Dies ist eine Beispielimplementierung für die Thread-Stack-Spoofing-Technik, die darauf abzielt, Malware-Analysten, AVs und EDRs zu umgehen, die nach Referenzen auf Shellcode-Frames in einem untersuchten Thread-Call-Stack suchen. Die Idee ist, Referenzen auf den Shellcode im Thread-Call-Stack zu verbergen und so Allokationen mit Malware-Code zu tarnen.

Die Implementierung zusammen mit meiner ShellcodeFluctuation bringt der Offensive-Security-Community Beispielimplementierungen, um mit dem Angebot kommerzieller C2-Produkte Schritt zu halten, damit wir in unseren Red-Team-Tools nicht schlechter dastehen. 💪

Implementation has changed

Die aktuelle Implementierung unterscheidet sich stark von der ursprünglich veröffentlichten. Das liegt daran, dass mir klar wurde, dass es einen viel einfacheren Ansatz gibt, um den Call-Stack-Prozess eines Threads zu beenden und shellcodebezogene Frames zu verstecken, indem man einfach 0 an die Rücksprungadresse des ersten Frames schreibt, den wir kontrollieren:

root@kitploit:~
void WINAPI MySleep(DWORD _dwMilliseconds)
{
    [...]
    auto overwrite = (PULONG_PTR)_AddressOfReturnAddress();
    const auto origReturnAddress = *overwrite;
    *overwrite = 0;

    [...]
    *overwrite = origReturnAddress;
}

Die vorherige Implementierung, die StackWalk64 nutzt, kann in diesem Commit c250724 eingesehen werden.

Diese Implementierung ist viel stabiler und funktioniert gut sowohl unter Debug als auch Release auf zwei Architekturen - x64 und x86.

Demo

So könnte ein Call-Stack aussehen, wenn er NICHT gespooft ist:

nicht-gespooft

Dies hingegen, wenn Thread-Stack-Spoofing aktiviert ist:

gespooft

Oben sehen wir, dass der letzte Frame in unserem Call-Stack unser MySleep-Callback ist. Man mag sich fragen, ob dies sofort neue IOCs mit sich bringt? Jagdregeln können nach Threads suchen, deren Call-Stacks nicht in die folgenden erwarteten Thread-Eintrittspunkte in Systembibliotheken entladen:

root@kitploit:~
kernel32!BaseThreadInitThunk+0x14
ntdll!RtlUserThreadStart+0x21

Der Call-Stack des gespooften Threads mag auf den ersten Blick seltsam aussehen, eine kurze Untersuchung meines Systems zeigte jedoch, dass es andere Threads gibt, die ebenfalls nicht zu den obigen Eintrittspunkten entladen:

legitimer Call-Stack

Der obige Screenshot zeigt einen Thread des unveränderten Total Commander x64. Wie wir sehen, ähnelt sein Call-Stack unserem in Bezug auf die initialen Call-Stack-Frames sehr.

Warum sollten wir uns darum kümmern, unseren Call-Stack sorgfältig zu fälschen, wenn es Prozesse gibt, die Eigenschaften zeigen, die wir einfach nachahmen können?

How it works?

Der grobe Algorithmus ist wie folgt:

  1. Lese den Inhalt des Shellcodes aus einer Datei.
  2. Beschaffe alle notwendigen Funktionszeiger aus dbghelp.dll, rufe SymInitialize auf.
  3. Hooke kernel32!Sleep, um auf unseren Callback zu verweisen.
  4. Injecte und starte den Shellcode via VirtualAlloc + memcpy + CreateThread. Der Thread sollte von unserer runShellcode-Funktion starten, um zu vermeiden, dass die StartAddress des Threads auf etwas Unerwartetes und Anomales zeigt (wie ntdll!RtlUserThreadStart+0x21).
  5. Sobald Beacon versucht zu schlafen, wird unser MySleep-Callback aufgerufen.
  6. Wir überschreiben dann die letzte Rücksprungadresse auf dem Stack mit 0, was effektiv den Call-Stack beenden sollte.
  7. Schließlich wird ein Aufruf von ::SleepEx getätigt, um den Beacon schlafen zu lassen, während auf weitere Kommunikation gewartet wird.
  8. Nachdem der Schlaf beendet ist, stellen wir die zuvor gespeicherten ursprünglichen Funktionsrücksprungadressen wieder her und die Ausführung wird fortgesetzt.

Funktionsrücksprungadressen sind über den gesamten Stack-Speicherbereich des Threads verstreut, auf den das RBP/EBP-Register zeigt. Um sie auf dem Stack zu finden, müssen wir zuerst Frame-Zeiger sammeln und sie dann dereferenzieren, um sie zu überschreiben:

Stack-Frame

(Das obige Bild wurde aus Eli Benderskys Beitrag namens Stack frame layout on x86-64 entliehen)

root@kitploit:~
	*(PULONG_PTR)(frameAddr + sizeof(void*)) = Fake_Return_Address;

Die anfängliche Implementierung von ThreadStackSpoofer tat dies in den Funktionen walkCallStack und spoofCallStack, jedoch zeigt die aktuelle Implementierung, dass diese Bemühungen nicht erforderlich sind, um einen verdeckten Call-Stack aufrechtzuerhalten.

Example run

Anwendungsfall:

root@kitploit:~
C:\> ThreadStackSpoofer.exe <shellcode> <spoof>

Wobei:

  • <shellcode> ein Pfad zur Shellcode-Datei ist
  • <spoof> bei 1 oder true das Thread-Stack-Spoofing aktiviert und alles andere deaktiviert es.

Beispiellauf, der den Thread-Call-Stack des Beacons spoofed:

root@kitploit:~
PS D:\dev2\ThreadStackSpoofer> .\x64\Release\ThreadStackSpoofer.exe .\tests\beacon64.bin 1
[.] Reading shellcode bytes...
[.] Hooking kernel32!Sleep...
[.] Injecting shellcode...
[+] Shellcode is now running.
[>] Original return address: 0x1926747bd51. Finishing call stack...

===> MySleep(5000)

[<] Restoring original return address...
[>] Original return address: 0x1926747bd51. Finishing call stack...

===> MySleep(5000)

[<] Restoring original return address...
[>] Original return address: 0x1926747bd51. Finishing call stack...

How do I use it?

Schauen Sie sich den Code und seine Implementierung an, verstehen Sie das Konzept und implementieren Sie es in Ihren eigenen Shellcode-Ladern, die Sie für Ihre Red-Team-Einsätze verwenden. Dies ist eine weitere Technik für fortgeschrittene In-Memory-Evasion, die die Chancen Ihres Teams erhöht, nicht von Antivirenprogrammen, EDRs und Malware-Analysten erwischt zu werden, die einen Blick auf Ihre Implants werfen.

Während der Entwicklung Ihres fortschrittlichen Shellcode-Laders möchten Sie vielleicht auch Folgendes implementieren:

  • Process Heap Encryption - lassen Sie sich von diesem Blogbeitrag inspirieren: Hook Heaps and Live Free - der es Ihnen ermöglicht, Beacon-Konfigurationsextraktoren wie BeaconEye zu umgehen.
  • Ändern Sie den Speicherschutz Ihrer Beacon-Seiten auf RW (von RX/RWX) und verschlüsseln Sie deren Inhalt - unter Verwendung der Shellcode Fluctuation-Technik - direkt vor dem Schlafen (das könnte Scanner wie Moneta oder pe-sieve umgehen).
  • Beseitigen Sie alle Überreste des Reflective Loaders, um signaturbasierte Erkennungen im Speicher zu vermeiden.
  • Entfernen Sie alle Hooks, die Sie möglicherweise gesetzt haben (wie AMSI, ETW, WLDP) vor dem Schlafen und setzen Sie sie danach wieder.

Actually this is not (yet) a true stack spoofing

Wie mir mitgeteilt wurde, wird die Technik hier ihrem Namen als Stack-Spoofer noch nicht wirklich gerecht. Da wir lediglich Rücksprungadressen auf dem Stack des Threads überschreiben, spoofen wir nicht die verbleibenden Bereiche des Stacks selbst. Darüber hinaus hinterlassen wir unseren Call-Stack unwindable, was ihn anomal erscheinen lässt, da das System nicht in der Lage sein wird, die gesamte Call-Stack-Frame-Kette ordnungsgemäß zu durchlaufen.

Ich bin mir dieser Mängel jedoch bewusst, habe es im Moment so belassen, da es mir hauptsächlich darum ging, automatisierte Scanner zu umgehen, die Prozesse durchlaufen, ihre Threads aufzählen, deren Stacks durchgehen und jede Rücksprungadresse erkennen könnten, die auf einen Nicht-Image-Speicher verweist (wie SEC_PRIVATE - den, der dynamisch von VirtualAlloc und Konsorten allokiert wird). Ein fokussierter Malware-Analyst würde die Seltsamkeit sofort erkennen und den Thread als eher ungewöhnlich betrachten und unser Implant aufspüren. Da bin ich mir mehr als sicher. Ich glaube jedoch nicht, dass heutige automatisierte Scanner wie AV/EDR Heuristiken implementiert haben, die tatsächlich den Stack jedes Threads durchgehen, um zu überprüfen, ob er entwindbar ist ¯\_(ツ)_/¯ .

Sicherlich gibt dieses Projekt (und die kommerzielle Implementierung in C2-Frameworks) AV- und EDR-Anbietern Argumente, darüber nachzudenken, geeignete Heuristiken zu implementieren, die eine solche neuartige Evasionstechnik abdecken.

Um diese Technik zu verbessern, kann man einen echten Thread-Stack-Spoofer anstreben, indem man sorgfältig erstellte falsche Stack-Frames einfügt, die in einem Reverse-Unwinding-Prozess eingerichtet werden. Lesen Sie mehr zu dieser Idee unten.

Implementing a true Thread Stack Spoofer

Ein stundenlanges Gespräch mit namazso lehrte mich, dass wir, um einen richtigen Thread-Stack-Spoofer zu erreichen, den x64-Call-Stack-Unwinding-Prozess umkehren müssten. Zunächst muss man den Stack-Unwinding-Prozess sorgfältig verstehen, der in (a) unten verlinkt erklärt wird. Das System verlässt sich beim Durchlaufen des Thread-Call-Stacks auf der x64-Architektur nicht einfach auf die über den Stack verstreuten Rücksprungadressen, sondern es:

  1. nimmt die Rücksprungadresse
  2. versucht, die Funktion zu identifizieren, die diese Adresse enthält (mit RtlLookupFunctionEntry)
  3. Diese Funktion gibt RUNTIME_FUNCTION, UNWIND_INFO und UNWIND_CODE-Strukturen zurück. Diese Strukturen beschreiben, wo die Anfangs- und Endadresse der Funktion sind und wo sich alle Codesequenzen befinden, die RBP oder RSP modifizieren.
  4. Das System muss über alle Stack- und Frame-Pointer-Änderungen Bescheid wissen, die in jeder Funktion über den Call-Stack hinweg stattgefunden haben, um dann diese Änderungen virtuell rückgängig zu machen und die Call-Stack-Pointer virtuell wiederherzustellen, wenn ein Aufruf an den verarbeiteten Call-Stack-Frame erfolgte (dies wird in RtlVirtualUnwind implementiert).
  5. Das System verarbeitet alle UNWIND_CODEs, die die untersuchte Funktion aufweist, um die genaue Position des Rücksprungadresse dieses Frames und den Stack-Pointer-Wert zu berechnen.
  6. Durch diese Emulation ist das System in der Lage, die Call-Stack-Kette hinabzusteigen und den Call-Stack effektiv zu "entwinden".

Um in diesen Prozess einzugreifen, müssten wir ihn umkehren, indem wir eine umgekehrte Form von RtlVirtualUnwind hätten. Wir müssten über die in einem Modul (sagen wir kernel32) definierten Funktionen iterieren, die UNWIND_CODE-Codes jeder Funktion scannen und sie rückwärts genau emulieren (im Vergleich zu RtlVirtualUnwind und genau RtlpUnwindPrologue), um Stellen auf dem Stack zu finden, an die wir unsere gefälschten Rücksprungadressen setzen können.

namazso erwähnt die Notwendigkeit, 3 gefälschte Stack-Frames einzuführen, um den Call-Stack sauber zusammenzunähen:

  1. Ein "Desync"-Frame (betrachten Sie es als Gadget-Frame), das sich anders entwindet als der Aufrufer unseres MySleep (mit einem anderen UWOP - Unwind-Operation-Code). Wir tun dies, indem wir alle Funktionen eines Moduls durchgehen, ihre UWOPs untersuchen und berechnen, wie groß das gefälschte Frame sein sollte. Dieses Frame muss UWOPs haben, die anders sind als die des Aufrufers von MySleep.
  2. Der nächste Frame, den wir finden wollen, ist eine Funktion, die sich entwindet, indem sie vom Stack in RBP poppt - im Grunde durch den UWOP_PUSH_NONVOL-Code.
  3. Dritter Frame: Wir brauchen eine Funktion, die RSP von RBP wiederherstellt, und zwar durch den Code UWOP_SET_FPREG.

Das wiederhergestellte RSP muss mit dem RSP gesetzt werden, das von dem Punkt genommen wird, an dem der Kontrollfluss in unser MySleep eingetreten ist, so dass alle unsere Frames verborgen werden, als Ergebnis des Unwindings des dritten Gadgets dort.

Um den Prozess zu beginnen, kann man über das .pdata des ausführbaren Programms iterieren, indem man den Datenverzeichniseintrag IMAGE_DIRECTORY_ENTRY_EXCEPTION dereferenziert. Betrachten Sie das folgende Beispiel:

root@kitploit:~
    ULONG_PTR imageBase = (ULONG_PTR)GetModuleHandleA("kernel32");
    PIMAGE_NT_HEADERS64 pNthdrs = PIMAGE_NT_HEADERS64(imageBase + PIMAGE_DOS_HEADER(imageBase)->e_lfanew);

    auto excdir = pNthdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION];
    if (excdir.Size == 0 || excdir.VirtualAddress == 0)
        return;

    auto begin = PRUNTIME_FUNCTION(excdir.VirtualAddress + imageBase);
    auto end = PRUNTIME_FUNCTION(excdir.VirtualAddress + imageBase + excdir.Size);

    UNWIND_HISTORY_TABLE mshist = { 0 };
    DWORD64 imageBase2 = 0;

    PRUNTIME_FUNCTION currFrame = RtlLookupFunctionEntry(
        (DWORD64)caller,
        &imageBase2,
        &mshist
    );

    UNWIND_INFO *mySleep = (UNWIND_INFO*)(currFrame->UnwindData + imageBase);
    UNWIND_CODE myFrameUwop = (UNWIND_CODE)(mySleep->UnwindCodes[0]);

    log("1. MySleep RIP UWOP: ", myFrameUwop.UnwindOpcode);

    for (PRUNTIME_FUNCTION it = begin; it < end; ++it)
    {
        UNWIND_INFO* unwindData = (UNWIND_INFO*)(it->UnwindData + imageBase);
        UNWIND_CODE frameUwop = (UNWIND_CODE)(unwindData->UnwindCodes[0]);

        if (frameUwop.UnwindOpcode != myFrameUwop.UnwindOpcode)
        {
            // Found candidate function for a desynch gadget frame

        }
    }

Der Prozess ist etwas verworren, läuft aber darauf hinaus, den Unwinding-Prozess des Thread-Call-Stacks umzukehren, indem beliebige Stack-Frames durch sorgfältig ausgewählte andere ersetzt werden, ähnlich einem ROP-Ansatz.

Dieses PoC repliziert diesen Algorithmus nicht, da mein derzeitiges Verständnis es mir erlaubt, den auf einem EXE-basierten Stack-Frame endenden Call-Stack zu akzeptieren und ich weder meine Shellcode-Lader noch dieses PoC übermäßig verkomplizieren möchte. Ich überlasse die Übung, dies zu implementieren und öffentlich zu teilen, einem interessierten Leser. Oder vielleicht setze ich mich hin und versuche es selbst, wenn ich etwas mehr Freizeit habe :)

Weitere Informationen:

  • a) x64-Ausnahmebehandlung - Stack-Unwinding-Prozess erklärt
  • b) Beispielimplementierung von RtlpUnwindPrologue und RtlVirtualUnwind
  • c) .pdata-Abschnitt
  • d) eine weitere Beispielimplementierung von RtlpUnwindPrologue

Word of caution

Wenn Sie planen, diese Funktionalität zu Ihren eigenen Shellcode-Ladern / Tools hinzuzufügen, stellen Sie sicher, dass Sie VERMEIDEN, kernel32.dll zu enthooken. Ein Versuch, kernel32 zu enthooken, stellt die ursprüngliche Sleep-Funktionalität wieder her und verhindert, dass unser Callback aufgerufen wird. Wenn unser Callback nicht aufgerufen wird, kann der Thread seinen eigenen Call-Stack nicht selbst spoofen.

Wenn Sie das möchten, müssen Sie möglicherweise einen weiteren Watchdog-Thread ausführen, der sicherstellt, dass der Beacon-Thread jedes Mal gespooft wird, wenn er schläft.

Wenn Sie Cobalt Strike und ein BOF unhook-bof von Raphael Mudge verwenden, sehen Sie sich unbedingt meinen Pull Request an, der dem BOF einen optionalen Parameter hinzufügt, um Bibliotheken anzugeben, die nicht enthookt werden sollen.

Auf diese Weise können Sie Ihre Hooks in kernel32 beibehalten:

root@kitploit:~
beacon> unhook kernel32
[*] Running unhook.
    Will skip these modules: wmp.dll, kernel32.dll
[+] host called home, sent: 9475 bytes
[+] received output:
ntdll.dll            <.text>
Unhook is done.

Modifiziertes unhook-bof mit Option zum Ignorieren bestimmter Module


Final remark

Dieses PoC wurde entwickelt, um mit Cobalt Strike Beacon Shellcodes zu arbeiten. Der Beacon ruft bekanntermaßen kernel32!Sleep auf, um auf weitere Anweisungen von seinem C2 zu warten. Dieser Lader nutzt diese Tatsache aus, indem er Sleep hookt, um seine Hausarbeiten zu erledigen.

Diese Implementierung funktioniert möglicherweise nicht mit anderen Shellcodes auf dem Markt (wie Meterpreter), wenn sie Sleep nicht zum Abkühlen verwenden. Da dies nur ein Proof of Concept zur Demonstration der Technik ist, habe ich nicht vor, Unterstützung für andere C2-Frameworks hinzuzufügen.

Wenn Sie das Konzept verstanden haben, werden Sie es sicherlich auf Ihre Shellcode-Anforderungen übertragen und die Lösung zu Ihrem Vorteil anpassen können.

Bitte eröffnen Sie keine Github-Issues mit "dieser Code funktioniert nicht mit XYZ-Shellcode", sie werden sofort geschlossen.


☕ Show Support ☕

Dieses und andere Projekte sind das Ergebnis schlafloser Nächte und viel harter Arbeit. Wenn Ihnen gefällt, was ich tue, und Sie schätzen, dass ich immer etwas an die Community zurückgebe, erwägen Sie, mir einen Kaffee zu kaufen (oder besser ein Bier), nur um Danke zu sagen! 💪


Author

root@kitploit:~
   Mariusz Banach / mgeeky, 21
   <mb [at] binary-offensive.com>
   (https://github.com/mgeeky)
Tool herunterladen