Martin's Blog

2. Anatomy of a Surveillance Platform

RCS is easiest to misunderstand when reduced to “spyware.” The implant was one part of the product, but the product was an investigation system. It connected covert collection to users, permissions, target records, evidence processing, search, alerting, topology management, and backups. The leaked manuals describe both an all-in-one installation and a distributed deployment, and the source tree supplies the services behind those diagrams.

I use platform deliberately. The implant collected. The surrounding system made that collection repeatable, searchable, shareable, and administratively ordinary.

The All-in-One RCS architecture from the leaked System Administrator's Guide
ORIGINAL MANUALThe vendor's All-in-One deployment view places the Master Node, Collector, and operator Console inside one managed system.

One product, two physical layouts

The System Administrator’s Guide presents two deployment patterns. The first, called All-in-One, concentrates the Master Node, the first database shard, and its Worker in one protected installation. That name can be misleading. It does not mean every RCS role lived on one computer or on one side of a firewall. The agent remained on the monitored device; an optional anonymizer chain could stand in front of the collector; and the collector remained the externally facing handoff point. Analyst consoles and optional Network Injector components also retained distinct roles.

The second pattern distributes the backend. Additional database shards each gain their own Worker, while a single Network Controller coordinates Network Injectors. The guide divides the platform into a public-facing front end and a protected back end, with a firewall separating them. Each anonymizer chain terminates at a collector, but several such paths can feed the same Master Node.

Logical RCS topology showing endpoint evidence, network injection, and analyst routes crossing a firewall into the Master, shard and Worker pairs, and Network Controller
SOURCE · MANUAL SYNTHESISThe distributed platform joined three routes at the protected backend: endpoint evidence through collectors, Network Injector traffic under controller management, and analyst-console access. The diagram is a logical synthesis, not a recovered customer deployment.

This schematic is logical, not a recovered customer topology. It combines the roles named in the two manual diagrams so that the boundaries are explicit. Real installations could omit optional components or use several collectors and anonymizer chains.

The two layouts change operational risk without changing the product model:

PropertyAll-in-One backendDistributed backend
Failure concentrationMaster, first shard, and Worker share a smaller failure domainProcessing and storage can continue across several shard/Worker pairs
Credential concentrationMore privileged functions converge on fewer hostsPrivileged material is spread across more hosts and links
Network observabilityFewer internal flows to monitorMore east-west component traffic and more places to collect logs
Acquisition strategyOne backend image may reveal much of the control planeInvestigators may need Master, shard, Worker, and configuration-server views
Compromise impactOne backend foothold can expose several functions at onceSegmentation can limit a foothold, but only if credentials and administrative paths are also separated

The distinction matters for the later security assessment. Distribution is not itself isolation. If every backend role trusts the same operator credentials or an exposed database router, adding shards creates capacity rather than a new security boundary.

This chapter establishes the vocabulary used throughout the book. “Agent” can mean a deployed implant in ordinary security writing; in the backend it also means the database record representing that instance. Likewise, a “factory” is not a server: it is an agent configuration template waiting to be cloned when an instance first synchronizes.

The investigation hierarchy

The operator view begins with a hierarchy:

work group
└── operation
    └── target
        ├── factory
        └── deployed agent instance

An operation is an investigation. A target is the person or subject inside it. A factory is the configured starting point for an agent. When a new instance first reports its identity, the backend clones the matching open factory into a deployed agent record named <factory> (N). Evidence and remote requests then attach to that record and its target.

Visibility is group-driven. Users belong to work groups, and operations inherit the users associated with their groups. Targets and agents sit below those operations. This model determines what appears in the console and, as the security assessment will examine, creates unusual edge cases when group membership changes asynchronously or an object is created without a group.

The hierarchy is more than presentation. Paths recur in authorization checks, entity relationships, alerts, connectors, searches, and configuration forms. The product’s data model makes the investigation itself the organizing unit for surveillance.

In the backend, these objects share a polymorphic items collection. A _kind field distinguishes group, operation, target, factory, and deployed-agent records. Arrays such as path, group_ids, and user_ids denormalize ancestry and visibility so that a query can decide access without walking the whole tree. That improves routine reads, but it creates an important invariant: when membership or ancestry changes, every affected copy must eventually agree. Later chapters show why deferred access-control rebuilding deserves attention.

Creating a target also creates storage around it. The backend establishes the target’s evidence collection, aggregate collections, and GridFS namespaces. Destroying or moving a target is therefore not merely editing one document; it can affect a family of collections and the objects that name them.

The factory/agent distinction is equally consequential. A factory holds an open configuration template. At first synchronization, the server resolves an identity, clones the factory, and turns the clone into the persistent record for a particular instance. The lab exercises that backend state transition with synthetic identifiers. It does not create, deliver, or run the executable that would ordinarily initiate it.

A work group is also a security object, not just a folder. Its members determine which users are copied into operations and their descendants. The Console makes this look like familiar collaboration, while the server must preserve a graph of duplicated authorization facts. That combination—easy administrative UI over denormalized authorization—is a recurring theme in the assessment.

The endpoint collection plane

The hierarchy explains where a deployed agent belongs. It does not explain what endpoint code could collect. For that, the relevant evidence is not a console label or a shared log constant but a chain through the source: a module is registered or dispatched, a concrete implementation exists, and the result reaches an evidence writer, log constructor, or companion IPC path.

Two naming systems meet here. In the product, Scout, Soldier, and Elite are agent levels. Scout was the small first stage used to inspect an installation environment before an operator chose whether to replace it. Soldier was an intermediate Windows stage, while Elite named the fuller agent level. In the leaked archive, however, the labels do not map to three parallel cross-platform source families. The inspected Scout and Soldier implementations live in the dedicated scout-win and soldier-win trees. The core-* label is this book’s shorthand for the platform-specific full-core repositories such as core-android, core-ios, and core-win32.

RCS endpoint taxonomy separating the Scout, Soldier, and Elite product levels from the scout-win, soldier-win, and platform-specific core source repositories
MANUAL · SOURCE TAXONOMYProduct levels and repository names are related but not interchangeable. Scout and Soldier have dedicated Windows source snapshots; the full cores are split by platform. The arrows show an available product upgrade direction, not a mandatory path taken by every deployment.

The diagram is deliberately asymmetric. The archive does not support treating Scout, Soldier, and every core-* tree as equivalent builds from one release, or assuming that each platform passed through all three levels.

With that distinction in place, a static audit across the frozen core-*, Scout, and Soldier trees finds broad but uneven coverage. Across the platform set, concrete paths cover communications, contacts and calendars, call records, device and application state, location, screenshots and camera images, microphone capture, credentials, clipboard and key input, web activity, and selected files or service data. This is a union of platform-specific source findings, not a list that every implant implemented. Android and Linux expose relatively clear module factories or registration tables. iOS explicitly divides concrete internal agents from generic remote-agent instances. macOS and Windows desktop mix local collection with injected or IPC-backed companions. Symbian and BlackBerry have smaller registered sets. Windows Mobile and Windows Phone differ even though they share names and ancestry.

The staged Windows sources make the danger of flattening especially obvious. Scout directly packages device information and screenshots, while its social source is empty and its webcam source is commented out. Soldier starts a wider configuration-controlled set, including social/account data, location, clipboard, credential, screenshot, camera, URL, device, and file-related paths, but its standalone photo thread is disabled. The 64-bit Windows tree is a partial companion for a larger desktop core, not a second feature-complete implant. Android’s native and audio trees are treated as supporting projects rather than independent products.

The audit therefore uses six states: Implemented, Conditional, Stub/disabled, Referenced only, Absent from the inspected registry, and Unresolved. Even “Implemented” is only a SOURCE conclusion. It does not show that a historical build included the module, that a customer was entitled to it, that a device met its prerequisites, or that collection succeeded. Application-specific chat and service collectors add further dependencies on installed versions, local schemas, privileges, and runtime state.

Representative anchors include Android’s factory at core-android/RCSAndroid/src/com/android/dvci/module/FactoryModule.java:25-45, Linux’s registration table at core-linux/core/src/module.c:15-31, iOS’s internal/remote split at core-ios/core/Modules/Agents/RCSIAgentManager.mm:368-450, Symbian’s compact factory at core-symbian/Core/src/AgentFactory.cpp:23-46, and the Windows desktop registrar sequence at core-win32/AM_Core.cpp:413-433. The complete frozen-revision matrix and registration-to-writer evidence appear in Appendices D–E.

This endpoint plane joins the rest of the architecture at an evidence-format boundary. Endpoint modules emit typed records; collectors and routing components move them; the Worker decodes them; target-scoped storage and the console make them searchable. The active reconstruction starts after the endpoint and collector. Chapter 7 preserves that gap when it distinguishes a source-derived record shape from a synthetic Worker replay and from a direct database fixture.

The operator plane

The original console is an Adobe AIR 15 application written in MXML and ActionScript. It authenticates to the database service over HTTPS and maintains a WebSocket connection for push events. Its major sections expose the product’s roles:

SectionOperator purpose
HomeSearch, recents, and investigation shortcuts
MonitorComponent health, version, licensing, and counters
OperationsTargets, agents, configuration, evidence, and requests
IntelligenceEntities, identities, links, and geographic views
DashboardPinned operations and evidence activity
AlertingPer-user rules and matching evidence notifications
Accounting/AuditUsers, groups, permissions, sessions, and activity
SystemCollectors, anonymizers, shards, backups, injectors, and connectors

This is why the console matters to capability assessment. A list of implant modules says what data might be collected. The console shows how collection becomes routine work: filters, saved views, maps, graphs, request queues, notifications, and collaboration.

The account model divides operators into four broad roles: administrator, system administrator, technician, and viewer. More granular privileges qualify those roles for actions such as evidence deletion, configuration, and system management. Role names alone are therefore insufficient when reconstructing an incident. An examiner needs the account record, its subprivileges, its group membership, and the audit history surrounding the action.

The operator protocol is also split. Ordinary commands and responses travel through the HTTPS REST service, while changes, status messages, alerts, and session events arrive over WebSocket push. A console can consequently show a state that was initially fetched and then incrementally patched. Forensic reconstruction must ask whether a fact came from a stored REST response, a later push event, or client-side state that was never persisted.

The backend plane

At the center is rcs-db, a Ruby EventMachine application. In the examined snapshot it serves the operator REST API over HTTPS and push over a WebSocket port one number higher. It manages accounts, investigations, agent records, evidence metadata, configuration, topology, tasks, licensing, audit records, and backups.

MongoDB holds that state. RCS expects a sharded deployment even in the small lab recreation: a mongos router, a shard server, and a configuration server. Evidence is separated into collections named for each target, with large binary objects stored in corresponding GridFS collections. That layout is both an implementation decision and a forensic fingerprint.

Storage falls into several useful forensic classes:

The per-target layout is convenient for lifecycle operations, but it raises the cost of complete acquisition. A collection list, shard map, and GridFS inventory can be as important as an export from the main items collection. It also means that a count from a global model and a count from a target evidence collection answer different questions.

The worker handles the evidence-processing path. Source defines the route from an encrypted evidence blob through decryption, storage, alert-queue insertion, matching, and push notification. Project notes record an earlier synthetic lab exercise of that route, but the current publication artifacts do not include a named automated test and sanitized trace. Even after that gate is repeated, it will prove only the backend portion of the pipeline, not an implant-to-collector session.

In the documented distributed design, each shard has a Worker. Backend source selects a Worker from the configured set using a stable calculation over the incoming identity. That ties processing distribution to storage layout and helps keep a given stream’s work predictable. It also makes Worker inventory and health relevant to apparent collection gaps: absent evidence can mean no collection, delayed forwarding, failed processing, or storage on a shard that was not acquired.

The server also owns tasks that would normally produce downloads, topology updates, database maintenance, backups, or builds. The web console permits benign administrative task paths needed for fidelity, but all implant and installer build controls are render-only and are tested never to create a build task.

Not every job is represented in the same way. Some tasks are short-lived, per-user, in-memory work coordinated by the running server. Backup definitions, archives, alert queues, and other operational objects are persistent. This distinction affects both recovery and evidence: restarting a service may erase one class of pending state while leaving another class intact. A screenshot of the task manager is therefore not a durable job ledger.

The collection and routing plane

The collector faces agent traffic. Source and manuals describe it as the point that receives synchronization traffic and forwards material toward the backend while presenting decoy behavior to invalid requests. Collectors can be placed behind chains of anonymizers, remote systems intended to separate a publicly visible endpoint from the backend and collector topology.

The manual describes continuity at both ends of this path. An agent can retain a bounded amount of material while it cannot reach its server, and a collector can store received data locally while the Master Node is unavailable, then forward it after connectivity returns. This is a MANUAL claim about intended operation. The reconstruction has not measured those edge buffers, their limits, or their failure behavior because doing so would require running excluded implant or edge components.

Collector records in the backend include topology relationships and a notion of whether an entry belongs to the public or protected side. Anonymizer chains are ordered routes rather than an undifferentiated pool: topology affects which collector receives traffic and what infrastructure an external observer may see. A seized frontend host may therefore reveal only the next hop, while the Master’s topology records can reveal the intended chain.

The product also supported Network Injectors positioned where they could identify selected HTTP traffic and alter or replace content. The manuals describe provider-installed and tactical forms. These components are central to understanding RCS as a delivery platform, but they are not active in this lab. Their behavior in later chapters is therefore SOURCE or MANUAL evidence, not a description of packets generated during reconstruction.

A Network Controller coordinates the injector side of the design. In the distributed diagram it is a shared control component even when storage and processing are spread across many shard/Worker pairs. This is a useful reminder that a platform can distribute data capacity while retaining a concentrated control point. Static analysis maps the modeled objects, rules, and file-transfer workflows. How a particular customer placed or used an injector remains unknown.

The RCS component catalogue showing agents, anonymizer chains, collectors, and the master node
ORIGINAL MANUALThe manual's component catalogue exposes the product's intended separation between endpoint agents, forwarding infrastructure, collection, and central control.

The analysis plane

Stored evidence is typed. The manuals enumerate address books, applications, calendars, calls, camera images, chats, clipboard contents, device data, files, filesystem listings, informational records, keylogs, messages, microphone recordings, mouse activity, passwords, positions, print activity, screenshots, and URLs. The backend and console add operational metadata around those types: date filters, relevance, notes, binary viewers, statistics, deletion, and export.

The Intelligence subsystem builds entities and handles above raw evidence. An analyst can represent people and virtual identities, associate accounts, create relationships, and view locations. Alerting connects new evidence to per-user rules scoped to an operation path. Connectors provide export pipelines to other systems. Together these features show the shift from collection to analysis: RCS was designed to keep an investigation alive, not merely exfiltrate a file.

That analytical layer mixes several kinds of provenance. A message or image can be collected material; a parsed handle or geographic point can be derived from it; an entity name, relationship, relevance flag, or note can be an analyst’s assertion. Exports and screenshots often flatten those distinctions. A careful report should preserve the raw evidence identifier, processing metadata, and analyst-created object separately instead of treating every field visible in the Console as a direct observation from a device.

Backups, connectors, and audit records extend this provenance problem beyond the live database. A backup may preserve records later deleted from the active view. A connector can create another copy under different retention controls. An audit record may show an operator action without proving the truth of the underlying evidence. These are distinct sources that should be correlated, not silently merged.

From collection to an analyst’s screen

The complete logical data flow is longer than the familiar agent-to-server arrow. A useful reconstruction separates collection, transport, processing, storage, notification, and presentation:

Eight-stage RCS evidence journey from monitored activity through the excluded edge, validated backend processing, Console presentation, and analyst action
SOURCE · MANUAL · LAB · WEB PORTThe reconstruction enters at the backend. Steps 1–4 remain outside active execution; steps 5–6 are supported by source and bounded lab evidence, step 7 by web-port validation, and step 8 by source and manuals.

The labels on the right are part of the methodology. They prevent a successful backend experiment from being reported as validation of the excluded edge. The lab enters this flow at two safe points. It can create a synthetic factory and send the authentic first-status transition that produces a database agent record. Separately, it can submit a synthetic encrypted evidence object to the Worker and observe backend processing. Some UI fixtures are inserted directly into lab MongoDB when the leaked REST endpoint is absent or broken. None of these actions constructs an installer, operates a deployed implant, activates an anonymizer, or intercepts third-party traffic.

The same diagram can be read backwards during an investigation. Start with the screen or export, recover its evidence identifier and target collection, locate the GridFS object if present, correlate Worker and alert-queue state, identify the agent database record and its path, and finally use collector/topology logs to reason about transport. Each backward step can fail independently. That is why “the Console showed it” is a weaker statement than a chain linking raw bytes, database metadata, processing records, and operator activity.

An architecture-led acquisition map

The component model suggests a minimum acquisition plan for a suspected RCS backend. This is not a substitute for normal host and network forensics; it is a map of where product-specific state can diverge.

SourceWhat it can establishWhat it cannot establish alone
Master application files and logsConfiguration, listener behavior, application errors, REST actions, component statusComplete evidence contents or every transient task
Main MongoDB application databaseAccounts, investigations, paths, topology, alerts, audits, persistent jobsLarge evidence binaries and state already removed or never persisted
Target evidence collectionsTyped evidence metadata and target-scoped recordsThe referenced GridFS bytes, operator intent, or original transport path
GridFS files and chunksBinary payloads corresponding to evidence metadataMeaning without the referencing document and decoder context
MongoDB config server and shard metadataWhich shards existed and how collections were distributedWhether every shard was successfully acquired
Worker logs and queuesReceipt, processing, errors, and downstream enqueue activityWhat occurred on the monitored device before receipt
Collector host and backlogEdge receipt, local delay, next-hop configuration, and possible queued materialAnalyst access or final backend processing
Console workstationCached exports, screenshots, credentials, user-visible state, and analyst artifactsAuthoritative server state at any arbitrary earlier time
Backup archives and connector destinationsHistorical or replicated copies outside the live databaseWhy a copy was created or whether it was later modified elsewhere

Order matters. Before querying through the application, preserve volatile process, socket, and queue state and identify every database member. Before interpreting target evidence counts, confirm that the relevant shard and its GridFS chunks are present. Before accepting a Console export as raw evidence, trace its identifiers back to stored records and determine which fields were derived or analyst-authored. These are general forensic cautions made concrete by RCS’s architecture.

The topology also helps explain negative findings. An empty active target collection is not evidence that no record ever existed: one may remain in a collector backlog, wait in a Worker stage, reside on a missing shard, survive only in a backup, or have been deleted. Conversely, an orphaned GridFS object only shows that bytes remain. Interrupted processing or deleted metadata may have kept them from an analyst. Claims should name the layer actually examined.

For network acquisition, the same principle applies. The Master exposes an operator-facing HTTPS service and a related WebSocket service; Workers, collectors, database roles, and optional edge components add separate flows. A packet capture at the operator boundary answers different questions from one at the collector or database boundary. The IOC appendix therefore records ports, headers, certificates, and protocol traits by component rather than publishing one undifferentiated “RCS traffic” signature.

Trust boundaries

For reconstruction, the architecture has four useful trust boundaries:

  1. Public edge: implants, collectors, anonymizers, and injectors.
  2. Component network: collectors, workers, the master service, and MongoDB.
  3. Operator boundary: REST, WebSocket push, sessions, users, groups, and administrative permissions.
  4. Evidence boundary: target collections, GridFS, exports, backups, and audit records.
Four-domain RCS trust map separating the public edge, trusted component network, operator identity and session state, and accumulated evidence
SOURCE · MANUAL SYNTHESISThe logical trust map separates externally reachable roles, backend components, operator state, and accumulated evidence. Arrows mark boundary crossings, not a recovered customer topology.

Each boundary carries different assets and different questions:

BoundaryAssets crossing itClaimed or observed controlsAssessment question
Public edgeIdentities, synchronization traffic, evidence batches, routing metadataAnonymizer chains, collector authentication/certificates, decoy responses, firewall placementCan an outsider identify, impersonate, bypass, or trace an edge component?
Component networkDecoded evidence, topology commands, Worker jobs, database queriesProtected network placement, component configuration, shard routingDoes one component foothold become trusted access to the backend or database?
OperatorCredentials, session cookies, REST commands, push messagesTLS, roles, subprivileges, groups, audit eventsAre authorization decisions consistent across endpoints and object states?
EvidenceRaw bytes, metadata, derived objects, exports, archivesTarget separation, GridFS references, application checks, audit and backup workflowsCan evidence be read, altered, deleted, or confused without a reliable record?

The original deployment assumed that several of these boundaries would be protected operationally. The security chapter will test what the code itself enforces when those assumptions fail. That distinction matters: an unauthenticated MongoDB port reachable only after a firewall mistake is not the same problem as an authorization bypass in the public REST service, but either can expose the same victims.

Trust boundaries also move with deployment choices. Putting a collector in a screened network is meaningful only if its management path, stored backlog, and credentials are protected independently. Distributing shards reduces a single storage bottleneck, but a broadly trusted database router can reunify them for an intruder. TLS protects a channel, but it does not correct an endpoint that authorizes the wrong user. The assessment therefore evaluates controls at the point of use rather than treating the manual’s firewall line as proof.

Availability was part of the surveillance design

Several architectural choices aim to keep an investigation producing data through intermittent failure: bounded buffering at the agent, local retention at the collector, multiple Worker/shard pairs, queues between processing stages, health monitoring, and scheduled backups. Availability is not ancillary here. Delayed evidence still has investigative value, so the platform is built to resume a data path rather than require every component to be continuously reachable.

That resilience creates ambiguity for incident responders. A missing interval may be sitting on an edge buffer; duplicated-looking records may result from a retry; an alert may lag behind storage; a backup may contain a different state from the active database. Timestamp semantics therefore matter. Device time, receipt time, processing time, and analyst modification time should not be collapsed into a single chronology.

The same mechanisms expand the protection problem. Buffers and backups are additional evidence stores. Queues can expose sensitive metadata before the final record is visible. Health and topology screens reveal internal structure. High availability without compartmentalized credentials and retention creates more durable copies for both defenders and intruders.

What this architecture establishes

Before examining version skew or individual vulnerabilities, the recovered design establishes several facts:

These statements are architectural, not claims that every customer enabled every feature or deployed the recommended topology. The archive describes a product’s possibilities; source code reveals implemented behavior; lab results show what the bounded reconstruction actually did. Keeping those categories separate is the foundation for the chapters that follow.

The next chapter complicates this clean diagram. The components in the archive come from different release lines, and the exact boundaries depend on which snapshot is speaking to which.

Sources and evidence

↑ HackingTeam's RCS: Bringing a Commercial Spyware Platform Back to Life