Martin's Blog

8. Inside an RCS Investigation

RCS makes the most sense when followed as a piece of work rather than a list of components. An administrator creates users and groups. An analyst opens an operation and its targets. A technician prepares a factory configuration. A synchronizing instance becomes an agent record. Evidence appears in typed views, acquires notes and relevance, feeds entities and dashboards, and can trigger personal alerts. Other sections expose the health and infrastructure that keep that workflow moving.

I use the lab’s fictional Operation Nightjar to walk through that loop. Every name, account, location, message, file, device, relationship, and alert described here is synthetic. Most evidence rows went directly into the lab database to exercise the server and Console contract. A separate narrow test used a synthetic encrypted blob to validate Worker and alert processing. No implant, exploit, collector, anonymizer, or Network Injector generated the scenario.

The reconstructed web console is also not the original AIR binary. When the chapter describes a visible interaction, it means one of three things:

Those categories often support the same step, but they are not interchangeable. A screenshot can show that a view renders. Evidence provenance requires a separate record.

The fictional case

The demo seed creates two operations so search and cross-operation views have meaningful choices. This walkthrough stays inside Operation Nightjar, described as a fictional financial-fraud investigation in Milan. The operation contains two targets, each with a desktop factory and a synthetic deployed agent. A shared demonstration group contains two analyst accounts and the lab administrator.

The principal path is:

demo_team
└── Operation Nightjar
    ├── A. Moretti (laptop)
    │   ├── desktop factory
    │   └── synthetic deployed agent
    └── R. Bianchi (associate)
        ├── desktop factory
        └── synthetic deployed agent

The seed also creates fictional entities—people, handles, a virtual laptop identity, relationships, and positions—and about two weeks of evidence-like records. Recent rows include chats, mail, keylogs, URLs, screenshots, calls, positions, an address-book entry, application activity, a file, clipboard text, and financial-address metadata. The values are invented and exist only to make filtering, viewers, maps, links, alerts, and counters visible.

This scenario is best treated as a UI fixture with investigative coherence. It is not a simulation of how frequently an actual product would collect each type, how reliable that collection would be, or how a real analyst should weigh the resulting material.

Access begins with the work group

An RCS operation is not simply a folder visible to every authenticated user. Its groups determine which users receive access. Targets, factories, and agents sit below the operation, and their denormalized user relationships are rebuilt from that hierarchy. The console loads only objects visible to the logged-in session.

In the Nightjar seed, demo_team contains the two fictional analysts and the administrator before the operations are created. Adding a user to the group after the user has already logged in does not necessarily update every piece of client and session state immediately. The live tests therefore create group membership first and log in afterward.

The distinction between role and case membership is important. Privileges decide whether a user can see a major section or invoke a category of action. Group-derived access decides which investigation objects populate that section. A user can possess view privileges yet see no Nightjar tiles because the operation is not in the user’s group-derived scope.

That division also explains several security findings in Chapter 10. Group and path maintenance are not mere interface bookkeeping; they are part of the authorization model. Stale, missing, or overly broad relationships can expose or hide an investigation even when the user’s named role appears correct.

The Accounting section presents this setup to administrators as Users and Groups. User forms combine enabled state, locale, timezone, contact details, and a large privilege matrix. Group forms join users and operations. Active sessions can be disconnected, subject to the same session behavior described in Chapter 5. The manual audit confirms the visible user/group workflow, while live tests exercise creation, update, membership, and deletion with disposable accounts.

Entering an operation

The Home screen offers global search, recent items, and quick actions. Searching for “Nightjar” can jump directly into the operation, but the most legible route begins in Operations. The root view shows the operations available to the current user as tiles or a table. Selecting a tile fills an information panel; double-clicking it moves into the operation’s targets.

The original manual describes operation details including name, description, contact, status, and groups. Operation Nightjar uses these fields to give the otherwise abstract container an owner and purpose. A breadcrumb records the path and provides shortcuts back to higher levels.

The port’s Operations Section State Manager represents the drill-down explicitly:

allOperations
  → singleOperation
    → singleTarget
      → singleAgent
      → agentConfigList → config
      → evidence / filesystem / commands / info / IPs / file transfer

This is more than navigation chrome. The state determines which manager view is active, which selected object supplies identifiers to REST calls, which columns are shown, and what the Back action must restore. Opening an evidence view under one agent, for example, hides the agent column because that context is already fixed. Opening evidence from a broader target or operation context requires the column to distinguish sources.

Closing an operation or target is also not a local visual flag. The backend cascades status to descendants. Destruction and moves affect paths, access, statistics, and sometimes deferred cleanup. Tests deliberately destroy child operations before their groups because reversing that order can empty access lists and leave an object the cleanup user can no longer delete.

Targets, factories, and agents are different records

Opening the fictional target A. Moretti (laptop) reveals both a factory and a deployed agent tile. The icons look related because both represent potential or actual collection configurations, but their lifecycle differs.

A factory is a template—closer to a blueprint than a running instance. It has a platform family, level, configuration, identifier, and an open place in the operation hierarchy. A deployed agent is the backend record created when an instance identifies itself through the first-synchronization status path. It carries an instance identifier, version and platform fields, runtime status, statistics, request queues, and accumulated configurations.

The demo uses the authentic /agent/status transition to clone each open factory into a synthetic deployed record. That proves the database’s factory to agent lifecycle without transferring or executing a payload. The record is named from the factory with an instance suffix, just as the original backend does.

This distinction prevents a common reporting error. A factory in a seized RCS database can show intent, preparation, or capacity. A deployed agent record is stronger evidence that the backend recognized an instance. Neither fact alone proves successful collection from a named person’s real device; attribution still requires identity, timeline, network, and host evidence.

The agent’s landing page turns one record into several pseudo-items: Evidence, File System, Commands, Info, IP addresses, File Transfer, and Configuration. These are not child documents of one uniform type. Each opens a different API and data model while preserving the selected agent context.

Synthetic RCS target view showing a factory and deployed agent
WEB PORT · FICTIONAL NIGHTJAR CASEThe target view places a factory template beside a deployed backend agent record. The shared screen makes their distinct lifecycle states easy to compare.

Configuration expresses a state machine

The factory configuration editor has Basic and Advanced modes. Basic mode presents recognizable collection categories—calls, contacts and calendars, messages, visited websites, files and photographs, keylogs, screenshots, camera, position, and online synchronization. Availability depends on the factory’s desktop/mobile type, platform, level, and demo status.

These rows express what an operator could configure, not proof that the frozen endpoint tree implemented every selection. The static audit in Appendices D–E checks the other half of that claim against platform factories, concrete modules, and evidence writers. It finds meaningful differences: iOS marks several categories as remote agents, BlackBerry’s camera is build-conditional, Scout is deliberately narrow, and the Windows desktop print registrar is disabled. A configuration-visible label is therefore evidence of product intent; only a matching source chain supports an implementation claim, and neither proves deployment or successful collection.

Toggling one row does not merely set enabled: true. The original BasicConfigBuilder constructs a graph of events, actions, subactions, and modules. It adds startup and repeating triggers, module start and stop work, and synchronization behavior. File rules and timing options become fields in that graph. The web port follows the builder logic so the backend receives the shape the AIR console would have produced.

Synchronization configuration also joins the factory to infrastructure. The first synchronize subaction names a collector entry point. Unless a separate hostname feature is licensed, the selected collector must match the agent’s good routing flag. A configuration can therefore be syntactically valid yet rejected because its collection path is inconsistent with the target record.

Advanced mode makes the generated graph visible in three columns:

events                 actions                   modules
startup ─────────────► start keylog ───────────► keylog
timer   ─────────────► take screenshot ────────► screenshot
timer   ─────────────► synchronize ────────────► transport settings

The actual demo graph contains more nodes and connections; this diagram only illustrates the model. Events provide start, repeat, and end pins. Actions can enable or disable events and start or stop modules. Module forms expose type-specific parameters. Editing or deleting a node has to repair invalid connections rather than leave dangling identifiers.

Advanced configuration editor for the fictional RCS factory
WEB PORT · FICTIONAL NIGHTJAR CASEThe fictional factory's advanced graph exposes event, action, and module connections. It documents the operator model without producing an installer.

Converting a basic configuration to advanced is a persistent action. The manual-audit test restores the seeded factory afterward so subsequent runs remain repeatable. Templates can save and reload configurations filtered by agent type and editor mode. Import and export operate on the configuration document.

The original console placed build choices near this editor. The web port shows their descriptive tree so the reader can understand the product workflow, but Create is render-only and never posts a build task. Configuration of a synthetic backend record is inside the lab boundary; creating an operational installer is not.

Synchronization turns preparation into activity

When an instance synchronizes, the backend updates the deployed agent’s version, source address, device and user strings, time, and status. It also propagates recent activity into target and operation statistics and can create a synchronization alert. In the console, an idle tile can become active and dashboard counters can change through push events.

The web demo does not keep a target device connected. Its deployed agent is a database record created through the status handshake, and its last-sync values are synthetic. That is sufficient to test how activity appears to an operator, but not command delivery or endpoint collection.

The IP-address pseudo-view exposes a version boundary. The 9.6 console library calls /evidence/sync_history, while the examined 9.2.3 server lacks that action. The request falls through and returns 404, so the live grid remains empty. The port does not invent a successful response to make the walkthrough look complete. A missing panel can be fidelity evidence.

The evidence grid is the analyst’s workbench

Opening Evidence under the synthetic Moretti agent produces the nine-column grid described in Chapters 6 and 7. By default it asks for the last 24 hours. Acquired and Received are separate clocks: one describes when the evidence claims to have been captured, the other when the backend processed it. An analyst can switch the basis and widen the period to a week, month, or all records.

The fictional recent rows were designed to create a plausible analytic thread: a chat mentions a shipment, a mail mentions delivery, a keylog contains a wallet or banking term, a URL shows a financial portal, a position falls near Milan, and a file path names an invoice. These facts are not real and do not collectively demonstrate wrongdoing. They simply let the interface show how heterogeneous records can be reviewed together.

Six kinds of filter shape the work:

Filters can be saved as named presets. Type summary chips display counts and can narrow the grid. Because four pseudo-view types are excluded from normal evidence statistics, raw database totals can disagree with visible summary counts without either being wrong.

Opening a row selects a viewer appropriate to its type. Chats become a conversation; messages fetch body and attachment detail; calls and microphone records expose audio controls; screenshots and camera records load GridFS images with zoom and rotation; positions open a map; keylogs and clipboard records use text views. Previous, next, and Escape let an analyst move through the current filtered result without returning to the grid each time.

The analyst can assign relevance, add a note, place a record in the report, export it, or delete it if privileges permit. These are mutations, not harmless display preferences. Relevance can affect alerts and triage; notes become shared investigative content; report flags select material for later use; deletion can remove metadata and linked GridFS content. The Audit section records many actions, but Chapter 7 explains why the audit trail is neither complete nor cryptographically tamper-evident.

Requests are promises, not results

File System, Commands, and File Transfer can look like direct remote-control panels. Their backend representation is more cautious: they combine stored evidence with queues of requests that may or may not be delivered later.

The filesystem begins as a lazy tree. Expanding a synthetic root asks for rows at that path. A red pending marker means a retrieval request exists; a green row means a filesystem result was received and stored. “Retrieve subtree” adds a request with a chosen depth. “Retrieve default” asks for a predefined set. Downloading a file creates another request, while exporting a filesystem structure creates a backend task over already stored data.

Commands has two related views. Stored command evidence shows output already received by the platform. The queued-command popup lists instructions waiting for an agent and permits creation or cancellation according to privileges. File Transfer similarly separates upload and download requests. An upload first places a temporary file on the backend, then creates an agent request; an optional execute choice adds a command request referencing the future agent path.

In this project those controls are exercised only against synthetic records and mock/live backend contracts. No implant polls the queues, no real device executes a command, and no target filesystem is browsed. A row saying “queued” establishes intent at the server. Delivery and execution remain outside the test.

That distinction is essential in incident response. An instruction in an RCS database may show operator intent. A returned evidence record, synchronized status, endpoint artifact, or network trace is needed to establish what happened beyond the server.

Intelligence turns records into assertions

The Intelligence section sits above raw evidence. It organizes entities by operation and provides list, table, geographic, and link-graph views. An entity can represent a person, target, or virtual identity; handles attach mail addresses, phone numbers, usernames, and similar identifiers. Links carry a type, direction, and relevance.

Operation Nightjar contains fictional people named Marco Riva and Elena Conti, a virtual Riva laptop identity, mail and phone handles, and links such as “known by” and “uses identity.” Positions around Milan make the map view visible. These relationships are seed assertions, not correlations inferred by the lab from the fictional evidence.

The Entity Sheet combines description, photograph, handles, profiling tabs, and last-known position. Some profiling endpoints derive frequent contacts or other summaries from stored evidence. The graph draws entities and their links; double-clicking a node opens the profile. The geographic view shows positioned entities and falls back cleanly if map tiles are unavailable.

Synthetic intelligence link graph connecting fictional entities
WEB PORT · FICTIONAL NIGHTJAR CASEThe Intelligence graph visualizes analyst-created relationships among fictional entities. Its clean edges are assertions that still require supporting evidence.

An analyst should treat this layer differently from the evidence grid. An evidence record has a provenance path, however imperfect, through an agent and target collection. An entity link may be a human interpretation, a manual entry, or a derived association. The interface’s clean graph can make an analytic hypothesis look more certain than its supporting records. Notes, audit events, and source evidence remain necessary.

A worked analytic pass through Nightjar

Consider a fictional shift beginning with a dashboard counter on Operation Nightjar. The counter does not identify the cause, so the analyst drills into the Moretti target and opens Evidence. The default 24-hour view contains a keylog mentioning a wallet term, a recent chat about a shipment, a URL for a financial site, a clipboard reference, and a position record.

The first useful action is not to connect them. It is to check time and provenance. The analyst compares Acquired with Received, confirms that the rows belong to the expected synthetic agent, and widens the filter to a week. An old message and earlier location now provide context. A large delay between the two timestamps would be recorded rather than silently sorted away; it could reflect offline collection, queueing, import, a clock problem, or fixture construction.

The analyst next filters to keylog, chat, message, URL, position, file, and clipboard rather than searching every evidence type for one substring. Type renderers expose different fields, and a text hit in one summary may not mean the same thing as a hit in another. The wallet term in a keylog is fictional captured text. A matching alert log is a derived record. A wallet address in a manually seeded entity would be an analyst assertion. They must not be counted as three independent observations.

Suppose the keylog and message appear relevant to the fictional case. The analyst gives them relevance tags and writes notes explaining the decision, then adds selected rows to the report. A useful note names the supporting record and uncertainty—“wallet string appears in synthetic keylog; ownership not established”—rather than turning a clue into attribution. The report flag means “selected for reporting,” not “verified true.”

The position viewer shows coordinates near Milan. The analyst can compare them with the Entity map, but geographic proximity alone does not identify the device user. Accuracy, acquisition time, source type, and the possibility of stale or synthetic data remain material. In this demo, the entity positions were explicitly seeded and are not derived from those evidence rows; their visual overlap is intentional illustration, not corroboration.

Map of fictional entity positions in the Nightjar case
WEB PORT · FICTIONAL NIGHTJAR CASEThe entity map combines seeded fictional positions. Spatial proximity is shown as an analytic lead, not proof of identity or association.

Next the analyst opens Intelligence. Existing fictional links connect Marco Riva, Elena Conti, and a laptop identity. The analyst may add a handle or link, but should cite the evidence that motivated it in the case notes. A “uses identity” edge records an interpretation. If later evidence contradicts it, the graph should change without erasing the original record or reasoning.

Finally, the analyst adjusts a personal alert for future lower-case keylog records containing the same term and confirms Nightjar remains pinned on the dashboard. These actions prepare continued review; they do not retroactively validate the old hit. Alert suppression may combine several future matches in one log, and dashboard counts may reset around synchronization.

The sequence produces several new database facts: relevance and note changes, report membership, perhaps an entity relationship, a user-owned alert, and dashboard state. Audit may record many of them. The original evidence bytes do not become more authentic because the interface accumulated annotations around them. A defensible export must preserve both layers and their timestamps.

Dashboard compresses the hierarchy

The Dashboard lets a user pin operations and targets from search. Tiles show names, descriptions, last synchronization state, descendants, and evidence activity. Their “new” count is not a fresh count of every record. The client subtracts a stored baseline from the current evidence total and combines it with a dashboard counter that is reset around synchronization.

Nightjar and the second fictional operation are pinned so the reader can compare them at a glance. A push can update counters without reopening the operation. Double-clicking returns to the corresponding hierarchy.

Dashboard identifiers belong to the user. The server updates them but does not emit a user push in this snapshot, and the session user is a login-time client snapshot. The console therefore patches its local dashboard_ids immediately after the save. A pin created outside the page flow may not appear until a new login.

This behavior illustrates why a dashboard is an interpretation layer. A red counter depends on baseline state, excluded evidence types, synchronization, push delivery, and the user’s current session. It is a cue to investigate, not an authoritative retention count.

Alerts are personal, scoped, and stateful

Alert rules belong to individual users. The Nightjar demo rule watches the operation path for lower-case keylog evidence containing one of several fictional financial terms and assigns high relevance. A rule also has enabled state, event/action category, notification type, and suppression interval.

For evidence alerts, the backend checks four relationships: the evidence type and keyword expression match, the alert path is contained in the agent’s operation/target/agent path, the agent remains visible to the alert owner, and the rule is enabled. A match can change evidence relevance, add work to the alert queue, create or extend an alert log, send a console push, and optionally send mail.

Suppression matters because a burst of records should not necessarily become a burst of notifications. Inside the interval, new evidence identifiers can be added to the most recent log instead of producing another notification. Logs therefore summarize alert activity rather than mirror evidence one-for-one.

The ordinary Nightjar demo evidence was inserted directly through Moped and never ran Evidence#enqueue. Its illustrative alert logs are fabricated in the same database shape the dispatcher uses. A separate named live replay now exercises the real backend path with one synthetic encrypted DEVICE record: Worker decode, storage, alert evaluation, queue dispatch, embedded log, and push. Its sanitized trace is preserved in WORKER-VALIDATION.md; it neither runs the excluded edge components nor makes the Nightjar rows Worker-derived. Screenshots of the demo alert table prove only rendering of the stored shape.

RCS alert table containing a synthetic Nightjar evidence rule
WEB PORT · FICTIONAL NIGHTJAR CASEThe alert table shows a personal, path-scoped rule and its stored synthetic result. The larger demo's log rows are fixtures; the separate Worker replay supplies the pipeline evidence.

The alert view reloads search metadata before alerts so it can resolve each stored identifier into a readable path. Alerts created through another user’s session are invisible because rules are per-user. Both constraints matter when testing and when interpreting an acquisition.

Audit and Monitor provide different accountability

Audit lists user and system actions with date, actor, action, description, and object-related fields. Column filters can narrow time, actor, action, and text; the visible result can be exported. During a Nightjar walkthrough, it can show logins, object creation, configuration changes, evidence edits, and other application events.

Monitor answers a different question. It lists backend components and their status, CPU, disk, licence capacity, version, and counters. An analyst who sees an empty evidence view might check Monitor for an unhealthy worker or database component; an administrator might see that a licence limit, not a user action, disabled something.

Neither section is ground truth on its own. Audit is stored inside the same database and omits or aggregates some effects. Monitor is a current health view whose heartbeat status can change after the relevant incident. Host logs, MongoDB state, network evidence, backups, and session history remain necessary for a defensible reconstruction.

Collaboration is mostly shared state

RCS does not present the Nightjar investigation as a document passed from one analyst to another. Collaboration happens through shared group visibility and mutations to common objects: target names, configurations, evidence notes and relevance, report flags, entities, handles, links, and queued requests. Other state remains personal, notably dashboard pins and alert rules.

That mixture can surprise a team. One analyst’s evidence note is visible to another user with access to the target; one analyst’s alert is not. A group change can alter access to the operation, while an already active privilege snapshot can survive an account change. A task created by one user can be invisible to another because the backend task manager is per-user and its list method is broken in this snapshot.

A handoff should therefore state more than “open Nightjar.” It should identify the saved evidence filter, relevant record identifiers, notes and report changes, entity assertions, outstanding queues, personal alerts that need to be recreated, and the time of the analyst’s last view. Audit and session records can help reconstruct that handoff, but neither substitutes for explicit case reasoning.

System administration frames the case

The System section is privilege-gated and normally belongs to specialist roles, but its state shapes every investigation. Frontend displays collectors and anonymizer chains. Backend shows shards and database statistics. Backup shows scheduled jobs and archives. Network Injectors and Connectors describe delivery-edge and export configuration.

The Nightjar fixture creates representative system records so these views are not empty: a fictional injector, a local JSON connector scoped to Nightjar, and a nightly full backup job. They are stored configuration only. No injector process is run, no traffic is altered, no connector exports real evidence, and the chapter does not execute a restore.

For an analyst, these sections answer provenance questions. Which collector path was supposed to receive data? Which shard should hold the target’s collections? Could a connector have copied the record elsewhere? Was a backup scheduled before deletion? For a defender, they identify additional hosts, credentials, logs, and archives that may need preservation.

The administrative view also exposes uncertainty. A configured connector need not have run successfully. A backup job need not have completed. A topology diagram is application state, not a verified network map. Chapter 9 examines the deployment edge from source and manuals without activating it.

One workflow, several kinds of proof

The reconstructed Nightjar investigation can be summarized as a loop:

Layered RCS investigation workflow showing users and groups, operation and target hierarchy, factory and synthetic agent registration, entity and configuration branches, stored typed evidence, operator views, and analyst-authored state
SOURCE · MANUAL · LAB · WEB PORTThe Nightjar workflow accumulates different artifacts at each layer. Access scope, configuration, registration, storage, presentation, alerts, and analyst-authored changes support different conclusions; no single screen proves the entire chain.

Every arrow needs its own evidence. A factory configuration proves preparation. An agent record proves backend registration. A queued request proves intent. A stored evidence document proves database presence in a particular shape. A GridFS file proves associated bytes were stored. An alert log proves the alert collection contains a match record. A console screenshot proves the web port rendered what it received.

None of those statements alone proves that a historical person was lawfully targeted, that a real device produced the bytes, that an operator saw the screen, or that a customer acted at HackingTeam’s direction. The clean visual hierarchy can conceal these epistemic gaps unless the provenance labels remain attached.

That is the value of rebuilding the workflow defensively. It reveals RCS as an enterprise process for turning collection into durable investigative state, while also showing where intent, execution, storage, interpretation, and attribution diverge. The following chapter moves outward from this analyst view to the collectors, anonymizers, injectors, and connectors at the platform edge—components this lab studies without operating.

Sources and evidence

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