Skip to content

Demon Agent

laptop tester edited this page Sep 6, 2026 · 2 revisions

Demon Agent

Demon is the built-in Windows implant (payloads/Demon/, C, compiled with MinGW by the teamserver's builder). This page documents its internals: useful both for understanding the framework and as a reference protocol implementation when writing your own agent.

Architecture

  • src/Demon.c: DemonMain() → DemonInit() (PEB walking, hash-resolved APIs via H_FUNC_* ROR13 hashes, config parse, indirect syscall setup) → DemonMetaData() (check-in package + random AES key/IV) → DemonRoutine() main loop: TransportInit() → CommandDispatcher() → SleepObf().
  • src/main/Main{Exe,Dll,Svc}.c: per-format entry points.
  • src/core/:
    • Command.c: command table DemonCommands[] + dispatcher.
    • Package.c / Parser.c: binary message build/parse (big-endian), AES encryption.
    • Transport.c, TransportHttp.c (WinHTTP), TransportSmb.c (named pipe).
    • crypt/AesCrypt.c: AES-256-CTR.
    • Syscalls.c + src/asm/Syscall.{x64,x86}.asm: indirect syscalls; SSNs extracted at runtime, shared syscall;ret gadget found in ntdll!NtAddBootEntry.
    • Obf.c: sleep obfuscation: SLEEPOBF_NO_OBF 0, EKKO 1, ZILEAN 2, FOLIAGE 3; jmp-gadget bypass variants; RC4 memory encryption during sleep (SystemFunction032).
    • inject/Inject.c: injection techniques: Win32 1, Syscall 2 (default), APC 3.
    • Token.c (token vault), Pivot.c (SMB pivot chaining), CoffeeLdr.c + ObjectApi.c (COFF/BOF loader with Cobalt-Strike-compatible Beacon API), Dotnet.c (CLR hosting), Socket.c (SOCKS5 / reverse port-forward), Kerberos.c (LSA ticket ops), Download.c (chunked downloads), HwBp*.c (hardware-breakpoint AMSI/ETW patching).
  • include/Demon.h: the global INSTANCE struct: session info, config, resolved API table, syscall SSNs, and all linked lists.

Wire protocol

All integers big-endian. Agent → teamserver package:

[ Size        ] 4 bytes   length of everything after this field
[ Magic Value ] 4 bytes   0xDEADBEEF
[ Agent ID    ] 4 bytes   random UINT32
[ Command ID  ] 4 bytes
[ Request ID  ] 4 bytes
[ Payload ... ] AES-256-CTR encrypted (per-agent random key/IV)
  • Key exchange: the agent generates a random AES-256 key (32 B) + IV (16 B) and sends them in the clear at the start of the first DEMON_INITIALIZE (99) package: [AES key 32][AES IV 16][AgentID 4][hostname][username][domain][IP][proc path][PID][TID][PPID][arch][elevated][base addr][OS info 5×4][OS arch][sleep][jitter][killdate 8][working hours]. A check-in option byte precedes this metadata (DEMON_CHECKIN_OPTION_*, include/core/Command.h:97). Currently only PIVOTS = 1 is defined/used (agent registers as part of a pivot graph). The teamserver replies with the encrypted AgentID as acknowledgment.
  • Tasking: agent polls with COMMAND_GET_JOB (1); the teamserver returns repeated [CommandID 4][RequestID 4][TaskSize 4][AES-encrypted TaskBuffer] entries, or COMMAND_NOJOB (10) when idle.
  • Limits: max single HTTP request DEMON_MAX_REQUEST_LENGTH = 0x300000 (3 MiB); SMB pipe max PIPE_BUFFER_MAX = 0x10000 (64 KiB).
  • HTTP transport: WinHTTP, POST only, random URI from config, custom UA/headers, optional TLS (ignores cert errors), proxy (explicit or WPAD/IE), host rotation (round-robin/random) with per-host failure counters.
  • SMB transport: pivot agent creates \\.\pipe\<name>; frames are [DemonID 4][PackageSize 4][package]. SMB agents never poll: the parent agent relays their tasking over its own channel.

Command table

Top-level command IDs (include/core/Command.h):

ID Name Purpose
1 DEMON_COMMAND_GET_JOB job-pull wrapper
10 DEMON_COMMAND_NO_JOB no tasking
11 DEMON_COMMAND_SLEEP set sleep/jitter
12 DEMON_COMMAND_PROC_LIST process list
15 DEMON_COMMAND_FS filesystem ops (dir 1, download 2, upload 3, cd 4, rm 5, mkdir 6, cp 7, mv 8, pwd 9, cat 10)
20 DEMON_COMMAND_INLINE_EXECUTE BOF/COFF execution
21 DEMON_COMMAND_JOB job mgmt (list 1, suspend 2, resume 3, kill 4, died 5)
22 DEMON_COMMAND_INJECT_DLL reflective DLL injection
24 DEMON_COMMAND_INJECT_SHELLCODE shellcode injection
26 DEMON_COMMAND_SPAWN_DLL spawn sacrificial + inject DLL
40 DEMON_COMMAND_TOKEN token vault (impersonate 1, steal 2, list 3, privs 4, make 5, getuid 6, revert 7, remove 8, clear 9, find 10)
89 / 90 / 91 DEMON_INFO / DEMON_OUTPUT / DEMON_ERROR output callbacks
92 DEMON_EXIT exit
93 DEMON_KILL_DATE kill date reached
94 BEACON_OUTPUT Cobalt-Strike-compatible BOF output callback (Command.h:35, commands.go:61)
99 DEMON_INITIALIZE registration / key exchange
100 DEMON_COMMAND_CHECKIN check-in with metadata
0x1010 DEMON_COMMAND_PROC process ops (modules 2, grep 3, create 4, memory 6, kill 7; sub-command 5 is parsed by the teamserver with a "TODO: is this used?" comment (teamserver/pkg/agent/demons.go:605-618) but not implemented in the implant)
0x2001 DEMON_COMMAND_ASSEMBLY_INLINE_EXECUTE inline .NET
0x2003 DEMON_COMMAND_ASSEMBLY_VERSIONS list CLR versions
2100 DEMON_COMMAND_NET domain recon (domain 1, logons 2, sessions 3, computer 4, dclist 5, share 6, localgroup 7, group 8, user 9)
2500 DEMON_COMMAND_CONFIG runtime config (show-all 0; verbose 4, sleep-obf start-addr 3, sleep-technique 5, coffee-threaded 6, coffee-veh 7; alloc 101, execute 102; inj-technique 150, inj-spoofaddr 151, spawn64 152, spawn32 153, killdate 154, workinghours 155). Note: DEMON_CONFIG_IMPLANT_SLEEPMASK = 1 is defined (Command.h:58) but has no case in the implant's CommandConfig() switch (Command.c:1963-2179) and no teamserver constant; dead, the console's config implant.sleep-mask command does not reach the implant
2510 DEMON_COMMAND_SCREENSHOT screenshot
2520 DEMON_COMMAND_PIVOT SMB pivot (list 1, connect 10, disconnect 11, command 12)
2530 DEMON_COMMAND_TRANSFER transfer mgmt (list 0, stop 1, resume 2, remove 3)
2540 DEMON_COMMAND_SOCKET SOCKS5 (add 5, list 6, remove 7, clear 8) / rportfwd (add 0, addlcl 1, list 2, clear 3, remove 4); socket ops open 0x10, read 0x11, write 0x12, close 0x13, connect 0x14
2550 DEMON_COMMAND_KERBEROS kerberos sub-commands (include/core/Kerberos.h:7-10): KERBEROS_COMMAND_LUID 0x0 (current logon session LUID), KLIST 0x1 (list tickets, optional /all), PURGE 0x2 (purge tickets), PTT 0x3 (pass-the-ticket; optional /luid <id>). Implant switch at Command.c:3224+; teamserver handler at demons.go:2323
2560 DEMON_COMMAND_MEM_FILE in-memory file store
2570 DEMON_PACKAGE_DROPPED oversized package notice

Output callback types (Cobalt-Strike compatible): CALLBACK_OUTPUT 0x0, CALLBACK_FILE 0x02, CALLBACK_FILE_WRITE 0x08, CALLBACK_FILE_CLOSE 0x09, CALLBACK_ERROR 0x0d, CALLBACK_OUTPUT_OEM 0x1e, CALLBACK_OUTPUT_UTF8 0x20; plus HAVOC_CONSOLE_MESSAGE 0x80, HAVOC_BOF_CALLBACK 0x81. Implant-side error subtypes (payloads/Demon/include/core/Command.h): CALLBACK_ERROR_WIN32 0x1, CALLBACK_ERROR_COFFEXEC 0x2, CALLBACK_ERROR_TOKEN 0x3.

Teamserver-only command constants (defined in teamserver/pkg/agent/commands.go, not in the implant's Command.h): COMMAND_PROC_PPIDSPOOF = 27 (dispatched in demons.go:704, unverifiable against the implant), COMMAND_PS_IMPORT = 0x1011 (dead, no handler), and the BOF-error relay constants COMMAND_EXCEPTION = 0x98 / COMMAND_SYMBOL_NOT_FOUND = 0x99 (commands.go:68-69). They are not part of the wire format above.

Configuration blob

The teamserver's builder (teamserver/pkg/common/builder/builder.go:561 PatchConfig) compiles the config into the binary via -DCONFIG_BYTES={...}; parsed in order by DemonConfig() (src/Demon.c:573): sleep, jitter, alloc/execute methods, spawn64/spawn32 paths, sleep-mask technique, jmp bypass, stack spoof, proxy loading, indirect syscalls, AMSI/ETW patching, then listener-specific fields. HTTP builds parse in order: kill date, working hours, method, HostRotation (int32), hosts+ports, secure, user agent, headers, URIs, proxy. SMB builds re-parse their own block in a different order: pipe name first, then kill date, then working hours (Demon.c:755-770). After compile the binary can be patched (Magic MZ bytes, image size, string replacement) per the profile's Binary block.

Build system & formats

  • Production builds are done by the teamserver: MinGW (x86_64-w64-mingw32-gcc / i686-w64-mingw32-gcc) + nasm, flags like -Os -fPIC -masm=intel -s --no-seh --gc-sections, defines TRANSPORT_HTTP/TRANSPORT_SMB, optionally DEBUG/SEND_LOGS.
  • Formats: WINDOWS_EXE 1 (-e WinMain -nostdlib -mwindows), SERVICE_EXE 2 (-DSVC_EXE), DLL 3 (-shared -e DllMain), REFLECTIVE_DLL 4, RAW_BINARY 5 (shellcode: payloads/Shellcode.x64.bin loader stub prepended to the DLL, KaynLdr-style reflective load).
  • payloads/DllLdr/ is a small stage-less DLL loader shellcode used by spawn/inject paths.
  • payloads/Demon/CMakeLists.txt is for local dev only; the makefile just cleans.

Clone this wiki locally