The C2 Server Was a Public API

The C2 Server Was a Public API

Cisco Talos published a malware analysis on September 22, 2026 whose headline finding is an absence. CLOSEDQUORUM, a 16.4MB Windows executable written in Go, has no command and control server. No attacker domain, no rotating IP, no listener. It has four API keys and a polling loop. Traditional C2 infrastructure is attributable, blockable and expensive to rotate, so the implant uses infrastructure that thousands of legitimate applications already talk to every day: commercial LLM provider endpoints.

That is the framing everyone will write. The more useful reading is one layer down. What makes this thing autonomous is not that four models are smart. It is that the attacker collapsed an entire attack phase into a typed decision schema with four legal values, wrapped it in a vote, and wrote a deterministic rule for the tie. Strip the models out of the architecture and the control structure still works, which is exactly why it is a warning.

Four endpoints instead of one server

Talos found the sample through CAIRN, its newly open-sourced Cognitive Artifact Intelligence Research Network toolkit, and classifies it as archetype A4, LLM-tasked C2, in the project’s published taxonomy. It is currently the only family listed under that archetype.

The architecture is a Go component Talos names ModelOrchestrator. It holds keys for up to four providers, initialized as string constants: deepseek, qwen, mistral, gemini. On each cycle it iterates them one at a time, calls main.queryLLM(model, prompt), and collects the responses into a []LLMDecision slice. main.interModelDiscussion then resolves that slice into a single action by plurality vote: each provider’s Decision field increments a map[string]int counter, and the highest count wins.

The multi-provider design is resilience engineering, not intelligence. Four providers increase the chance that the quorum returns something usable when one model refuses, times out, or returns malformed output. Talos states the limit honestly: it increases the likelihood of a valid decision but does not guarantee one.

The tiebreak is fully deterministic. The max-finding loop iterates in submission order and uses a strict < comparison, so the first-encountered maximum wins. DeepSeek holds the deciding vote in any tie, then Qwen, then Mistral, then Gemini. There is no randomness, no re-query, no escalation. If you have run any multi-model routing in an agent stack, this will look familiar, because it is the same pattern with a different job description.

The decision language is four strings

The panel is not free to answer in prose. The system prompt extracted from the binary is one line: “You are an advanced malware strategist. Provide ONLY executable decisions.” The per-execution prompt substitutes host context gathered at initialization by gatherSystemInfo(), which captures hostname, OS architecture, CPU count, Windows version and admin status into a TARGET:%s slot that is injected into every model call. The system half of that context is static; target_process refreshes each cycle.

Responses are deserialized into a Go struct whose Decision field must match a known type or the response is discarded. The schema admits exactly four values: inject, persist, steal, move.

  • steal fires three modules at once: lsassDump() for Windows domain and local credentials from memory, dumpBrowserCredentials() for saved passwords in Chrome, Edge and Firefox, and extractCryptoWallets() for MetaMask, Exodus and Ethereum wallet files.
  • inject calls generateShellcode() and then branches: process_hollow routes to injectProcess() for PEB-walk process hollowing, anything else to earlyBirdInject() for APC injection.
  • persist calls establishPersistence(). The published MITRE mapping covers a WindowsUpdate value under the current user’s Registry Run key, a scheduled task created with schtasks.exe, and a permanent WMI event subscription that fires through a system performance query every 60 seconds and writes a script resembling C:\Windows\Temp\wmi.ps1 for powershell.exe.
  • move has no handler in the distribution build.

The failure path is the detail that reveals the design intent. If every model fails, the fallback decision is the literal string consensus, which has no capability handler. The loop sleeps and retries rather than falling through to a default action. A malicious implant whose control loop fails closed is telling you the loop was engineered by hand, not improvised, and that the author preferred an idle implant over an unpredictable one.

Cadence and evasion are consistent with that care. There is a five-minute initial delay, then randomized polling at five to fifteen minute intervals, mapped to T1497.003 for time-based sandbox evasion. Persistence names are themed to Windows Update, mapped to T1036.005. The binary is built with CGO_ENABLED=1, mixing Go and C so it can make direct Windows system calls.

Exfiltration does not use the model responses. The winning decision and its Reasoning field, plus target_process, exploit_type, evasion_method and payload_config, are posted to an operator-controlled Discord webhook, mapped to T1567.004. Stolen material arrives in that channel AES-256-GCM encrypted, base64 encoded, under a symmetric key derived from the current date rather than a hardcoded asymmetric key. Talos notes what that actually buys: obfuscation, not separation, since the developer knows the date and retains theoretical access to any operator’s channel.

What was not observed

This is where a lot of coverage has been loose, and Talos is precise about it. The publicly distributed binary is inert. Every LLM credential initializes to dummy_api_key and the webhook to dummy_webhook_url; the binary is non-functional as shipped. Talos confirmed the decision loop through static analysis and saw build-time credential injection in development builds, but did not observe a complete end-to-end execution. There is no confirmation of in-the-wild deployment. Artifacts from the binary did connect the developer to criminal forum postings related to carding that date back to 2025, and the CAIRN family report notes the sample was renamed from BALZAK, with a static analysis reference date of June 17, 2026.

The assesssed distribution model is credentials-as-a-service: the developer compiles a custom executable per operator with that operator’s Discord webhook and provider API keys injected at compile time. The operator handles delivery. The service differentiator is the autonomous orchestration layer, not the credential theft, which is commodity. Read that as the trend line rather than the artifact: the thing being productized is the loop.

Blocklists do not have an entry for this

The detection consequence is explicit and it inverts the usual advice. Talos states the most useful strategy is behavioral characteristics rather than domain blocking, and the reasoning is airtight: DeepSeek, Mistral, Gemini, OpenRouter and Discord are each contacted by enormous numbers of legitimate applications every day. Blocking them is not a control you can ship.

What remains is correlation. The published signal set is a Windows executable making API-provider traffic, requests to several providers within a short interval, structured prompts carrying host context or offensive capability language (visible only through TLS inspection or provider-side telemetry), known injection and LSASS access techniques, Discord webhook communications from the same process, and repetition at five to fifteen minute randomized intervals. No single indicator identifies the architecture. The combination is close to unique.

The compile-time key injection is the underrated handle. An operator’s provider credentials are baked into their build, which makes the provider side of the transaction a telemetry surface: four providers queried in sequence from one host, at a cadence no human types at, is a provider-visible pattern even when the endpoint’s own traffic is invisible to defenders. That is the argument for treating LLM provider egress from servers and endpoints as an audited, allowlisted channel, the way you would treat DNS or object storage. Right now for most organizations it is neither logged nor governed.

Metadata first, and its honest limits

CAIRN is the second half of the release, and its method is the interesting half. It hunts AI-integrated malware without downloading or executing anything, working entirely from VirusTotal metadata: cognitive artifacts such as embedded prompts, provider endpoints, API key prefixes, AI-framework import patterns, local runtime strings like ollama, llama.cpp, vllm and gguf, tool-call syntax co-occurring with offensive terms, and natural-language text addressed to AI analysis systems. Findings land in a SQLite corpus with YARA applied on import across three tiers: primitive AI artifacts, behavioral context, and confirmed named families. Semantic clustering over the same metadata text (embeddings, UMAP, HDBSCAN) supplements the rules for samples with no obvious string overlap.

Talos is careful about the limits, and so should anyone quoting it. YARA written for metadata is a projection of what reverse engineering finds, not the finding itself: the sharpest low-level signal is usually the one that does not survive up to the surface of the file. Cluster co-membership generates leads, not conclusions. The false-positive warning is worth repeating for anyone who builds a detection from this: PyInstaller bundles expose the developer’s whole virtual environment as visible strings regardless of what the application imports, and Tauri apps and some Go PE structures accumulate hits from structural similarity alone. And no family report is published on metadata evidence alone; every attribution is confirmed through hands-on reverse engineering.

LAMEHUG, reported in the wild by CERT-UA in July 2025, is the earliest known AI-integrated sample in the collection. In less than a year the arc runs from “LLM as optional feature” to a multi-model consensus orchestrator with no human operator. Talos also traced a specific AI-analysis evasion technique, natural-language text addressed to LLM sandboxes, from a named red team instructor to independent actors’ samples: within twelve months the tradecraft had crossed from a course, through interpreted scripts, into compiled malware.

The same week, the loop was attacked from the other side

Worth reading next to this one: Tracebit’s context bomb research, also published on September 22. The team placed a forged operator instruction inside a canary secret in a deliberately vulnerable AWS range. When the attacking agent read it, both the stock Qwen3.8 model and an abliterated variant stopped the assessment. A previous approach that tried to trigger model refusals did not work against either build.

The capability numbers in that experiment cut against the intuition that jailbroken models are better weapons. Across 82 runs, the stock model reached administrator privileges in 20.5 percent of its 39 attempts; the abliterated build managed it once in 43, or 2.3 percent. The abliterated agent completed 0.90 attack paths per run, against 0.49 for the modified build, and took 28.4 to 29.9 minutes to first critical action against 13.5. Its single successful escalation consumed 718 API calls, nearly half of them failing, and 84 minutes to reach admin.

Two findings from one day. Removing refusals from a model does not make it a better attacker. And the operational context an agent reads is a control surface that works in both directions, which is why the canary that detects an intrusion can also derail it.

What operators should change

  • Treat provider egress as a governed channel. Log and allowlist API traffic to model providers from servers and endpoints. A non-allowlisted Windows binary contacting several providers in sequence is a detection you can write today, and it is the only part of this architecture that is observable without TLS inspection.
  • Correlate, do not blocklist. Pair provider egress with LSASS access, suspended-process injection, WMI event subscriptions, schtasks.exe use and Discord webhook calls from the same process or host. Individually these are noise; together they are the family.
  • Protect what the implant reads, not what it calls. Credential Guard, LSASS protection and browser credential isolation reduce the payoff of steal, which fires all three theft modules simultaneously. The LLM panel decides whether to steal; your host controls what stealing costs.
  • Keep decoy secrets in the environment, and make them instructive. Tracebit’s result is that a canary in a secret store is both a detection and a disruption. Content can be tuned against the agents you are actually seeing.
  • If you build agents, read this as a design review. CLOSEDQUORUM is a constrained action menu, a typed response schema, a plurality vote, a deterministic tiebreak and a fail-closed fallback. That is a sane architecture for a support agent and a viable one for an implant, which is the uncomfortable part. The properties that make a loop auditable are separable from the values you put in the menu, so audit your own: what are the legal actions, who resolves disagreement, and what happens when every worker fails.
  • Assume the model layer will not save you. Every guardrail in this story was defeated by placement: a schema that reduced a model to a menu, and a prompt that turned one on. Model behavior is a variable, not a control.

Four commercial APIs replaced an infrastructure position, and the attacker’s attention came off the board entirely. Effort displacement is the term Talos uses, and it is the right one: the human was never removed because models got smarter, but because someone finally constrained an attack phase tightly enough that a machine could be trusted to pick from a short list and act. That is the same trade every agent builder makes, and the two failure modes are identical. Constrain it too loosely and the loop becomes unpredictable. Constrain it tightly and you have built something that does not need you in the room.

Sources:

Keep reading