
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.
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.
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. 💪
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 eingesehen werden.
Diese Implementierung ist viel stabiler und funktioniert gut sowohl unter Debug als auch Release auf zwei Architekturen - x64 und x86.
So könnte ein Call-Stack aussehen, wenn er NICHT gespooft ist:

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

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:

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?
Der grobe Algorithmus ist wie folgt:
dbghelp.dll, rufe SymInitialize auf.kernel32!Sleep, um auf unseren Callback zu verweisen.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).MySleep-Callback aufgerufen.0, was effektiv den Call-Stack beenden sollte.::SleepEx getätigt, um den Beacon schlafen zu lassen, während auf weitere Kommunikation gewartet wird.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:

(Das obige Bild wurde aus Eli Benderskys Beitrag namens 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.
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...
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:
BeaconEye zu umgehen.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).