How to Remove Persistent Malware in Python

Learn how to detect and remove persistent malware using Python in this step-by-step tutorial. Explore methods for automating malware removal across Windows and Linux systems by manipulating startup entries and crontab files.
  · 12 min read · Updated may 2024 · Ethical Hacking

Struggling with multiple programming languages? No worries. Our Code Converter has got you covered. Give it a go!

In this tutorial, you'll learn how to remove persistent malware using Python. Persistent malware is malicious software designed to maintain a continuous presence on an infected system, even after system reboots, software reinstallations, or user logouts.

When really experienced hackers write malware, they often include persistence so that if or when the target realizes that their computer has been compromised and deletes the suspected file, or even unwittingly just restart their computer, their malware will still be running on the system — they'll still have access to the system. 

You may or may not know, but every time you log in or start your computer, some applications start running automatically, without user interaction. 

This is the feature that hackers exploit. They program their malware in such a way that it copies itself to a somewhat discrete location - like AppData (on Windows) and  /root/.config (on Linux),  including the copy to the section (in the OS) where those applications get executed automatically. This way, every time you start your computer, their malware will get executed.

By the way, If you want to persist your Python program, then check this tutorial.

Table of contents:

Persistence on Windows

On Windows, those files that are executed automatically at startup are stored in the registry. To access the said registry, on your search bar, type in regedit. You should see:

 

From here, open up HKEY_CURRENT_USER > SOFTWARE > Microsoft > Windows > CurrentVersion > Run

You should see a display similar to:

 

All these applications at the right part of the screen are executed at system startup. So ideally, a hacker would make a copy of their malware (placing it in a discrete location), and refer it here so that every time your system comes on, the malware gets executed. Mind you, achieving this is very simple, we cover it in this tutorial.

Let's say you notice a suspicious file that wasn't there, you google it and you don't see any relevant indications that it's an expected file. The next step would be to remove it. You can just right-click and delete it, then trace the file it's referencing for execution and also delete it. But we're going to save the hassle of always having to navigate to this directory and delete it manually. We'll automate the process using Python.

For a typical scenario, imagine you just checked the registry, and you noticed a new file you did not install and after googling, found out that it's not part of the default Windows file. Something like:

 

No hacker that wants to be discrete would name it "Tracker". They could give it harmless names like "Microsoft Edge" or even "Anti-Virus". So don't just trust a file because it has a familiar name. Trace the file to where it's being referenced from — the paths under Data and see if it's legit. 

In this scenario, what you have to do to stop the persistence is delete that entry and the file it is executing - in this case, C:\Users\muham\AppData\Roaming\tracker.exe.

Persistence on Linux

On Linux, one way an attacker might make his malware persistent is by adding it to the cron table. In Linux, crontab is a utility that allows you to schedule commands or scripts to run automatically at specified times or intervals. It is part of the cron service, which is a time-based job scheduler in Unix-like operating systems.

So an attacker may place the malware in the cron table. All we need to do is access the cron table and delete the suspicious entries.

To access the cron table, run:

$ crontab -e

Result:

 

These are some malicious files I put there for demo purposes. We already covered how to do this here.

So, if you see any entries in your cron table that are suspicious, what you want to do is remove them. We'll achieve that using Python.

Removing Persistent Malware with Python

Now that we've seen how hackers make malware persistent, we'll write a program to remove persistent malware. We will make the program cross-platform so it runs on Windows and Linux.

Open up a Python file, and name it meaningfully like remove_persistent_malware.py. Let's start by importing the necessary libraries:

import os
import platform
import subprocess
import tempfile

# Windows-specific imports
if platform.system() == "Windows":
    import winreg

We imported various Python modules, including os for operating system interactions, platform for determining the system type, subprocess for running external commands, and tempfile for creating temporary files; it also conditionally imports the winreg module for accessing the Windows registry if the operating system is detected as Windows.

Moving on (starting with Windows):

# Get Windows start-up entries and display
def list_windows_startup_entries():
    key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run")
    entries = []
    try:
        i = 0
        while True:
            entry_name, entry_value, entry_type = winreg.EnumValue(key, i)
            entries.append((i + 1, entry_name, entry_value))
            i += 1
    except OSError:
        pass
    winreg.CloseKey(key)
    return entries


# Remove Windows start-up entries
def remove_windows_startup_entry(index, entries):
    key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", 0, winreg.KEY_SET_VALUE)
    try:
        entry_name, entry_value = entries[index - 1][1], entries[index - 1][2]
        winreg.DeleteValue(key, entry_name)
        print(f"[+] Entry {entry_name} has been removed successfully.")
        if os.path.isfile(entry_value):
            os.remove(entry_value)
            print(f"[+] File '{entry_value}' has been deleted successfully.")
        else:
            print(f"[-] File '{entry_value}' not found or unable to delete.")
    except IndexError:
        print("[-] Invalid entry index.")
    except OSError  as e:
        print(f"[-] Error removing entry: {e}")
    finally:
        winreg.CloseKey(key)

The first function list_windows_startup_entries() retrieves and displays the current startup entries in the Windows registry key HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run. It opens this registry key, enumerates through its values, and appends each entry's index, name, and value to a list. Finally, it returns this list of startup entries.

The second function, remove_windows_startup_entry(index, entries) takes an index and the list of startup entries as arguments. It opens the same registry key with write access and attempts to remove the specified entry using the provided index. It first retrieves the entry name and value from the entries list, and then deletes the corresponding registry value. If the entry value corresponds to a file path, it also attempts to delete that file. The function prints success or error messages based on the outcome of the removal process. Finally, it closes the registry key.

Please be aware that the code also attempts to delete the file, not just the Windows registry entry. Therefore, do not attempt to delete a system program that you may have added for demonstration purposes.

Next, we handle the cron table on Linux:

# Get the cron tab entries
def list_linux_crontab_entries():
    try:
        output = subprocess.check_output(["crontab", "-l"], stderr=subprocess.STDOUT).decode('utf-8').strip()
        if output:
            entries = output.split("\n")
            return [(i + 1, entry) for i, entry in enumerate(entries)]
        else:
            return []
    except subprocess.CalledProcessError as e:
        if "no crontab" in e.output.decode('utf-8'):
            return []
        else:
            raise

def remove_linux_crontab_entry(index, entries):
    try:
        entry = entries[index - 1][1]
        all_entries = [e[1] for e in entries if e[1] != entry]
        with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
            tmp_file.write("\n".join(all_entries).encode('utf-8'))
            tmp_file.write(b"\n")
            tmp_file_path = tmp_file.name
        subprocess.check_output(["crontab", tmp_file_path], stderr=subprocess.STDOUT)
        os.unlink(tmp_file_path)
        print(f"[+] Entry '{entry}' has been removed successfully.")
    except IndexError:
        print("[-] Invalid entry index.")
    except Exception as e:
        print(f"[-] Error removing crontab entry: {e}")

The function list_linux_crontab_entries() retrieves and lists the current cron job entries in the user's crontab file on Linux systems. It runs the crontab -l command to list the crontab entries and capture the output. If there are entries, it splits the output by newlines and returns a list of tuples, where each tuple contains the entry index and the entry text. If there are no entries or the user has no crontab file, it returns an empty list.

The function remove_linux_crontab_entry(index, entries) takes an index and the list of crontab entries as arguments. It first retrieves the entry text corresponding to the provided index from the entries list. Then, it creates a new list all_entries containing all entries except the one to be removed. Next, it creates a temporary file and writes the all_entries list, separated by newlines. It then runs the crontab command with the temporary file path to replace the user's crontab file with the updated entries. Finally, it prints a success message and deletes the temporary file. If an invalid index is provided or an exception occurs during the process, it prints an error message.

Finally, the main() function:

def main():
    os_name = platform.system()
    if os_name == "Windows":
        entries = list_windows_startup_entries()
        if not entries:
            print("[-] No startup entries found.")
        else:
            print("[+] Startup entries:")
            for index, name, value in entries:
                print(f"{index}. {name}: {value}")
            print("\n")
            choice = int(input("[!] Enter the number of the entry you want to remove (0 to exit): "))
            if choice == 0:
                return
            elif 0 < choice <= len(entries):
                remove_windows_startup_entry(choice, entries)
            else:
                print("[-] Invalid choice.")
    elif os_name == "Linux":
        entries = list_linux_crontab_entries()
        if not entries:
            print("[-] No crontab entries found.")
        else:
            print("[+] Crontab entries:")
            for index, entry in entries:
                print(f"{index}. {entry}")
            print("\n")
            choice = int(input("[!] Enter the number of the entry you want to remove (0 to exit): "))
            if choice == 0:
                return
            elif 0 < choice <= len(entries):
                remove_linux_crontab_entry(choice, entries)
            else:
                print("[-] Invalid choice.")
    else:
        print(f"[-] Unsupported operating system: {os_name}")

if __name__ == "__main__":
    main()

The main() function is the entry point of the script. It first determines the operating system using the platform.system() function.

For Windows:

  1. It calls list_windows_startup_entries() to retrieve the list of startup entries from the registry.
  2. If there are no entries, it prints a message indicating so.
  3. Otherwise, it prints the list of entries with their index, name, and value.
  4. It prompts the user to enter the number of the entry they want to remove (0 to exit).
  5. If the user enters 0, it exits the script.
  6. If the user enters a valid index, it calls remove_windows_startup_entry() with the chosen index and the list of entries.
  7. If the user enters an invalid index, it prints an error message.

For Linux:

  1. It calls list_linux_crontab_entries() to retrieve the list of crontab entries.
  2. If there are no entries, it prints a message indicating so.
  3. Otherwise, it prints the list of entries with their index and the entry text.
  4. It prompts the user to enter the number of the entry they want to remove (0 to exit).
  5. If the user enters 0, it exits the script.
  6. If the user enters a valid index, it calls remove_linux_crontab_entry() with the chosen index and the list of entries.
  7. If the user enters an invalid index, it prints an error message.

If the operating system is neither Windows nor Linux, it prints a message indicating that it is unsupported.

Running our Script on Windows

Now, we'll run our script starting with the Windows OS. Navigate to the directory where your code is saved and run:

Result:

$ python remove_peristent_malware.py

We can see that there's a suspicious entry Tracker. To remove that, we simply specify its number (which is 7):

 

Pretty simple. Now when you run the program again, you'll see only 6 entries:

 

Running our Script on Linux

Running our script is also very straightforward. Navigate to your working directory (where you have your script) and run:

$ python3 remove_persistent_malware.py

Result:

 

Assuming we want to remove the first entry, we simply specify 1:

 

It's really that simple. When you run the crontab -e command again, you'll notice that you now have just one entry to be executed at start-up:

 

Additional Notes

Please note that the crontab you view is for a particular user. For example, if you switch to an admin user and check the cron tables, the entries may be different. Also, a good challenge for you (Linux users) is to extend the code so that after removing the unwanted file from the entry, it also deletes it from the source - similar to what I did on Windows. It's pretty simple.

Also, for Linux users, in addition to crontab, other ways to run applications or scripts at startup on Linux include the .bashrc file, systemd services, autostart entries for desktop environments, init scripts, bash scripts in /etc/profile.d/, desktop entries, and the XDG Autostart specification. So feel free to experiment with removing files using the other methods. Please be careful so you do not delete essential files.

If you want to learn how to recover forgotten passwords on Windows - perfect for digital forensics, check out this tutorial.

Check the complete code here.

Happy coding ♥

Let our Code Converter simplify your multi-language projects. It's like having a coding translator at your fingertips. Don't miss out!

View Full Code Improve My Code
Sharing is caring!



Read Also



Comment panel

    Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!