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 file | Change size | Class | Why it changed | Claim-sensitive effect |
|---|---|---|---|---|
rcs-db/Gemfile | +17/−49 | Dependency capsule | Uses HTTPS sources, narrows the era runtime graph, keeps the HackingTeam minitar fork, and omits unsupported optional/development dependencies | Several ranges remain unlocked; runtime is reconstructed, not the original lockfile environment |
rcs-db/lib/rcs-worker/libs/SRC/src.rb | +4/−2 | Linux native binding | Adds Linux libsamplerate names and avoids terminating the worker when the optional binding cannot load | Resampling requires separate validation; base worker startup can continue |
rcs-db/lib/rcs-worker/libs/amr/amr.rb | +2/−0 | Linux native binding | Adds Linux OpenCORE AMR library names | AMR media decoding is lab-adapted |
rcs-db/lib/rcs-worker/libs/lame/lame.rb | +2/−0 | Linux native binding | Adds Linux LAME library names | MP3 media encoding is lab-adapted |
rcs-db/lib/rcs-worker/libs/speex/speex.rb | +4/−2 | Linux native binding | Adds Linux Speex names and avoids terminating the worker if the optional binding is unavailable | Speex decoding requires separate validation; base worker startup can continue |
rcs-common/lib/rcs-common/evidence/money.rb | +8/−2 | Optional dependency guard | Makes unavailable sbdb/bdb bindings nonfatal at class load | Money evidence decoding is not validated and may still fail when invoked |
rcs-common/lib/rcs-common/evidence/url.rb | +1/−1 | Case-sensitive require | Changes require 'CGI' to Ruby’s lowercase cgi library name | Loadability fix only; parser logic is unchanged |
rcs-common/lib/rcs-common/heartbeat.rb | +13/−1 | Cross-snapshot shim | Restores before/after heartbeat registration and execution attested in 9.2.3 history | The interface is historical; ordering on the selected 9.6 body is lab-adapted |
rcs-common/lib/rcs-common/systemstatus.rb | +13/−0 | Cross-snapshot shim | Restores status accessors expected by the database and attested in 9.2.3 history | State mapping onto the selected 9.6 body depends on lab code |
rcs-common/rcs-common.gemspec | +6/−42 | Dependency/package capsule | Avoids Git-dependent file enumeration, aligns Mongoid with the backend, pins system gems, and removes unused development/optional dependencies | Gem 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:
| File | Role | Original behavior preserved | Deliberate difference |
|---|---|---|---|
docker-lab/Dockerfile | Builds the Jessie/Ruby 2.1 image and installs the copied gems | Ruby-era APIs and original database/worker entry scripts | Linux container replaces vendor installers; packages come from archives |
docker-lab/entrypoint.sh | Waits for MongoDB, commissions first boot, then starts database or worker | Original config tool, certificate generator, and service binaries | Adds container lifecycle, environment wiring, and synthetic licence creation |
docker-lab/docker-compose.yml | Connects Mongo, database, worker, and web console with persistent volumes | Expected Mongo roles and application ports inside the private network | Co-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 file | Omitted files | Reason | Consequence |
|---|---|---|---|
Gemfile.lock | 1 | Lab resolves the narrowed compatibility Gemfile | Exact vendor resolution is not reproduced; resolved lab dependencies must be frozen separately |
bin/ bundled binaries | 15 | Windows runtimes, installers, utilities, and DLLs are unnecessary in the Linux image | Vendor Windows packaging and those binaries are static evidence only |
hasp/ | 13 | Hardware-dongle package is not used by the synthetic serial: off licence | Physical token behavior is source/manual evidence only |
mongodb/ | 27 | Bundled Windows MongoDB distribution is replaced by the mongo:2.6 container | Binary provenance and installer layout differ |
nsis/ | 11 | Original Windows installer is not run | Installation paths and services are analyzed statically |
ocr/ | 395 | Bundled OCR runtime/data is outside the reconstructed path | OCR completion is not validated |
ocr-src/ | 14 | OCR source is outside the reconstructed path | No OCR build is performed |
spec/ | 125 | Original development test tree is not copied into the runtime image | Lab 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
- Read-only comparisons between the two reference roots under
data/and the copied roots underdocker-lab/, refreshed for this appendix. - Full SHA-256 and
--numstatoutput for all ten modified files, reproduced in the tables above. docker-lab/Dockerfile,entrypoint.sh,docker-compose.yml,rcs-db/make_license.rb, andconsole-web/nginx.conffor new runtime code.- Chapters 3–6 and the source/version/fidelity appendix for provenance and behavioral consequences.
docker-lab/console-web/MILESTONES.mdandMANUAL-AUDIT.mdfor the separate web-port test and audit boundary.
↑ HackingTeam's RCS: Bringing a Commercial Spyware Platform Back to Life