Researchers have determined that two fake AWS packages, downloaded hundreds of times from the open source NPM JavaScript repository, contained carefully hidden code that, when executed, allowed access to developers’ computers.
The packages—img-aws-s3-object-multipart-copy And legacyaws-s3-object-multipart-copy— were attempts to impersonate aws-s3-object-multipart-copy, a legitimate JavaScript library for copying files using Amazon’s S3 cloud service. The fake files contained all of the code found in the legitimate library, but added an additional JavaScript file named loadformat.js. That file produced what appeared to be benign code and three JPG images that were processed during the package installation. One of the images contained code snippets that, when reconstructed, constituted code for backdooring the developer device.
Growing refinement
“We flagged these packages for removal, but the malicious packages remained available on npm for almost two days,” wrote researchers from Phylum, the security firm that discovered the packages. “This is concerning because it implies that most systems cannot detect and report these packages immediately, leaving developers vulnerable to attacks for a longer period of time.”
In an email, Phylum Head of Research Ross Bryant said that img-aws-s3-object-multipart-copy received 134 downloads before it was taken down. The other file, legacyaws-s3-object-multipart-copy, received 48.
The care put into the code by package developers and the effectiveness of their tactics underscores the growing sophistication of attacks against open-source repositories, which in addition to NPM include PyPI, GitHub, and RubyGems. These advances allowed the vast majority of malware scanning products to miss the backdoors embedded in these two packages. In the past 17 months, threat actors backed by the North Korean government have attacked developers twice, once using a zero-day vulnerability.
Tribal researchers have conducted an in-depth analysis of how the cloak worked:
Analyzing the loadformat.js file we find what at first glance seems like a fairly innocent code for image analysis.
However, upon closer inspection, we see that this code does some interesting things, resulting in execution on the victim’s computer.
After the image file is read from disk, each byte is analyzed. All bytes with a value between 32 and 126 are converted from Unicode values to a character and added to the 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 threatening actor then defines two separate bodies of a function and stores each body in its own variables, imagebyte And analyzePixels.
If convertertree set to true, imagebyte set to analyzepixelsIn plain language, as converttree is set, everything in the script we extracted from the image file will be executed.
if (convertertree) {
console.log("Optimization complete. Applying advanced features...");
imagebyte = analyzepixels;
} else {
console.log("Optimization complete. No advanced features applied.");
}
Looking back above, we notice that convertertree will be set to true if the length of the bytes found in the image is greater than 2,000.
if (analyzepixels.length > 2000) {
convertertree = true;
break;
}
The author then creates a new function using code that returns an empty POST request to cloudconvert.com or start executing whatever was extracted from the image files.
These three files can be found in the root of the package. They are included below unchanged, unless otherwise noted.
Appears as logo1.jpg in the packageAppears as logo2.jpg in the packageAppears as logo3.jpg in the package. Adjusted here because the file is corrupt and in some cases was not displayed properly.
If we take each of these through the processImage(...) function from above, we find that the Intel image (i.e. logo1.jpg) does not contain enough “valid” bytes to converttree variable to true. The same applies to logo3.jpgthe AMD logo. For the Microsoft logo (logo2.jpg), we find the following, formatted for readability:
Then an interval is set that periodically goes through the attacker’s commands and retrieves them every 5 seconds.
let fetchInterval = 0x1388;
let intervalId = setInterval(fetchAndExecuteCommand, fetchInterval);
Received commands are executed on the device and the output is sent back to the attacker on the endpoint /post-results?clientId=<targetClientInfoName>.
One of the most innovative methods of hiding an open source backdoor in recent history was discovered in March, just weeks before it was scheduled to be included in a production version of XZ Utils, a data compression utility available on nearly all Linux installations. The backdoor was deployed via a five-stage loader that used a series of simple but clever techniques to hide itself. Once installed, the backdoor allowed threat actors to log into infected systems with administrative privileges.
The responsible person or group worked on the backdoor for years. In addition to refining the cloaking method, the entity spent a lot of time producing high-quality code for open source projects in a successful attempt to build trust with other developers.
In May, Phylum disrupted a separate campaign that blocked a backdoor in a package in PyPI that also used steganography, a technique for embedding secret code into images.
“In recent years, we have seen a dramatic increase in the sophistication and volume of malicious packages being published in open source ecosystems,” Phylum researchers wrote. “Make no mistake, these attacks are successful. It is imperative that developers and security organizations are acutely aware of this and are highly vigilant about the open source libraries they consume.”