m0n1t0r Architecture

Architecture

Overview

m0n1t0r is a cross-platform C2 framework written in Rust with a React web dashboard. It uses a client-server model where agents connect back to the server over TLS, and operators interact through a REST/WebSocket API served alongside a single-page web application.

1
2
3
4
┌───────────┐  TLS (remoc RPC)  ┌───────────────┐  REST / WebSocket  ┌────────────┐
│ Client │◄────────────────►│ Server │◄──────────────────►│ Web UI │
│ (Agent) │ Port 27853 │ (Actix-web) │ Port 10801 │ (React) │
└───────────┘ └───────────────┘ └────────────┘

Workspace Structure

1
2
3
4
5
6
7
8
9
m0n1t0r/
├── m0n1t0r-server/ Server binary — TLS listener + HTTP/WebSocket API
├── m0n1t0r-client/ Agent binary — connects back to server over TLS
├── m0n1t0r-common/ Shared contract — RPC trait definitions, error types, data models
├── m0n1t0r-ui/ Web dashboard — React + TypeScript + Ant Design
├── m0n1t0r-build/ Build-time utilities — cert embedding, version tracking, dep validation
├── m0n1t0r-macro/ Procedural macros
├── xtask/ Build automation — cert generation, UPX compression
└── deps/ Vendored dependencies — qqkey, scrap (wayland), upx

Communication Model

Client ↔ Server: Bidirectional Async RPC

The transport layer uses TLS (rustls) on port 27853. On top of TLS, remoc establishes typed bidirectional channels with MessagePack serialization.

Connection handshake:

  1. Client initiates TLS connection. The CA certificate is embedded in the client binary at compile time (certs/ca.crt).
  2. remoc::Connect::io() creates two typed channel pairs over the TLS stream.
  3. Server sends a ServerClient RPC stub to the client; client sends a ClientClient RPC stub to the server.
  4. Both sides can now invoke methods on each other asynchronously.
  5. Server registers the client in ServerMap and broadcasts a ConnectEvent::Connect notification.

The client auto-reconnects every 10 seconds on connection failure.

Server ↔ UI: REST + WebSocket

Actix-web serves the HTTP API on port 10801 at /api/v1/. WebSocket endpoints are used for:

  • Interactive shell sessions (/client/{addr}/process/interactive)
  • Remote desktop streaming (/client/{addr}/rd/stream/*)
  • Server event notifications (/server/notification)

Server Architecture

Entry Point

The server runs two concurrent tasks:

  • TLS Listener (conn::run) — Accepts client connections on port 27853, establishes remoc channels, tracks clients in ServerMap.
  • HTTP API (api::run) — Actix-web server on port 10801 with session middleware, CORS, and route handlers.

State Management

1
2
3
4
5
6
7
8
9
10
11
pub struct ServerMap {
map: HashMap<SocketAddr, Arc<RwLock<ServerObj>>>,
notification_rx: WatchReceiver<ConnectEvent>,
notification_tx: WatchSender<ConnectEvent>,
}

pub struct ServerObj {
addr: SocketAddr,
client_client: ClientClient, // RPC stub to invoke client methods
canceller: CancellationToken,
}

API handlers look up clients in ServerMap, obtain the ClientClient RPC stub, and call methods through it. The get_agent! macro encapsulates this pattern:

1
2
let (agent, _) = get_agent!(data, &addr, fs_agent)?;
agent.list(path).await?

API Route Layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/api/v1/
├── /client
│ ├── GET / List all clients
│ ├── GET /{addr} Client details
│ ├── DELETE /{addr} Terminate client
│ ├── /{addr}/fs File system operations
│ ├── /{addr}/process Process management + interactive shell
│ ├── /{addr}/proxy SOCKS5 / TCP forward proxy
│ ├── /{addr}/rd Remote desktop (display list + streams)
│ ├── /{addr}/network Download file to client
│ ├── /{addr}/qq QQ integration
│ ├── /{addr}/update Client self-update
│ └── /{addr}/autorun Persistence management
├── /server
│ ├── GET / Server info
│ ├── GET /notification WebSocket: connect/disconnect events
│ └── /proxy Proxy lifecycle
└── /session Authentication (TODO)

Response Envelope

All API responses use a standard envelope:

1
{ "code": 0, "body": <T> }

Errors are mapped to HTTP status codes in web/error.rs.

Client Architecture

Entry Point

The client binary hides its console window on Windows (#![windows_subsystem = "windows"]). In debug builds it connects to 127.0.0.1:27853; in release builds the server address is baked in via the M0N1T0R_DOMAIN environment variable at compile time.

Agent Pattern

The client implements the Client RPC trait from m0n1t0r-common. Each capability is exposed as a factory method that returns an RPC stub:

1
2
3
4
5
6
async fn fs_agent(&self) -> AppResult<fs::AgentClient> {
let obj = Arc::new(RwLock::new(fs::AgentObj::new()));
let (server, client) = fs::AgentServerSharedMut::new(obj, 1);
tokio::spawn(server.serve(true)); // Spawn background RPC server
Ok(client) // Return stub to caller
}

When the server-side API handler calls client.fs_agent(), the client spawns a new agent task and returns an RPC stub. The server then calls methods on that stub, which are executed on the client side.

Platform Dispatch

Platform-specific code is selected at compile time via feature flags and the declare_agents! macro:

1
2
3
4
declare_agents!(general, [proxy, network, qq, rd], ["general", "macos", "linux", "winnt"]);
declare_agents!(windows, [process, autorun, charset, fs, blind], ["winnt"]);
declare_agents!(general, [process, charset, fs, blind], ["general", "macos", "linux"]);
declare_agents!(unix, [autorun], ["linux", "macos"]);

Platform-specific modules:

Directory Platforms Capabilities
client/general/ All proxy, network, remote desktop, QQ
client/windows/ Windows ETW patching, registry autorun, shellcode injection, drive enumeration
client/unix/ Linux, macOS systemd/launchd persistence

Common Library

m0n1t0r-common defines the contract between server and client.

RPC Traits

All agent interfaces use the #[rtc::remote] macro from remoc, which generates serializable client/server stubs:

  • Client — Top-level trait: version info, system info, agent factory methods, self-update
  • fs::Agent — File operations: list, read, write, delete, drives, rename, copy
  • process::Agent — Process list, execute, interactive shell, kill, shellcode injection
  • proxy::Agent — TCP connect, TCP forward (for SOCKS5/port forwarding)
  • rd::Agent — Display enumeration, video streaming (VP9/MPEG-TS encoded frames)
  • network::Agent — File download to client
  • qq::Agent — QQ protocol integration
  • autorun::Agent — Persistence mechanisms
  • charset::Agent — Charset conversion (Windows ANSI ↔ UTF-8)

Error Handling

A typed error hierarchy using thiserror, fully serializable for RPC transmission:

1
2
3
4
5
6
7
8
9
10
Error
├── Network (RpcCall, ChannelDisconnect, InvalidPeer, ...)
├── Io (Tokio, InvalidUserDirectory, Environment)
├── Parse (Protobuf, InvalidParameter, Certificate, DnsName)
├── External (Ffi, QQKey)
├── NotFound
├── InitializationFailed
├── Unsupported
├── Unimplemented
└── Unknown

Web UI

Tech Stack

React 19 + TypeScript + Vite + Ant Design (dark theme) + React Router. Uses bun as the package manager.

Structure

1
2
3
4
5
6
7
src/
├── api/ Typed API client wrappers (axios-based)
├── components/ Reusable UI: Terminal (xterm.js), RemoteDesktop (jsmpeg), FileManager, etc.
├── pages/ Route pages: Dashboard, ClientList, ClientDetail, ServerInfo, Settings
├── hooks/ useWebSocket, useNotification (server event listener)
├── utils/ Formatting helpers, persistent settings
└── types/ TypeScript declarations for JS-only packages

Routing

1
2
3
4
5
/                   Dashboard
/clients Client list
/clients/:addr Client detail (tabs: fs, process, proxy, rd, network, ...)
/server Server info
/settings Settings

Remote Desktop Streaming

The UI supports three stream formats via WebSocket:

  • RGB — Raw pixel data
  • YUV — Raw YUV frames
  • MPEG1Video — MPEG-TS container decoded by @cycjimmy/jsmpeg-player (jsmpeg)

Build System

Platform Feature Flags

Mutually exclusive, required at compile time:

1
2
3
4
cargo build --features macos -r    # macOS
cargo build --features linux -r # Linux
cargo build --features winnt -r # Windows
cargo build --features winnt-uac -r # Windows with UAC elevation

TLS Certificate Generation

cargo xtask -c generates a self-signed CA and end-entity certificate. Environment variables control the certificate subject fields (M0N1T0R_DOMAIN, M0N1T0R_COUNTRY, etc.). The CA cert is embedded in the client at compile time for certificate pinning.

C++ FFI

Platform-specific native code is built with xmake and linked via the cxx crate (m0n1t0r-cpp-*-lib).

Deployment

Docker (Multi-Stage Build)

1
2
3
Stage 1 (server-builder):  Rust nightly + vcpkg → server binary
Stage 2 (ui-builder): Bun → static React assets
Stage 3 (runtime): Debian + nginx + server binary + UI assets

nginx serves the UI on port 80 and proxies /api/ to the server on port 10801. Port 27853 is exposed for client TLS connections.

CI/CD

  • build.yml — Matrix build across macOS, Linux, Windows. Installs Rust, xmake, vcpkg dependencies. Produces binary artifacts.
  • docker.yml — Builds Docker image and pushes to ghcr.io/mmitsuha/m0n1t0r:nightly on master push.

Design Principles

  1. Contract-first: RPC traits in m0n1t0r-common define the interface; server and client implement against the same contract.
  2. Agent-per-capability: Each feature (fs, process, proxy, rd) is an independent agent with its own RPC channel, allowing fine-grained lifecycle management.
  3. Platform abstraction via compile-time dispatch: Feature flags and macros select platform-specific implementations without runtime overhead.
  4. Size-optimized release builds: opt-level = "z", LTO, single codegen unit, symbol stripping, panic = abort, optional UPX compression.
  5. Typed errors across boundaries: All errors are serializable and transmitted over RPC, then mapped to HTTP status codes at the API layer.