
Thread Stack Spoofing - PoC pour une technique avancée d'évasion en mémoire permettant de mieux cacher l'allocation mémoire du shellcode injecté aux scanners et analystes.
Une implémentation de preuve de concept pour une technique avancée d'évasion en mémoire qui usurpe la pile d'appels du thread (Thread Call Stack). Cette technique permet de contourner les règles d'examen de la mémoire basées sur les threads et de mieux cacher les shellcodes en mémoire intra-processus.
Ceci est un exemple d'implémentation pour la technique Thread Stack Spoofing visant à contourner les analystes de malwares, les AV et les EDR qui recherchent des références aux frames du shellcode dans la pile d'appels d'un thread examiné. L'idée est de masquer les références au shellcode sur la pile d'appels du thread, dissimulant ainsi les allocations contenant le code du malware.
Cette implémentation, associée à mon projet ShellcodeFluctuation, apporte à la communauté de la sécurité offensive des implémentations d'exemples pour rattraper l'offre faite par les produits C2 commerciaux, afin que nous ne fassions pas moins bien dans nos outils Red Team. 💪
L'implémentation actuelle diffère considérablement de ce qui a été publié initialement.
Cela est dû au fait que j'ai réalisé qu'il existe une approche bien plus simple pour terminer le traitement de la pile d'appels du thread et masquer les frames liées au shellcode en écrivant simplement 0 dans l'adresse de retour de la première frame que nous contrôlons :
void WINAPI MySleep(DWORD _dwMilliseconds)
{
[...]
auto overwrite = (PULONG_PTR)_AddressOfReturnAddress();
const auto origReturnAddress = *overwrite;
*overwrite = 0;
[...]
*overwrite = origReturnAddress;
}
L'implémentation précédente, utilisant StackWalk64, est accessible dans ce commit c250724.
Cette implémentation est beaucoup plus stable et fonctionne parfaitement à la fois en Debug et Release sous les deux architectures - x64 et x86.
Voici à quoi peut ressembler une pile d'appels lorsqu'elle N'EST PAS usurpée :

Ceci, en revanche, lorsque l'usurpation de la pile de thread est activée :

Ci-dessus, nous pouvons voir que la dernière frame de notre pile d'appels est notre callback MySleep.
On peut se demander si cela apporte immédiatement de nouveaux IOCs ? Les règles de chasse peuvent rechercher des threads dont la pile d'appels ne se déroule pas jusqu'aux points d'entrée de thread attendus suivants, situés dans les bibliothèques système :
kernel32!BaseThreadInitThunk+0x14
ntdll!RtlUserThreadStart+0x21
Cependant, la pile d'appels du thread usurpé peut sembler plutôt étrange au premier abord ; un bref examen de mon système a montré qu'il existe d'autres threads qui ne se déroulent pas non plus jusqu'aux points d'entrée ci-dessus :

La capture d'écran ci-dessus montre un thread de Total Commander x64 non modifié. Comme nous pouvons le voir, sa pile d'appels ressemble beaucoup à la nôtre en termes de frames initiales de la pile.
Pourquoi devrions-nous nous soucier de falsifier soigneusement notre pile d'appels alors qu'il existe des processus présentant des caractéristiques que nous pouvons simplement imiter ?
L'algorithme approximatif est le suivant :
dbghelp.dll, appeler SymInitializekernel32!Sleep en pointant vers notre callback.VirtualAlloc + memcpy + CreateThread. Le thread doit démarrer à partir de notre fonction runShellcode pour éviter que l'adresse de démarrage du thread pointe vers un endroit inattendu et anormal (comme ntdll!RtlUserThreadStart+0x21)MySleep est invoquée.0, ce qui devrait effectivement terminer la pile d'appels.::SleepEx est effectué pour laisser Beacon dormir en attendant une communication ultérieure.Les adresses de retour des fonctions sont dispersées dans toute la zone mémoire de la pile du thread, pointées par le registre RBP/EBP.
Pour les trouver sur la pile, nous devons d'abord collecter les pointeurs de frame, puis les déréférencer pour les écraser :

(l'image ci-dessus est empruntée au billet d'Eli Bendersky intitulé Stack frame layout on x86-64)
*(PULONG_PTR)(frameAddr + sizeof(void*)) = Fake_Return_Address;
L'implémentation initiale de ThreadStackSpoofer faisait cela dans les fonctions walkCallStack et spoofCallStack, mais l'implémentation actuelle montre que ces efforts ne sont pas nécessaires pour maintenir une pile d'appels furtive.
Utilisation :
C:\> ThreadStackSpoofer.exe <shellcode> <spoof>
Où :
<shellcode> est un chemin vers le fichier shellcode<spoof> quand 1 ou true active l'usurpation de la pile de thread, toute autre valeur la désactive.Exemple d'exécution qui usurpe la pile d'appels du thread de beacon :
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...
Regardez le code et son implémentation, comprenez le concept et réimplémentez-le dans vos propres chargeurs de shellcode que vous utilisez pour mener vos engagements Red Team. C'est une technique supplémentaire d'évasion avancée en mémoire qui augmente les chances de vos équipes de ne pas être détectées par les antivirus, les EDR et les analystes de malwares qui examinent vos implants.
Lors du développement de votre chargeur de shellcode avancé, vous pourriez également vouloir implémenter :