Mugen無限 Mugen
/

Evasion techniques built into Tengu - sleep obfuscation, string obfuscation, and in-memory execution.

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