Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Einreichen
ToolsExploitsBlog
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.2k19219vor 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](https://github.com/mgeeky/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:

```
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](https://github.com/mgeeky/ThreadStackSpoofer/tree/c2507248723d167fb2feddf50d35435a17fd61a2) 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](https://assets.kitploit.com/production/public/readmes/4783/3b8962b3574320c202b79b901eedc739455970dc7d01c94d013012374cb58cac.png)

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

![gespooft](https://assets.kitploit.com/production/public/readmes/4783/8b3bc4e864e2087abb351adeec43edd8d59b0de6024302ac911befbabdb87407.png)

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:

```
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](https://assets.kitploit.com/production/public/readmes/4783/62193f641b5e4f052a6a2700d50f821bb9580e4213c6a04a40f77174006e004d.png)

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](https://assets.kitploit.com/production/public/readmes/4783/624e562610d8dfc6d15dd84cab8e2a89015e25f37f5c4a1f57b955c37ef4ffbd.png)

_(Das obige Bild wurde aus **Eli Benderskys** Beitrag namens [Stack frame layout on x86-64](https://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/) entliehen)_

```
	*(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:

```
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:

```
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](https://www.arashparsa.com/hook-heaps-and-live-free/) - der es Ihnen ermöglicht, Beacon-Konfigurationsextraktoren wie [`BeaconEye`](https://github.com/CCob/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](https://github.com/mgeeky/ShellcodeFluctuation)-Technik - direkt vor dem Schlafen (das könnte Scanner wie [`Moneta`](https://github.com/forrest-orr/moneta) oder [`pe-sieve`](https://github.com/hasherezade/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](https://twitter.com/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](https://docs.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-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](https://docs.microsoft.com/ru-ru/windows/win32/api/winnt/nf-winnt-rtlvirtualunwind) implementiert).
5. Das System verarbeitet alle `UNWIND_CODE`s, 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](https://twitter.com/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:

```
    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](https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-160)
- **b)** [Beispielimplementierung von `RtlpUnwindPrologue` und `RtlVirtualUnwind`](https://github.com/mic101/windows/blob/master/WRK-v1.2/base/ntos/rtl/amd64/exdsptch.c)
- **c)** [`.pdata`-Abschnitt](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#the-pdata-section)
- **d)** [eine weitere Beispielimplementierung von `RtlpUnwindPrologue`](https://github.com/hzqst/unicorn_pe/blob/master/unicorn_pe/except.cpp#L773)

---

## 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](https://github.com/Cobalt-Strike/unhook-bof/pull/1) 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:

```
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](https://github.com/mgeeky/unhook-bof)

---

## 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](https://github.com/sponsors/mgeeky) _(oder besser ein Bier)_, nur um Danke zu sagen! 💪 

---

## Author

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