Voltar
Inteligência do adversário
Tabela de conteúdo
Nenhum item encontrado.

Executive Summary

An operator has spent the summer of 2026 publishing malicious packages to the public npm registry under names that belong to somebody else. Not names resembling popular libraries, which is the usual pattern, but the internal package names of one company. We track the activity as TXTBOOK. It began on PyPI, moved into npm with its execution trigger rebuilt for the new ecosystem, and now runs to 993 npm packages.

CloudSEK's supply chain monitoring surfaced them and from those packages we pivoted to the rest: the staging infrastructure, the command and control estate, the third stage implant, and finally the target.

What matters is who the 993 packages are named after. They reproduce the internal, private package namespace of a single organisation: Tinkoff, the Russian financial group now trading as T-Bank. The names cover the group's BNPL product, its business banking platform, its analytics client libraries, and the proprietary counterparts of its own published open source projects. This is a dependency confusion operation aimed at one company, run at a scale normally associated with indiscriminate registry flooding.

The targeting is not inferred from names alone. The second stage loader carries a hardcoded, obfuscated table of hostnames, and three of its nine entries are victim infrastructure rather than attacker infrastructure. All three belong to the T-Bank group. Before contacting its own command and control servers, the loader resolves the group's internal artifact repository and proceeds only if it answers. This ensures that the malware has been run inside the bank’s internal network. The malware carries its own definition of the target.

A small secondary cluster of online betting names is present in the same advisory data. It is four packages out of 993, none of which were held in our corpus, and it is reported here as an observation rather than a co-equal target.

TXTBOOK's delivery technique remains its most distinctive feature. Rather than downloading a binary over HTTP, the loader reassembles a native executable from several hundred DNS TXT records, a few hundred bytes per record. To a network monitor the activity is a burst of DNS lookups. The third stage that arrives by this route is a Sliver implant with an effectively complete command set, and its command and control protocol has now been reversed from captured traffic, recovered key material and static analysis of the binary.

Key Findings

  • The campaign comprises 993 packages, and the naming is a private namespace reconstruction of one organisation. Strict prefix matching attributes 850 of 993 to T-Bank; the remainder are generic banking-platform names (claims, deposits, cards, certificates, business process management) that are consistent with the same source. Seven names, under one percent, point anywhere else.
  • The loader carries a hardcoded target gate. Its obfuscated hostname table decodes to nine entries on Windows, macOS and Linux ARM64 alike, of which three are victim infrastructure: the group's Nexus artifact repository, its Linux package repository, and the alerting endpoint of CloudPayments, a payment processor T-Bank has wholly owned since 2023. The malware defines its own target.
  • The squat list leaks an internal service catalogue. Package names of the form statist-browser-typed-client-* embed internal service paths such as mb.product.payments, sme.rko.conversionpayments.web, investaccounting.events, leasing.admin.events and dwh.chimera.base. These are not guessable strings; they indicate the operator worked from a real inventory of the target's internal systems.
  • The operator squats the proprietary siblings of the target's public open source. T-Bank publishes the Tramvai application framework and the Taiga UI design system openly; the campaign registers tramvai-* and taiga-ui-proprietary-* names. Public repositories give an attacker an accurate, free guess at private package names.
  • A previously undocumented version band is the largest we hold. Published versions were understood to fall into four generations. A 20.x band accounts for 199 of 331 verified archives, more than the 33.x, 34.x and 35.x bands combined, and reassembles the same command and control batch, so it belongs to the same infrastructure era.
  • The third stage is a Sliver implant, and its protocol is now fully understood. The implant encrypts with age rather than Sliver's native scheme, authenticates its server with an Ed25519 minisign signature on every response, and selects one of five traffic encoders per request. Beacon traffic was decrypted in the analysis environment by substituting a controlled key into the running process, which yielded the plaintext of the command and control exchange.
  • The campaign runs two separate operator servers. Two independent cryptographic identities, an age recipient key and a minisign signing key, partition the platform builds identically: Windows and Linux ARM64 to one server, Linux x64 to another. Both are fronted by the same Cloudflare Workers estate, so shared front end infrastructure does not imply a shared back end.
  • Obfuscation was applied inconsistently. The Windows and macOS builds have their string literals encrypted; neither Linux build does. The Linux implants carry the operator's keys, their own private keys and their command and control hostname in plaintext on disk, which makes captured server to implant traffic for those builds decryptable with no runtime analysis at all.

Required Actions

  • Treat this as a targeted campaign against a named organisation, not as registry flooding. If you operate any part of the T-Bank estate, or build software that resolves dependencies inside it, assume the private namespace is known to the operator and enumerate which of the 993 names exist internally.
  • Configure registry scoping so private package names cannot resolve to the public registry. Dependency confusion is defeated by resolution policy, not by blocklists; the 993 names in Appendix A are the symptom.
  • Block the DNS staging zones and the Cloudflare Workers command and control estate listed under Indicators of Compromise, and hunt DNS telemetry for sequentially numbered TXT lookups that reassemble into an executable.
  • Pivot on the operator's minisign server keys rather than on hostnames. A minisign key identifies a Sliver server installation and survives infrastructure rotation, campaign renaming and rebuilds.
  • Treat the presence of any listed package in a lockfile as a compromise and rotate every credential available to the affected build environment.

Analysis

How the Campaign Unfolded

The operation did not begin with npm. It was first documented on PyPI in July 2026, as the campaign tracked 2026-07-andreiiiiiii_i and, before that, as activity around a configuration client package. The move into npm reused the infrastructure wholesale: the same staging domains, the same command and control estate, the same third stage. What changed was the execution trigger, which was rebuilt for the new ecosystem. On PyPI the operator installs a path-configuration file that the interpreter runs at startup. On npm it appends a required call to the package entry point, which runs on import. Both achieve code execution without declaring an install hook. That is a deliberate port, not a reused payload.

Publishing proceeded in waves rather than continuously, and the version numbers separate them cleanly. Each wave changed the thing that had made the previous one detectable. The earliest packages avoided npm lifecycle scripts altogether. A later wave reintroduced a postinstall hook. The most recent returned to import-time execution and added per-package payload variation, so that no two packages share a hash and the loader filename differs in every one.

The supporting infrastructure was provisioned in batches ahead of the packages that would use it. The command and control estate spans roughly two dozen Cloudflare accounts in two clearly separate naming batches, and a second staging domain was registered, shipped inside live packages, and left dormant, resolving but serving nothing. The operator was building the next stage of the campaign while the current one was still running.

Registry enforcement has not displaced them. When npm removed nine of the first ten names on 1 August 2026, publishing resumed within roughly a day, and by 3 August three of those names carried live malicious versions again. The publishing accounts explain why: they are disposable, and almost every one holds a single package, so removing an account removes a package rather than an operator.

When What the operator did
When July 2026 What the operator did Operation documented on PyPI; infrastructure and third stage established
When What the operator did Third stage compiled, per the build timestamp in its own signing material
When July 2026 What the operator did Expansion into npm; execution trigger rebuilt for the ecosystem
When Ongoing What the operator did Publishing in waves, each altering what made the previous wave detectable
When What the operator did Nine of the first ten names removed by npm
When What the operator did Publishing resumes, roughly 25 hours after removal
When What the operator did Staging infrastructure serving a valid native executable
When Pre-positioned What the operator did Second staging domain registered and shipped, still dormant
Table 1. The campaign's own chronology. Every entry is derived from the packages, the infrastructure or the implant rather than from reporting about them.

CloudSEK's supply chain monitoring surfaced the first ten packages of the npm arm, and those names are what made the rest reachable. Everything that follows, the staging channel, the native loader, the implant and the target, was recovered by following them.

Tradecraft Evolution

Published version numbers cluster into distinct bands, and the bands correspond to changes in execution technique. The earliest avoid npm lifecycle scripts entirely, triggering from a single line appended to the package entry point so that importing the module suffices. A later band reintroduces a postinstall hook. The most recent returns to import-time execution and adds per-package payload variation, defeating hash-based clustering.

Version band Archives Characteristics
Version band 5.x – 9.x Archives — Characteristics Shared setup.js loader, byte-identical payload across packages
Version band 11.x – 12.x Archives — Characteristics postinstall hook reintroduced
Version band 20.x Archives 199 Characteristics Largest band held; same C2 batch as 33–35.x. Not previously documented.
Version band 33.x – 34.x Archives 23 Characteristics Randomised loader filename, per-package payload
Version band 35.x Archives 98 Characteristics Current generation, fragment-assembled indicators
Table 2. Archive counts are those confirmed in the corpus sweep, so they reflect our holdings rather than the operator's total output.

The 20.x band is a correction to the previously published four-generation model. It is the largest single band in our holdings and reassembles the same command and control account batch as the 33.x to 35.x bands, so it belongs to the same infrastructure era rather than to an earlier one. Any taxonomy of this campaign built on the four generations alone is incomplete.

Malware Analysis

Following the packages meant following the payload, and the payload did not cooperate. Each step below depended on the one before it, so the order is the order of work. The chain has four stages: an npm package that executes on import, a loader that retrieves its payload over DNS, a native second stage, and a Sliver implant. Recovering the fourth stage required building a purpose-made detonation environment, reading keys out of the running process, and modifying that process in memory while it ran.

Building the Analysis Environment

The sample refuses to run in commercial sandboxes, so a bespoke environment was built. A Windows 10 guest runs under a QEMU/KVM hypervisor on an isolated virtual bridge with no uplink. A local service answers every DNS query and accepts every TCP connection, minting per-name TLS certificates from a certificate authority already trusted in the guest's machine store, so the implant's HTTPS beacons complete a handshake and expose their payloads at the capture layer. Packet capture happens on the host, outside the guest's reach.

Countermeasure How the environment defeats it
Countermeasure Hypervisor detection How the environment defeats it SMBIOS vendor, product and serial strings present a Dell OptiPlex; hypervisor CPU flag disabled
Countermeasure Disk enumeration check How the environment defeats it Registry disk-enum keys populated with a consumer SSD model and serial
Countermeasure Physical-memory floor How the environment defeats it Guest allocated 4.6 GB, above the sample's minimum
Countermeasure Timing checks How the environment defeats it Full hardware virtualisation rather than emulation
Countermeasure TLS inspection avoidance How the environment defeats it Lab certificate authority pre-trusted at machine level, with a published revocation list so revocation checking succeeds
Table 3. The sample self-terminates in five commercial sandboxes. Each check is answered rather than patched out, so the sample follows its normal execution path.
Figure 1. The analysis guest. An ordinary Windows 10 desktop is the objective: every environmental check the sample performs returns the answer it expects.

Detonation, and a Loader That Cannot Run Its Own Payload

The first detonation produced no beacon. A memory capture taken during execution explained why: the third stage payload was resident in memory still compressed, its packer section names intact and no runtime initialised. The loader maps its payload reflectively, allocating memory and parsing the executable header by hand, but it performs no decompression, and the Windows third stage is packed.

Figure 2. The operational consequence is severe: an analyst who detonates the loader observes a failed load and concludes the chain is inert.

This is the most likely explanation for the sparse public behavioural record on this family. The third stage must be executed directly to be observed at all.

Recovering the Loader's Configuration

The loader stores its hostname table obfuscated rather than as plaintext strings, using a single-byte XOR key that differs per build. Recovery needs nothing more sophisticated than trying all 256 keys and scoring each result by how many well formed hostnames appear, without reference to any previously recorded key. Nine hostnames emerged. Six are the operator's own infrastructure. The other three are not, and they are what the rest of this report turns on.

Identifying the Third Stage

Sliver is an open source adversary emulation framework, a legitimate red team tool in the same category as Cobalt Strike, and routinely repurposed by criminal operators.

An operator runs a Sliver server. Each compiled implant is a full remote access agent that beacons to it and executes tasks on demand.

Finding one at the end of a dropper tells you the objective was hands on keyboard access. The npm packages are only the delivery mechanism.

The implant is a statically linked Go binary with no import table beyond the bare runtime, no package paths and no program strings, because it was compiled with an obfuscator. Conventional string-based capability profiling returns nothing. What survives is Go's reflection metadata, which the runtime requires in order to marshal messages and which therefore cannot be stripped.

Probing that metadata for the framework's command-message type names recovered 79 of 82. The three absent names are spelling variants, not missing capabilities, so the implant carries an effectively complete command set.

Capability group Confirmed present
Capability group Execution Confirmed present In-process .NET assembly execution, DLL sideloading and spawning, native and WebAssembly extensions
Capability group Privilege and identity Confirmed present Five token and privilege escalation primitives, process migration, memory dumping
Capability group Collection Confirmed present Full filesystem and in-memory file operations, screenshot capture, registry access including offline hive reading
Capability group Lateral movement Confirmed present Complete SSH client; Kerberos implementation supporting constrained-delegation abuse
Capability group Tunnelling Confirmed present SOCKS proxy, port forwarding, WireGuard
Capability group Control Confirmed present Service control, pivot listeners, session and beacon management
Table 4. Capability inventory recovered from reflection metadata. Presence of a message type is strong evidence a handler is compiled in; absence proves nothing, because a name may straddle an obfuscation boundary.

Why the Keys Are Not on Disk

The obfuscator encrypts every string constant in the binary and decrypts each one in code, at the moment it is used.

Alice ships a program containing a secret. Rather than storing the secret she stores a recipe for rebuilding it, run only when the secret is needed and discarded straight after.

So searching the file on disk for keys, hostnames or protocol strings returns nothing, however the search is transformed. The secrets exist only inside the running process.

This was confirmed exhaustively rather than assumed. The operator key, the implant keys, the protocol identifiers and even the command and control hostname are all absent from the unpacked binary under single-byte XOR, additive and subtractive constants, position-dependent XOR, and repeating-key XOR up to period eight. A full pairwise search, testing every offset in the file against an index of every eight-byte window for the relation ciphertext XOR key equals plaintext, also returned nothing.

Reading the Keys Out of Live Memory

If the secrets exist only at runtime, the runtime is where they must be read. The guest's entire physical memory is a single mapping inside the hypervisor process on the host, and guest physical addresses are a fixed offset within it, so the host can read guest memory directly while the guest runs.

age is a file encryption format built on X25519 key agreement and ChaCha20-Poly1305. Bob publishes a public key, anyone may encrypt to it, and only Bob's private key opens the result.

The implant carries the operator's public key and seals every beacon to it. Whoever captures that traffic holds ciphertext they cannot open, because the private half never leaves the operator's server.

The implant has its own key pair too, which lets the server encrypt replies to it. Recovering that private key opens the operator to implant direction only.

Scanning the guest's memory for the format's key encoding recovered both the operator's public key and the implant's own key pair. Note that a search for conventional padded base64 finds nothing here, because the format uses unpadded encoding, which is why the keys had not surfaced earlier.

Figure 3. Holding the implant's private key does not decrypt its beacons: those are sealed to the operator, and unwrapping fails on every captured message. Confirmed by attempt, not assumed.

Substituting a Key in the Running Process

The implant encrypts to whichever public key it holds in memory. Replace that value with a key we control and the next message is sealed to us instead of to the operator.

Alice writes to Bob using an address book. Edit one line of that book and she keeps writing exactly as before, but the letters arrive at our address. Her own keys are untouched.

This reads the implant's traffic. It does not impersonate the operator or issue commands, and it needs access to the running process, so it is an analysis technique rather than a remote attack.

Two representations of the operator key exist in memory: the human-readable encoded string that the implant parses, and the raw decoded bytes derived from it. Only the encoded string is authoritative. The decoded bytes are a by-product regenerated on every use, so overwriting them achieves nothing durable, which cost considerable time to establish. Overwriting the single encoded string, with the guest paused and the target verified before and after the write, causes every subsequent beacon to be sealed to a key we hold.

The Decrypted Beacon

With beacons sealed to a controlled key, the plaintext of the command and control exchange becomes readable. Each beacon carries a freshly generated symmetric session key wrapped in the framework's session-initialisation message. Every beacon carried a new one, which established that no session was ever completed during analysis: the implant was repeatedly restarting a handshake that the isolated environment never answered.

Figure 4. An early reading treated the two-byte marker as a field separator. It is a protobuf tag, and misreading it shifts every subsequent offset.

The Transport Layer

Before encryption is even reached, each message is wrapped in one of five interchangeable transport encoders, chosen at random per request. The choice is communicated to the server in a single-letter query parameter whose value is a numeric nonce; the encoder identifier is that nonce modulo 65537. Random letters are spliced into the decimal value before transmission, so the digits must be extracted before the modulus is taken.

Encoder ID Appearance on the wire
Encoder Base64 ID 909 Appearance on the wire Custom 64-character alphabet, unpadded; contains + - and _ together, and omits lowercase g
Encoder Gzip ID 17894 Appearance on the wire Ordinary compressed stream
Encoder Hex ID 51439 Appearance on the wire Lowercase hexadecimal
Encoder English ID 52716 Appearance on the wire Space-separated English words from a 512-word table, two words per byte value
Encoder PNG ID 61017 Appearance on the wire A genuine, valid PNG image carrying the payload in its pixels
Table 5. Encoder identifiers are randomised per build, which makes this set of five values a build fingerprint independent of any hostname.
Figure 5. A real captured beacon, rendered as an image. This 10x10 pixel PNG is a structurally valid image file that passes file-type validation, and its 300 bytes of pixel data are the encrypted beacon. Uploads of images to script paths are the tell, not the file format.

The image encoder is not simple pixel packing. Payload bytes are written down columns rather than along rows, and the values zero and one are escape-encoded before packing, because a raw zero byte would be lost to the image encoder's padding. A decoder that reads rows in the obvious order recovers only the first thirty bytes and appears to have found a truncated message.

How the Server Authenticates Itself

The implant checks an Ed25519 signature on every server reply before it attempts decryption, using a verification key compiled into the binary.

Bob reads only letters bearing Alice's seal, and he checks the seal before opening the envelope. A forged letter is discarded unread, and copying Alice's letters still does not let anyone write new ones.

Traffic is authenticated one way and encrypted both ways. Implant to server messages carry no signature. Server to implant messages must.

The verification keys were recovered from the same memory image, and they are the most durable indicator in this report. A signing key is generated once per server installation and signs every implant that server builds, so it identifies the operator's infrastructure independently of any hostname.

Locating the Cryptography Without Symbols

Function names are destroyed by the obfuscator, so the cryptographic routines were located structurally instead. Two properties survive obfuscation: the constants a cipher must use, and the arithmetic a construction must perform. The authenticated-encryption routines are identifiable from length arithmetic alone, because sealing appends a sixteen-byte authentication tag and opening removes one.

Figure 6. Disassembly of the two authenticated-encryption primitives. The sign of the 0x10 adjustment identifies the direction with no symbols present. The repeated comparison against a value at r14+0x10 is the language runtime's stack-growth check, which reliably marks genuine function entry points.

Measuring the proportion of indirect calls established that only names and strings are obfuscated, and that control flow is intact: 0.2 percent in the cryptographic routines and 2.09 percent across a sample of the code section, consistent with ordinary compiled Go rather than with flattened control flow, which would show fifteen to forty percent.

Two Operator Servers

Extracting the signing key from each platform build partitions the campaign, and the encryption keys partition it identically. Two independent cryptographic identities agreeing on the same split is strong evidence of two distinct server installations.

Attribute Server A Server B
Signing key ID Server A 262CA2380CC0AB31 Server B 68BAEB7614479037
Encryption key Server A age1pzpn8l7...ssd8l2g9 Server B age1rnmw52sl...jd38qrzjrnh
Builds Server A Windows x64, Linux ARM64 Server B Linux x64
Table 6. Both servers sit behind the same Cloudflare Workers estate, so shared front end infrastructure is not evidence of a shared back end.

Obfuscation was applied inconsistently across the four builds, and the inconsistency is itself useful. String-literal encryption was applied to the Windows and macOS builds but to neither Linux build. The Linux implants therefore carry the operator's keys, their own private keys and their command and control hostname in plaintext on disk. Captured server to implant traffic for those builds is decryptable offline with no runtime analysis whatsoever, and the macOS build is the only one whose server cannot be attributed by this method.

Who Is Being Targeted

Recovering the loader's configuration in the previous section produced something the package names alone could not: a list of hostnames the malware carries but does not own. Three of them belong to the victim. That is where the campaign stops looking like registry spam and starts looking like an operation with an address.

The 993 package names are the primary evidence, and they are unusually legible. A conventional typosquat imitates something popular: a character transposition of a widely installed library, or a plausible-sounding utility. These names do neither. They are internally structured, product-specific and organisationally coherent, in the way that a company's private package registry is coherent.

Namespace familyPackagesWhat it corresponds to
Namespace familybnpl-* Packages156 What it corresponds toBuy-now-pay-later product line
Namespace familydolyame-* Packages133 What it corresponds toDolyame, T-Bank's consumer BNPL brand
Namespace familydevplatform-* Packages108 What it corresponds toInternal developer platform
Namespace familybigops-* Packages97 What it corresponds toInternal operations tooling
Namespace familyboxy-* Packages70 What it corresponds toInternal component framework
Namespace familycheckout-* Packages61 What it corresponds toPayment checkout components
Namespace familyclaims-* Packages41 What it corresponds toInsurance and disputes
Namespace familytinkoff-* Packages38 What it corresponds toExplicit corporate prefix
Namespace familystatist-*, tinkoff-statist-* Packages54 What it corresponds toAnalytics client libraries
Namespace familybpm-foundation-* Packages25 What it corresponds toBusiness-process platform
Namespace familybeaver-ui-* Packages22 What it corresponds toInternal design system
Namespace familytwork-* Packages21 What it corresponds toBusiness-banking data services
Table 7. The twelve largest namespace families, covering 805 of 993 packages. The distribution is that of one company's internal registry, not of a public ecosystem.

The malware names its own target

Name analysis establishes intent but not much more, so the second line of evidence matters more. The second stage loader stores its hostnames as an obfuscated table rather than as plaintext strings. Recovering the table by exhaustive key search, rather than by reusing a previously recorded key, yields the same nine entries from the Windows, macOS and Linux ARM64 builds. Six are attacker infrastructure. Three are not.

Figure 7. The loader's decoded hostname table. XOR keys 0x9C (Windows, macOS) and 0x0E (Linux ARM64) were recovered independently of any prior analysis.

All three victim entries belong to one corporate group. CloudPayments is a Russian payment processor that T-Bank acquired in stages, taking a 55 percent holding in October 2017, 95 percent by August 2019 and full ownership in January 2023. It reads as an unrelated third party only if the ownership is not known.

The gate is also behavioural, not merely declarative. Across three independent detonations, nexus.tcsbank.ru was the first DNS query the sample issued, resolved twice, before any command and control host was contacted. The loader proceeds down the list only until one entry answers, which is why the second and third victim hostnames appear in traffic only when the first is unavailable. The malware is asking whether it has landed inside the target network.

An internal service catalogue in the package names

The analytics client family is the most revealing part of the squat list. These packages follow a rigid convention in which the trailing segment is an internal service path, and the paths are far too specific to be invented.

Figure 8. Nine of 54 analytics client names. The segments after the prefix are internal service identifiers spanning mobile banking, business banking, leasing, insurance, data warehousing and internal messaging.

A name such as beaver-ui-drawer could be guessed. A path such as sme.rko.conversionpayments.web could not. The operator worked from a real inventory of the target's internal systems, obtained before the packages were published. How that inventory was obtained is not established by this analysis, but the plausible sources are narrow: a leaked lockfile or build manifest, an exposed internal registry index, a public code-search hit on a private configuration file, or a former insider.

Squatting the proprietary siblings of published open source

T-Bank maintains a visible open source presence, including the Tramvai application framework and the Taiga UI design system. The campaign registers names in both lineages, among them tramvai-module-feature-toggle, tramvai-tinkoff-module-legacy-popup and taiga-ui-proprietary-navigation. The last is the most instructive: the public design system is Taiga UI, and the name asserts the existence of a proprietary counterpart.

This is a low-cost, high-yield reconnaissance route that generalises well beyond this victim. An organisation's public repositories reveal its internal naming conventions, its module boundaries and often the names of private packages referenced in configuration. Any company publishing open source under a corporate identity should assume the private namespace adjacent to it is inferable.

How Far the Campaign Reaches

Knowing who the operator was aiming at changes the question from what the ten packages were to how many more of them exist. The answer came from sweeping the package corpus for the loader's structure rather than for any name or hostname.

The expansion happened in three steps, and the provenance of each matters when weighing the total.

StagePackagesVersionsSource
StageInitial detection Packages10 Versions58 SourceCloudSEK supply chain monitoring
StageExtended tracking Packages18 Versions71 SourceContinued monitoring of the cluster
StageAdvisory correlation Packages811 Versions974 SourceCross-reference with published advisories
StageCorpus sweep Packages993 Versions1,156 SourceCloudSEK npm package corpus
Table 8. The ten packages from the initial detection are what made the rest reachable; every later step pivoted from indicators derived from them.

The final step swept all 190,045 npm archives held in CloudSEK's package corpus for the loader's structural signature. Because the loader assembles its hostnames from string fragments at runtime, a search for whole hostnames finds nothing; the sweep therefore keyed on fragment-safe substrings combined with the campaign's characteristic file layout. Every archive that matched was then opened and confirmed by reassembling the fragments back into command and control hostnames, which is the part that cannot occur by coincidence.

Cada um dos 187 novos pacotes foi verificado individualmente, e não em conjunto. Todos eles geram pelo menos um nome de host de campanha quando os literais de string fragmentados em seu carregador são remontados, e nenhum foi aceito apenas com base no layout do arquivo. O nome do arquivo do carregador é aleatório por pacote em pelo menos quinze formas neste conjunto, entre elas _adapter.js, _bridge.js, _init.js, _compat.js, _platform.js, _runtime.js e setup.js, portanto, heurísticas baseadas em nomes de arquivo não os agrupam.

Figura 9. A confirmação é feita por remontagem, não por correspondência de palavras-chave.

O número resultante é um piso, não um censo. Ele conta apenas o que nosso corpus já continha, e os nomes dos clientes de análise são tão formulaicos que é muito provável que o conjunto publicado pelo operador seja ainda maior.

Indicadores de Comprometimento

Chaves de Assinatura do Servidor do Operador

Estes são os pivôs de maior valor neste relatório. Uma chave minisign identifica uma instalação de servidor Sliver, não uma campanha, e, portanto, agrupa amostras em toda a rotação de infraestrutura, renomeação e reconstruções.

Nomes de Host de Destino (Gate)

Esta é a infraestrutura da vítima, não a do atacante. Eles devem ser usados para reconhecer o alvo da campanha e não devem ser bloqueados ou redirecionados (sinkholed) como se fossem maliciosos.

Comando e Controle e Preparação (Staging)

Impressão Digital da Compilação

Impacto

Para o alvo nomeado, a exposição é direta. Um ataque de confusão de dependência é bem-sucedido quando um sistema de compilação resolve um nome de pacote privado no registro público, o que ocorre devido a uma configuração incorreta, e não por erro do usuário. Qualquer compilação que resolvesse um dos 993 nomes teria executado código nativo na importação, com o implante Sliver resultante fornecendo uma capacidade de pós-exploração efetivamente completa dentro de um ambiente de compilação, que normalmente está entre os contextos mais ricos em credenciais que uma organização opera.

Para todos os outros, a lição aplicável é o reconhecimento, não o payload. O operador montou um inventário detalhado dos nomes de pacotes e serviços internos de um alvo antes de publicar qualquer coisa, e parte desse inventário era dedutível dos próprios repositórios de código aberto públicos do alvo. Qualquer organização com presença visível em código aberto e um registro privado compartilha essa exposição.

Nada nesta análise comprova um comprometimento bem-sucedido. A publicação de um nome ocupado (squatted) demonstra intenção e capacidade; não demonstra que qualquer compilação o tenha resolvido.

Recomendações

Imediato

  • Aplique o escopo de registro para que namespaces privados não possam ser redirecionados para o registro público. Este é o controle que derrota toda a classe de ataque; colocar 993 nomes em uma lista de bloqueio não resolve.
  • Enumere quais dos nomes do Apêndice A existem como pacotes privados em seu ambiente. Qualquer correspondência indica que o operador possuía conhecimento interno preciso.
  • Bloqueie as zonas de preparação (staging) de DNS e trate as consultas TXT numeradas sequenciais como hostis, independentemente da zona envolvida.

Engenharia de Detecção

  • Crie alertas para pacotes cujo ponto de entrada exija um módulo aleatório prefixado com sublinhado dentro de um bloco try/catch omitido. O nome do arquivo varia; a estrutura, não.
  • Considere a montagem de nomes de host em tempo de execução a partir de fragmentos de array como um forte sinal no conteúdo do pacote. Isso existe para contornar exatamente a correspondência de indicadores realizada pela maioria das ferramentas.
  • Busque pelas chaves de servidor minisign e pela impressão digital do ID do codificador, em vez de nomes de host. Ambos sobrevivem à rotação de infraestrutura.

Estratégico

  • Presuma que seus repositórios públicos revelam seu namespace privado. Revise quais nomes de pacotes e serviços internos podem ser inferidos a partir de código, configuração e documentação publicados.
  • Registre ou reserve defensivamente nomes de pacotes internos em registros públicos, priorizando aqueles que podem ser inferidos a partir de código aberto publicado.

Conclusão

O TXTBOOK foi inicialmente avaliado como um dropper distinguido pelo staging de carga útil via DNS. Isso estava correto, mas incompleto. O staging via DNS é como a carga útil chega. A campanha em torno dele é uma operação sustentada de confusão de dependências contra um único grupo financeiro, executada em uma escala que se assemelha a um flooding indiscriminado, sem ser, de fato, indiscriminada.

A distinção tem consequências práticas. Interpretada como flooding de registro, a resposta apropriada é bloquear uma lista de nomes. Interpretada como confusão de dependências direcionada, a resposta apropriada é corrigir a política de resolução de dependências, auditar quais nomes internos são publicamente inferíveis e tratar a lista de pacotes como um sintoma. Apenas a segunda resposta aborda os próximos 993 nomes.

O reconhecimento merece mais atenção do que a carga útil. Caminhos de serviços internos incorporados na lista de squatting indicam acesso a um inventário real dos sistemas do alvo, e uma parte adicional desse inventário era inferível a partir do próprio código aberto publicado pelo alvo, sem custo e sem risco. A parte cara desta campanha foi o reconhecimento, e ela é repetível contra qualquer organização que publique código sob uma identidade corporativa.

Os dez pacotes iniciais revelados pelo monitoramento da cadeia de suprimentos da CloudSEK foram o que tornou o restante alcançável. Cada descoberta subsequente — a infraestrutura de staging, o conjunto de comando e controle, o implante de terceiro estágio, os dois servidores de operador e o inventário de 993 pacotes — foi alcançada a partir desse ponto de partida.

Apêndice A: Inventário Completo de Pacotes

Todos os 993 nomes de pacotes atribuídos a esta campanha, agrupados por família de namespace e ordenados pelo tamanho da família. Nomes marcados com uma adaga foram identificados recentemente na varredura do corpus descrita em "Até onde a campanha alcança" e não aparecem em dados de avisos publicados anteriormente.

bnpl  (156)

dolyame  (133)

devplatform  (108)

bigops  (97)

boxy  (70)

checkout  (61)

reivindicações  (41)

tinkoff  (38)

tinkoff-statist-browser-typed-client  (28)

statist-browser-typed-client  (26)

bpm-foundation  (25)

beaver-ui  (22)

twork  (21)

entrega  (14)

pfp  (11)

cardsmobile  (9)

cobrowsing  (9)

contas  (8)

cartões  (6)

ded  (6)

sme  (6)

construtor  (5)

eacq  (5)

hubert  (5)

fb  (4)

pfa  (3)

tcb-web  (3)

a.poltoradnev  (2)

bpm  (2)

certificados  (2)

depósitos  (2)

dp  (2)

eventea  (2)

plataforma  (2)

saas  (2)

especiais  (2)

sso  (2)

Blogs relacionados