A reference implementation of a Tuoni listener plugin that uses plain TCP as its C2 transport. It is intended as a starting point and learning resource for anyone building their own listener plugin — fork it, rename it, swap the transport, and you have a new plugin.
This repo contains two cooperating projects:
| Project | Language | Role |
|---|---|---|
TcpListenerPlugin/ |
Java 21 / Gradle | Server-side plugin that loads inside the Tuoni server. Opens a TCP ServerSocket, accepts implant connections, and bridges them into the Tuoni agent runtime. |
TcpListenerExecUnit/ |
C# / .NET Framework 4.6.2 | Client-side "exec unit" that runs inside the implant process on the target. Talks to the Tuoni agent over a local named pipe and forwards traffic to the listener over TCP. |
TcpListenerExecUnit/ExecUnitUtils/ is a shared MSBuild project provided by Tuoni — leave it
alone.
┌──────────────────┐ TCP ┌──────────────────────────┐
│ Tuoni server │ <─── framed bytes ──> │ Target machine │
│ │ │ │
│ TcpListener │ │ ┌─────────────────────┐ │
│ (this plugin) │ │ │ Implant process │ │
│ │ │ │ │ │ │
│ │ Agent │ │ │ Tuoni agent │ │
│ │ runtime │ │ │ │ named pipe │ │
│ ▼ │ │ │ ▼ │ │
│ Tuoni core │ │ │ TcpListenerExecUnit│ │
└──────────────────┘ │ └─────────────────────┘ │
└──────────────────────────┘
The Java plugin produces a Donut-converted shellcode of the .NET exec unit, embedded as a classpath resource. When Tuoni asks the plugin to generate a payload, the plugin patches the shellcode with a per-payload pipe name and hands it back; Tuoni does the rest (delivery, injection, running it inside an implant).
A single message on the TCP socket is:
┌──────────────┬───────────────────┬──────────────┬──────────────────┐
│ metaLen (u32 │ meta bytes │ dataLen (u32 │ data bytes │
│ little-end.) │ (metaLen bytes) │ little-end.) │ (dataLen bytes) │
└──────────────┴───────────────────┴──────────────┴──────────────────┘
or
┌──────────────┬──────────────────┐
│ dataLen (u32 │ data bytes │
│ little-end.) │ (dataLen bytes) │
└──────────────┴──────────────────┘
metais the agent metadata blob produced by the Tuoni SDK.datais an opaque serialized request/response.dataLen == 0is allowed and is used by the exec unit's first frame to register the agent.- Server-to-client frames omit the meta section and carry only
<lenLE><bytes>(a single command payload). SeeTcpConnectionHandler#runCommandPusherandProgram.RunReadLoopfor the authoritative encoders.
The build has two halves and one ordering rule: the .NET exec unit must be built first,
because the Gradle build embeds the resulting .shellcode file into the plugin JAR.
You need:
- MSBuild (Visual Studio Build Tools, .NET Framework 4.6.2 targeting pack)
donut.exeon disk atTcpListenerExecUnit/donut.exe(the.csprojpost-build event invokes it to convert the built.exeinto position-independent shellcode)
msbuild TcpListenerExecUnit/TcpListenerExecUnit.slnx /p:Configuration=ReleaseOutput: TcpListenerExecUnit/TcpListenerExecUnit/bin/Release/TcpListenerExecUnit.shellcode.
You need JDK 21 (Gradle's toolchain support will fetch it automatically if missing).
cd TcpListenerPlugin
./gradlew shadowJarOutput: TcpListenerPlugin/build/libs/tuoni-example-plugin-tcp-listener-0.0.1.jar — a single fat
jar containing the plugin code, its runtime dependencies, and the embedded shellcode at
shellcodes/TcpListenerExecUnit.shellcode.
Drop the shadow jar into your Tuoni server's plugins directory and restart. The plugin advertises
itself with Plugin-Id = shelldot.listener.examples.tcp (set in build.gradle.kts).
The plugin exposes a tiny JSON schema (declared in
TcpListenerPluginConfiguration.JSON_SCHEMA):
{
"connectBackAddress": "0.0.0.0",
"port": 4444
}connectBackAddress is informational (used as the connect-back address baked into generated payloads); port
is the actual TCP port the listener binds to. Tuoni's UI renders the schema as a form when an
operator creates a new listener instance.
Both TcpListener.DEFAULT_PIPE_NAME (Java) and Program.DefaultPipeName (C#) hold the literal
string "QQQWWWEEE". This is not a bug or a leftover — it is a deliberate marker.
- In the .NET exec unit it is used as the named-pipe name to connect to.
- In the compiled shellcode it appears verbatim as a UTF-16LE byte sequence.
- When the Java plugin generates a payload,
TcpListener#generateShellCodesearches the shellcode bytes for that exact UTF-16LE sequence and overwrites it in place with the per-payload pipe name supplied by Tuoni.
Both sides therefore must declare the exact same literal, and the literal must survive into the compiled binary as a contiguous UTF-16LE string. If you change one side, change the other, and keep the byte length identical (the patch is in-place, not a resize).
A short checklist for turning this into a brand-new listener plugin:
- Rename the Gradle root project (
settings.gradle.kts) and the shadow jar (build.gradle.kts: archiveBaseName). - Change the
Plugin-Id,Plugin-Name,Plugin-Description,Plugin-Providermanifest attributes inbuild.gradle.kts. - Rename the Java package under
TcpListenerPlugin/src/main/java/com/shelldot/tuoni/examples/plugin/tcplistenerto your own. - Replace the TCP transport in
TcpConnectionHandler(and the matching client logic inProgram.cs) with your own — HTTP, DNS, SMB, whatever — keeping the framing protocol or designing your own. - Update
TcpListenerPluginConfiguration(and its JSON schema) to expose the configuration fields your transport needs. - Rebuild .NET → rebuild Java → drop the new jar into Tuoni.
.
├── README.md
├── TcpListenerPlugin/ Java/Gradle plugin (server-side)
│ ├── build.gradle.kts shadowJar build, manifest, shellcode embedding
│ ├── settings.gradle.kts
│ └── src/main/java/.../tcplistener
│ ├── TcpListenerPlugin.java Plugin entry point (init / create)
│ ├── TcpListener.java Listener lifecycle (start/stop/reconfigure)
│ ├── TcpConnectionHandler.java Per-connection read/write loops
│ ├── TcpFrameCodec.java Length-prefixed framing helpers
│ ├── TcpListenerPluginConfiguration.java Config record + JSON schema
│ ├── ShellcodeUtil.java Read/patch the embedded shellcode
│ └── configuration/ Thin SDK adapters
└── TcpListenerExecUnit/ .NET exec unit (target-side)
├── ExecUnitUtils/ **Untouched — provided by Tuoni**
└── TcpListenerExecUnit/
├── Program.cs Pipe ↔ TCP bridge
├── TcpListenerExecUnit.csproj
└── Properties/AssemblyInfo.cs