-
Notifications
You must be signed in to change notification settings - Fork 1
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.
-
src/Demon.c:DemonMain()→DemonInit()(PEB walking, hash-resolved APIs viaH_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 tableDemonCommands[]+ 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, sharedsyscall;retgadget found inntdll!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: Win321, Syscall2(default), APC3. -
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 globalINSTANCEstruct: session info, config, resolved API table, syscall SSNs, and all linked lists.
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 onlyPIVOTS = 1is 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, orCOMMAND_NOJOB (10)when idle. - Limits: max single HTTP request
DEMON_MAX_REQUEST_LENGTH = 0x300000(3 MiB); SMB pipe maxPIPE_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.
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.
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.
- 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, definesTRANSPORT_HTTP/TRANSPORT_SMB, optionallyDEBUG/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.binloader 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.txtis for local dev only; themakefilejust cleans.