-
Notifications
You must be signed in to change notification settings - Fork 1
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, also0.8.2); the fork keeps both in step, and the release level is tracked inCHANGELOG.md.
(*Teamserver).Start() (teamserver/cmd/server/teamserver.go):
- Resolve listen host/port from the profile's
Teamserverblock (t.Flags.Server.Host/Portexist but no CLI flags populate them; the profile is the only source). - Build the gin engine (
gin.ReleaseMode) with routes:-
GET /havoc/→ WebSocket upgrade for operator clients; each client getsClientID = utils.GenerateID(6)and ahandleRequestgoroutine (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 registeredEndpoints (teamserver.go:232, used by External listeners)
-
- Generate a self-signed RSA certificate (
common/certs.HTTPSGenerateRSACertificate) →data/server.cert/data/server.key→Engine.RunTLS(host:port, ...). - Configure the Discord webhook if
WebHook.Discordis set (pkg/webhook). - If the profile has a
Serviceblock →service.NewService(engine)andService.Start()(see Service API Reference). - The SQLite DB
data/teamserver.dbis already open; it is opened inNewTeamserver(teamserver.go:49-52, called fromcmd/server.go:37), andStart()only logs "existing/creating" (pkg/db, drivergithub.com/mattn/go-sqlite3). - Start listeners declared in the profile, then restore listeners and agents (including SMB pivot links) from the DB, re-notifying clients via events.
-
FindSystemPackages()(called fromcmd/server.go:75) locatesx86_64-w64-mingw32-gcc,i686-w64-mingw32-gccandnasm, honoring the profile'sTeamserver.Build.Compiler64/Compiler86/Nasmoverrides before falling back toexec.LookPath(teamserver.go:1250-1296). - 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.
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 | 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. |
handleRequest → ClientAuthenticate: 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 Protocol → Connection lifecycle for the full ordering.
DispatchEvent(pk packager.Package) is a large switch on pk.Head.Event / pk.Body.SubEvent:
-
Type.Session:Inputbuilds agent tasks (Agent.TaskPrepare/TeamserverTaskPrepare),MarkAsDeadmarks sessions. -
Type.Chat: chat messages, session/listener notifications. -
Type.Listener:Add/Edit/RemoveforAGENT_HTTP/AGENT_HTTPS/AGENT_PIVOT_SMB/AGENT_EXTERNAL. -
Type.Gate:Stagelesspayload build requests (compiles Demon via MinGW in a temp dir, or forwards to a service agent's builder). -
Type.Loot:GetFileserves previously looted files back to the requesting client (64 MiB cap, path-traversal checked).
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)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.