Skip to content

Payload Generation

kthreatt edited this page Sep 1, 2026 · 1 revision

Payload Generation

Payloads are built by the teamserver's builder (teamserver/pkg/common/builder/builder.go) when a client sends a Gate/Stageless request (from the Payload dialog, or havoc.GeneratePayload() in Python). For Demon it compiles from source with MinGW; for service agents the request is forwarded to the agent's controller (see Service API ReferenceAgentBuild).

Request fields (client → server, Gate event)

Info keys: AgentType, Listener, Arch (x64/x86), Format, Config (JSON string of build options). Results come back as PayloadArray (base64) + FileName, or progress via MessageType/Message.

Formats

From builder.go:31:

Value Format Notes
1 Windows Exe MainExe.c, -e WinMain -nostdlib -mwindows
2 Windows Service Exe MainSvc.c, -DSVC_EXE, service name define
3 Windows Dll MainDll.c, -shared -e DllMain
4 Windows Reflective Dll
5 Windows Shellcode (raw binary) DLL compiled with -DSHELLCODE, then the payloads/Shellcode.x64.bin (or x86) loader stub is prepended; the stub reflectively loads the appended Demon DLL (KaynLdr-style)

Compile flow

The builder copies the Demon sources to a temp dir and invokes x86_64-w64-mingw32-gcc / i686-w64-mingw32-gcc (plus nasm for src/asm/*.asm) with flags like -Os -fPIC -masm=intel -s --no-seh --gc-sections. Defines:

  • -DCONFIG_BYTES={...}: the serialized implant configuration (parsed by DemonConfig() at runtime)
  • -DTRANSPORT_HTTP or -DTRANSPORT_SMB: transport selection
  • -DDEBUG when the server runs with --debug-dev; -DSEND_LOGS with --send-logs (implant streams debug logs over C2)

Config option keys

Keys sent by the Payload dialog (consumed around builder.go:691-727 and PatchConfig, builder.go:561). All four Injection keys are mandatory: the build errors if any is missing.

  • Sleep, Jitter: implant timing
  • Sleep Technique: WaitForSingleObjectEx / Foliage / Ekko / ZileanSLEEPOBF_NO_OBF 0 / FOLIAGE 3 / EKKO 1 / ZILEAN 2 (payloads/Demon/include/core/SleepObf.h:7-10)
  • Sleep Jmp Gadget: jmp rax / jmp rbx (→ SLEEPOBF_BYPASS_JMPRAX 1 / SLEEPOBF_BYPASS_JMPRBX 2, SleepObf.h:12-14). Only honored when a real sleep-obfuscation technique (not WaitForSingleObjectEx) is selected; otherwise the option is ignored. ⚠️ Known bug: jmp rax currently sets the technique constant instead of the bypass constant (builder.go:750-751), corrupting the sleep-obf technique. Use jmp rbx until fixed.
  • Stack Duplication (bool): only honored when a real sleep-obf technique is selected; silently ignored otherwise
  • Injection: an object with four required keys (builder.go:651-712):
    • Alloc: "Win32" or "Native"/"Syscall" (memory allocation method)
    • Execute: "Win32" or "Native"/"Syscall" (memory execution method)
    • Spawn64: sacrificial x64 process path for spawn-and-inject
    • Spawn32: sacrificial x86 process path
    • (APC injection exists in the implant header inject/Inject.h but is not selectable via the builder)
  • Amsi/Etw Patch: only non-default value is "Hardware breakpoints" (AMSIETW_PATCH_HWBP, builder.go:843-878); anything else means no patching. (There is no MemoryPatch option.)
  • Proxy Loading: value string as shown in the UI, e.g. "None (LdrLoadDll)" / RtlRegisterWait / RtlCreateTimer / RtlQueueWorkItem variants (builder.go:800)
  • Service Name: for the Windows Service Exe format only: the service display name injected as a SERVICE_NAME define (charset-validated, random 6-char fallback; builder.go:622-640)
  • Proxy settings, kill date, working hours: copied from the chosen listener's config

Post-build binary patching

After compilation, builder.Patch() applies the profile's Demon.Binary block (builder.go:513):

  • Binary.Header.MagicMz-x64/x86: replace the MZ magic bytes (hyphenated names)
  • Binary.Header.ImageSize-x64/x86, CompileTime: PE header tweaks
  • Binary.ReplaceStrings-x64/x86: string replacement lists (string laundering)

This is per-architecture, keyed by the requested Arch.

Clone this wiki locally