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.
Tengu supports four transports selected at build time. All of them use ChaCha20 application-layer encryption after the initial registration.
| Transport | ID | Description |
|---|---|---|
| HTTP / HTTPS | 0 (default) | Standard HTTP POST to the configured URI |
| DNS | 1 | TXT record polling, frames encoded as base32 in QNAME labels |
| DoH | 2 | DNS-over-HTTPS, same DNS protocol over HTTPS POST /dns-query |
| TCP | 3 | Raw TCP socket, used when pivoting through a parent agent |
The default transport. Tengu sends a POST request to the configured C2 host and URI on each beacon.
| Option | Description |
|---|---|
| Hosts | One or more C2 hostnames or IPs |
| Port | Connection port (default: 80 for HTTP, 443 for HTTPS) |
| URI | Request path (e.g. /updates, /cdn/assets/bundle.js) |
| User-Agent | User-Agent header value |
| Sleep / Jitter | Beacon interval and randomisation percentage |
| Kill date | Agent exits after this date (YYYY-MM-DD) |
| Working hours | Beacon window (HH:MM-HH:MM), agent sleeps outside it |
| HTTP Proxy | http://[user:pass@]host:port for HTTP traffic |
| HTTPS Proxy | Same, 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.
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).
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.
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.
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.
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.
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.
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)./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).PROT_NONE. The agent is now invisible to scanners.PROT_READ|EXEC and signals completion via a semaphore.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().
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.
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.
The memfd command executes arbitrary ELF binaries entirely in memory using memfd_create(2). The steps are:
memfd_create("kworker/u:0", MFD_CLOEXEC)./proc/self/fd/<n> using execve./proc as kworker/u:0 (a legitimate kernel worker thread name).This bypasses filesystem-based detection (file creation events, inode monitoring, AV scanning of new files).
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.
| Technique | Enabled by default |
|---|---|
| Sleep code-page encryption | Yes |
| Compile-time string obfuscation | Yes (per-build key) |
| In-memory binary execution (memfd) | On demand (memfd command) |
| ChaCha20 traffic encryption | Yes |
| ELF section-based exclusion (GCC-safe) | Yes |
