Npm packages mathmain, mathsbase and math-universe hide a password-triggered encrypted RAT loader in a mathjs copy
SafeDep found a remote access implant in mathmain, an npm package that copies mathjs, and in two sibling packages, mathsbase and math-universe. The malicious code ships AES-256-GCM encrypted and activates only when lusolve() is called with matrix data whose LU lower factor serializes to a specific password. Once decrypted, the implant takes encrypted code from an operator over Slack, Telegram and a smart contract on the Base Sepolia test network, then writes it to disk and runs it with Node.js. The article does not attribute the campaign to any actor. The loader does not appear in the public GitHub sources of mathsbase and math-universe, and no victim or caller using the trigger was found.
Technical Details
- Loader: The CommonJS
lusolve()build makes an extra call toremoveSolveValidation(), which leads to an addedisGraph()inlib/cjs/utils/is.js.isGraph()usesJSON.stringifyof its input as a password and decrypts a filename blob. The decryption is scrypt, producing a 256-bit key, followed by AES-256-GCM. The layout is a 16-byte salt, a 12-byte IV and a 16-byte tag, followed by the ciphertext. Theevent()helper writes the decrypted file to disk, andrequire()then loads it. - Trigger: The password is the JSON form of the LU lower factor
[[1,0,0],[1,1,0],[1,0.5,1]]. A 3x3 Pascal matrix[[1,1,1],[1,2,3],[1,3,6]]produces it. Passing L itself as the coefficient matrix, or supplying it through the object form oflusolve(), gives the same password. JFrog first reported the input, and SafeDep reproduced it. SafeDep's own password guessing (16,922 and 533 candidates) failed. - Stage 1,
graph.js: It reads host data, generates an X25519 key pair, spawns detached Node.js processes, and reads a Base Sepolia smart contract through a bundled ethers 5.7.2 copy (bignumber/type.js). It reports to Slackchat.postMessageandapi.telegram.org. - Stage 2,
fraction.js: This is the Slack agent. It pollsconversations.historyevery 10 seconds and decrypts accepted messages into start, chunk and end packets. It reassembles the file aslib/cjs/utils/subwatcherand launches it detached with Node.js. The blockchain path also decrypts remote data, writessubwatcherand runs it. The later code delivered through these channels was not recovered. - Spread: Five versions across the three packages share identical loader files, with one different
graph.jsblob in one version. Both deployments share the Alchemy project keyD2-TbkB2m05WXSnSDOCDI. The default npm version of mathmain on September 17 did not contain the loader, but version 1.0.1 did. - Other findings: The manifest has no install hooks, and a plain import does not activate the loader. With a wrong password, authentication fails before any file is written.
Remediation
- Search lockfiles, SBOMs and registries for mathmain, mathsbase and math-universe, checking all versions and not only the default.
- Hunt for
graph.js,bignumber/type.js,fraction.jsandsubwatcherunderlib/cjs/utils/, and for the published hashes. - Block or alert on the listed Base Sepolia RPC endpoints and on unexpected Slack or Telegram API traffic from build or application hosts.
- If a host may have run the loader, treat it as compromised and rotate its credentials.
- A package or hash match alone does not prove execution, because a caller must trigger the loader.
Linha do tempo
-
Npm packages mathmain, mathsbase and math-universe hide a password-triggered encrypted RAT loader in a mathjs copy
ler a análise desta fonte ocultar
SafeDep found a remote access implant in mathmain, an npm package that copies mathjs, and in two sibling packages, mathsbase and math-universe. The malicious code ships AES-256-GCM encrypted and activates only when
lusolve()is called with matrix data whose LU lower factor serializes to a specific password. Once decrypted, the implant takes encrypted code from an operator over Slack, Telegram and a smart contract on the Base Sepolia test network, then writes it to disk and runs it with Node.js. The article does not attribute the campaign to any actor. The loader does not appear in the public GitHub sources of mathsbase and math-universe, and no victim or caller using the trigger was found.Technical Details
- Loader: The CommonJS
lusolve()build makes an extra call toremoveSolveValidation(), which leads to an addedisGraph()inlib/cjs/utils/is.js.isGraph()usesJSON.stringifyof its input as a password and decrypts a filename blob. The decryption is scrypt, producing a 256-bit key, followed by AES-256-GCM. The layout is a 16-byte salt, a 12-byte IV and a 16-byte tag, followed by the ciphertext. Theevent()helper writes the decrypted file to disk, andrequire()then loads it. - Trigger: The password is the JSON form of the LU lower factor
[[1,0,0],[1,1,0],[1,0.5,1]]. A 3x3 Pascal matrix[[1,1,1],[1,2,3],[1,3,6]]produces it. Passing L itself as the coefficient matrix, or supplying it through the object form oflusolve(), gives the same password. JFrog first reported the input, and SafeDep reproduced it. SafeDep's own password guessing (16,922 and 533 candidates) failed. - Stage 1,
graph.js: It reads host data, generates an X25519 key pair, spawns detached Node.js processes, and reads a Base Sepolia smart contract through a bundled ethers 5.7.2 copy (bignumber/type.js). It reports to Slackchat.postMessageandapi.telegram.org. - Stage 2,
fraction.js: This is the Slack agent. It pollsconversations.historyevery 10 seconds and decrypts accepted messages into start, chunk and end packets. It reassembles the file aslib/cjs/utils/subwatcherand launches it detached with Node.js. The blockchain path also decrypts remote data, writessubwatcherand runs it. The later code delivered through these channels was not recovered. - Spread: Five versions across the three packages share identical loader files, with one different
graph.jsblob in one version. Both deployments share the Alchemy project keyD2-TbkB2m05WXSnSDOCDI. The default npm version of mathmain on September 17 did not contain the loader, but version 1.0.1 did. - Other findings: The manifest has no install hooks, and a plain import does not activate the loader. With a wrong password, authentication fails before any file is written.
Remediation
- Search lockfiles, SBOMs and registries for mathmain, mathsbase and math-universe, checking all versions and not only the default.
- Hunt for
graph.js,bignumber/type.js,fraction.jsandsubwatcherunderlib/cjs/utils/, and for the published hashes. - Block or alert on the listed Base Sepolia RPC endpoints and on unexpected Slack or Telegram API traffic from build or application hosts.
- If a host may have run the loader, treat it as compromised and rotate its credentials.
- A package or hash match alone does not prove execution, because a caller must trigger the loader.
- Loader: The CommonJS