Mugen無限 Mugen
/
Agent - Linux

Tengu

天狗

Tengu

Tengu is the Linux agent for the Mugen C2 framework.

Tengu is written in C, requires no external dependencies on the target, and runs as a single ELF64 binary. It supports HTTP/HTTPS, DNS, DNS-over-HTTPS, and TCP transports.

Key features

  • File system operations (ls, cat, upload, download, mkdir, rm, cp, chmod)
  • Process management (list, kill)
  • Shell and in-memory execution (shell, memfd)
  • ELF BOF execution in-process (inline-execute)
  • Network recon (netstat, arp, route, ifconfig, portscan)
  • Credential harvesting (SSH keys, cloud tokens, shell history, /etc/shadow)
  • Process memory scanning for credentials (procdump)
  • Keylogger via /dev/input or X11
  • Persistence (cron, systemd, bash)
  • Privilege escalation recon (SUID, sudo, capabilities)
  • Reverse port forwarding
  • SOCKS5 proxy over C2 channel
  • TCP pivot (parent-child chaining)
  • Sleep obfuscation (XOR code pages during sleep)
  • Compile-time string obfuscation

Credits

Tengu supports four transports selected at build time. All of them use ChaCha20 application-layer encryption after the initial registration.


Transports

TransportIDDescription
HTTP / HTTPS0 (default)Standard HTTP POST to the configured URI
DNS1TXT record polling, frames encoded as base32 in QNAME labels
DoH2DNS-over-HTTPS, same DNS protocol over HTTPS POST /dns-query
TCP3Raw TCP socket, used when pivoting through a parent agent

HTTP / HTTPS

The default transport. Tengu sends a POST request to the configured C2 host and URI on each beacon.

Configurable options

OptionDescription
HostsOne or more C2 hostnames or IPs
PortConnection port (default: 80 for HTTP, 443 for HTTPS)
URIRequest path (e.g. /updates, /cdn/assets/bundle.js)
User-AgentUser-Agent header value
Sleep / JitterBeacon interval and randomisation percentage
Kill dateAgent exits after this date (YYYY-MM-DD)
Working hoursBeacon window (HH:MM-HH:MM), agent sleeps outside it
HTTP Proxyhttp://[user:pass@]host:port for HTTP traffic
HTTPS ProxySame, for HTTPS traffic

If no proxy is configured at build time, Tengu falls back to HTTP_PROXY / HTTPS_PROXY environment variables at runtime. Both NTLM and Basic auth are supported via libcurl CURLAUTH_ANY.

HTTPS

TLS is handled by libcurl on the agent side. For production use, point the agent to a hostname with a valid certificate (or a redirector that terminates TLS).


DNS

Tengu polls TXT records from the configured domain. Each frame is split into base32-encoded chunks embedded in QNAME labels. Responses are returned as TXT record values.

Useful when HTTP is blocked but outbound DNS is not. Higher latency than HTTP.


DNS-over-HTTPS (DoH)

Same DNS protocol as the DNS transport but sent over HTTPS POST to /dns-query. Traffic looks like standard DoH traffic to a resolver (e.g. Cloudflare, Google). Useful when raw DNS is also blocked.


TCP

Raw TCP socket transport. Used exclusively for pivot chaining: a Tengu agent compiled with TCP connects to a parent Tengu agent that has HTTP/HTTPS C2 reach. The parent relays the child's traffic to the teamserver.

The parent agent must be listening:

pivot tcp listen <port>

The child is compiled with the TCP transport pointing to the parent's IP and port.


Application-layer encryption

All four transports encrypt the body of every packet with ChaCha20 using a 32-byte session key.

The key is generated at build time, embedded in the binary config, and sent to the teamserver in the INIT packet. After the INIT, all subsequent packets (including over plain HTTP) are encrypted.

Wire format (agent to teamserver):

[header: 12 bytes][nonce: 12 bytes][ChaCha20(body)]

Wire format (teamserver to agent):

[nonce: 12 bytes][ChaCha20(payload)]

This means traffic is end-to-end encrypted regardless of whether TLS is used. A passive observer on an HTTP listener sees only ciphertext.

Tengu was not designed to evade EDR products out of the box. The primitives documented here are a solid operational baseline, but Tengu is primarily designed to be capable and modular - not to win a cat-and-mouse game with vendors. Operators are expected to wrap it with their own tooling where stealth is a hard requirement.


Sleep obfuscation

During each sleep interval, Tengu XOR-encrypts its own r-x code pages in memory. An in-memory scanner running while Tengu sleeps sees only garbage.

How it works

  1. A helper thread is spawned before sleep. It holds pre-resolved libc function pointers (nanosleep, mprotect, sem_post, sem_wait) obtained via dlsym before encryption starts. These pointers are stored in a heap-allocated context, so the thread can operate without touching the PLT (which is inside the encrypted region).
  2. The main thread reads /proc/self/maps and collects all r-x segments belonging to the agent binary. The pages containing the obfuscation code itself are excluded using GNU ld section boundary symbols (__start_obf_text / __stop_obf_text).
  3. Each segment is XOR-encrypted with a 32-byte random key and re-mprotected to PROT_NONE. The agent is now invisible to scanners.
  4. After the sleep interval, the helper thread decrypts each segment back to PROT_READ|EXEC and signals completion via a semaphore.
  5. The main thread resumes from the semaphore wait with fully restored code.

Robustness

All obfuscation functions are placed in a dedicated ELF section (obf_text) using __attribute__((section("obf_text"))). GNU ld auto-generates __start_obf_text and __stop_obf_text symbols that bracket the section exactly, regardless of how GCC orders functions within the binary (GCC -Os does not guarantee source order). This makes the page exclusion robust across all optimisation levels.

If any setup step fails (dlsym, /proc/self/maps parse, memory allocation), Tengu falls back silently to plain sleep().

Tunnel bypass

Sleep obfuscation is skipped when SOCKS5, reverse port forwarding, or a TCP pivot server is active. These tunnels require fast polling and cannot tolerate the agent being unresponsive for the full sleep interval.


String obfuscation

All sensitive strings in the binary are XOR-encrypted at compile time using a per-build key. This includes paths (/proc/self/maps, /proc/self/exe), library symbol names (mprotect, nanosleep, sem_post), and any other string that would be a detection indicator.

The SXOR(SXOR_CONSTANT) macro decrypts the string inline at the point of use. The plaintext never exists as a static string in the binary - only the encrypted bytes are present in the .rodata section.

The key and encrypted constants are generated by the teamserver builder at payload compile time via an auto-generated obfstr_data.h header. Each build gets a unique key.


In-memory execution (memfd)

The memfd command executes arbitrary ELF binaries entirely in memory using memfd_create(2). The steps are:

  1. The agent creates an anonymous file descriptor with memfd_create("kworker/u:0", MFD_CLOEXEC).
  2. The binary is written to the file descriptor.
  3. The binary is executed via /proc/self/fd/<n> using execve.
  4. The process appears in /proc as kworker/u:0 (a legitimate kernel worker thread name).
  5. No bytes are written to the filesystem at any point.

This bypasses filesystem-based detection (file creation events, inode monitoring, AV scanning of new files).


ChaCha20 application-layer encryption

All C2 traffic is encrypted with ChaCha20 regardless of transport. See C2 Profiles for details.

Even over plain HTTP, a passive observer sees only ciphertext. Network signatures based on plaintext payload patterns do not apply.


Summary

TechniqueEnabled by default
Sleep code-page encryptionYes
Compile-time string obfuscationYes (per-build key)
In-memory binary execution (memfd)On demand (memfd command)
ChaCha20 traffic encryptionYes
ELF section-based exclusion (GCC-safe)Yes