Skip to content

Teamserver

laptop tester edited this page Sep 6, 2026 · 2 revisions

Teamserver

The teamserver is a Go module named Havoc (teamserver/go.mod). Entry point: teamserver/main.go → cobra CLI (teamserver/cmd/).

./havoc server --profile profiles/havoc.yaotl [-v] [--debug] [--debug-dev] [--send-logs] [--default/-d]
./havoc client        # just execs client/Havoc

Flags (teamserver/cmd/cmd.go:35-40): --profile (profile path), --default/-d (load data/havoc.yaotl), --verbose/-v (also enables log timestamps), --debug (server debug logging), --debug-dev (compiles Demon with -DDEBUG), --send-logs (compiles Demon with -DSEND_LOGS; the implant sends debug logs over its C2 channel). There are no CLI host/port or DB flags: the listen address always comes from the profile's Teamserver block, and the DB path is hardcoded data/teamserver.db (cmd.go:17).

./havoc client execs client/Havoc and forwards any extra arguments to it, so ./havoc client --debug works (teamserver/cmd/client.go:16).

Version constants (teamserver/cmd/cmd.go:14-16): VersionNumber = "0.8.2", VersionName = "Bites The Dust"; VersionCommit is injected at build time via -X Havoc/cmd.VersionCommit=$(git rev-parse HEAD) (makefile ts-build target).

Note on versions: the teamserver banner version matches the client (client/src/global.cc, also 0.8.2); the fork keeps both in step, and the release level is tracked in CHANGELOG.md.

Startup flow

(*Teamserver).Start() (teamserver/cmd/server/teamserver.go):

  1. Resolve listen host/port from the profile's Teamserver block (t.Flags.Server.Host/Port exist but no CLI flags populate them; the profile is the only source).
  2. Build the gin engine (gin.ReleaseMode) with routes:
    • GET /havoc/ → WebSocket upgrade for operator clients; each client gets ClientID = utils.GenerateID(6) and a handleRequest goroutine (teamserver.go:178), with a 64 MiB WebSocket read limit (teamserver.go:43,191) and a 10s per-event write deadline (SendEventTimeout, teamserver.go:47,1152)
    • POST /:endpoint → dispatcher over registered Endpoints (teamserver.go:232, used by External listeners)
  3. Generate a self-signed RSA certificate (common/certs.HTTPSGenerateRSACertificate) → data/server.cert / data/server.keyEngine.RunTLS(host:port, ...).
  4. Configure the Discord webhook if WebHook.Discord is set (pkg/webhook).
  5. If the profile has a Service block → service.NewService(engine) and Service.Start() (see Service API Reference).
  6. The SQLite DB data/teamserver.db is already open; it is opened in NewTeamserver (teamserver.go:49-52, called from cmd/server.go:37), and Start() only logs "existing/creating" (pkg/db, driver github.com/mattn/go-sqlite3).
  7. Start listeners declared in the profile, then restore listeners and agents (including SMB pivot links) from the DB, re-notifying clients via events.
  8. FindSystemPackages() (called from cmd/server.go:75) locates x86_64-w64-mingw32-gcc, i686-w64-mingw32-gcc and nasm, honoring the profile's Teamserver.Build.Compiler64/Compiler86/Nasm overrides before falling back to exec.LookPath (teamserver.go:1250-1296).
  9. Append the profile event and block on <-ServerFinished.

Loot/logs go to data/loot/<timestamp>/ (pkg/logr): a fresh timestamped root per teamserver run. Layout: data/loot/<2006.01.02._15:04:05>/{agents,listener}/ with per-agent Download/ (singular) and Screenshots/ subdirectories (teamserver/pkg/logr/logr.go:27-57, demon.go:141,184). The Discord webhook (pkg/webhook) posts an embed notification on new agent check-ins only, using the profile's WebHook.Discord URL with optional avatar/username overrides.

Core types (teamserver/cmd/server/types.go)

type Teamserver struct {
    Flags      TeamserverFlags
    Profile    *profile.Profile
    Clients    sync.Map            // map[string]*Client  (operator clients)
    Users      []Users
    EventsList []packager.Package  // replayed to new clients
    Service    *service.Service    // third-party agent API (nil if not configured)
    WebHooks   *webhook.WebHook
    DB         *db.DB
    Server     struct{ Path string; Engine *gin.Engine }
    Agents     agent.Agents
    Listeners  []*Listener          // {Name, Type, Config any}
    Endpoints  []*Endpoint          // {Endpoint, func(ctx *gin.Context)}
    Settings   struct{ Compiler64, Compiler32, Nasm string }
}

type Client struct {
    ClientID, Username, GlobalIP, ClientVersion string
    Connection   *websocket.Conn
    Packager     *packager.Packager
    Authenticated bool
    SessionID    string
}

Package map (teamserver/pkg/)

Package Responsibility
agent Agent model (Agent, AgentInfo, Job, Header), wire-protocol constants (commands.go), ParseHeader, ParseDemonRegisterRequest / RegisterInfoToInstance, BuildPayloadMessage, task preparation (TaskPrepare, ~6.7k lines in demons.go) and dispatch (TaskDispatch). Defines the TeamServer interface implemented by the core.
colors Terminal colors.
common Subpackages: builder (Demon payload builds via MinGW), certs (RSA cert generation), crypt, packer (binary writer), parser (binary reader incl. DecryptBuffer).
db SQLite persistence (TS_Listeners, TS_Agents, TS_Links).
events Constructors for all client-bound events (events.Demons.*, events.Listener.*, events.Service.*, events.Gate.*, ...).
handlers Listener implementations: HTTP (http.go), SMB (smb.go), External (external.go), and the shared agent-request parser parseAgentRequest (handlers.go): the two-side split between the Demon handler (0xDEADBEEF) and third-party agent handlers. See Handlers.
logger Stdout/stderr logger (logger.LoggerInstance).
logr On-disk loot/session logging (logr.LogrInstance, DemonAddDownloadedFile).
packager Operator-client JSON envelope and all event type constants.
profile Profile loading (vendored HCL fork "yaotl"); all config structs in config.go.
service Third-party agent/listener WebSocket API.
socks SOCKS5 server tunnelled over agents.
utils Helpers, e.g. GenerateID(n).
webhook Discord notifications on new agents.
win32 Win32 type definitions.

Operator authentication

handleRequestClientAuthenticate: the first message from a client must be Head.Event = 0x1, Body.SubEvent = 0x3 (OAuthRequest) with Head.User (an operator name from the profile) and Info["Password"] = hex SHA3-256 of the profile password. The server replies with an events.Authenticated(true/false) event; duplicate or unknown users are rejected. Failed logins are rate-limited per source IP, and the pre-auth read is bounded by UnauthenticatedClientTimeout. After auth the entire EventsList is replayed (SendAllPackagesToNewClient), then new messages are routed by DispatchEvent. See Client Teamserver ProtocolConnection lifecycle for the full ordering.

Event dispatch (cmd/server/dispatch.go)

DispatchEvent(pk packager.Package) is a large switch on pk.Head.Event / pk.Body.SubEvent:

  • Type.Session: Input builds agent tasks (Agent.TaskPrepare / TeamserverTaskPrepare), MarkAsDead marks sessions.
  • Type.Chat: chat messages, session/listener notifications.
  • Type.Listener: Add/Edit/Remove for AGENT_HTTP/AGENT_HTTPS/AGENT_PIVOT_SMB/AGENT_EXTERNAL.
  • Type.Gate: Stageless payload build requests (compiles Demon via MinGW in a temp dir, or forwards to a service agent's builder).
  • Type.Loot: GetFile serves previously looted files back to the requesting client (64 MiB cap, path-traversal checked).

Database schema

SQLite at data/teamserver.db:

TS_Listeners("Name" text UNIQUE, "Protocol" text, "Config" text)   -- Config is JSON
TS_Agents("AgentID" int, "Active" int, "Reason", "AESKey", "AESIv",
          "Hostname", "Username", "DomainName", "ExternalIP", "InternalIP",
          "ProcessName", BaseAddress int, "ProcessPID" int, "ProcessTID" int,
          "ProcessPPID" int, "ProcessArch", "Elevated", "OSVersion", "OSArch",
          "SleepDelay" int, "SleepJitter" int, "KillDate" int,
          "WorkingHours" int, "FirstCallIn", "LastCallIn")
TS_Links("ParentAgentID" int, "LinkAgentID" int)

Events (pkg/events)

Every state change produces a packager.Package appended to EventsList (unless Head.OneTime == "true") and broadcast to clients: events.ChatLog.*, events.Demons.NewDemon/DemonOutput/CallBack/MarkAs, events.Authenticated, events.UserAlreadyExits/UserDoNotExists (InitConnection/Error, key Message), events.SendProfile, events.Gate.SendStageless/SendConsoleMessage, events.Listener.ListenerAdd/Edit/Error/Remove/Mark, events.Service.AgentRegister/ListenerRegister, events.Loot.SendFile/SendError, events.Teamserver.Logger/Profile.

Clone this wiki locally