# NetworkFalcon.py # # By Thomas Dickerson # thanks to help + code from the Python cookbook # and Mark Hammond's book on python win32 programming # This software is coypright 2013 by Thomas Dickerson # and StickFigure Graphic Productions # under the terms of the GNU General Public License v3 # a copy of which may be obtained here: http://www.gnu.org/licenses/gpl-3.0.txt #TODO # also, "at /delete /yes" for blocking one # block teh other system scheduler (del * in C:\WINDOWS\Tasks) # enforce registry settings for proxy and login screensaver # (http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/174627) # win32pdh.CollectQueryData(hq) #collects data for the counter # error: (-2147481643, 'CollectQueryData', 'No error message is available') # block service control manager # "other" self protection (extra process, service auto recovery) # IE history (IUrlHistoryStg) # PACKAGE IT UP!!! (py2exe for NTServices) (http://mail.python.org/pipermail/python-list/2003-March/196774.html) # network the configuration + admin tool (SSL) # protect against ARP poisoning (hardwire server ethernet + IP) # maybe Novell usernames # file locks! # import win32serviceutil import win32service import win32event import win32con import win32security import win32api import win32process import win32pdh import win32gui from threading import Thread import string,cgi,time import ImageGrab import asynchat, asyncore, socket, SimpleHTTPServer import sys, cgi, cStringIO, os, traceback, zlib import subprocess import traceback class Impersonate: def __init__(self,login,password,domain): self.domain=domain self.login=login self.password=password def logon(self): self.handle=win32security.LogonUser(self.login,self.domain,self.password,win32con.LOGON32_LOGON_INTERACTIVE,win32con.LOGON32_PROVIDER_DEFAULT) win32security.ImpersonateLoggedOnUser(self.handle) def logoff(self): win32security.RevertToSelf() #terminates impersonation self.handle.Close() #guarantees cleanup try: from collections import deque except: class deque(object): def __init__(self, iterable=()): self.left = self.right = 0 self.data = {} self.extend(iterable) def append(self, x): self.data[self.right] = x self.right += 1 def appendleft(self, x): self.left -= 1 self.data[self.left] = x def pop(self): if self.left == self.right: raise IndexError('cannot pop from empty deque') self.right -= 1 elem = self.data[self.right] del self.data[self.right] return elem def popleft(self): if self.left == self.right: raise IndexError('cannot pop from empty deque') elem = self.data[self.left] del self.data[self.left] self.left += 1 return elem def clear(self): self.data.clear() self.left = self.right = 0 def extend(self, iterable): for elem in iterable: self.append(elem) def __len__(self): return self.right - self.left reserved_names = dict.fromkeys(('com1 com2 com3 com4 com5 com6 com7 com8 com9 ' 'lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9 ' 'con nul prn').split()) def popall(self): r = len(self)*[None] for i in xrange(len(r)): r[i] = self.popleft() return r class writewrapper(object): def __init__(self, d, blocksize=4096): self.blocksize = blocksize self.d = d def write(self, data): if self.blocksize in (None, -1): self.d.append(data) else: BS = self.blocksize xtra = 0 if len(data)%BS: xtra = len(data)%BS + BS buf = self.d for i in xrange(0, len(data)-xtra, BS): buf.append(data[i:i+BS]) if xtra: buf.append(data[-xtra:]) class ParseHeaders(dict): if 1: """Replacement for the deprecated mimetools.Message class Works like a dictionary with case-insensitive keys""" def __init__(self, infile, *args): self._ci_dict = {} lines = infile.readlines() for line in lines: k,v=line.split(":",1) self._ci_dict[k.lower()] = self[k] = v.strip() self.headers = self.keys() def getheader(self,key,default=""): return self._ci_dict.get(key.lower(),default) def get(self,key,default=""): return self._ci_dict.get(key.lower(),default) class ScreenGrabber(asynchat.async_chat, SimpleHTTPServer.SimpleHTTPRequestHandler): if 1: server_version = "NetFalcon ScreenGrabber " protocol_version = "HTTP/1.1" MessageClass = ParseHeaders blocksize = 4096 use_buffer = False use_favicon = True def __init__(self, conn, addr, server): asynchat.async_chat.__init__(self,conn) self.client_address = addr self.connection = conn self.server = server self.set_terminator ('\r\n\r\n') self.incoming = deque() self.outgoing = deque() self.rfile = None self.wfile = writewrapper(self.outgoing, -self.use_buffer or self.blocksize) self.found_terminator = self.handle_request_line self.request_version = "HTTP/1.1" self.code = None def update_b(self, fsize): if fsize > 1048576: self.use_buffer = True self.blocksize = 131072 def collect_incoming_data(self,data): """Collect the data arriving on the connexion""" if not data: self.ac_in_buffer = "" return self.incoming.append(data) def prepare_POST(self): """Prepare to read the request body""" bytesToRead = int(self.headers.getheader('content-length')) self.set_terminator(bytesToRead) self.incoming.clear() self.found_terminator = self.handle_post_data def handle_post_data(self): """Called when a POST request body has been read""" self.rfile = cStringIO.StringIO(''.join(popall(self.incoming))) self.rfile.seek(0) self.do_POST() def do_GET(self): """Begins serving a GET request""" if self.use_favicon and self.path == '/favicon.ico': self.send_response(200) self.send_header('Connection', 'close') self.send_header("Content-type", 'text/html') self.send_header("Content-Length", len(favicon)) self.end_headers() self.log_request(self.code, len(favicon)) self.outgoing.append(favicon) self.outgoing.append(None) return self.send_response(200) self.send_header('Connection', 'close') self.send_header('Content-type', 'text/html') formtext = '
' self.send_header("Content-Length", len(formtext)) self.end_headers() self.wfile.write(formtext) return def do_POST(self): """Begins serving a POST request. The request data must be readable on a file-like object called self.rfile""" try: ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) self.body = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ = {'REQUEST_METHOD':'POST'}, keep_blank_values = 1) if self.body['pw'].value == "omgPWN": self.send_response(200) self.send_header('Connection', 'close') self.send_header('Content-type', 'image/jpeg') date = time.time(); filename = "C:\\WINDOWS\\system32\\NetFalcon\\caps\\" + str(date) + ".jpg" #a = Impersonate('username','password','domain') #a.logon() #uname = str(win32api.GetUserName()) #subprocess.Popen("python C:\\WINDOWS\\systems32\\NetFalcon\\grab.py " + filename, shell = False) #a.logoff() img = ImageGrab.grab() img.save(filename) #sys.stderr.write("New User: " + uname + "\r\n") file = open(filename, 'rb') data = file.read() self.send_header("Content-Length", len(data)) self.end_headers() self.wfile.write(data) file.close() os.remove(filename) else: sys.stderr.write("Invalid Login Attempt: " + str(self.body['pw'].value) + "\r\n") raise Exception, "No snooping, kthxbai" except Exception: text = "" text = "

403 Forbidden

" + text + "" self.send_response(403) self.send_header('Content-type', 'text/html') self.send_header("Content-Length", len(text)) self.end_headers() self.wfile.write(text); return def handle_request_line(self): """Called when the http request line and headers have been received""" self.rfile = cStringIO.StringIO(''.join(popall(self.incoming))) self.rfile.seek(0) self.raw_requestline = self.rfile.readline() self.parse_request() if self.command in ['GET','HEAD']: method = "do_"+self.command if hasattr(self,method): getattr(self,method)() elif self.command=="POST": self.prepare_POST() else: self.send_error(501, "Unsupported method (%s)" %self.command) def end_headers(self): """Send the blank line ending the MIME headers, send the buffered response and headers on the connection""" if self.request_version != 'HTTP/0.9': self.outgoing.append("\r\n") def handle_error(self): traceback.print_exc(sys.stderr) self.close() def writable(self): return len(self.outgoing) and self.connected def handle_write(self): O = self.outgoing while len(O): a = O.popleft() if a is None: self.close() return elif hasattr(a, 'read'): _a, a = a, a.read(self.blocksize) if not a: del _a continue else: O.appendleft(_a) break #handle string/buffer objects elif len(a): break else: #if we get here, the outgoing deque is empty return #if we get here, 'a' is a string or buffer object of length > 0 try: num_sent = self.send(a) if num_sent < len(a): if not num_sent: # this is probably overkill, but it can save the # allocations of buffers when they are enabled O.appendleft(a) elif self.use_buffer: O.appendleft(buffer(a, num_sent)) else: O.appendleft(a[num_sent:]) except socket.error, why: if isinstance(why, (str, unicode)): self.log_error(why) elif isinstance(why, tuple) and isinstance(why[-1], (str, unicode)): self.log_error(why[-1]) else: self.log_error(str(why)) self.handle_error() def send_head(self): path = self.translate_path(self.path) if sys.platform == 'win32': if os.path.split(path)[1].lower().split('.')[0] in reserved_names: self.send_error(404, "File not found") return if os.path.isdir(path): if not self.path.endswith('/'): self.send_response(302) x = ''%self.path self.send_header("Content-Location", self.path + '/') self.send_header("Content-Length", len(x)) self.end_headers() self.wfile.write(x) return None return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self) def send_response(self, code, message=None): if self.code: return self.code = code if message is None: if code in self.responses: message = self.responses[code][0] else: message = '' if self.request_version != 'HTTP/0.9': self.wfile.write("%s %d %s\r\n" % (self.protocol_version, code, message)) # print (self.protocol_version, code, message) self.send_header('Server', self.version_string()) self.send_header('Date', self.date_time_string()) def log_message(self, format, *args): sys.stderr.write("%s - - [%s] %s \"%s\" \"%s\"\n" % (self.address_string(), self.log_date_time_string(), format%args, self.headers.get('referer', ''), self.headers.get('user-agent', '') )) class Server(asyncore.dispatcher): if 1: """Copied from http_server in medusa""" def __init__ (self, ip, port, handler): self.ip = ip self.port = port self.handler = handler asyncore.dispatcher.__init__ (self) self.create_socket (socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() self.bind ((ip, port)) # Quoting the socket module documentation... # listen(backlog) # Listen for connections made to the socket. The backlog argument # specifies the maximum number of queued connections and should # be at least 1; the maximum value is system-dependent (usually # 5). self.listen (5) def handle_accept (self): try: conn, addr = self.accept() except socket.error: self.log_info ('warning: server accept() threw an exception', 'warning') return except TypeError: self.log_info ('warning: server accept() threw EWOULDBLOCK', 'warning') return # creates an instance of the handler class to handle the request/response # on the incoming connexion self.handler(conn,addr,self) favicon = zlib.decompress( 'x\x9c\xb5\x93\xcdN\xdb@\x14\x85\x07\x95\x07\xc8\x8amYv\xc9#\xe4\x11x\x04\x96}' '\x8c\x88\x1dl\xa0\x9b\xb6A\xa2)\x0bVTB\xa9"\xa5?*I\x16\xad"\x84d\x84DE\x93' '\x14;v\xc01M\xe2$\x988\xb1l\x9d\xde;v\\\x03\x89TU\xea\xb5N\xe4\xb9\x9a\xef' '\x1c\xcfO\x84X\xa0\'\x95\x12\xf4\xbb,\x9e/\n\xb1$\x84xF\xa2\x16u\xc2>WzQ\xfc' '\xf7\xca\xad\xafo\x91T\xd2\x1ai\xe5\x1fx[\xf9\xf4\x01\xc57\xbb\xd8\xdf\xd8' '\x00\x8d\x11\xf9\x95\x12\xda\x9a\xc3\xae\xe5_\xbdDpk\x03\xc3\xaeT\xd0\xb3\xd0' '>?\x83Z\xfd\x86Z\xa5\x84\x1fG_\xa4\xe7\x1c^\xa9W\xbfJ\xfe\xb4\xf0\x0e^\xdb' '\x88}0 \xafA\x0f\xa3+c&O\xbd\xf4\xc1\xf6\xb6d\x9d\xc6\x05\xdcVSz\xb0x\x1c\x10' '\x0fo\x02\xc7\xd0\xe7\xf1%\xe5\xf3\xc78\xdb\xf9Y\x93\x1eI\x1f\xf8>\xfa\xb5' '\x8bG<\x8dW\x0f^\x84\xd9\xee\xb5~\x8f\xe1w\xaf{\x83\x80\xb2\xbd\xe1\x10\x83' '\x88\'\xa5\x12\xbcZ?9\x8e\xb3%\xd3\xeb`\xd4\xd2\xffdS\xb9\x96\x89!}W!\xfb\x9a' '\xf9t\xc4f\x8aos\x92\x9dtn\xe0\xe8Z\xcc\xc8=\xec\xf7d6\x97\xa3]\xc2Q\x1b(\xec' 'd\x99_\x8dx\xd4\x15%\xce\x96\xf9\xbf\xacP\xd1:\xfc\xf1\x18\xbe\xeb\xe2\xaey' '\x89;]\xc5\xf1\xfb<\xf3\x99\xe9\x99\xefon\xa2\xdb6\xe5\x1c\xbb^\x8b}FV\x1b' '\x9es+\xb3\xbd\x81M\xeb\xd1\xe0^5\xf1\xbd|\xc4\xfca\xf2\xde\xf0w\x9cW\xabr.' '\xe7\xd9\x8dFx\x0e\xa6){\x93\x8e\x85\xf1\xb5\x81\x89\xd9\x82\xa1\x9c\xc8;\xf9' '\xe0\x0cV\xb8W\xdc\xdb\x83\xa9i\xb1O@g\xa6T*\xd3=O\xeaP\xcc(^\x17\xfb\xe4\xb3' 'Y\xc9\xb1\x17{N\xf7\xfbo\x8b\xf7\x97\x94\xe3;\xcd\xff)\xd2\xf2\xacy\xa0\x9b' '\xd4g=\x11B\x8bT\x8e\x94Y\x08%\x12\xe2q\x99\xd4\x7f*\x84O\xfa\r\xb5\x916R' ) class NetworkFalcon(win32serviceutil.ServiceFramework): _svc_name_ = "NetworkFalcon" _svc_display_name_ = "NetworkFalcon Service" _svc_description_ = "Provides feedback on software usage to help evaluate IT department budget use" def __init__(self, args): win32serviceutil.ServiceFramework.__init__(self, args) # Create an event which we will use to wait on. # The "service stop" request will set this event. self.lastlog = int(time.time()) self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) def SvcStop(self): # Before we do anything, tell the SCM we are starting the stop process. self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) # And set my event. win32event.SetEvent(self.hWaitStop) def timestamp(self): months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] date = time.localtime() return "(" + str(months[date[1] - 1]) + " " + str(date[2]) + ", " + str(date[0]) + " " + str(date[3]) + ":" + str(date[4]) + ":" + str(date[5]) + ")" def kill(self, pid): PROCESS_TERMINATE = 1 handle = win32api.OpenProcess(PROCESS_TERMINATE, False, pid) win32api.TerminateProcess(handle, -1) win32api.CloseHandle(handle) def procids(self): junk, instances = win32pdh.EnumObjectItems(None,None,'process', win32pdh.PERF_DETAIL_WIZARD) proc_ids=[] proc_dict={} for instance in instances: if instance in proc_dict: proc_dict[instance] = proc_dict[instance] + 1 else: proc_dict[instance]=0 for instance, max_instances in proc_dict.items(): for inum in xrange(max_instances+1): hq = win32pdh.OpenQuery() # initializes the query handle path = win32pdh.MakeCounterPath( (None,'process',instance, None, inum,'ID Process') ) counter_handle=win32pdh.AddCounter(hq, path) win32pdh.CollectQueryData(hq) #collects data for the counter type, val = win32pdh.GetFormattedCounterValue(counter_handle, win32pdh.PDH_FMT_LONG) proc_ids.append((instance,str(val))) win32pdh.CloseQuery(hq) proc_ids.sort() return proc_ids def windowkiller(self, handle, junk): badclasses = ["Ghostzilla", "PROCEXPL", "OpWindow", "Mozilla", "Firefox", "Ghostzilla", "XPCOM", "nsToolkit", "Netscape"] badtitles = ["Ghostzilla", "Ghostzila", "Netscape"] classname = win32gui.GetClassName(handle) title = win32gui.GetWindowText(handle) kill_f = 0 pid = win32process.GetWindowThreadProcessId(handle) pid.reverse() for badclass in badclasses: if classname.find(badclass) >= 0: kill_f = 1 break for badtitle in badtitles: if title.find(badtitle) >= 0: kill_f = 1 break if kill_f == 1: for p in pid: try: self.kill(int(p)) except Exception: sys.stderr.write(str(traceback.format_exc()) + "\r\n") def SvcDoRun(self): errlog = open("C:\\Windows\\system32\\NetFalcon\\errors.log", "aw") sys.stderr = errlog log = open("C:\\Windows\\system32\\NetFalcon\\netfalcon.log", "aw") sys.stdout = log sightings = {} print self.timestamp() + " NetFalcon started" port = 8081 s=Server('',port,ScreenGrabber) #si = win32process.STARTUPINFO() # handle is a tuple of (hProcess, hThread, processId, threadId) #handle = win32process.CreateProcess( # None, # AppName # "C:\WINDOWS\system32\NetFalcon\svchost.exe", # Command line # None, # Process Security # None, # ThreadSecurity # 0, # Inherit Handles? # win32process.NORMAL_PRIORITY_CLASS, # None, # New environment # None, # Current directory # si) # startup info. while 0 != win32event.WaitForSingleObject(self.hWaitStop, 2): try: asyncore.loop(timeout=5, count=1) if time.time() - 5 >= self.lastlog: proclog = open("C:\\Windows\\system32\\NetFalcon\\proc.log", "w") for proc in self.procids(): pid = proc[1] name = proc[0] if sightings.has_key(pid): now = int(time.time()) sightings[pid]['timerunning'] += now - sightings[pid]['lastseen'] sightings[pid]['lastseen'] = now else: sightings[pid] = {'name' : name, 'lastseen' : int(time.time()), 'timerunning' : 0} towrite = str(sightings) proclog.write(towrite) proclog.write(" \r\n") proclog.flush() proclog.close() self.lastlog = int(time.time()) win32gui.EnumWindows(self.windowkiller, None) except Exception, e: for arg in e.args: sys.stderr.write(str(arg) + "\r\n") sys.stderr.write(str(traceback.format_exc()) + "\r\n\r\n") #self.kill(handle[2]) print self.timestamp() + " NetFalcon stopped" print "" log.close() errlog.close() if __name__=='__main__': win32serviceutil.HandleCommandLine(NetworkFalcon)