Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/python
- # sidlog.py
- ### CONFIGURATIONS
- _REFRESH_RATE = 1 # The seconds between displayer updates
- FOREVER = 1000000 # This is the setting for how long forever is considered
- ### HINTS
- #This program contains tags for bug-tracking, information, and development.
- #
- #If you know Python, and are up to the task, here is what you need to know.
- #
- #Read the code to get a feel for the style and method. Edit and incrementally
- #make changes ensuring functionality. Here are some tags to guide your journey.
- #
- #TODO: Denotes areas where further functions should be added.
- #BUG: Denotes an area where something is working, but not 100%
- #INFO: Denotes a tag that gives hints to the methodology of the snippet of code
- #
- #If you edit, please comment and follow the tagging standard!
- #
- # ~blerbl
- ###
- ####################################################
- ### BELOW IS THE PROGRAM, EDIT AT YOUR OWN PERIL ###
- ####################################################
- ### About this program ###
- _name = "sidlog"
- _version = 0.4
- _date = "22 Jan 2013"
- _authors = "blerbl"
- _about = """
- This tool listens for 802.11 probe requests. It then logs the SSIDs in a database.
- It will keep track of the associated client SSIDs, the date last seen, and
- other information about the data collected.
- """
- ### Platforming ###
- import sys
- _supported = ['linux2',]
- _LINUX = 'linux2'
- _ENV = sys.platform
- if not _ENV in _supported: raw_input('[#]' + _ENV + " not supported");exit(0)
- if sys.version_info[0] != 2 or sys.version_info[1] <= 6:
- raw_input("[#]Must be run with Python 2.x minor version >= .6\n You are running" + sys.version); exit(1)
- ### Imports ###
- import os, logging, socket, time, sqlite3, threading, argparse, re
- from urllib import urlretrieve as pyget
- from zipfile import ZipFile as pyzip
- from Tkinter import *
- # SCAPY!!! The embedded installer is a little buggy
- logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
- try:
- from scapy.all import *
- except ImportError:
- #TODO This assumes that the ImportError is because scapy isnt installed
- # this may not be true. Further checking is needed
- print( "[!] Scapy is required to run " + _name)
- ans = raw_input( "Would you like to install Scapy at this time[Y|n]")
- if not 'n' in ans.lower():
- print "[+]Installing scapy..."
- print "[++]Getting File"
- try:
- t_a,t_b = pyget('http://scapy.net','scapy.zip')
- temp = pyzip(t_a)
- print "[++]Extracting..."
- temp.extractall("scapyout")
- temp.close()
- print "[++]Running setup"
- os.chdir("scapyout")
- os.chdir(os.listdir('.')[0])
- cmd = "setup.py install"
- if _ENV==_LINUX: cmd = './' + cmd
- errno = os.system(cmd)
- if errno :
- print "[#]There was an error installing scapy"
- exit(1)
- print "[++]Cleaning up"
- os.chdir("..")
- os.chdir("..")
- cmd = "rm -rf scapyout" #BUG needs os agnostic command
- os.system(cmd)
- os.remove("scapy.zip")
- try:
- print "[++]Scapy Installed!"
- from scapy.all import *
- except Exception as e:
- print "[#]Problem still not resolved. Check other dependencies"
- exit(1)
- except Exception as e:
- print "[#]Could not complete scapy install"
- exit(1)
- else:
- print("[#]Scapy requires for operation of " + _name + " v" + str(_version))
- print("[-]Exiting...")
- exit(0)
- ### Globals ###
- re_essid = re.compile("\A[0-9A-z_.'\" \-&]+\Z")
- #re_essid = re.compile("\A[ -z]+\Z")
- sys.running = False
- sys.verbose = 0
- def Error(message, level=1):
- """ Use this method to throw fatal errors"""
- print("[!]"+message)
- exit(level)
- def validessid(name):
- """ Determine if the tuple 'tup' meets the current filter criteria
- @returns
- True - If the tuple satisfies the current displayer filter states
- False - If the tuple does not satisfy the current displayer filter state"""
- if name is None:
- return False
- elif re_essid.match(name):
- return True
- return False
- class SidlogGUI(Frame):
- """ This is the GUI object for sidlog. It is dependent on the listener object.
- Create this object like any other Tkinter object."""
- def __init__(self, master=None):
- Frame.__init__(self, master)
- self.listener = Listener()
- #TODO Organize this file and make the different components easy to identify
- self.armed = False
- self.pairs = []
- #TODO Add a self.presentation to hold the things being displayed.
- # This will separate reponsibility from the self.pairs which will
- # then take on the role of just storing all information and be liked to
- # the listener. It may not even need the pairs directive but this may take
- # reference load off the listener.
- self.pack(expand=1, fill=BOTH)
- #INFO The menus, like all the other components in the GUI, are grouped in the program like they
- # are spacially.
- menu_main = Menu(self)
- menu_file = Menu(menu_main, tearoff=0)
- #TODO Refresh available interfaces. Use in conjuction with the self.entry_iface todos
- #menu_file.add_command(label="Refresh Ifaces", command=self.query_available_ifaces)
- #self.add_separator()
- menu_file.add_command(label=" Exit ", command=self.quit2)
- menu_main.add_cascade(label="Options", menu=menu_file)
- menu_sort = Menu(menu_main, tearoff=0)
- menu_sort.add_command(label="Order by Client",command=lambda: self.displayer_order_column(0))
- menu_sort.add_command(label="Order by ESSID", command=lambda: self.displayer_order_column(1))
- #TODO This is for grouping with self.presentation. When ready uncomment
- #menu_sort.add_separator()
- #menu_sort.add_command(label="Group by Client", command=self.displayer_group_client)
- #menu_sort.add_command(label="Group by ESSID", command=self.displayer_group_essid)
- #menu_sort.add_command(label="No Grouping", command=self.displayer_group_none)
- menu_main.add_cascade(label="View", menu=menu_sort)
- menu_act = Menu(menu_main, tearoff=0)
- menu_act.add_command(label="Monitor Mode",command=self.set_mon)
- menu_act.add_command(label="Managed Mode",command=self.unset_mon)
- menu_main.add_cascade(label="Action", menu=menu_act)
- root.configure(menu=menu_main)
- self.createWidgets()
- self.listener.statout = self.var_stat #very important, binds the status area
- threading.Thread(None,self.displayer,'diplayer').start()
- self.stat("Welcome")
- def createWidgets(self):
- """ Create the different widgets that are members of the main display"""
- #INFO the naming standard is 'self.<part>_<name>'
- #Frames #INFO This is to create the underlying grid
- self.frame_control = Frame(self)
- self.frame_control.pack(side=LEFT,fill=Y)
- self.frame_view = Frame(self, height=300)
- self.frame_view.pack(side=RIGHT, fill=BOTH, expand=1)
- #The viewing frame components
- scrollbary = Scrollbar(self.frame_view)
- scrollbarx = Scrollbar(self.frame_view,orient=HORIZONTAL)
- scrollbary.pack(side=RIGHT, fill=Y)
- scrollbarx.pack(side=BOTTOM, fill=X)
- self.disp_results = Listbox(self.frame_view, xscrollcommand=scrollbarx.set, yscrollcommand=scrollbary.set, height=17, width=30)
- self.disp_results.config(font = ("Courier",10))
- self.disp_results.pack(side=LEFT, fill=BOTH,expand=1)
- scrollbary.config(command=self.disp_results.yview)
- scrollbarx.config(command=self.disp_results.xview)
- #TODO add a search bar to filter results
- #TODO separate the listviews into two. Use the displayer and self.presentation to keep inline
- # and to group. e.g.
- # | 00:11:22:33:44:55 | airweb, TehTubez, Monkeycheesepants
- # OR
- # | PANERA | 00:11:22:33:44:55, 55:44:33:22:11:00
- #
- # Perhaps use the StrVars and calculate every time, only updateing on difs
- # Or use list, identifying new items base on them not being in self.pairs
- # then checking if and where they deserve to be in the displayer based on the
- # state of displayer (self.displayer_state_<> objects)
- #The control frame components
- self.lable_iface = Label(self.frame_control)
- self.lable_iface["text"] = "Interface"
- self.lable_iface.pack(side=TOP)
- #TODO this should be a dropdown of available interfaces. Updated by a refresh interfaces action
- # in Options
- self.entry_iface = Entry(self.frame_control)
- self.entry_iface.insert(0,"wlan0")
- self.entry_iface.pack(side=TOP)
- self.lable_dbase = Label(self.frame_control)
- self.lable_dbase["text"] = "Database"
- self.lable_dbase.pack(side=TOP)
- self.entry_dbase = Entry(self.frame_control)
- self.entry_dbase.insert(0,"sidlog.db")
- self.entry_dbase.pack(side=TOP)
- self.var_stat = StringVar(self, "","status")
- self.disp_stat = Listbox(self.frame_control, listvariable=self.var_stat, font=("Courier",8))
- dispx = Scrollbar(self.disp_stat,orient=HORIZONTAL)
- dispx.pack(side=BOTTOM, fill=X)
- self.disp_stat.config(xscrollcommand=dispx.set)
- self.disp_stat.pack(fill=BOTH, expand=1)
- dispx.config(command=self.disp_stat.xview)
- self.disp_stat.state=DISABLED
- self.btn_load = Button(self.frame_control)
- self.btn_load["text"] = "Load Config"
- self.btn_load['command'] = self.loadconf
- self.btn_load.pack(side=LEFT,fill=X)
- self.btn_toggle = Button(self.frame_control)
- self.btn_toggle["text"] = "Start"
- self.btn_toggle["command"] = self.toggle
- self.btn_toggle["state"]=DISABLED
- self.btn_toggle.pack(side=LEFT,fill=X)
- def quit2(self):
- """ Ensures that the things that need shutdown get shutdown
- before their master quits"""
- #INFO If you have a thread with a loop or something of the sorts
- # be sure to terminate it here
- self.running=False
- self.listener.stop()
- self.quit()
- def set_mon(self):
- """ Puts the interface in the entry into monitor mode"""
- if not self.listener.state == Listener.STOPPED:
- self.stat("Stop listener first",1)
- return 1
- iface = self.entry_iface.get()
- t_s = socket.socket()
- try:
- t_s.setsockopt(socket.SOL_SOCKET, 25, iface)
- cmds = ["ifconfig " + iface + " down","iwconfig " + iface + " mode monitor","ifconfig " + iface + " up"]
- try:
- for cmd in cmds:
- os.system(cmd)
- self.stat("<" + iface + "> Monitoring")
- except:
- self.stat("<" + iface + "> Mon failed",1)
- except:
- self.stat("<" +iface + "> not a valid",1)
- t_gtg = False
- finally:
- del t_s
- def unset_mon(self):
- """ Puts interface in the entry into manage mode"""
- if not self.listener.state == Listener.STOPPED:
- self.stat("Stop listener first",1)
- return 1
- iface = self.entry_iface.get()
- t_s = socket.socket()
- try:
- t_s.setsockopt(socket.SOL_SOCKET, 25, iface)
- cmds = ["ifconfig " + iface + " down","iwconfig " + iface + " mode managed","ifconfig " + iface + " up"]
- try:
- for cmd in cmds:
- os.system(cmd)
- self.stat("<" + iface + "> Managed")
- except:
- self.stat("<" + iface + "> mon failed",1)
- except:
- self.stat("<" +iface + "> not a valid",1)
- t_gtg = False
- finally:
- del t_s
- def loadconf(self):
- """ Loads the interface, makes sure it is good, loads the database,
- makes sure it is good. Enables and disables fields and buttons."""
- if self.armed:
- self.armed = False
- self.btn_toggle['state']=DISABLED
- self.btn_load['text']="Load Config"
- self.entry_iface['state']=NORMAL
- self.entry_dbase['state']=NORMAL
- self.stat("Unlocked")
- return
- t_gtg = True
- iface = self.entry_iface.get()
- dbase = self.entry_dbase.get()
- t_s = socket.socket()
- try:
- t_s.setsockopt(socket.SOL_SOCKET, 25, iface)
- except:
- self.stat("<" +iface + "> not a valid",1)
- t_gtg = False
- finally:
- del t_s
- self.listener.iface = iface
- #check database
- try:
- self.listener.set_db(dbase)
- self.pairs = []
- except Exception as e:
- self.stat("Bad DB: " + dbase,1)
- t_gtg = False
- if t_gtg:
- self.armed = True
- self.btn_toggle['state']=NORMAL
- self.btn_load['text']=" Unlock "
- self.entry_iface['state']=DISABLED
- self.entry_dbase['state']=DISABLED
- self.stat("Configured")
- self.displayer_needrefresh = True
- def toggle(self):
- if self.listener.state == Listener.RUNNING:
- self.btn_toggle["text"] = "Start"
- self.btn_load["state"]=NORMAL
- self.listener.stop()
- elif self.listener.state == Listener.STOPPED:
- self.btn_toggle["text"] = "Stop"
- self.btn_load["state"]=DISABLED
- self.listener.start()
- def stat(self, message, value=0):
- values = ["[+]","[-]"]
- ct = time.strftime("%H:%M")
- message = ct + values[value] + message
- cur = self.var_stat.get() or "()"
- cur = eval(cur)
- self.var_stat.set((message,) + cur)
- #################################INFO##################################
- ### Displayer family of commands and states. Keep most of them here ###
- #TODO Determine which way is better, go with it, and annotate why (Currently using 1)
- #1) The listener stores to the database AND exports to the GUI e.g.: DB <-> Listener <-> GUI
- #2) The listener stores to the database and the GUI querys it. e.g.: Listener <-> DB <-> GUI
- #TODO?1 add commands to query the listener
- #TODO?2 add commands to query the database
- def displayer_order_column(self,by=0,descend = True):
- """ sort the display by the column indicated in "by" then and
- inverte is based on the boolean descend"""
- try:
- tsave = self.pairs
- if by ==0:
- self.pairs = sorted(self.pairs, key=lambda x: int(x[0].replace(':',''),16),reverse=descend)
- elif by == 1:
- self.pairs = sorted(self.pairs, key=lambda x: (x[by]or"").lower(),reverse=descend)
- else:
- self.pairs = sorted(self.pairs, key=lambda x: x[by],reverse=descend)
- self.displayer_needrefresh = True
- except Exception as e:
- self.stat("Sort by "+str(by)+" failed: "+str(e),1)
- self.pairs = tsave #make the sort atomic
- def displayer_initStates(self):
- """ initialize all the result displayer's constants """
- self.displayer_needrefresh = True
- def displayer(self):
- """ Updates the displayed results and applies necessary filtering.
- This is a working method meant to be launched in a thread."""
- #INFO Notice how a single call initialized the states for the displayer.
- self.displayer_initStates()
- displayed = [] #this may become self.presentation depending on how the GUI gets its info
- while sys.running:
- time.sleep(_REFRESH_RATE)
- #INFO by filtering newpairs, it saves us time one the displayer when display filters
- # and insertions become intensive
- new_pairs = filter( lambda x: not x in self.pairs, self.listener.pairs)
- self.pairs.extend(new_pairs)
- if self.displayer_needrefresh:
- self.disp_results.delete(0,END)
- displayed = []
- self.displayer_needrefresh = False
- new_pairs = self.pairs #all pairs are new when refreshing
- for newpair in new_pairs:
- if (not newpair in displayed) and (not newpair is None) and validessid(newpair[1]):
- displayed.append(newpair)
- self.disp_results.insert(0,newpair[0] + " | " + newpair[1] )
- #BUG there is a bug that lets weird SSIDS display still
- ################# END DISPLAYER SECTION #################
- #########################################################
- ### END OF SidlogGUI
- ####################################################################################
- class Listener:
- """ Listener object, used to capture packets and log them to a sqlite3 database"""
- STOPPED = 0
- STARTING = 1
- RUNNING = 2
- STOPPING = 3
- CLIENT_TABLE_TEST="INSERT INTO clients (mac,time) values ('00:00:00:00:00:00',0)"
- ESSIDS_TABLE_TEST="INSERT INTO essids (mac,name) values ('00:00:00:00:00:00','test')"
- CLIENT_TABLE_CREATE="CREATE TABLE clients (mac,time INTEGER)"
- ESSIDS_TABLE_CREATE="CREATE TABLE essids (sid INTEGER PRIMARY KEY AUTOINCREMENT,mac,name)"
- #TODO Export the Database definitions so that other objects can use them
- def __init__(self,db=None):
- self.state = Listener.STOPPED
- self.statout = sys.stdout
- self.t = None
- self.iface = ''
- self.pairs = []
- self.clients = []
- self.running = False
- if db:
- self.set_db(db)
- #use stat to update the current status output method as determined by self.statout.
- #this device is usually stdout for console and the disp_stat for the GUI
- def stat(self, message, level=0):
- levels = ["[+]","[-]"]
- if level <len(levels):
- ct = time.strftime("%H:%M")
- message = ct + levels[level] + str(message)
- if not self.statout:
- Error("Status output not defined!")
- elif self.statout == sys.stdout:
- self.statout.write(message.strip().strip('\r\n') + '\n')
- self.statout.flush()
- else: # self.statout is Tkinter.Variable:
- cur = self.statout.get()
- cur = eval(cur)
- self.statout.set((message,)+cur)
- def set_db(self,db):
- self.db_name = db
- try:
- self.db = sqlite3.connect(self.db_name)
- try:
- self.db.execute("INSERT INTO clients (mac,time) values ('00:00:00:00:00:00',0)")
- self.db.execute("INSERT INTO essids (mac,name) values ('00:00:00:00:00:00','test')")
- self.db.rollback()
- except Exception as e1:
- try:
- self.db.execute("CREATE TABLE clients (mac,time INTEGER)")
- self.db.execute("CREATE TABLE essids (sid INTEGER PRIMARY KEY AUTOINCREMENT,mac,name)")
- self.db.commit()
- except Exception as e2:
- self.stat("Couldn't make tables")
- raise Exception
- pairs = self.db.execute("SELECT mac,name FROM clients LEFT JOIN essids USING (mac)").fetchall()
- self.pairs =[]
- self.clients = []
- for pair in pairs:
- self.pairs.append(pair)
- self.clients.append(pair[0])
- self.db.close()
- except:
- self.stat("Couldn't connect to db file",1)
- exit(1)
- def count(self):
- return len(self.pairs)
- #INFO Flush is dangerous so I don't use it.. This is good for dev
- def flush(self):
- """ Clears the database and the displayer"""
- try:
- self.db = sqlite3.connect(self.db_name)
- self.db.execute("DELETE FROM essids")
- self.db.execute("DELETE FROM clients")
- self.pairs = []
- self.clients = []
- self.db.commit()
- self.db.close()
- self.displayer_needrefresh = True
- except Exception as e:
- self.stat("FLUSH"+str(e),1)
- def worker(self):
- """ Method that listens. Includes the packet handler callback
- This method is meant to be run as a thread"""
- db = sqlite3.connect(self.db_name)
- def w_handle(pkt):
- if Dot11ProbeReq in pkt or Dot11ProbeResp in pkt:
- if validessid(pkt.info):
- if Dot11ProbeResp in pkt:
- t_pair = (pkt[Dot11].getfieldval('addr1'),pkt.info)
- else:
- t_pair = (pkt[Dot11].getfieldval('addr2'),pkt.info)
- if not (t_pair in self.pairs):
- self.pairs.append(t_pair)
- db.execute("INSERT INTO essids (mac,name) values "+ str(t_pair))
- if not t_pair[0] in self.clients:
- info = (t_pair[0],str(time.time()))
- db.execute("INSERT INTO clients (mac,time) values "+str(info))
- self.clients.append(t_pair[0])
- else:
- t_cmd = "UPDATE clients SET time="+str(time.time())+" where mac='"+t_pair[0]+"'"
- db.execute(t_cmd)
- db.commit()
- self.state = Listener.RUNNING
- while self.running:
- try:
- sniff(iface=self.iface,count = 10, store = 0,prn=w_handle,timeout=5)
- except Exception as e:
- self.stat("LISTENER:"+str(e),1)
- self.state = Listener.STOPPED
- def start(self):
- if not self.iface:
- self.stat( "please set interface")
- else:
- self.state = Listener.STARTING
- self.running = True
- self.t = threading.Thread(None,self.worker,"listener")
- self.t.start()
- self.stat( "Listening <" + self.iface + ">")
- def stop(self):
- self.state = Listener.STOPPING
- self.running = False
- if self.t:
- self.t.join()
- self.stat("Stopped...")
- if __name__ == "__main__":
- sys.running = True
- parser = argparse.ArgumentParser(description=_about,prog=_name)
- parser.add_argument('--version', action='version', version="%(prog)s v"+str(_version))
- parser.add_argument('--headless','-',action="store_const",default=False,const=True)
- parser.add_argument('--verbose', '-v', action='count')
- parser.add_argument('--force', '-f', action="store_const", default=False, const=True)
- parser.add_argument("-d", nargs=1, default="./sidlog.db",help="The database to store results.")
- parser.add_argument("-i","--iface", nargs=1, help="The interface to sniff on.")
- args = ["",]
- if len(sys.argv) > 0:
- args = parser.parse_args(sys.argv[1:])
- else:
- args = parser.parse_args(sys.argv)
- ###### Important variables and stuff
- gl_dbName = args.d[0]
- gl_force = args.force
- sys.verbose = args.verbose or 0
- if not args.headless:
- ### HERE BE THE GUI
- root = Tk()
- root.title("Sidlog v" + str(_version))
- app = SidlogGUI(master=root)
- try:
- app.mainloop()
- root.destroy()
- except KeyboardInterrupt as e:
- print "Bye!"
- except:
- sys.stderr.write("[+]GUI exited unexpectedly\n")
- sys.running = False
- finally:
- sys.running = False
- else:
- ### HERE BE THE COMMAND LINE LOGIC
- if len(sys.argv)==2: parser.parse_args(['-h',])
- else: gl_iface = args.iface[0]
- listen = Listener(gl_dbName)
- #make sure user put in valid iface
- t_s = socket.socket()
- try:
- t_s.setsockopt(socket.SOL_SOCKET, 25, gl_iface)
- except:
- Error("<"+ gl_iface+"> not a valid interface")
- finally:
- del t_s
- # repond to forcing monitor
- if gl_force:
- try:
- os.system("ifconfig " + gl_iface + " down")
- os.system("iwconfig " + gl_iface + " mode monitor")
- os.system("ifconfig " + gl_iface + " up")
- except:
- print("Could not force iface into monitor mode")
- try:
- listen.iface = gl_iface
- listen.start()
- if sys.verbose == 0:
- print "My PID is " + str(os.getppid())
- try:
- time.sleep(FOREVER)
- except:
- pass
- listen.stop()
- exit(0)
- elif sys.verbose == 1:
- try:
- rotator = ['\\','|','/','-']
- i = 0
- try: os.system('setterm -cursor off')
- except: pass
- while FOREVER > 0:
- FOREVER -= 1
- i = (i + 1) % 4
- time.sleep(1)
- info = "Capturing: "+str(listen.count())+"\t so far [" + rotator[i] + "] \r"
- print(info),
- sys.stdout.flush()
- except:
- listen.stop()
- exit(0)
- finally:
- try: os.system('setterm -cursor on')
- except: pass
- else:
- time.sleep(FOREVER)
- except KeyboardInterrupt:
- print "Bye!"
- except:
- sys.stderr.write("[!]Sidlog Exited Unexpectedly\n")
- finally:
- sys.running = False
- if gl_force:
- try:
- os.system("ifconfig " + gl_iface + " down")
- os.system("iwconfig " + gl_iface + " mode managed")
- os.system("ifconfig " + gl_iface + " up")
- except:
- pass
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement