Link copied!
Daksh

TXTBOOK: A Supply Chain Theft, Explained in Public

Aug 14, 2026 4850 words · 69 min read Share

We discovered a highly targeted dependency confusion campaign known as TXTBOOK in the summer of 2026. Before switching to npm, the threat actor released 993 malicious packages that were deliberately created to replicate the internal private package namespace of a single target: T-Bank (previously Tinkoff, a significant Russian banking organization). T-Bank's internal business banking, BNPL, analytics, and open-source technologies were all imitated by the packages.

 

The campaign's execution flow is hardcoded with its targeting. Three of the nine hostnames in the obfuscated table carried by the second-stage loader are part of T-Bank's internal infrastructure. To make sure the malware only explodes within the bank's internal network, the loader actively resolves T-Bank's internal artifact repository before contacting its command-and-control (C2) servers. If it doesn't receive a response, it terminates.

 

The delivery technique of TXTBOOK is its most unique feature: the loader reassembles a native executable from hundreds of DNS TXT records rather than downloading a binary via regular HTTP.

 

Network monitoring only sees this payload as an abrupt spike in DNS queries. A completely working Sliver C2 implant, whose C2 protocol CloudSEK fully reverse-engineered utilizing static analysis, key material, and intercepted traffic, is the resultant third-stage payload. There was also a small, four-package cluster aimed at online betting.

Key Findings

The following are some key findings from the report:

1.    Package Distribution: Of the 993 malicious packages, 850 directly match the namespace of T-Bank; the remaining packages utilize generic banking terminology that is consistent with the same target.

2.    Target-Gating: Before contacting C2 servers, the malware's loader verifies that it is operating within the bank's internal network using an obfuscated list of nine hostnames, three of which belong to T-Bank.

3.    Service Catalogue Leak: The attacker's use of an actual inventory of T-Bank's internal systems is demonstrated by the squatting package names, which incorporate extremely particular internal service paths.

4.    Open Source Exploitation: The operator correctly identified the bank's private package names by developing malicious versions of T-Bank's public open-source tools (Tramvai and Taiga UI).

5.    Undocumented Versioning: The majority of authenticated archives belong to a recently discovered 20.x version band, indicating that it, along with other version bands, is part of the campaign's core infrastructure era.

6.    Implant & Encryption: By substituting the age protocol and Ed25519 signatures for native encryption, the third-stage Sliver implant enables researchers to decrypt C2 communication using process memory key substitution.

7.    Backend Infrastructure: Despite sharing a Cloudflare Workers front end, the campaign divides the Linux-x64 and Windows/Linux-ARM64 builds between two different C2 operator servers.

8.    Inconsistent Obfuscation: Linux builds leave C2 hostnames and private keys in plaintext, making their C2 traffic easily decryptable, but Windows and macOS builds encrypt their strings.

Required Actions

The following are some required actions:

     Target-Focused Mindset: Businesses should audit their build environments against the 993 known private package names in order to treat this as a targeted attack on T-Bank's particular estate.

     Registry Policy Control: Instead of depending on package blocklists, prevent dependency confusion by setting up stringent registry scoping constraints that prevent private names from resolving to public registries.

     DNS Threat Hunting: Block known C2 domains and keep an eye out for unusual, sequential TXT record lookups in DNS telemetry that might be used to reconstruct malicious executables.

     Key-Based Pivoting: Threat actors can be tracked by focusing on their static minisign C2 server keys, which are unaffected by campaign rebuilds and infrastructure rotations.

     Incident Response Protocol: Rotate all credentials inside the impacted build pipeline and treat any match of the infected packages in lockfiles as an active compromise right away.

Analysis

How the Campaign Unfolded?

In July 2026, the TXTBOOK campaign moved from PyPI to npm with the same C2 and staging architecture. In order to adjust, the operator completely avoided install hooks by substituting npm import-time execution triggers for PyPI path-configuration files.

 

Each iteration of publishing changed detection triggers, moving from lifecycle script avoidance to postinstall hooks before going back to import-time execution with distinct file hashes for each package.

 

About two dozen Cloudflare accounts and dormant staging domains received batch pre-provisioning of the supporting infrastructure. The attacker used disposable, single-package accounts to resume publishing within a day of npm removing the initial packages in August 2026, demonstrating the ineffectiveness of registry enforcement.

image shows txtbook-suplly

 

The first ten packages of the npm arm were revealed by CloudSEK's supply chain monitoring, and it was because of those names that the further packages were accessible. The staging channel, the native loader, the implant, and the target were all recovered by following them.

Tradecraft Evolution

Published version numbers are grouped into discrete bands that correspond to different execution strategies: early versions circumvented npm scripts by appending code to package entry points; a middle band reinstated postinstall hooks; and the most recent version returned to import-time execution while introducing payload variations per package to evade hash-based detection.

 

image shows txt

 

The largest group in the holdings, the recently discovered 20.x band, shares the same batch of command-and-control infrastructure as the 33.x through 35.x bands, indicating that it is part of the same operational era and completing earlier four-generation campaign models.

Malware Analysis

Tracing a four-stage execution chain from npm package imports and DNS-based payloads to a native loader was necessary to recover the final-stage Sliver implant. Researchers had to create a unique detonation environment in order to extract keys and change the running process in memory because the virus was difficult to analyze.

Building the Analysis Environment

Researchers created a unique, air-gapped QEMU/KVM Windows 10 environment with a local service that resolves all DNS and allows all TCP/TLS connections in order to get around the malware's sandbox evasion. This made it possible for the implant's HTTPS beacons to safely finish handshakes, allowing the host layer to record unencrypted packets.

image shows what-is-txt-bookimage shows why-txt-book

Detonation, and a Loader That Cannot Run Its Own Payload

Because the loader reflectively maps the third stage without decompressing its packed payload or initializing the runtime, the starting detonations did not result in any network beacons.

image shows detonation-and-a-loader-that-cannot-run

The lack of public behavioral records for this malware family is probably due to the fact that it is necessary to execute the third stage directly in order to observe its actual activity.

Recovering the Loader's Configuration

Instead of storing its hostname table as plaintext strings, the loader uses a single-byte XOR key that varies depending on the build. Nothing more complex is required for recovery than attempting all 256 keys and rating each outcome based on the number of well-formed hostnames that emerge, independent of any previously recorded key. There were nine hostnames.

 

The operator's own infrastructure consists of six. The remainder of this article is based on the other three, which are not.

Identifying the Third Stage

image shows best-txt-book

The implant is an obfuscated Go binary that eliminates typical import tables and strings, making string-based profiling useless. However, researchers were able to extract 79 out of 82 framework command-message types by examining Go's unstrippable reflection metadata, indicating that the implant contains an essentially full command set.

image shows what-is-textbook-explained

Why the Keys Are Not on Disk?

image shows Why_the_Keys-are-not-on-disk

By thoroughly checking the unpacked binary against several XOR methods and pairwise offset searches, automated analysis verified the lack of cryptographic assets. These searches confirmed that instead of just being obfuscated, all operating keys, protocol identifiers, and C2 hostnames were completely deleted.

Reading the Keys Out of Live Memory

If the secrets are only present during runtime, they must be read during runtime. The host may access guest memory directly while the guest is operating because the guest's whole physical memory is a single mapping inside the hypervisor process on the host, and the guest physical addresses are a fixed offset within it.

 

image shows single-mapping

 

The operator's public key and the implant's personal key pair were recovered by scanning the guest's memory for the format's key encoding. Because the format used unpadded encoding, a search for traditional padded base64 yields no results, which explains why the keys had not appeared earlier.

image shows operator_public_key

Substituting a Key in the Running Process

 

image shows substituting-a-key-in-the-running-process

The human-readable encoded string that the implant parses and the raw decoded bytes that are produced from it are the two representations of the operator key that are stored in memory. The only authoritative string is the encoded string.

 

Overwriting the decoded bytes results in nothing long-lasting because they are a by-product that is regenerated with each use, which takes a lot of time to develop. Every succeeding beacon is sealed to a key we hold; when the single encoded string is overwritten, the guest is paused, and the target is confirmed both before and after the write.

The Decrypted Beacon


The command and control exchange's plaintext is readable when beacons are locked to a controlled key. A newly generated symmetric session key enclosed in the framework's session-initialization message is carried by each beacon.

                                             

The implant was continuously resuming a handshake that the isolated environment never responded to, proving that no session was ever finished during analysis because each beacon carried a new one.

image shows The-decrypted-beacon

The Transport Layer

Each message is wrapped in one of five interchangeable transport encoders, selected at random each request, before encryption even occurs. The encoder identifier is that nonce modulo 65537; the decision is sent to the server as a single-letter query parameter.

image shows the-transport-layerimage shows txt-book-layer

The digits must be removed before the modulus is calculated because random letters are spliced into the decimal value before transmission.

Pixel packing is not the image encoder. Because a raw zero byte would be lost to the padding of the image encoder, payload bytes are written along columns instead of rows, and the values zero and one are escape-encoded before packing.

 

Only the first thirty bytes are recovered by a decoder that reads rows in the obvious sequence, giving the impression that a message has been truncated.

How the Server Authenticates Itself?


The most reliable indicator in this research is the verification keys, which were recovered from the same memory image. In order to identify the operator's infrastructure regardless of hostname, a signing key is generated once per server installation and signs each implant that the server generates.

Locating the Cryptography Without Symbols

The cryptographic routines were found architecturally because the obfuscator destroys function names. The constants a cipher must employ, and the arithmetic a construction must carry out, are two characteristics that endure obfuscation. Because sealing adds a sixteen-byte authentication tag and opening removes one, length arithmetic alone can be used to identify the authenticated-encryption algorithms.

Measuring the percentage of indirect calls revealed that only names and strings are obfuscated and that control flow is unaltered: 2.09 percent throughout a sample of the code section and 0.2 percent in the cryptographic routines, which is consistent with regular compiled Go as opposed to flattened control flow, which would show fifteen to forty percent.

Two Operator Servers

The campaign is divided by extracting the signature key from each platform build, and the encryption keys do the same. Strong evidence of two different server installations can be found when two separate cryptographic identities agree on the same split.

 

Throughout the four builds, obfuscation was applied inconsistently, which is advantageous in and of itself. The Linux build did not use string-literal encryption, although the Windows and macOS builds did.

As a result, the Linux implants store their command-and-control hostname in plaintext on disk together with the operator's keys and their own private keys. For those builds, captured server-to-implant traffic may be decrypted offline without requiring any runtime analysis; the macOS build is the only one whose server cannot be identified using this technique.

Who Is Being Targeted?
The preceding section's recovery of the loader's configuration yielded a list of hostnames that the malware carries but does not own, something that package names alone could not. The victim owns three of them. At that point, the campaign begins to resemble an operation with an address rather than registry spam.

The main piece of evidence is the 993 package names, which are remarkably readable. A typical typosquat mimics something popular, such as a tool that sounds realistic or a character transposition of a commonly installed library. Neither do these names. Like a company's private package registry, they are internally organized, product-specific, and cohesive from an organizational standpoint.

The malware names its own target

The second line of evidence is more important because name analysis only proves intent. Instead of storing hostnames as plaintext strings, the second-stage loader stores them as an obfuscated table.

 

The identical nine items from the Windows, macOS, and Linux ARM64 builds are obtained by recovering the table through exhaustive key search instead of reusing a previously recorded key. There are six attacker infrastructures. Three aren't.

One corporate group is represented by all three victim entries. T-Bank gradually bought CloudPayments, a Russian payment processor, acquiring a 55 percent stake in October 2017, a 95 percent stake by August 2019, and complete ownership in January 2023. Only when the ownership is unknown does it appear as an unrelated third party.

 

The gate is not just declarative; it is also behavioral. Nexus.tcsbank.ru was the initial DNS query the sample sent out during three separate detonations, and it was answered twice before any command and control host was reached. The second and third victim hostnames only show up in traffic when the first is unavailable because the loader only moves down the list until one entry responds. The malware is inquiring as to whether it has entered the intended network.

An internal service catalogue in the package names

The most illuminating section of the squat list is the analytics client family. These packages adhere to a strict norm where the paths are too specialized to be created, and the trailing section is an internal service path.

 

One could guess a name like beaver-ui-drawer. A route like SME.rko.conversionpayments.web was unable to. Before the packages were released, the operator used an actual inventory of the target's internal systems.

 

This research does not determine how that inventory was obtained, but there are only a few likely sources: a public code-search hit on a secret configuration file, an exposed internal registry index, a leaked lockfile or build manifest, or a former insider.

Squatting the proprietary siblings of published open source

The Taiga UI design system and the Tramvai application framework are two examples of T-Bank's noticeable open source presence. Names such as tramvai-module-feature-toggle, tramvai-tinkoff-module-legacy-popup, and taiga-ui-proprietary-navigation are registered in both lineages. The final one is the most instructive: Taiga UI is the public design system, and the name implies that there is a proprietary equivalent.

 

This is a high-yield, low-cost reconnaissance method that extends well beyond this particular victim. Internal naming conventions, module borders, and frequently the names of private packages referenced in configuration are all visible in an organization's public repositories. Any business that publishes open source under a corporate identity should presume that the private namespace next to it can be deduced.

How Far the Campaign Reaches?

The question shifts from what the ten items were to how many more there are when you know who the operator was targeting. Instead of searching for any name or hostname, the solution was found by searching the package corpus for the loader's structure.

 

When calculating the sum, the provenance of each of the three phases that made up the expansion is important.

The last stage looked for the loader's structural signature in all 190,045 npm archives stored in CloudSEK's package corpus. The sweep focused on fragment-safe substrings in conjunction with the campaign's distinctive file layout because the loader assembles its hostnames from string fragments at runtime, making a search for complete hostnames fruitless. Then, by reassembling the parts back into command and control hostnames the portion that cannot happen by accident every archive that matched was opened and verified.

 

Rather than being confirmed collectively, each of the 187 new packages was examined separately. When the fragmented string literals in its loader are reassembled, each one produces at least one campaign hostname; none were accepted based solely on file layout. Filename heuristics do not cluster the loader filename since it is randomized per package across at least fifteen forms in this collection, including _adapter.js, _bridge.js, _init.js, _compat.js, _platform.js, _runtime.js, and setup.js.

 

Instead of a census, the final figure is a floor. The operator's published set is probably even larger because it just counts what our corpus already included and the analytics client names are sufficiently formulaic.

Indicators of Compromise

Operator Server Signing Keys

In this study, these pivots have the highest values. Samples are clustered throughout infrastructure rotation, renaming, and rebuilds because a minisign key identifies a Sliver server installation rather than a campaign.

Target Gate Hostnames

These are not attacker infrastructure, but rather victim infrastructure. They shouldn't be banned or sinkholed as if they were harmful; instead, they should be used to identify the campaign's targeting.

Command-and-Control and Staging

Build Fingerprint

Impact

The exposure is direct for the designated target. When a build system resolves a private package name against the public registry as a result of misconfiguration rather than user error, a dependency confusion attack is successful. The resulting Sliver implant offers an essentially full post-exploitation capability inside a build environment, which is usually one of the most credential-rich environments an organization runs in. Any build that resolved one of the 993 names would have executed native code on import.

 

The reconnaissance, not the payload, is the lesson that applies to everyone else. Before publishing anything, the operator created a thorough inventory of a target's internal package and service names, some of which could be inferred from the target's own public open source repositories. This exposure is shared by any organization that has a private registry and a prominent open source presence.

 

This analysis shows no signs of a successful compromise. Publication of a squatting name does not prove that a build fixed it; rather, it shows intent and capacity.

Recommendations

Immediate

     To prevent private namespaces from leaking into the public registry, enforce registry scope. Blocklisting 993 names does not defeat the entire class of assault; this control does.

     List the names in Appendix A that are private packages in your estate. Any match suggests that the operator's internal knowledge was correct.

     Regardless of the zone in question, treat successive numbered TXT lookups as hostile and block the DNS staging zones.

Detection Engineering

     Packages whose entry point necessitates a random underscore-prefixed module inside a swallowed try/catch should be alerted. The structure does not change, but the filename does.

     In package content, consider the runtime string assembly of hostnames from array fragments as a powerful signal. It is designed to undermine the precise indicator matching that the majority of tools do.

     Instead of focusing on hostnames, look for the encoder-ID fingerprint and the minisign server keys. Both endure the rotation of infrastructure.

Strategic

     Assume that your private namespace is revealed via your public repositories. Examine which internal package and service names can be deduced from published code, configuration, and documentation.

     Register or reserve internal package names in public registries defensively, giving priority to those that may be deduced from published open source.

Conclusion

Initially, DNS payload staging was used to evaluate TXTBOOK as a dropper. That was accurate, but it wasn't complete. The payload is delivered via DNS staging. The campaign surrounding it is an ongoing attempt to confuse a single financial group with reliance, conducted on a scale that is similar to indiscriminate flooding without being indiscriminate.

 

There are practical implications to the distinction. Blocking a list of names is the proper response to what is perceived as registry flooding. Fixing the dependency resolution policy, auditing which internal names are publicly inferable, and treating the package list as a symptom are the proper responses to targeted dependency confusion. The next 993 names are only covered in the second response.

 

The payload is not as important as the reconnaissance. Access to an actual inventory of the target's systems is indicated by internal service paths included in the squat list, and part of that inventory might be deduced from the target's own disclosed open source at little risk or expense. The reconnaissance was the most costly aspect of this effort, and it can be repeated against any organization that releases code under a corporate identity.

 

The further parcels were accessible because of the first ten packages that CloudSEK's supply chain monitoring revealed. Pivoting from that seed led to all subsequent discoveries, including the staging infrastructure, the command and control estate, the third stage implant, the two operator servers, and the 993 package inventory.

Appendix A: Complete Package Inventory

This campaign's 993 package names are arranged by family size and grouped by namespace family. Names indicated with a dagger do not appear in previously released advisory data and were recently discovered in the corpus sweep detailed under How Far the Campaign Reaches.

‍Appendix B: Publisher Accounts

To retrieve its publishing account, each package in Appendix A was queried on the public npm registry. One disposable mail domain and a strict naming convention a twelve-character lowercase alphanumeric username with the mailbox local part matching the username are shared by a fleet of one-time accounts.

image shows appendix

appendix

image shows what-is-appendix

what-is-appendix

what-is-appendix


There are two direct detections that don't rely on a package name. Any npm account should be regarded as operator-controlled if its username follows the twelve-character pattern and its registered mailbox is the same string at web-library.net. As the registered mailbox for 153 accounts distributing packages that mimic one bank's private namespace, the domain itself is a high-confidence signal.

The statistic should be interpreted as follows: 178 of the 993 names resolve to one of these accounts today, indicating partial coverage. The fleet is undoubtedly greater than 153 because npm's own security account currently holds packages that the registry has since removed, and unpublished names yield nothing at all. Each recovered account, along with its mailbox, is listed below.

Disposable publisher accounts (153), all @web-library.net

 

 

Topics
Share this article
🧑‍💻
Daksh
Lead Threat Analyst · ThreatFusionAI

Cyber security researcher specializing in mobile malware analysis, OSINT, and digital forensics. Tracks financially motivated threat actors across South & Southeast Asia.

✖ @threatfusionai in/company/threatfusionai Contact
Previous
What Is Threat Intelligence Enrichment and Why Does It Matter?

Related Posts

Latest Threat Research

View all