Commits

ludovig  committed dd29394

new keylogger

  • Participants

Comments (0)

Files changed (4)

+#!/usr/bin/python
+import ConfigParser, logging, time
+import logging.handlers
+
+from subprocess import *
+from pyxhook import HookManager
+from scan2symcodes import Scan2Symcodes
+
+LOG_FILENAME = 'key.log'
+
+class HookKey(HookManager):
+    """ Log key pressed and more """
+
+    def __init__(self):
+        """ Create the hooker """
+
+        # initialize scancode translater
+        self.translator = Scan2Symcodes()
+
+        # initialize logger
+        self.create_keys_logger()
+
+        # hook
+	HookManager.__init__(self)
+	self.HookKeyboard()
+
+    def create_keys_logger(self):
+        """ Set up the log file to save the keys"""
+
+        # log location
+        config = ConfigParser.RawConfigParser()
+        config.readfp(open('xkeyvents.cfg'))
+        log_location  = config.get('hookkey', 'log_location')
+
+        # Set up a specific logger with our desired output level
+        self.logger = logging.getLogger(log_location)
+        self.logger.setLevel(logging.INFO)
+
+        # Add the log message handler to the logger
+        handler = logging.handlers.RotatingFileHandler(
+            log_location, maxBytes=1000000, backupCount=999)
+        self.logger.addHandler(handler)
+
+        # Signature
+        self.logger.info("Starting recording key press at: " + str(time.time()))
+        xset = Popen(["xset", "q"], stdout=PIPE)
+        grep = Popen(["grep", "auto repeat delay"], stdin=xset.stdout, stdout=PIPE)
+        xset.stdout.close()
+        xset_repeat_status = check_output(["sed", "s/[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)/\\1,\\2/"], stdin=grep.stdout)
+        grep.stdout.close
+
+        self.logger.info("Auto repeat delay and interval: " + xset_repeat_status)
+
+    def log(self, event):
+	""" Log key when pressed or released """
+
+        self.logger.info('%s;%s;%f;%s;%s;%s;%s' % (event.Key, event.WindowProcName, time.time(), self.translator.keycodes[event.ScanCode], event.MessageName[4:], event.ShiftOn, event.CapsOn))
+
+if __name__ == '__main__':
+    """ Execute this file to start logging """
+
+    hk = HookKey()
+    hk.KeyDown = hk.log
+    hk.KeyUp = hk.log
+    hk.start()
+    time.sleep(10)
+    hk.cancel()
+#!/usr/bin/python
+#
+# pyxhook -- an extension to emulate some of the PyHook library on linux.
+#
+#    Copyright (C) 2008 Tim Alexander <dragonfyre13@gmail.com>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+#    Thanks to Alex Badea <vamposdecampos@gmail.com> for writing the Record
+#    demo for the xlib libraries. It helped me immensely working with these
+#    in this library.
+#
+#    Thanks to the python-xlib team. This wouldn't have been possible without
+#    your code.
+#
+#    This requires:
+#    at least python-xlib 1.4
+#    xwindows must have the "record" extension present, and active.
+#
+#    This file has now been somewhat extensively modified by
+#    Daniel Folkinshteyn <nanotube@users.sf.net>
+#    So if there are any bugs, they are probably my fault. :)
+#
+#    Tweaked by Ludovic Vigouroux <ludo@mundoludo.fr>
+#    to return shift, caps and alt_gr states
+
+import sys
+import os
+import re
+import time
+import threading
+import Image
+
+from Xlib import X, XK, display, error
+from Xlib.ext import record
+from Xlib.protocol import rq
+
+#######################################################################
+########################START CLASS DEF################################
+#######################################################################
+
+class HookManager(threading.Thread):
+    """This is the main class. Instantiate it, and you can hand it KeyDown and KeyUp (functions in your own code) which execute to parse the pyxhookkeyevent class that is returned.
+
+    This simply takes these two values for now:
+    KeyDown = The function to execute when a key is pressed, if it returns anything. It hands the function an argument that is the pyxhookkeyevent class.
+    KeyUp = The function to execute when a key is released, if it returns anything. It hands the function an argument that is the pyxhookkeyevent class.
+    """
+
+    def __init__(self):
+        threading.Thread.__init__(self)
+        self.finished = threading.Event()
+
+        # Give these some initial values
+        self.mouse_position_x = 0
+        self.mouse_position_y = 0
+        self.ison = {"shift":False, "caps":False}
+
+        # Compile our regex statements.
+        self.isshift = re.compile('^Shift')
+        self.iscaps = re.compile('^Caps_Lock')
+        self.shiftablechar = re.compile('^[a-z0-9]$|^minus$|^equal$|^bracketleft$|^bracketright$|^semicolon$|^backslash$|^apostrophe$|^comma$|^period$|^slash$|^grave$')
+        self.logrelease = re.compile('.*')
+        self.isspace = re.compile('^space$')
+
+        # Assign default function actions (do nothing).
+        self.KeyDown = lambda x: True
+        self.KeyUp = lambda x: True
+        self.MouseAllButtonsDown = lambda x: True
+        self.MouseAllButtonsUp = lambda x: True
+
+        self.contextEventMask = [X.KeyPress,X.MotionNotify]
+
+        # Hook to our display.
+        self.local_dpy = display.Display()
+        self.record_dpy = display.Display()
+
+    def run(self):
+        # Check if the extension is present
+        if not self.record_dpy.has_extension("RECORD"):
+            print "RECORD extension not found"
+            sys.exit(1)
+        r = self.record_dpy.record_get_version(0, 0)
+        print "RECORD extension version %d.%d" % (r.major_version, r.minor_version)
+
+        # Create a recording context; we only want key and mouse events
+        self.ctx = self.record_dpy.record_create_context(
+                0,
+                [record.AllClients],
+                [{
+                        'core_requests': (0, 0),
+                        'core_replies': (0, 0),
+                        'ext_requests': (0, 0, 0, 0),
+                        'ext_replies': (0, 0, 0, 0),
+                        'delivered_events': (0, 0),
+                        'device_events': tuple(self.contextEventMask), #(X.KeyPress, X.ButtonPress),
+                        'errors': (0, 0),
+                        'client_started': False,
+                        'client_died': False,
+                }])
+
+        # Enable the context; this only returns after a call to record_disable_context,
+        # while calling the callback function in the meantime
+        self.record_dpy.record_enable_context(self.ctx, self.processevents)
+        # Finally free the context
+        self.record_dpy.record_free_context(self.ctx)
+
+    def cancel(self):
+        self.finished.set()
+        self.local_dpy.record_disable_context(self.ctx)
+        self.local_dpy.flush()
+
+    def printevent(self, event):
+        print event
+
+    def HookKeyboard(self):
+        pass
+        # We don't need to do anything here anymore, since the default mask
+        # is now set to contain X.KeyPress
+        #self.contextEventMask[0] = X.KeyPress
+
+    def HookMouse(self):
+        pass
+        # We don't need to do anything here anymore, since the default mask
+        # is now set to contain X.MotionNotify
+
+        # need mouse motion to track pointer position, since ButtonPress events
+        # don't carry that info.
+        #self.contextEventMask[1] = X.MotionNotify
+
+    def processevents(self, reply):
+        if reply.category != record.FromServer:
+            return
+        if reply.client_swapped:
+            print "* received swapped protocol data, cowardly ignored"
+            return
+        if not len(reply.data) or ord(reply.data[0]) < 2:
+            # not an event
+            return
+        data = reply.data
+        while len(data):
+            event, data = rq.EventField(None).parse_binary_value(data, self.record_dpy.display, None, None)
+            if event.type == X.KeyPress:
+                hookevent = self.keypressevent(event)
+                self.KeyDown(hookevent)
+            elif event.type == X.KeyRelease:
+                hookevent = self.keyreleaseevent(event)
+                self.KeyUp(hookevent)
+            elif event.type == X.ButtonPress:
+                hookevent = self.buttonpressevent(event)
+                self.MouseAllButtonsDown(hookevent)
+            elif event.type == X.ButtonRelease:
+                hookevent = self.buttonreleaseevent(event)
+                self.MouseAllButtonsUp(hookevent)
+            elif event.type == X.MotionNotify:
+                # use mouse moves to record mouse position, since press and release events
+                # do not give mouse position info (event.root_x and event.root_y have
+                # bogus info).
+                self.mousemoveevent(event)
+
+        #print "processing events...", event.type
+
+    def keypressevent(self, event):
+        matchto = self.lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0))
+        if self.shiftablechar.match(self.lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0))): ## This is a character that can be typed.
+            if self.ison["shift"] == False:
+                keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
+                return self.makekeyhookevent(keysym, event)
+            else:
+                keysym = self.local_dpy.keycode_to_keysym(event.detail, 1)
+                return self.makekeyhookevent(keysym, event)
+        else: ## Not a typable character.
+            keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
+            if self.isshift.match(matchto):
+                self.ison["shift"] = self.ison["shift"] + 1
+            elif self.iscaps.match(matchto):
+                if self.ison["caps"] == False:
+                    self.ison["shift"] = self.ison["shift"] + 1
+                    self.ison["caps"] = True
+                if self.ison["caps"] == True:
+                    self.ison["shift"] = self.ison["shift"] - 1
+                    self.ison["caps"] = False
+            return self.makekeyhookevent(keysym, event)
+
+    def keyreleaseevent(self, event):
+        if self.shiftablechar.match(self.lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0))):
+            if self.ison["shift"] == False:
+                keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
+            else:
+                keysym = self.local_dpy.keycode_to_keysym(event.detail, 1)
+        else:
+            keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
+        matchto = self.lookup_keysym(keysym)
+        if self.isshift.match(matchto):
+            self.ison["shift"] = self.ison["shift"] - 1
+        return self.makekeyhookevent(keysym, event)
+
+    def buttonpressevent(self, event):
+        #self.clickx = self.rootx
+        #self.clicky = self.rooty
+        return self.makemousehookevent(event)
+
+    def buttonreleaseevent(self, event):
+        #if (self.clickx == self.rootx) and (self.clicky == self.rooty):
+            ##print "ButtonClick " + str(event.detail) + " x=" + str(self.rootx) + " y=" + str(self.rooty)
+            #if (event.detail == 1) or (event.detail == 2) or (event.detail == 3):
+                #self.captureclick()
+        #else:
+            #pass
+
+        return self.makemousehookevent(event)
+
+        #    sys.stdout.write("ButtonDown " + str(event.detail) + " x=" + str(self.clickx) + " y=" + str(self.clicky) + "\n")
+        #    sys.stdout.write("ButtonUp " + str(event.detail) + " x=" + str(self.rootx) + " y=" + str(self.rooty) + "\n")
+        #sys.stdout.flush()
+
+    def mousemoveevent(self, event):
+        self.mouse_position_x = event.root_x
+        self.mouse_position_y = event.root_y
+
+    # need the following because XK.keysym_to_string() only does printable chars
+    # rather than being the correct inverse of XK.string_to_keysym()
+    def lookup_keysym(self, keysym):
+        for name in dir(XK):
+            if name.startswith("XK_") and getattr(XK, name) == keysym:
+                return name.lstrip("XK_")
+        return "[%d]" % keysym
+
+    def asciivalue(self, keysym):
+        asciinum = XK.string_to_keysym(self.lookup_keysym(keysym))
+        if asciinum < 256:
+            return asciinum
+        else:
+            return 0
+
+    def makekeyhookevent(self, keysym, event):
+        storewm = self.xwindowinfo()
+        if event.type == X.KeyPress:
+            MessageName = "key down"
+        elif event.type == X.KeyRelease:
+            MessageName = "key up"
+        return pyxhookkeyevent(storewm["handle"], storewm["name"], storewm["class"], self.lookup_keysym(keysym), self.asciivalue(keysym), event.detail, MessageName, self.ison["shift"], self.ison["caps"])
+
+    def makemousehookevent(self, event):
+        storewm = self.xwindowinfo()
+        if event.detail == 1:
+            MessageName = "mouse left "
+        elif event.detail == 3:
+            MessageName = "mouse right "
+        elif event.detail == 2:
+            MessageName = "mouse middle "
+        elif event.detail == 5:
+            MessageName = "mouse wheel down "
+        elif event.detail == 4:
+            MessageName = "mouse wheel up "
+        else:
+            MessageName = "mouse " + str(event.detail) + " "
+
+        if event.type == X.ButtonPress:
+            MessageName = MessageName + "down"
+        elif event.type == X.ButtonRelease:
+            MessageName = MessageName + "up"
+        return pyxhookmouseevent(storewm["handle"], storewm["name"], storewm["class"], (self.mouse_position_x, self.mouse_position_y), MessageName)
+
+    def xwindowinfo(self):
+        try:
+            windowvar = self.local_dpy.get_input_focus().focus
+            wmname = windowvar.get_wm_name()
+            wmclass = windowvar.get_wm_class()
+            wmhandle = str(windowvar)[20:30]
+        except:
+            ## This is to keep things running smoothly. It almost never happens, but still...
+            return {"name":None, "class":None, "handle":None}
+        if (wmname == None) and (wmclass == None):
+            try:
+                windowvar = windowvar.query_tree().parent
+                wmname = windowvar.get_wm_name()
+                wmclass = windowvar.get_wm_class()
+                wmhandle = str(windowvar)[20:30]
+            except:
+                ## This is to keep things running smoothly. It almost never happens, but still...
+                return {"name":None, "class":None, "handle":None}
+        if wmclass == None:
+            return {"name":wmname, "class":wmclass, "handle":wmhandle}
+        else:
+            return {"name":wmname, "class":wmclass[0], "handle":wmhandle}
+
+class pyxhookkeyevent:
+    """This is the class that is returned with each key event.f
+    It simply creates the variables below in the class.
+
+    Window = The handle of the window.
+    WindowName = The name of the window.
+    WindowProcName = The backend process for the window.
+    Key = The key pressed, shifted to the correct caps value.
+    Ascii = An ascii representation of the key. It returns 0 if the ascii value is not between 31 and 256.
+    ScanCode = Please don't use this. It differs for pretty much every type of keyboard. X11 abstracts this information anyway.
+    MessageName = "key down", "key up".
+    ShiftOn = Is Shift pressed
+    Caps_Lock = Is Caps_Lock on
+    """
+
+    def __init__(self, Window, WindowName, WindowProcName, Key, Ascii, ScanCode, MessageName, ShiftOn, CapsOn):
+        self.Window = Window
+        self.WindowName = WindowName
+        self.WindowProcName = WindowProcName
+        self.Key = Key
+        self.Ascii = Ascii
+        self.ScanCode = ScanCode
+        self.MessageName = MessageName
+        self.ShiftOn = ShiftOn
+        self.CapsOn = CapsOn
+
+    def __str__(self):
+        return "Window Handle: " + str(self.Window) + "\nWindow Name: " + str(self.WindowName) + "\nWindow's Process Name: " + str(self.WindowProcName) + "\nKey Pressed: " + str(self.Key) + "\nAscii Value: " + str(self.Ascii) + "\nScanCode: " + str(self.ScanCode) + "\nMessageName: " + str(self.MessageName) + "\nShiftOn: " + str(self.ShiftOn) + "\nCapsOn: " + str(self.CapsOn) + "\n"
+
+class pyxhookmouseevent:
+    """This is the class that is returned with each key event.f
+    It simply creates the variables below in the class.
+
+    Window = The handle of the window.
+    WindowName = The name of the window.
+    WindowProcName = The backend process for the window.
+    Position = 2-tuple (x,y) coordinates of the mouse click
+    MessageName = "mouse left|right|middle down", "mouse left|right|middle up".
+    """
+
+    def __init__(self, Window, WindowName, WindowProcName, Position, MessageName):
+        self.Window = Window
+        self.WindowName = WindowName
+        self.WindowProcName = WindowProcName
+        self.Position = Position
+        self.MessageName = MessageName
+
+    def __str__(self):
+        return "Window Handle: " + str(self.Window) + "\nWindow Name: " + str(self.WindowName) + "\nWindow's Process Name: " + str(self.WindowProcName) + "\nPosition: " + str(self.Position) + "\nMessageName: " + str(self.MessageName) + "\n"
+
+#######################################################################
+#########################END CLASS DEF#################################
+#######################################################################
+
+if __name__ == '__main__':
+    hm = HookManager()
+    hm.HookKeyboard()
+    hm.HookMouse()
+    hm.KeyDown = hm.printevent
+    hm.KeyUp = hm.printevent
+    hm.MouseAllButtonsDown = hm.printevent
+    hm.MouseAllButtonsUp = hm.printevent
+    hm.start()
+    time.sleep(10)
+    hm.cancel()

File scan2symcodes.py

+import ConfigParser, os, re
+
+class Scan2Symcodes():
+    """ map keyboard scancodes to actual xkb key names """
+
+    def __init__(self):
+        """ Create the mapping """
+        self.retrieve_xkb_actual_config()
+        self.parse_xkb_config_keycodes()
+
+    def retrieve_xkb_actual_config(self):
+        """ retrieve actual xkb config and save it in specified sources location """
+
+        # specified sources location
+        config = ConfigParser.RawConfigParser()
+        config.readfp(open('xkeyvents.cfg'))
+        xkb_config_src  = config.get('scan2symcodes', 'xkb_config_src')
+
+        # retrieve actual config and save it
+        os.system('xkbcomp -xkb $DISPLAY ' + xkb_config_src)
+        self.src = open(xkb_config_src)
+
+    def parse_xkb_config_keycodes(self):
+        """ Parse xkb sources to create the mapping"""
+
+        self.keycodes = {}
+        lines = self.src.readlines()
+        start_parse = False
+        for line in lines:
+            if (None != re.match("xkb_keycodes", line)):
+                start_parse = True
+            if (start_parse):
+                if (None != re.match("}", line)):
+                    break
+                if (None != re.match(" *<", line)):
+                    m = re.search("^ *<([^>]*)>[^=]*=[^\d]*(\d*);", line)
+                    self.keycodes[int(m.group(2))] = m.group(1)
+
+
+if __name__ == "__main__":
+    translation = Scan2Symcodes()
+    print translation.keycodes

File xkeyvent.cfg.sample

+[scan2symcodes]
+xkb_config_src = ./config.xkb
+
+[hookkey]
+log_location = ./keylog