Martin's Blog

Appendix C: Original-to-Lab Patch Inventory

This appendix accounts for the differences between the read-only leaked source and the copies used by the defensive lab. It exists to prevent a lab observation from being silently attributed to original HackingTeam code.

The comparison was refreshed after the reproduction guide. Excluding generated runtime directories and Git metadata, the copied backend contains five modified original files, one lab-only source file, and 601 intentionally omitted original files. The copied common library contains all 115 original files, of which five are modified. In total, exactly ten carried source files differ from their reference copies.

Container orchestration and the web console are new lab artifacts, not patches hidden inside those totals. They are inventoried separately below.

Rules of the comparison

The reference roots are:

data/HackingTeam/unpacked/hackedteam/rcs-db
data/HackingTeam/unpacked/hackedteam/rcs-common

The corresponding mutable lab roots are:

docker-lab/rcs-db
docker-lab/rcs-common

The reference repositories remain untouched. The source/version appendix identifies their examined commits as 6cff59d28634d718cac9fdd17cb629fd59a3cf3f for rcs-db and 38290d4eab2b2c295bea021429848a3666647827 for rcs-common.

The inventory excludes .git metadata and the runtime directories log/, data/, temp/, backup/, and cores/. The original tree contains three placeholder files under log/ and data/; the lab directories are writable runtime locations or volume mount points. Their changing contents are state, not source patches.

The file-level comparison can be repeated without writing to either tree:

cd /path/to/hackingteam

diff -qr \
  --exclude=.git --exclude=log --exclude=data --exclude=temp \
  --exclude=backup --exclude=cores \
  data/HackingTeam/unpacked/hackedteam/rcs-db docker-lab/rcs-db

diff -qr --exclude=.git \
  data/HackingTeam/unpacked/hackedteam/rcs-common docker-lab/rcs-common

Use git diff --no-index -- <original> <lab-copy> for a unified diff of one file. A nonzero exit status means the files differ; it is not a command failure.

Complete changed-file matrix

Lab fileChange sizeClassWhy it changedClaim-sensitive effect
rcs-db/Gemfile+17/−49Dependency capsuleUses HTTPS sources, narrows the era runtime graph, keeps the HackingTeam minitar fork, and omits unsupported optional/development dependenciesSeveral ranges remain unlocked; runtime is reconstructed, not the original lockfile environment
rcs-db/lib/rcs-worker/libs/SRC/src.rb+4/−2Linux native bindingAdds Linux libsamplerate names and avoids terminating the worker when the optional binding cannot loadResampling requires separate validation; base worker startup can continue
rcs-db/lib/rcs-worker/libs/amr/amr.rb+2/−0Linux native bindingAdds Linux OpenCORE AMR library namesAMR media decoding is lab-adapted
rcs-db/lib/rcs-worker/libs/lame/lame.rb+2/−0Linux native bindingAdds Linux LAME library namesMP3 media encoding is lab-adapted
rcs-db/lib/rcs-worker/libs/speex/speex.rb+4/−2Linux native bindingAdds Linux Speex names and avoids terminating the worker if the optional binding is unavailableSpeex decoding requires separate validation; base worker startup can continue
rcs-common/lib/rcs-common/evidence/money.rb+8/−2Optional dependency guardMakes unavailable sbdb/bdb bindings nonfatal at class loadMoney evidence decoding is not validated and may still fail when invoked
rcs-common/lib/rcs-common/evidence/url.rb+1/−1Case-sensitive requireChanges require 'CGI' to Ruby’s lowercase cgi library nameLoadability fix only; parser logic is unchanged
rcs-common/lib/rcs-common/heartbeat.rb+13/−1Cross-snapshot shimRestores before/after heartbeat registration and execution attested in 9.2.3 historyThe interface is historical; ordering on the selected 9.6 body is lab-adapted
rcs-common/lib/rcs-common/systemstatus.rb+13/−0Cross-snapshot shimRestores status accessors expected by the database and attested in 9.2.3 historyState mapping onto the selected 9.6 body depends on lab code
rcs-common/rcs-common.gemspec+6/−42Dependency/package capsuleAvoids Git-dependent file enumeration, aligns Mongoid with the backend, pins system gems, and removes unused development/optional dependenciesGem packaging and dependency resolution differ from the source snapshot

“Change size” is the line count reported by git diff --no-index --numstat. It is included to make accidental scope expansion visible, not as a measure of risk.

Dependency reconstruction

The original backend Gemfile was a development declaration rather than a self-contained modern build recipe. It used an HTTP Rubygems source, broad version ranges for several runtime dependencies, a git:// minitar source, JRuby conditionals, development tools, and optional OCR and cryptocurrency libraries. Its committed lockfile records the resolution available to that snapshot.

The lab Gemfile narrows the runtime graph to what the backend and worker need in the container. It exactly pins EventMachine 1.0.3, em-websocket 0.3.8, em-http-server 0.1.8, Rubyzip 1.0.0, and RestClient 1.6.7, but its Mongoid constraint is ~> 3.1.6 rather than an exact pin. The tested resolution is Mongoid 3.1.7/Moped 1.5.3, not the historical lockfile’s 3.1.6/1.5.2. It switches package transport to HTTPS and changes minitar’s transport to HTTPS while retaining the vendor fork and tested commit.

The lab intentionally does not carry the original Gemfile.lock. Bundler resolves from the narrowed Gemfile during image construction. Most important versions are pinned, but minitar still names a mutable master branch and some dependencies retain compatible ranges. This is an acknowledged reproducibility weakness, not evidence that the lab reproduces an original customer bundle byte for byte. A release gate must archive the resolved graph and fetched source digests.

The common-library gemspec changes serve a related purpose. The original uses git ls-files to populate the gem, which is unsuitable after copying the tree without its embedded Git repository. The lab selects lib/**/*, the Rakefile, and README directly. It aligns Mongoid with the 3.1 generation expected by the database rather than the common snapshot’s declared Mongoid 4.0.1, pins the system-information gems, and drops dependencies not used by the exercised backend paths.

These changes are why a passing lab request may support a claim about original application logic but not about the vendor’s package manager, installer, or complete dependency distribution.

Cross-snapshot API shims

The selected database source invokes heartbeat registration methods that the surviving common-library snapshot does not define. The lab adds before_heartbeat and after_heartbeat class methods, saves one callback for each, and executes them around the original heartbeat operation. This is the smallest interface that satisfies the observed call sites.

The selected database also assigns SystemStatus.my_status and SystemStatus.my_error_msg, which are absent from the common snapshot. The lab adds accessors over the common library’s existing class state. The getter returns the current status; the writers update status or the additional error field.

The expected interfaces are also present in common-library revision 34da36c873873a50a5ef053aa8f055207c1f1f2f, dated 28 April 2014 and declaring 9.2.3. Revision 261e08dddf2f581e96a8798e041c182551dfd536, dated 13 May and still declaring 9.2.3, refactors the heartbeat and removes or reshapes the legacy surface; 9a770f8455d9c250b62ae1a0034c1d45cdc3fbbc then bumps the visible version to 9.3.0. The database’s path lockfile does not identify an exact common-library commit.

The lab behavior is therefore not a guess that those interfaces once existed, but it remains an adaptation of them onto a later implementation. Claims about callback ordering, status mutation, or long-running heartbeat behavior must carry LAB or LAB PATCH provenance. The shims should never be used to argue that the original build contained byte-identical code.

Linux loadability and worker media bindings

The copied common library changes one case-sensitive require. Windows and some development filesystems tolerate require 'CGI'; the Linux image needs require 'cgi'. No URL evidence parsing statement changes.

The money decoder presents a deeper limitation. Its original top-level load requires Berkeley DB bindings that are not installable in the compatibility image. The lab rescues LoadError so unrelated evidence decoders can load. It does not provide replacement database classes or claim that money evidence works. If that decoder is invoked, missing constants or behavior can still fail. The demo’s financial-looking synthetic metadata is a UI fixture, not validation of this original decoder.

Four worker FFI files add Linux shared-library names. The Dockerfile installs the corresponding distribution libraries for sample-rate conversion, AMR, LAME, and Speex. The SRC and Speex wrappers also stop treating failure to load an optional codec as a reason to terminate the whole worker. This allows the non-media evidence path used by the named synthetic Worker replay to start. A clean rebuilt replay completed on 12 September 2026; its sanitized record is research/hackingteam-rcs/WORKER-VALIDATION.md.

Those edits do not establish every audio transform. A test that decrypts and stores a non-media evidence blob cannot validate AMR, Speex, resampling, or MP3 conversion. Any chapter claim about a media type needs its own fixture, expected output, library version, and failure analysis.

Synthetic licence generator

docker-lab/rcs-db/make_license.rb is the only lab-only source file inside the copied backend tree. On first boot it writes a time-valid synthetic licence by using the verifier design found in the original license.rb. It extracts the embedded generation material from that copied source at runtime so a private-use Unicode character cannot be corrupted by manual transcription.

The generator proves a narrow security property: once verifier source and its embedded keys are disclosed, accepted lab licences can be minted. It does not weaken HMAC or AES as primitives and does not show that an outsider without the source could forge a historical licence.

The generated licence exposes broad backend feature states for research, but archive and scout modes are disabled. More importantly, entitlement is not execution authorization. The Compose stack includes no collector, anonymizer, injector, implant, or exploit service, and the web console blocks build tasks. The book does not reproduce the embedded key literals.

New orchestration files

Three top-level files create the runtime capsule:

FileRoleOriginal behavior preservedDeliberate difference
docker-lab/DockerfileBuilds the Jessie/Ruby 2.1 image and installs the copied gemsRuby-era APIs and original database/worker entry scriptsLinux container replaces vendor installers; packages come from archives
docker-lab/entrypoint.shWaits for MongoDB, commissions first boot, then starts database or workerOriginal config tool, certificate generator, and service binariesAdds container lifecycle, environment wiring, and synthetic licence creation
docker-lab/docker-compose.ymlConnects Mongo, database, worker, and web console with persistent volumesExpected Mongo roles and application ports inside the private networkCo-locates Mongo roles, binds host interfaces to loopback, adds web-port proxy

The Mongo service runs configuration server, shard server, and router in one container. That preserves the roles and wire-era behavior needed by the application but not the physical isolation of a distributed installation. The worker and Mongo ports remain internal. Backend REST, WSS, and the web console are explicitly loopback-bound on the host. The Worker does not mount the database service’s rcs-config volume, so its shared entry point regenerates ephemeral local configuration, certificates, and a lab licence on every Worker container recreation before it loads operative licence state from MongoDB. That orchestration seam was confirmed during disposable commissioning.

The SHA-256 values at the time of this inventory are:

Dockerfile          e48ac3e3957044631abe7b33cd825bf878ee60ca3a98a43dacd35c9695dabe68
entrypoint.sh       181d4cc549818a72ffa6c5c81543ad4170d32a0fff937f26f64d9ad9b0fc57d1
docker-compose.yml  ec855af3413d4ac7f831cf6fad8f70b6bd5d4ba3ca231c49da18c52600afbca0
make_license.rb     39c4f32f8dcdd3665294d9f5f84ea79872d91acb7c72ebddb214527b90681849

These hashes identify the documented files, not a signed release. Any later edit requires regenerating this table.

Intentionally omitted backend material

The runtime copy is a subset of the backend repository. Omission prevents old installers, bundled executables, unsupported modules, and development fixtures from entering the image and reduces accidental execution surface.

Original path or fileOmitted filesReasonConsequence
Gemfile.lock1Lab resolves the narrowed compatibility GemfileExact vendor resolution is not reproduced; resolved lab dependencies must be frozen separately
bin/ bundled binaries15Windows runtimes, installers, utilities, and DLLs are unnecessary in the Linux imageVendor Windows packaging and those binaries are static evidence only
hasp/13Hardware-dongle package is not used by the synthetic serial: off licencePhysical token behavior is source/manual evidence only
mongodb/27Bundled Windows MongoDB distribution is replaced by the mongo:2.6 containerBinary provenance and installer layout differ
nsis/11Original Windows installer is not runInstallation paths and services are analyzed statically
ocr/395Bundled OCR runtime/data is outside the reconstructed pathOCR completion is not validated
ocr-src/14OCR source is outside the reconstructed pathNo OCR build is performed
spec/125Original development test tree is not copied into the runtime imageLab verification relies on separate console/backend contracts, not a claim that vendor tests pass

The 15 omitted bin/ files are Silverlight_x64.exe, baretail.exe, dotNetFx40_Client_x86_x64.exe, haspdinst.exe, libdb-5.1.dll, libeay32.dll, nssm.exe, openssl.exe, ruby_x64.dll, ssleay32.dll, the 2008 and 2010 x86/x64 Visual C++ redistributables, and zip.exe. Other script files under bin/ remain in the copy and match the originals unless listed in the changed-file matrix.

None of the omitted material was deleted from data/. It remains available for read-only hashing and static analysis.

The web console is a replacement, not a patch

docker-lab/console-web/ is newly written code. The original Adobe AIR application is neither copied into it nor transpiled. The port follows MXML views, ActionScript managers and services, locale bundles, and the original manuals, but consolidates their behavior into browser modules.

At this inventory point the runtime surface contains 93 project JavaScript files, ten CSS files, and one root HTML document. A separate test tree contains 36 JavaScript files when dependencies, OCR cache, and generated results are excluded. The manual audit also preserves 479 per-page OCR files plus four combined manual texts. Vendored Leaflet files and copied visual/localization assets remain identifiable within their directories.

Nginx is also lab code. console-web/nginx.conf serves static files and provides same-origin REST/WSS proxies because the original backend has no browser CORS layer. It disables backend certificate verification inside the private lab network and preserves the case-sensitive Connection: Upgrade value needed by the old WebSocket library. Its current SHA-256 is:

43cb1be49bce309000e6b26ca7750901764368f336a71dc5d536b4b6e3769069

UI claims therefore require WEB PORT provenance unless an original source or manual independently supports them. The 135-mock/44-live test gate and the 38-claim manual audit measure defined behavioral coverage; they do not turn the port into an original HackingTeam artifact.

Research artifacts and generated state

docker-lab/README.md, IOCs.md, manual-audit.md, console-web/MILESTONES.md, and console-web/MANUAL-AUDIT.md are research documentation. Console test fixtures, seed scripts, screenshot scripts, and images are research or publication artifacts. They do not belong to the original product.

Named-volume contents are generated state. The synthetic licence, local CA, service certificates, default administrator, MongoDB documents, logs, seeded operations, and screenshots can change across runs. They must never appear in a source patch count. A forensic or reproducibility bundle should hash them separately and record when and how they were produced.

Verification hashes for the ten modified files

Each line contains the relative path, original SHA-256, then lab SHA-256:

rcs-db/Gemfile
  f6f5658974f5f00a1ac7de222faf0620464d2a7eb0516b5e1ccc2be5494d0048
  07a44228b4fd710d706abcd9aebd5341215c0120a8a5508c663f69bd0922f3f2
rcs-db/lib/rcs-worker/libs/SRC/src.rb
  929d160e5089d0753138242ecdb108c537e4051ffb6db253dc954ffdb59cf276
  e87505e37871c4838bbe913084128cf459ff4b3875bf0a6854f07e7c0dad44f3
rcs-db/lib/rcs-worker/libs/amr/amr.rb
  f82cb4c4c5993038480a8f428fb3649871eeb9a2f7e7e6072e56f93b35a96bb6
  2caf785b2a035ab62f302e524e58a0bd2a75c9396e33b95b13d44baf4ecfd8a3
rcs-db/lib/rcs-worker/libs/lame/lame.rb
  48e4c7b1aacfb877d9d904899cc603d427517661bd220f35f3852f3f6b269564
  9dd586509ff5fecd46c944ac507a0526e0ce24d21365f68f07083164687c6d26
rcs-db/lib/rcs-worker/libs/speex/speex.rb
  c544c0e8ba5e4aee7f3bd3dec54667277a8d2e597d82acf96fd3c2d34438dccc
  579534c4f18077db1a99787133d0deddcc9d8b743629b11dfa6cdd7078f8a792
rcs-common/lib/rcs-common/evidence/money.rb
  dc66976ef2b73aae97d3a9fbbb9ee7abacbf7f875aea69b2924402673a05e517
  24c3af1aa5ae993316f6eda9bdc5f3f59e5c2c338968577a8c50d457121e0046
rcs-common/lib/rcs-common/evidence/url.rb
  8b16392bcc15e378811ff1fc48ee93998cea0ed4beb345022b4126bf0b06488c
  378b22d842a8164858d485fec11e76b72e91c90ac8e934226c9bfaa1cf6bf748
rcs-common/lib/rcs-common/heartbeat.rb
  ad2325dc30937499006ea5dba0e0bbd4e7f1060a4119da070d536c9439204349
  1c1e662d0ff94edb73e86da6e7375a4c3f9f12cf78a44450d281f3192d3fbefe
rcs-common/lib/rcs-common/systemstatus.rb
  6146a3f80007da8eb7f5e259be535ba0cc736911987eb679ed952007a1ce0958
  34639ef75c35893ee4e9c3d0958eddde9fbe21b8a9b90da55305cebec315836e
rcs-common/rcs-common.gemspec
  c7abaf29f3f2e496ed916b59bdb4940a7965d126ae56a5b67409774d64ca8a58
  159051908c73ec91c91ac5ddc4d0b1ba54acb39a7120ecc5e1f8bf2c2aa5996c

The ordering is deliberately original then lab. These hashes make the inventory falsifiable: a future comparison that produces another value must update both the rationale and every affected claim.

What the patch set permits us to claim

The lab can support observations about original REST routing, sessions, authorization, MongoDB models, worker control flow, WebSocket behavior, and other paths whose relevant source remains unchanged. It can also support explicit LAB observations about the compatibility capsule and web port.

It cannot support unqualified claims about original packaging, dependency resolution, Windows services, hardware licensing, OCR, optional financial- evidence decoding, every media codec, the AIR executable, or operational edge components. The heartbeat and status shims require special care because their interfaces are historical but their integration into the selected 9.6 body changes live control flow.

Completeness here means every carried source difference is named and every large omission is classified. It does not mean every behavioral consequence of those differences has been tested. That distinction belongs in the claim ledger and remains a publication gate.

Sources and evidence

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