From 88ce3b930472842e22b773bfcb67bec5e5290c67 Mon Sep 17 00:00:00 2001 From: stryngs Date: Thu, 20 May 2021 03:05:30 -0400 Subject: [PATCH 1/4] added requirements for pip purposes --- requirements.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f4cd791 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,20 @@ +certifi==2020.12.5 +chardet==4.0.0 +cycler==0.10.0 +gps3==0.33.3 +idna==2.10 +kiwisolver==1.3.1 +matplotlib==3.4.2 +numpy==1.20.3 +Pillow==8.2.0 +pyparsing==2.4.7 +PyQt5==5.15.4 +PyQt5-Qt5==5.15.2 +PyQt5-sip==12.9.0 +PyQtChart==5.15.4 +PyQtChart-Qt5==5.15.2 +python-dateutil==2.8.1 +requests==2.25.1 +scapy==2.4.5 +six==1.16.0 +urllib3==1.26.4 From cb509dbd30dbff77915ee676f9464f7ed0c7aaa0 Mon Sep 17 00:00:00 2001 From: stryngs Date: Thu, 20 May 2021 03:07:31 -0400 Subject: [PATCH 2/4] Instantiated WirelessEngine so that regex is compiled but once --- sparrow-wifi.py | 1215 ++++++++++++++++++++++++----------------------- 1 file changed, 609 insertions(+), 606 deletions(-) diff --git a/sparrow-wifi.py b/sparrow-wifi.py index eec525d..b5f52cd 100755 --- a/sparrow-wifi.py +++ b/sparrow-wifi.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 -# +# # Copyright 2017 ghostop14 -# +# # This is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. -# +# # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this software; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, # Boston, MA 02110-1301, USA. -# +# import sys import csv @@ -64,11 +64,11 @@ hasOUILookup = True except: hasOUILookup = False - + # ------------------ oui db function ----------------------- def getOUIDB(): ouidb = None - + if hasOUILookup: if os.path.isfile('manuf'): # We have the file but let's not update it every time we run the app. @@ -76,7 +76,7 @@ def getOUIDB(): last_modified_date = datetime.datetime.fromtimestamp(os.path.getmtime('manuf')) now = datetime.datetime.now() age = now - last_modified_date - + if age.days > 90: updateflag = True else: @@ -84,16 +84,16 @@ def getOUIDB(): else: # We don't have the file, let's get it updateflag = True - + try: ouidb = manuf.MacParser(update=updateflag) except: ouidb = None else: ouidb = None - + return ouidb - + # ------------------ Global functions for agent HTTP requests ------------------------------ def makeGetRequest(url, waitTimeout=6): try: @@ -101,10 +101,10 @@ def makeGetRequest(url, waitTimeout=6): response = requests.get(url, timeout=waitTimeout) except: return -1, "" - + if response.status_code != 200: return response.status_code, "" - + htmlResponse=response.text return response.status_code, htmlResponse @@ -112,23 +112,23 @@ def makeGetRequest(url, waitTimeout=6): def requestRemoteGPS(remoteIP, remotePort): url = "http://" + remoteIP + ":" + str(remotePort) + "/gps/status" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: try: gpsjson = json.loads(responsestr) gpsStatus = GPSStatus() - + gpsStatus.gpsInstalled = stringtobool(gpsjson['gpsinstalled']) gpsStatus.gpsRunning = stringtobool(gpsjson['gpsrunning']) gpsStatus.isValid = stringtobool(gpsjson['gpssynch']) - + if gpsStatus.isValid: # These won't be there if it's not synchronized gpsStatus.latitude = float(gpsjson['gpspos']['latitude']) gpsStatus.longitude = float(gpsjson['gpspos']['longitude']) gpsStatus.altitude = float(gpsjson['gpspos']['altitude']) gpsStatus.speed = float(gpsjson['gpspos']['speed']) - + return 0, "", gpsStatus except: return -2, "Error parsing remote agent response", None @@ -138,27 +138,27 @@ def requestRemoteGPS(remoteIP, remotePort): # ------------------ WiFi scan requests ------------------------------ def requestRemoteNetworks(remoteIP, remotePort, remoteInterface, channelList=None): url = "http://" + remoteIP + ":" + str(remotePort) + "/wireless/networks/" + remoteInterface - + if (channelList is not None) and (len(channelList) > 0): url += "?frequencies=" for curChannel in channelList: url += str(curChannel) + ',' - + if url.endswith(','): url = url[:-1] - + # Pass a higher timeout since the scan may take a bit statusCode, responsestr = makeGetRequest(url, 20) - + if statusCode == 200: try: networkjson = json.loads(responsestr) wirelessNetworks = {} - + for curNetDict in networkjson['networks']: newNet = WirelessNetwork.createFromJsonDict(curNetDict) wirelessNetworks[newNet.getKey()] = newNet - + return networkjson['errCode'], networkjson['errString'], wirelessNetworks except: return -2, "Error parsing remote agent response", None @@ -169,7 +169,7 @@ def requestRemoteNetworks(remoteIP, remotePort, remoteInterface, channelList=Non def remoteHackrfStatus(agentIP, agentPort): url = "http://" + agentIP + ":" + str(agentPort) + "/spectrum/hackrfstatus" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: try: responsedict = json.loads(responsestr) @@ -178,7 +178,7 @@ def remoteHackrfStatus(agentIP, agentPort): hashackrf = responsedict['hashackrf'] scan24Running = responsedict['scan24running'] scan5Running = responsedict['scan5running'] - + return errcode, errmsg, hashackrf, scan24Running, scan5Running except: return -1, 'Error parsing response', False, False, False @@ -191,7 +191,7 @@ def startRemoteSpectrumScan(agentIP, agentPort, scan5): else: url = "http://" + agentIP + ":" + str(agentPort) + "/spectrum/scanstart24" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: try: responsedict = json.loads(responsestr) @@ -202,11 +202,11 @@ def startRemoteSpectrumScan(agentIP, agentPort, scan5): return -1, 'Error parsing response' else: return -2, 'Bad response from agent [' + str(statusCode) + ']' - + def stopRemoteSpectrumScan(agentIP, agentPort): url = "http://" + agentIP + ":" + str(agentPort) + "/spectrum/scanstop" statusCode, responsestr = makeGetRequest(url, 10) - + if statusCode == 200: try: responsedict = json.loads(responsestr) @@ -217,11 +217,11 @@ def stopRemoteSpectrumScan(agentIP, agentPort): return -1, 'Error parsing response' else: return -2, 'Bad response from agent [' + str(statusCode) + ']' - + def getRemoteSpectrumScan(agentIP, agentPort): url = "http://" + agentIP + ":" + str(agentPort) + "/spectrum/scanstatus" statusCode, responsestr = makeGetRequest(url, 10) - + if statusCode == 200: try: responsedict = json.loads(responsestr) @@ -236,12 +236,12 @@ def getRemoteSpectrumScan(agentIP, agentPort): return -1, 'Error parsing response', None else: return -2, 'Bad response from agent [' + str(statusCode) + ']', None - + # ------------------ Bluetooth requests ------------------------------ def remoteHasBluetooth(agentIP, agentPort): url = "http://" + agentIP + ":" + str(agentPort) + "/bluetooth/present" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: try: responsedict = json.loads(responsestr) @@ -249,7 +249,7 @@ def remoteHasBluetooth(agentIP, agentPort): errmsg = responsedict['errmsg'] btPresent = responsedict['hasbluetooth'] scanRunning = responsedict['scanrunning'] - + return errcode, errmsg, btPresent, scanRunning except: return -1, 'Error parsing response', False, False @@ -260,7 +260,7 @@ def remoteHasBluetooth(agentIP, agentPort): def startRemoteBluetoothBeacon(agentIP, agentPort): url = "http://" + agentIP + ":" + str(agentPort) + "/bluetooth/beaconstart" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: try: responsedict = json.loads(responsestr) @@ -271,11 +271,11 @@ def startRemoteBluetoothBeacon(agentIP, agentPort): return -1, 'Error parsing response' else: return -2, 'Bad response from agent [' + str(statusCode) + ']' - + def stopRemoteBluetoothBeacon(agentIP, agentPort): url = "http://" + agentIP + ":" + str(agentPort) + "/bluetooth/beaconstop" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: try: responsedict = json.loads(responsestr) @@ -287,11 +287,11 @@ def stopRemoteBluetoothBeacon(agentIP, agentPort): else: return -2, 'Bad response from agent [' + str(statusCode) + ']' -# These scan functions are for the spectrum, not discovery +# These scan functions are for the spectrum, not discovery def startRemoteBluetoothScan(agentIP, agentPort): url = "http://" + agentIP + ":" + str(agentPort) + "/bluetooth/scanstart" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: try: responsedict = json.loads(responsestr) @@ -302,11 +302,11 @@ def startRemoteBluetoothScan(agentIP, agentPort): return -1, 'Error parsing response' else: return -2, 'Bad response from agent [' + str(statusCode) + ']' - + def stopRemoteBluetoothScan(agentIP, agentPort): url = "http://" + agentIP + ":" + str(agentPort) + "/bluetooth/scanstop" statusCode, responsestr = makeGetRequest(url, 6) - + if statusCode == 200: try: responsedict = json.loads(responsestr) @@ -317,11 +317,11 @@ def stopRemoteBluetoothScan(agentIP, agentPort): return -1, 'Error parsing response' else: return -2, 'Bad response from agent [' + str(statusCode) + ']' - + def getRemoteBluetoothRunningServices(agentIP, agentPort): url = "http://" + agentIP + ":" + str(agentPort) + "/bluetooth/running" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: try: responsedict = json.loads(responsestr) @@ -332,17 +332,17 @@ def getRemoteBluetoothRunningServices(agentIP, agentPort): spectrumScanRunning = responsedict['spectrumscanrunning'] discoveryScanRunning = responsedict['discoveryscanrunning'] beaconRunning = responsedict['beaconrunning'] - + return errcode, errmsg, hasBluetooth, hasUbertooth, spectrumScanRunning, discoveryScanRunning, beaconRunning except: return -1, 'Error parsing response', False, False, False, False, False else: return -2, 'Bad response from agent [' + str(statusCode) + ']', False, False, False, False, False - + def getRemoteBluetoothScanSpectrum(agentIP, agentPort): url = "http://" + agentIP + ":" + str(agentPort) + "/bluetooth/scanstatus" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: try: responsedict = json.loads(responsestr) @@ -357,42 +357,42 @@ def getRemoteBluetoothScanSpectrum(agentIP, agentPort): return -1, 'Error parsing response', None else: return -2, 'Bad response from agent [' + str(statusCode) + ']', None - + # ------------------ System interface and config requests ------------------------------ def requestRemoteInterfaces(agentIP, agentPort): url = "http://" + agentIP + ":" + str(agentPort) + "/wireless/interfaces" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: try: interfaces = json.loads(responsestr) - + retList = interfaces['interfaces'] return statusCode, retList except: return statusCode, None else: return statusCode, None - + def requestRemoteConfig(remoteIP, remotePort): url = "http://" + remoteIP + ":" + str(remotePort) + "/system/config" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: cfgjson = json.loads(responsestr) startupCfg = AgentConfigSettings() runningCfg = AgentConfigSettings() - + if 'startup' in cfgjson: startupCfg.fromJsondict(cfgjson['startup']) else: return -2, "No startup configuration present in the response", None, None - + if 'running' in cfgjson: runningCfg.fromJsondict(cfgjson['running']) else: return -2, "No running configuration present in the response", None, None - + return 0, "", startupCfg, runningCfg else: return -1, "Error connecting to remote agent", None, None @@ -404,25 +404,25 @@ class Divider(QFrame): def __init__(self, parent): super().__init__(parent) self.mainWin = parent - + def enterEvent(self, event): self.setCursor(Qt.SplitVCursor) - + def mouseMoveEvent(self, event): if event.buttons() == Qt.LeftButton: newPos = self.mapToParent(event.pos()) newPos.setX(1) self.move(newPos) - + self.mainWin.resizeEvent(event) - + # ------------------ Hover Line ------------------------------ class QHoverLineSeries(QLineSeries): def __init__(self, parentChart, color=Qt.white): super().__init__() self.textColor = color self.parentChart = parentChart - + self.hovered.connect(self.onHover) self.callout = Callout(self.name(), parentChart, self.textColor) @@ -434,16 +434,16 @@ def __init__(self, parentChart, color=Qt.white): self.labelTimer.timeout.connect(self.onLabelTimer) self.labelTimer.setSingleShot(True) self.timerRunning = False - + def onLabelTimer(self): self.callout.hide() self.timerRunning = False - + def onClicked(self, point): self.callout.show() self.timerRunning = True self.labelTimer.start(self.labelTimerTimeout) - + def onHover(self, point, state): if state: self.callout.setTextAndPos(self.name(), point) @@ -453,7 +453,7 @@ def onHover(self, point, state): else: if not self.timerRunning: self.callout.hide() - + # ------------------ Graphics Callout ------------------------------ class Callout(QGraphicsSimpleTextItem): def __init__(self, displayText, parent, textColor=Qt.white): @@ -461,7 +461,7 @@ def __init__(self, displayText, parent, textColor=Qt.white): super().__init__(parent=parent) self.textColor = textColor self.chartParent = parent - + # Pen draws the outline, brush does the character fill # The doc says the pen is slow so don't use it unless you have to # penBorder = QPen(self.textColor) @@ -470,7 +470,7 @@ def __init__(self, displayText, parent, textColor=Qt.white): newBrush = QBrush(self.textColor) self.setBrush(newBrush) - + # Has setText() and text() methods def setTextAndPos(self, displayText, point): self.setText(displayText) @@ -489,14 +489,19 @@ def __init__(self, interface, mainWin, channelList=None): self.mainWin = mainWin self.scanDelay = 0.5 # seconds self.channelList = channelList - + def run(self): + + ## Grab the wireless engine + wEng = WirelessEngine() + + ## Threads are running self.threadRunning = True - + while (not self.signalStop): # Scan all / normal mode if (self.channelList is None) or (len(self.channelList) == 0): - retCode, errString, wirelessNetworks = WirelessEngine.scanForNetworks(self.interface) + retCode, errString, wirelessNetworks = wEng.scanForNetworks(self.interface) if (retCode == 0): # self.statusBar().showMessage('Scan complete. Found ' + str(len(wirelessNetworks)) + ' networks') if wirelessNetworks and (len(wirelessNetworks) > 0) and (not self.signalStop): @@ -504,7 +509,7 @@ def run(self): else: if (retCode != WirelessNetwork.ERR_DEVICEBUSY): self.mainWin.errmsg.emit(retCode, errString) - + if (retCode == WirelessNetwork.ERR_DEVICEBUSY): # Shorter sleep for faster results # sleep(0.2) @@ -523,7 +528,7 @@ def run(self): else: if (retCode != WirelessNetwork.ERR_DEVICEBUSY): self.mainWin.errmsg.emit(retCode, errString) - + if (retCode == WirelessNetwork.ERR_DEVICEBUSY): # Shorter sleep for faster results # sleep(0.2) @@ -531,7 +536,7 @@ def run(self): sleep(self.scanDelay) else: sleep(self.scanDelay) - + self.threadRunning = False # ------------------ Remote single-shot scan thread ------------------------------ @@ -543,18 +548,18 @@ def __init__(self, interface, mainWin, remoteAgentIP, remoteAgentPort, channelLi self.huntChannelList = channelList self.remoteAgentIP = remoteAgentIP self.remoteAgentPort = remoteAgentPort - + def run(self): self.threadRunning = True - + # Run one shot and emit results if (not self.huntChannelList) or (len(self.huntChannelList) == 0): retCode, errString, wirelessNetworks = requestRemoteNetworks(self.remoteAgentIP, self.remoteAgentPort, self.interface) else: retCode, errString, wirelessNetworks = requestRemoteNetworks(self.remoteAgentIP, self.remoteAgentPort, self.interface, self.huntChannelList) - + mainWin.singleshotscanresults.emit(wirelessNetworks, retCode, errString) - + self.threadRunning = False # ------------------ Remote agent network scan thread ------------------------------ @@ -567,10 +572,10 @@ def __init__(self, interface, mainWin, channelList=None): self.remoteAgentIP = "127.0.0.1" self.remoteAgentPort = 8020 self.channelList = channelList - + def run(self): self.threadRunning = True - + while (not self.signalStop): retCode, errString, wirelessNetworks = requestRemoteNetworks(self.remoteAgentIP, self.remoteAgentPort, self.interface, self.channelList) if (retCode == 0): @@ -580,13 +585,13 @@ def run(self): else: if (retCode != WirelessNetwork.ERR_DEVICEBUSY): self.mainWin.errmsg.emit(retCode, errString) - + if (retCode == WirelessNetwork.ERR_DEVICEBUSY): # Shorter sleep for faster results sleep(0.2) else: sleep(self.scanDelay) - + self.threadRunning = False # ------------------ GPSEngine override onGPSResult to notify the main window when the GPS goes synchnronized ------------------------------ @@ -610,7 +615,7 @@ def onGPSResult(self, gpsResult): # ------------------ Main Application Window ------------------------------ class mainWindow(QMainWindow): - + # Notify signals resized = QtCore.pyqtSignal() scanresults = QtCore.pyqtSignal(dict) @@ -623,7 +628,7 @@ class mainWindow(QMainWindow): agentListenerClosed = QtCore.pyqtSignal() bluetoothDiscoveryClosed = QtCore.pyqtSignal() rescanInterfaces = QtCore.pyqtSignal() - + # For help with qt5 GUI's this is a great tutorial: # http://zetcode.com/gui/pyqt5/ @@ -632,18 +637,18 @@ def checkForBluetooth(self): self.hasUbertooth = False self.hasRemoteBluetooth = False self.hasRemoteUbertooth = False - + numBtAdapters = len(SparrowBluetooth.getBluetoothInterfaces()) if numBtAdapters > 0: self.hasBluetooth = True - + if SparrowBluetooth.getNumUbertoothDevices() > 0: #SparrowBluetooth.ubertoothStopSpecan() errcode, errmsg = SparrowBluetooth.hasUbertoothTools() # errcode, errmsg = SparrowBluetooth.ubertoothOnline() if errcode == 0: self.hasUbertooth = True - + if self.hasBluetooth or self.hasUbertooth: self.bluetooth = SparrowBluetooth() else: @@ -653,7 +658,7 @@ def __init__(self): super().__init__() self.rescanInterfaces.connect(self.onRescanInterfaces) - + self.hackrf = SparrowHackrf() self.hackrfShowSpectrum24 = False self.hackrfLastSpectrumState24 = False @@ -667,14 +672,14 @@ def __init__(self): self.spectrum24Line = None self.spectrum5Line = None - + self.checkForBluetooth() - + self.bluetoothWin = None self.bluetoothDiscoveryClosed.connect(self.onBtDiscoveryClosed) - + self.btSpectrumGain = 1.0 - + self.btShowSpectrum = False self.btLastSpectrumState = False self.btLastBeaconState = False @@ -684,33 +689,33 @@ def __init__(self): self.btSpectrumTimeoutRemote = 200 self.btSpectrumTimer.timeout.connect(self.onSpectrumTimer) self.btSpectrumTimer.setSingleShot(True) - + self.ouiLookupEngine = getOUIDB() - + self.agentListenerWindow = None self.agentListenerClosed.connect(self.onAgentListenerClosed) - + self.telemetryWindows = {} self.advancedScan = None - + self.scanMode="Normal" self.huntChannelList = [] - + # GPS engine self.gpsEngine = GPSEngineNotifyWin(self) self.gpsSynchronized = False self.gpsSynchronizedsignal.connect(self.onGPSSyncChanged) - + self.gpsCoordWindow = None - + # Advanced Scan self.advScanClosed.connect(self.onAdvancedScanClosed) self.advScanUpdateSSIDs.connect(self.onAdvScanUpdateSSIDs) - + # Local network scan self.scanRunning = False self.scanIsBlocking = False - + self.nextColor = 0 self.lastSeries = None @@ -721,7 +726,7 @@ def __init__(self): self.singleshotscanresults.connect(self.onSingleShotScanResults) self.scanresultsfromadvanced.connect(self.scanResultsFromAdvanced) self.errmsg.connect(self.onErrMsg) - + # Remote Scans self.remoteAgentIP = '' self.remoteAgentPort = 8020 @@ -734,18 +739,18 @@ def __init__(self): self.lastRemoteState = False self.remoteAgentUp = False self.remoteHasHackrf = False - + self.missedAgentCycles = 0 self.allowedMissedAgentCycles = 1 - + desktopSize = QApplication.desktop().screenGeometry() #self.mainWidth=1024 #self.mainHeight=768 self.mainWidth = int(desktopSize.width() * 3 / 4) self.mainHeight = int(desktopSize.height() * 3 / 4) - + self.initUI() - + if os.geteuid() != 0: self.runningAsRoot = False self.statusBar().showMessage('You need to have root privileges to run local scans. Please exit and rerun it as root') @@ -755,10 +760,10 @@ def __init__(self): #self.close() else: self.runningAsRoot = True - + def ouiLookup(self, macAddr): clientVendor = "" - + if hasOUILookup: try: if self.ouiLookupEngine: @@ -767,18 +772,18 @@ def ouiLookup(self, macAddr): clientVendor = '' except: clientVendor = "" - + return clientVendor - + def initUI(self): # self.setGeometry(10, 10, 800, 600) self.resize(self.mainWidth, self.mainHeight) self.center() self.setWindowTitle('Sparrow-WiFi Analyzer') - self.setWindowIcon(QIcon('wifi_icon.png')) + self.setWindowIcon(QIcon('wifi_icon.png')) self.createMenu() - + self.createControls() #self.splitter1 = QSplitter(Qt.Vertical) @@ -787,19 +792,19 @@ def initUI(self): #self.splitter1.addWidget(self.splitter2) #self.splitter2.addWidget(self.Plot24) #self.splitter2.addWidget(self.Plot5) - + self.setBlackoutColors() - + self.setMinimumWidth(800) self.setMinimumHeight(400) - + self.show() - + # Set up GPS check timer self.gpsTimer = QTimer() self.gpsTimer.timeout.connect(self.onGPSTimer) self.gpsTimer.setSingleShot(True) - + self.gpsTimerTimeout = 5000 self.gpsTimer.start(self.gpsTimerTimeout) # Check every 5 seconds @@ -808,32 +813,32 @@ def resizeEvent(self, event): # self.statusBar().showMessage('Window resized.') # return super(mainWin, self).resizeEvent(event) size = self.geometry() - + if self.initializingGUI: self.horizontalDivider.setGeometry(1, int(size.height()/2+2), size.width()-2, 4) self.networkTable.setGeometry(10, 103, size.width()-20, int(size.height()/2)-105) self.Plot24.setGeometry(10, int(size.height()/2)+10, int(size.width()/2)-10, int(size.height()/2)-40) self.Plot5.setGeometry(int(size.width()/2)+5, int(size.height()/2)+10, int(size.width()/2)-15, int(size.height()/2)-40) - + self.initializingGUI = False - + # self.splitter1.setGeometry(10, 103, size.width()-20, size.height()-20) - + if size.width() < 800: self.setGeometry(size.x(), size.y(), 800, size.height()) size = self.geometry() - + self.lblGPS.move(size.width()-90, 30) self.btnGPSStatus.move(size.width()-50, 34) - + dividerPos = self.horizontalDivider.pos() if dividerPos.y() < 200: dividerPos.setY(200) elif dividerPos.y() > size.height()-180: dividerPos.setY(size.height()-180) - + self.horizontalDivider.setGeometry(dividerPos.x(), dividerPos.y(), size.width()-2, 5) self.networkTable.setGeometry(10, 103, size.width()-20, dividerPos.y()-105) self.Plot24.setGeometry(10, dividerPos.y()+6, int(size.width()/2)-10, size.height()-dividerPos.y()-30) @@ -852,20 +857,20 @@ def createControls(self): # Interface droplist self.lblInterface = QLabel("Local Interface", self) self.lblInterface.setGeometry(5, 30, 120, 30) - + self.combo = QComboBox(self) self.combo.move(130, 30) interfaces=WirelessEngine.getInterfaces() - + if (len(interfaces) > 0): for curInterface in interfaces: self.combo.addItem(curInterface) else: self.statusBar().showMessage('No wireless interfaces found.') - self.combo.activated[str].connect(self.onInterface) - + self.combo.activated[str].connect(self.onInterface) + # Scan Button self.btnScan = QPushButton("&Scan", self) self.btnScan.setCheckable(True) @@ -873,18 +878,18 @@ def createControls(self): self.btnScan.setStyleSheet("background-color: rgba(0,128,192,255); border: none;") self.btnScan.move(260, 30) self.btnScan.clicked[bool].connect(self.onScanClicked) - + # Scan Mode self.lblScanMode = QLabel("Scan Mode:", self) self.lblScanMode.setGeometry(380, 30, 120, 30) - + self.scanModeCombo = QComboBox(self) self.scanModeCombo.setStatusTip('All-channel normal scans can take 5-10 seconds per sweep. Use Hunt mode for faster response time on a selected channel.') self.scanModeCombo.move(455, 30) self.scanModeCombo.addItem("Normal") self.scanModeCombo.addItem("Hunt") self.scanModeCombo.currentIndexChanged.connect(self.onScanModeChanged) - + self.lblScanMode = QLabel("Hunt Channel or Frequencies(s):", self) self.lblScanMode.setGeometry(565, 30, 200, 30) self.huntChannels = QLineEdit(self) @@ -895,13 +900,13 @@ def createControls(self): # Hide them to start self.huntChannels.setVisible(False) self.lblScanMode.setVisible(False) - + # Age out checkbox self.cbAgeOut = QCheckBox(self) self.cbAgeOut.move(10, 70) self.lblAgeOut = QLabel("Remove networks not seen in the past 3 minutes", self) self.lblAgeOut.setGeometry(30, 70, 300, 30) - + # Network Table self.networkTable = QTableWidget(self) self.networkTable.setColumnCount(14) @@ -915,51 +920,51 @@ def createControls(self): self.networkTable.horizontalHeader().sectionClicked.connect(self.onTableHeadingClicked) self.networkTable.cellClicked.connect(self.onTableClicked) - + self.networkTable.setSelectionMode( QAbstractItemView.SingleSelection ) self.networkTable.itemSelectionChanged.connect(self.onNetworkTableSelectionChanged) self.networkTableSortOrder = Qt.DescendingOrder self.networkTableSortIndex = -1 - + # Set flag for first time self.initializingGUI = True - + # Set divider self.horizontalDivider = Divider(self) self.horizontalDivider.setFrameShape(QFrame.HLine) self.horizontalDivider.setFrameShadow(QFrame.Sunken) - + # Network Table right-click menu self.ntRightClickMenu = QMenu(self) - newAct = QAction('Telemetry', self) + newAct = QAction('Telemetry', self) newAct.setStatusTip('View network telemetry data') newAct.triggered.connect(self.onShowTelemetry) self.ntRightClickMenu.addAction(newAct) - + self.ntRightClickMenu.addSeparator() - - newAct = QAction('Copy', self) + + newAct = QAction('Copy', self) newAct.setStatusTip('Copy data to clipboard') newAct.triggered.connect(self.onCopyNet) self.ntRightClickMenu.addAction(newAct) - + self.ntRightClickMenu.addSeparator() - - newAct = QAction('Delete', self) + + newAct = QAction('Delete', self) newAct.setStatusTip('Remove network from the list') newAct.triggered.connect(self.onDeleteNet) self.ntRightClickMenu.addAction(newAct) - + # Attach it to the table self.networkTable.setContextMenuPolicy(Qt.CustomContextMenu) self.networkTable.customContextMenuRequested.connect(self.showNTContextMenu) - + self.createCharts() - + # GPS Indicator self.lblGPS = QLabel("GPS:", self) self.lblGPS.move(850, 30) - + rect = QRect(0,0,20,20) region = QRegion(rect,QRegion.Ellipse) self.btnGPSStatus = QPushButton("", self) @@ -968,7 +973,7 @@ def createControls(self): self.btnGPSStatus.setFixedHeight(30) self.btnGPSStatus.setMask(region) self.btnGPSStatus.clicked.connect(self.onGPSStatusIndicatorClicked) - + if GPSEngine.GPSDRunning(): if self.gpsEngine.gpsValid(): self.btnGPSStatus.setStyleSheet("background-color: green; border: 1px;") @@ -977,7 +982,7 @@ def createControls(self): else: self.btnGPSStatus.setStyleSheet("background-color: red; border: 1px;") - + def setBlackoutColors(self): global colors global orange @@ -989,7 +994,7 @@ def setBlackoutColors(self): mainTitleBrush = QBrush(Qt.red) self.chart24.setTitleBrush(mainTitleBrush) self.chart5.setTitleBrush(mainTitleBrush) - + self.chart24.setBackgroundBrush(QBrush(Qt.black)) self.chart24.axisX().setLabelsColor(Qt.white) self.chart24.axisY().setLabelsColor(Qt.white) @@ -1002,21 +1007,21 @@ def setBlackoutColors(self): self.chart5.axisY().setLabelsColor(Qt.white) self.chart5.axisX().setTitleBrush(titleBrush) self.chart5.axisY().setTitleBrush(titleBrush) - + #$ self.networkTable.setStyleSheet("QTableCornerButton::section{background-color: white;}") # self.networkTable.cornerWidget().setStylesheet("background-color: black") self.networkTable.setStyleSheet("QTableView {background-color: black;gridline-color: white;color: white} QTableCornerButton::section{background-color: white;}") headerStyle = "QHeaderView::section{background-color: white;border: 1px solid black;color: black;} QHeaderView::down-arrow,QHeaderView::up-arrow {background: none;}" self.networkTable.horizontalHeader().setStyleSheet(headerStyle) self.networkTable.verticalHeader().setStyleSheet(headerStyle) - + def createMenu(self): # Create main menu bar menubar = self.menuBar() fileMenu = menubar.addMenu('&File') - + # Create File Menu Items - newAct = QAction('&New', self) + newAct = QAction('&New', self) newAct.setShortcut('Ctrl+N') newAct.setStatusTip('Clear List') newAct.triggered.connect(self.onClearData) @@ -1025,37 +1030,37 @@ def createMenu(self): # import importMenu = fileMenu.addMenu('&Import') - newAct = QAction('&Saved CSV', self) + newAct = QAction('&Saved CSV', self) newAct.setStatusTip('Import from saved CSV') newAct.triggered.connect(self.onImportCSV) importMenu.addAction(newAct) - - newAct = QAction('&Saved JSON', self) + + newAct = QAction('&Saved JSON', self) newAct.setStatusTip('Import from saved JSON') newAct.triggered.connect(self.onImportJSON) importMenu.addAction(newAct) importMenu.addSeparator() - - newAct = QAction('&Import iw scan (iw dev scan > )', self) + + newAct = QAction('&Import iw scan (iw dev scan > )', self) newAct.setStatusTip("Run 'iw dev scan > ' and import the data directly with this option") newAct.triggered.connect(self.onImportIWData) importMenu.addAction(newAct) - + # export exportMenu = fileMenu.addMenu('&Export') - newAct = QAction('&To CSV', self) + newAct = QAction('&To CSV', self) newAct.setStatusTip('Export to CSV') newAct.triggered.connect(self.onExportCSV) exportMenu.addAction(newAct) - - newAct = QAction('&To JSON', self) + + newAct = QAction('&To JSON', self) newAct.setStatusTip('Export to JSON') newAct.triggered.connect(self.onExportJSON) exportMenu.addAction(newAct) - - # exitAct = QAction(QIcon('exit.png'), '&Exit', self) - exitAct = QAction('&Exit', self) + + # exitAct = QAction(QIcon('exit.png'), '&Exit', self) + exitAct = QAction('&Exit', self) exitAct.setShortcut('Ctrl+X') exitAct.setStatusTip('Exit application') exitAct.triggered.connect(self.close) @@ -1063,139 +1068,139 @@ def createMenu(self): # Agent Menu Items helpMenu = menubar.addMenu('&Agent') - self.menuRemoteAgent = QAction('Connect to Remote Agent', self) + self.menuRemoteAgent = QAction('Connect to Remote Agent', self) self.menuRemoteAgent.setStatusTip('Use a Remote Agent') self.menuRemoteAgent.setCheckable(True) self.menuRemoteAgent.changed.connect(self.onRemoteAgent) helpMenu.addAction(self.menuRemoteAgent) - self.menuRemoteFiles = QAction('Remote Recordings', self) + self.menuRemoteFiles = QAction('Remote Recordings', self) self.menuRemoteFiles.setStatusTip('Get/Manage remote recordings') self.menuRemoteFiles.triggered.connect(self.onRemoteFiles) helpMenu.addAction(self.menuRemoteFiles) - + helpMenu.addSeparator() - - self.menuRemoteAgentListener = QAction('Agent Discovery', self) + + self.menuRemoteAgentListener = QAction('Agent Discovery', self) self.menuRemoteAgentListener.setStatusTip('Listen for remote agents') self.menuRemoteAgentListener.triggered.connect(self.onRemoteAgentListener) helpMenu.addAction(self.menuRemoteAgentListener) - + helpMenu.addSeparator() - - self.menuRemoteAgentConfig = QAction('Agent Configuration', self) + + self.menuRemoteAgentConfig = QAction('Agent Configuration', self) self.menuRemoteAgentConfig.setStatusTip('Configure a remote agent') self.menuRemoteAgentConfig.triggered.connect(self.onRemoteAgentConfig) helpMenu.addAction(self.menuRemoteAgentConfig) - + # GPS Menu Items gpsMenu = menubar.addMenu('&Geo') - newAct = QAction('Create Access Point Map', self) + newAct = QAction('Create Access Point Map', self) newAct.setStatusTip('Plot access point coordinates from the table on a Google map') newAct.triggered.connect(self.onGoogleMap) gpsMenu.addAction(newAct) - - newAct = QAction('Create SSID Map from Telemetry', self) + + newAct = QAction('Create SSID Map from Telemetry', self) newAct.setStatusTip('Plot coordinates for a single SSID saved from telemetry window on a Google map') newAct.triggered.connect(self.onGoogleMapTelemetry) gpsMenu.addAction(newAct) - + gpsMenu.addSeparator() - + # This has been hidden for now. Really didn't do anything new since this is covered with the timer now - newAct = QAction('GPS Status', self) + newAct = QAction('GPS Status', self) newAct.setStatusTip('Show GPS Status') newAct.triggered.connect(self.onGPSStatus) - newAct.setVisible(False) + newAct.setVisible(False) gpsMenu.addAction(newAct) - - newAct = QAction('GPS Coordinate Monitoring', self) + + newAct = QAction('GPS Coordinate Monitoring', self) newAct.setStatusTip('Show GPS Coordinates') newAct.triggered.connect(self.onGPSCoordinates) gpsMenu.addAction(newAct) - + if self.hasXGPS(): gpsMenu.addSeparator() - newAct = QAction('Launch XGPS - Local', self) + newAct = QAction('Launch XGPS - Local', self) newAct.setStatusTip('Show GPS GUI against local gpsd') newAct.triggered.connect(self.onXGPSLocal) gpsMenu.addAction(newAct) - - newAct = QAction('Launch XGPS - Remote', self) + + newAct = QAction('Launch XGPS - Remote', self) newAct.setStatusTip('Show GPS GUI against remote gpsd') newAct.triggered.connect(self.onXGPSRemote) gpsMenu.addAction(newAct) - + # View Menu Items ViewMenu = menubar.addMenu('&Telemetry') - newAct = QAction('Telemetry For Selected Network', self) + newAct = QAction('Telemetry For Selected Network', self) newAct.setStatusTip('Show screen for selected network with history, signal strength, and historical data') newAct.triggered.connect(self.onShowTelemetry) ViewMenu.addAction(newAct) - + # Bluetooth ViewMenu = menubar.addMenu('&Bluetooth') - self.menuBtBeacon = QAction('iBeacon Mode', self) + self.menuBtBeacon = QAction('iBeacon Mode', self) self.menuBtBeacon.setStatusTip("Become an iBeacon") self.menuBtBeacon.setCheckable(True) self.menuBtBeacon.triggered.connect(self.onBtBeacon) ViewMenu.addAction(self.menuBtBeacon) ViewMenu.addSeparator() - - self.menuBtBluetooth = QAction('Bluetooth Discovery', self) + + self.menuBtBluetooth = QAction('Bluetooth Discovery', self) self.menuBtBluetooth.setStatusTip("Find bluetooth devices. Basic discovery or Ubertooth depending on hardware.") self.menuBtBluetooth.triggered.connect(self.onBtBluetooth) ViewMenu.addAction(self.menuBtBluetooth) # Spectrum SpectrumMenu = menubar.addMenu('&Spectrum') - self.menuBtSpectrumGain = QAction('Spectrum Analyzer Gain', self) + self.menuBtSpectrumGain = QAction('Spectrum Analyzer Gain', self) self.menuBtSpectrumGain.setStatusTip("Manually override gain to better match spectrum with wifi adapter") self.menuBtSpectrumGain.triggered.connect(self.onBtSpectrumOverrideGain) SpectrumMenu.addAction(self.menuBtSpectrumGain) - + SpectrumMenu.addSeparator() - - self.menuBtSpectrum = QAction('2.4 GHz Spectrum Analyzer via Ubertooth', self) + + self.menuBtSpectrum = QAction('2.4 GHz Spectrum Analyzer via Ubertooth', self) self.menuBtSpectrum.setStatusTip("Use Ubertooth for 2.4 GHz spectrum analyzer. NOTE: Wireless cards may have different gain, so results may vary.") self.menuBtSpectrum.setCheckable(True) self.menuBtSpectrum.triggered.connect(self.onBtSpectrumAnalyzer) SpectrumMenu.addAction(self.menuBtSpectrum) - - self.menuHackrfSpectrum24 = QAction('2.4 GHz Spectrum Analyzer via HackRF', self) + + self.menuHackrfSpectrum24 = QAction('2.4 GHz Spectrum Analyzer via HackRF', self) self.menuHackrfSpectrum24.setStatusTip("Use HackRF for 2.4 GHz spectrum analyzer. NOTE: Wireless cards may have different gain, so results may vary.") self.menuHackrfSpectrum24.setCheckable(True) self.menuHackrfSpectrum24.triggered.connect(self.onHackrfSpectrumAnalyzer24) SpectrumMenu.addAction(self.menuHackrfSpectrum24) - + SpectrumMenu.addSeparator() - - self.menuHackrfSpectrum5 = QAction('5 GHz Spectrum Analyzer via HackRF', self) + + self.menuHackrfSpectrum5 = QAction('5 GHz Spectrum Analyzer via HackRF', self) self.menuHackrfSpectrum5.setStatusTip("Use HackRF for 5 GHz spectrum analyzer. NOTE: Wireless cards may have different gain, so results may vary.") self.menuHackrfSpectrum5.setCheckable(True) self.menuHackrfSpectrum5.triggered.connect(self.onHackrfSpectrumAnalyzer5) SpectrumMenu.addAction(self.menuHackrfSpectrum5) - + if not self.hackrf.hasHackrf: self.menuHackrfSpectrum24.setEnabled(False) self.menuHackrfSpectrum5.setEnabled(False) - + self.setBluetoothMenu() - + # Falcon if hasFalcon: # Falcon Menu Items ViewMenu = menubar.addMenu('&Falcon') if hasFalcon: - newAct = QAction('Advanced Scan', self) + newAct = QAction('Advanced Scan', self) newAct.setStatusTip("Run a scan to find hidden SSID's and client stations") newAct.triggered.connect(self.onAdvancedScan) ViewMenu.addAction(newAct) # Help Menu Items helpMenu = menubar.addMenu('&Help') - newAct = QAction('About', self) + newAct = QAction('About', self) newAct.setStatusTip('About') newAct.triggered.connect(self.onAbout) helpMenu.addAction(newAct) @@ -1205,12 +1210,12 @@ def hasXGPS(self): return True else: return False - + def setBluetoothMenu(self): self.menuBtBeacon.setEnabled(True) self.menuBtSpectrum.setEnabled(True) self.menuBtSpectrumGain.setEnabled(True) - + if not self.remoteAgentUp: # Local if not self.hasUbertooth: @@ -1226,7 +1231,7 @@ def setBluetoothMenu(self): if not self.hasRemoteBluetooth: self.menuBtBluetooth.setEnabled(False) self.menuBtBeacon.setEnabled(False) - + def createCharts(self): self.chart24 = QChart() self.chart24.setAcceptHoverEvents(True) @@ -1237,7 +1242,7 @@ def createCharts(self): self.chart24.setTitleBrush(titleBrush) self.chart24.setTitle('2.4 GHz') self.chart24.legend().hide() - + # Axis examples: https://doc.qt.io/qt-5/qtcharts-multiaxis-example.html self.chart24axisx = QValueAxis() self.chart24axisx.setMin(0) @@ -1246,7 +1251,7 @@ def createCharts(self): self.chart24axisx.setLabelFormat("%d") self.chart24axisx.setTitleText("Channel") self.chart24.addAxis(self.chart24axisx, Qt.AlignBottom) - + self.chart24yAxis = QValueAxis() self.chart24yAxis.setMin(-100) self.chart24yAxis.setMax(-10) @@ -1254,7 +1259,7 @@ def createCharts(self): self.chart24yAxis.setLabelFormat("%d") self.chart24yAxis.setTitleText("dBm") self.chart24.addAxis(self.chart24yAxis, Qt.AlignLeft) - + chartBorder = Qt.darkGray self.Plot24 = QChartView(self.chart24, self) self.Plot24.setBackgroundBrush(chartBorder) @@ -1267,7 +1272,7 @@ def createCharts(self): self.chart5.setTitle('5 GHz') self.chart5.createDefaultAxes() self.chart5.legend().hide() - + self.chart5axisx = QValueAxis() self.chart5axisx .setMin(30) self.chart5axisx .setMax(170) @@ -1275,7 +1280,7 @@ def createCharts(self): self.chart5axisx .setLabelFormat("%d") self.chart5axisx .setTitleText("Channel") self.chart5.addAxis(self.chart5axisx , Qt.AlignBottom) - + newAxis = QValueAxis() newAxis.setMin(-100) newAxis.setMax(-10) @@ -1283,14 +1288,14 @@ def createCharts(self): newAxis.setLabelFormat("%d") newAxis.setTitleText("dBm") self.chart5.addAxis(newAxis, Qt.AlignLeft) - + self.Plot5 = QChartView(self.chart5, self) self.Plot5.setBackgroundBrush(chartBorder) self.Plot5.setRenderHint(QPainter.Antialiasing) self.Plot5.setViewportUpdateMode(QGraphicsView.SmartViewportUpdate) - + def onBtSpectrumOverrideGain(self): - text, okPressed = QInputDialog.getText(self, "Spectrum Analyzer Gain","Enter a gain to apply to the spectrum:", + text, okPressed = QInputDialog.getText(self, "Spectrum Analyzer Gain","Enter a gain to apply to the spectrum:", QLineEdit.Normal, str(self.btSpectrumGain)) if okPressed and text != '': try: @@ -1305,11 +1310,11 @@ def onBtSpectrumOverrideGain(self): def createSpectrumLine(self): if not self.spectrum24Line: # add it - + # 2.4 GHz self.spectrum24Line = self.createNewSeries(Qt.white, self.chart24) self.spectrum24Line.setName('Spectrum') - + self.chart24.addSeries(self.spectrum24Line) self.spectrum24Line.attachAxis(self.chart24.axisX()) self.spectrum24Line.attachAxis(self.chart24.axisY()) @@ -1318,12 +1323,12 @@ def createSpectrumLine(self): # 5 GHz self.spectrum5Line = self.createNewSeries(Qt.white, self.chart5) self.spectrum5Line.setName('Spectrum') - + self.chart5.addSeries(self.spectrum5Line) self.spectrum5Line.attachAxis(self.chart5.axisX()) self.spectrum5Line.attachAxis(self.chart5.axisY()) # self.spectrum5Line.setUseOpenGL(True) # This causes the entire window to go blank - + def onBtBeacon(self): if (self.menuBtBeacon.isChecked() == self.btLastBeaconState): # There's an extra bounce in this for some reason. @@ -1341,7 +1346,7 @@ def onBtBeacon(self): self.btBeacon = False self.btLastBeaconState = False return - + if self.btShowSpectrum: reply = QMessageBox.question(self, 'Question',"Bluetooth hardware is being used for spectrum. Would you like to stop it?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) @@ -1352,10 +1357,10 @@ def onBtBeacon(self): self.btBeacon = False self.btLastBeaconState = False return - + self.btBeacon = self.menuBtBeacon.isChecked() self.btLastBeaconState = self.btBeacon - + if self.btBeacon: self.bluetooth.startBeacon() else: @@ -1363,7 +1368,7 @@ def onBtBeacon(self): else: # Remote errcode, errmsg, self.hasRemoteBluetooth, self.hasRemoteUbertooth, scanRunning, discoveryRunning, beaconRunning = getRemoteBluetoothRunningServices(self.remoteAgentIP, self.remoteAgentPort) - + if errcode == 0: if discoveryRunning: reply = QMessageBox.question(self, 'Question',"Bluetooth hardware is being used for discovery. Would you like to stop it?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) @@ -1394,7 +1399,7 @@ def onBtBeacon(self): self.btBeacon = False self.btLastBeaconState = False return - + self.btBeacon = self.menuBtBeacon.isChecked() self.btLastBeaconState = self.btBeacon @@ -1414,7 +1419,7 @@ def onBtBeacon(self): self.menuBtBeacon.setChecked(False) self.btBeacon = False self.btLastBeaconState = False - + def onBtBluetooth(self): if not self.bluetoothWin: # Check if we have other bluetooth functions running, if so give the user a chance to decide what to do @@ -1429,7 +1434,7 @@ def onBtBluetooth(self): self.stopSpectrum24Line() else: return - + if self.btBeacon: reply = QMessageBox.question(self, 'Question',"Bluetooth hardware is being used for beaconing. Would you like to stop it?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) @@ -1449,7 +1454,7 @@ def onBtBluetooth(self): return # We're good so let's create the window - + # Create a window, we don't have one yet self.bluetoothWin = BluetoothDialog(self, self.bluetooth, self.remoteAgentUp, self.remoteAgentIP, self.remoteAgentPort, None) # Need to set parent to None to allow it to not always be on top @@ -1459,7 +1464,7 @@ def onBtBluetooth(self): def onBtDiscoveryClosed(self): if self.bluetoothWin: self.bluetoothWin = None - + def onHackrfSpectrumAnalyzer24(self): if (self.menuHackrfSpectrum24.isChecked() == self.hackrfLastSpectrumState24): # There's an extra bounce in this for some reason. @@ -1467,7 +1472,7 @@ def onHackrfSpectrumAnalyzer24(self): self.hackrfShowSpectrum24 = self.menuHackrfSpectrum24.isChecked() self.hackrfLastSpectrumState24 = self.hackrfShowSpectrum24 - + if self.hackrfShowSpectrum24: # Enable it # Add it on the plot @@ -1476,27 +1481,27 @@ def onHackrfSpectrumAnalyzer24(self): if not self.remoteAgentUp: if self.hackrf.hasHackrf: self.hackrf.startScanning24() - self.menuHackrfSpectrum5.setEnabled(False) + self.menuHackrfSpectrum5.setEnabled(False) else: if self.remoteHasHackrf: errcode, errmsg = startRemoteSpectrumScan(self.remoteAgentIP, self.remoteAgentPort, False) - self.menuHackrfSpectrum5.setEnabled(False) - + self.menuHackrfSpectrum5.setEnabled(False) + if errcode != 0: self.statusBar().showMessage(errmsg) - + # Disable Ubertooth 2.4 GHz scan since we're using the HackRF self.menuBtSpectrum.setEnabled(False) - + if not self.remoteAgentUp: self.hackrfSpectrumTimer.start(self.hackrfSpectrumTimeoutLocal) else: self.hackrfSpectrumTimer.start(self.hackrfSpectrumTimeoutRemote) else: # Disable it - self.menuHackrfSpectrum5.setEnabled(True) + self.menuHackrfSpectrum5.setEnabled(True) self.hackrfSpectrumTimer.stop() - + if (not self.remoteAgentUp): # Local if self.hackrf.hasHackrf: @@ -1508,17 +1513,17 @@ def onHackrfSpectrumAnalyzer24(self): else: # Remote errcode, errmsg = stopRemoteSpectrumScan(self.remoteAgentIP, self.remoteAgentPort) - + if errcode != 0: self.statusBar().showMessage(errmsg) # If we have bluetooth let's re-enable it. if self.hasRemoteBluetooth: self.menuBtSpectrum.setEnabled(True) - + # remove it from the plot self.stopSpectrum24Line() - + def onHackrfSpectrumAnalyzer5(self): if (self.menuHackrfSpectrum5.isChecked() == self.hackrfLastSpectrumState5): # There's an extra bounce in this for some reason. @@ -1526,7 +1531,7 @@ def onHackrfSpectrumAnalyzer5(self): self.hackrfShowSpectrum5 = self.menuHackrfSpectrum5.isChecked() self.hackrfLastSpectrumState5 = self.hackrfShowSpectrum5 - + if self.hackrfShowSpectrum5: # Add it on the plot self.createSpectrumLine() @@ -1536,15 +1541,15 @@ def onHackrfSpectrumAnalyzer5(self): if self.hackrf.hasHackrf: self.hackrf.startScanning5() else: - # Remote + # Remote if self.remoteHasHackrf: errcode, errmsg = startRemoteSpectrumScan(self.remoteAgentIP, self.remoteAgentPort, True) - + if errcode != 0: self.statusBar().showMessage(errmsg) - - self.menuHackrfSpectrum24.setEnabled(False) - + + self.menuHackrfSpectrum24.setEnabled(False) + if not self.remoteAgentUp: self.hackrfSpectrumTimer.start(self.hackrfSpectrumTimeoutLocal) else: @@ -1552,22 +1557,22 @@ def onHackrfSpectrumAnalyzer5(self): else: # Disable it self.hackrfSpectrumTimer.stop() - + if not self.remoteAgentUp: if self.hackrf.hasHackrf: self.hackrf.stopScanning() else: errcode, errmsg = stopRemoteSpectrumScan(self.remoteAgentIP, self.remoteAgentPort) - + if errcode != 0: self.statusBar().showMessage(errmsg) - + # remove it from the plot self.stopSpectrum5Line() - + if not self.btShowSpectrum: - self.menuHackrfSpectrum24.setEnabled(True) - + self.menuHackrfSpectrum24.setEnabled(True) + def onBtSpectrumAnalyzer(self): if (self.menuBtSpectrum.isChecked() == self.btLastSpectrumState): # There's an extra bounce in this for some reason. @@ -1585,7 +1590,7 @@ def onBtSpectrumAnalyzer(self): self.btShowSpectrum = False self.btLastSpectrumState = False return - + if self.btBeacon: reply = QMessageBox.question(self, 'Question',"Bluetooth hardware is being used for beaconing. Would you like to stop it?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) @@ -1596,13 +1601,13 @@ def onBtSpectrumAnalyzer(self): self.btShowSpectrum = False self.btLastSpectrumState = False return - + errcode = 0 errmsg = "" else: # Remote errcode, errmsg, self.hasRemoteBluetooth, self.hasRemoteUbertooth, scanRunning, discoveryRunning, beaconRunning = getRemoteBluetoothRunningServices(self.remoteAgentIP, self.remoteAgentPort) - + if errcode == 0: if discoveryRunning: reply = QMessageBox.question(self, 'Question',"Bluetooth hardware is being used for discovery. Would you like to stop it?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) @@ -1615,7 +1620,7 @@ def onBtSpectrumAnalyzer(self): self.btShowSpectrum = False self.btLastSpectrumState = False return - + if beaconRunning: reply = QMessageBox.question(self, 'Question',"Bluetooth hardware is being used for beaconing. Would you like to stop it?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) @@ -1626,14 +1631,14 @@ def onBtSpectrumAnalyzer(self): self.btShowSpectrum = False self.btLastSpectrumState = False return - + self.btShowSpectrum = self.menuBtSpectrum.isChecked() self.btLastSpectrumState = self.btShowSpectrum - + if self.btShowSpectrum: # Add it on the plot self.createSpectrumLine() - + if self.bluetooth and (not self.remoteAgentUp): self.bluetooth.startScanning() elif self.remoteAgentUp: @@ -1645,9 +1650,9 @@ def onBtSpectrumAnalyzer(self): self.btLastSpectrumState = False self.btSpectrumTimer.stop() return - + self.menuHackrfSpectrum24.setEnabled(False) - + if not self.remoteAgentUp: self.btSpectrumTimer.start(self.btSpectrumTimeoutLocal) else: @@ -1662,33 +1667,33 @@ def onBtSpectrumAnalyzer(self): if errcode != 0: self.statusBar().showMessage(errmsg) self.setArrowCursor() - + # If we're local: if not self.remoteAgentUp: hackrfsetting = self.hackrf.hasHackrf else: hackrfsetting = self.remoteHasHackrf - + if hackrfsetting and (not self.hackrfShowSpectrum5): self.menuHackrfSpectrum24.setEnabled(True) - + # remove it from the plot if self.spectrum24Line: self.chart24.removeSeries(self.spectrum24Line) self.spectrum24Line = None - + def setWaitCursor(self): self.setCursor(Qt.WaitCursor) - + def setArrowCursor(self): self.setCursor(Qt.ArrowCursor) - + def onAdvancedScanClosed(self): self.advancedScan = None - + def onAdvScanUpdateSSIDs(self, wirelessNetworks): rowPosition = self.networkTable.rowCount() - + if rowPosition > 0: # Range goes to last # - 1 for curRow in range(0, rowPosition): @@ -1696,7 +1701,7 @@ def onAdvScanUpdateSSIDs(self, wirelessNetworks): curData = self.networkTable.item(curRow, 2).data(Qt.UserRole+1) except: curData = None - + if (curData): # We already have the network. just update it for curKey in wirelessNetworks.keys(): @@ -1708,59 +1713,59 @@ def onAdvScanUpdateSSIDs(self, wirelessNetworks): self.networkTable.item(curRow, 2).setText(curData.ssid) curSeries = self.networkTable.item(curRow, 2).data(Qt.UserRole) curSeries.setName(curData.ssid) - + def onAdvancedScan(self): if not hasFalcon: return - + if not self.advancedScan: self.advancedScan = AdvancedScanDialog(self.remoteAgentUp, self.remoteAgentIP, self.remoteAgentPort, self, self) # Need to set parent to None to allow it to not always be on top self.checkNotifyAdvancedScan() - + self.advancedScan.show() self.advancedScan.activateWindow() def checkNotifyAdvancedScan(self): if not self.advancedScan: return - + # If we've changed from local<->remote, or something about the remote end has changed, let's signal update - if ((self.advancedScan.usingRemoteAgent != self.remoteAgentUp) or + if ((self.advancedScan.usingRemoteAgent != self.remoteAgentUp) or (self.remoteAgentUp and (self.remoteAgentIP !=self.advancedScan.remoteAgentIP or self.remoteAgentPort !=self.advancedScan.remoteAgentPort))) : if self.remoteAgentUp: self.advancedScan.setRemoteAgent(self.remoteAgentIP, self.remoteAgentPort) else: self.advancedScan.setLocal() - + def checkNotifyBluetoothDiscovery(self): if not self.bluetoothWin: return - + # If we've changed from local<->remote, or something about the remote end has changed, let's signal update - if ((self.bluetoothWin.usingRemoteAgent != self.remoteAgentUp) or + if ((self.bluetoothWin.usingRemoteAgent != self.remoteAgentUp) or (self.remoteAgentUp and (self.remoteAgentIP !=self.bluetoothWin.remoteAgentIP or self.remoteAgentPort !=self.bluetoothWin.remoteAgentPort))) : if self.remoteAgentUp: self.bluetoothWin.setRemoteAgent(self.remoteAgentIP, self.remoteAgentPort) else: self.bluetoothWin.setLocal() - + def onScanModeChanged(self): self.scanMode = str(self.scanModeCombo.currentText()) - + if self.scanMode == "Normal": self.huntChannels.setVisible(False) self.lblScanMode.setVisible(False) else: self.huntChannels.setVisible(True) self.lblScanMode.setVisible(True) - + self.getHuntChannels() - + def getHuntChannels(self): channelStr = self.huntChannels.text() channelStr = channelStr.replace(' ', '') - + if (',' in channelStr): tmpList = channelStr.split(',') else: @@ -1772,25 +1777,25 @@ def getHuntChannels(self): tmpList.append(channelStr) except: pass - + for curItem in tmpList: if len(curItem) > 0: try: freqForChannel = WirelessEngine.getFrequencyForChannel(curItem) - + if freqForChannel is not None: self.huntChannelList.append(int(freqForChannel)) else: self.huntChannelList.append(int(curItem)) except: QMessageBox.question(self, 'Error',"Could not figure channel out from " + curItem, QMessageBox.Ok) - + def onXGPSLocal(self): try: subprocess.Popen('xgps') except: QMessageBox.question(self, 'Error',"Unable to open xgps. You may need to 'apt install gpsd-clients'", QMessageBox.Ok) - + def onXGPSRemote(self): text, okPressed = QInputDialog.getText(self, "Remote Agent","Please provide gpsd IP:", QLineEdit.Normal, "127.0.0.1:2947") if okPressed and text != '': @@ -1801,20 +1806,20 @@ def onXGPSRemote(self): agentSpec = p.search(text).group(1) remoteIP = agentSpec.split(':')[0] remotePort = int(agentSpec.split(':')[1]) - + if remotePort < 1 or remotePort > 65535: QMessageBox.question(self, 'Error',"Port must be in an acceptable IP range (1-65535)", QMessageBox.Ok) specIsGood = False except: QMessageBox.question(self, 'Error',"Please enter it in the format :", QMessageBox.Ok) specIsGood = False - + if not specIsGood: self.menuRemoteAgent.setChecked(False) return - + args = ['xgps', remoteIP + ":" + str(remotePort)] - + try: subprocess.Popen(args) except: @@ -1822,14 +1827,14 @@ def onXGPSRemote(self): def onCopyNet(self): self.updateLock.acquire() - + curRow = self.networkTable.currentRow() curCol = self.networkTable.currentColumn() - + if curRow == -1 or curCol == -1: self.updateLock.release() return - + if curCol != 11: curText = self.networkTable.item(curRow, curCol).text() else: @@ -1838,21 +1843,21 @@ def onCopyNet(self): curText += 'Strongest Signal Coordinates:\n' curText += 'Strongest Signal: ' + str(curNet.strongestsignal) + '\n' curText += str(curNet.strongestgps) - + clipboard = QApplication.clipboard() clipboard.setText(curText) - + self.updateLock.release() - + def onDeleteNet(self): self.updateLock.acquire() - + curRow = self.networkTable.currentRow() - + if curRow == -1: self.updateLock.release() return - + curNet = self.networkTable.item(curRow, 2).data(Qt.UserRole+1) curSeries = self.networkTable.item(curRow, 2).data(Qt.UserRole) @@ -1861,55 +1866,55 @@ def onDeleteNet(self): self.chart24.removeSeries(curSeries) else: self.chart5.removeSeries(curSeries) - + self.networkTable.removeRow(curRow) - + self.updateLock.release() - + def onShowTelemetry(self): self.updateLock.acquire() - + curRow = self.networkTable.currentRow() - + if curRow == -1: self.updateLock.release() return - + curNet = self.networkTable.item(curRow, 2).data(Qt.UserRole+1) - + if curNet == None: self.updateLock.release() return - + if curNet.getKey() not in self.telemetryWindows.keys(): telemetryWindow = TelemetryDialog() telemetryWindow.show() self.telemetryWindows[curNet.getKey()] = telemetryWindow else: telemetryWindow = self.telemetryWindows[curNet.getKey()] - + # Can also key off of self.telemetryWindow.isVisible() telemetryWindow.show() telemetryWindow.activateWindow() - + # Can do telemetry window updates after release self.updateLock.release() - + # User could have selected a different network. - telemetryWindow.updateNetworkData(curNet) - + telemetryWindow.updateNetworkData(curNet) + def showNTContextMenu(self, pos): curRow = self.networkTable.currentRow() - + if curRow == -1: return - + self.ntRightClickMenu.exec_(self.networkTable.mapToGlobal(pos)) - + def onSpectrumTimer(self): - + # This is actually for the Ubertooth spectrum. HackRF spectrums are handled by OnHackrfSpectrumAnalyzer<24/5> methods - + if self.btShowSpectrum: # and self.bluetooth: if (not self.remoteAgentUp): # Get Local data @@ -1921,7 +1926,7 @@ def onSpectrumTimer(self): if errcode != 0: self.statusBar().showMessage(errmsg) channelData = {} - + # Plot it self.createSpectrumLine() @@ -1929,10 +1934,10 @@ def onSpectrumTimer(self): if len(channelData) > 0: sortedKeys = sorted(channelData.keys()) - + # For testing the waterfall # newArray=[] - + for curKey in sortedKeys: fCurKey = float(curKey) if self.btSpectrumGain != 1.0: @@ -1941,23 +1946,23 @@ def onSpectrumTimer(self): dBm = float((channelData[curKey] + 110.0)*self.btSpectrumGain - 110.0) else: dBm = float(channelData[curKey]) - + self.spectrum24Line.append(fCurKey, dBm) # For testing the waterfall # newArray.append(dBm) - + # For testing the waterfall #if not self.waterfall24: # self.waterfall24 = WaterfallPlotWindow(self, None) - + #self.waterfall24.update - + if not self.remoteAgentUp: self.btSpectrumTimer.start(self.btSpectrumTimeoutLocal) else: # Slow it down just a bit for remote agents self.btSpectrumTimer.start(self.btSpectrumTimeoutRemote) - + def onHackrfSpectrumTimer(self): if self.hackrfShowSpectrum24 or self.hackrfShowSpectrum5: # and self.bluetooth: if (not self.remoteAgentUp): @@ -1966,7 +1971,7 @@ def onHackrfSpectrumTimer(self): channelData = self.hackrf.spectrum5ToChannels() else: channelData = self.hackrf.spectrum24ToChannels() - + errcode = 0 else: # Get remote data @@ -1975,7 +1980,7 @@ def onHackrfSpectrumTimer(self): if errcode != 0: self.statusBar().showMessage(errmsg) channelData = {} - + # Plot it self.createSpectrumLine() @@ -2001,7 +2006,7 @@ def onHackrfSpectrumTimer(self): self.Plot24.setViewportUpdateMode(QGraphicsView.SmartViewportUpdate) # Not needed # self.Plot24.update() - + if len(channelData) > 0 and self.hackrfShowSpectrum5: # Disable chart update self.Plot5.setViewportUpdateMode(QGraphicsView.NoViewportUpdate) @@ -2021,49 +2026,49 @@ def onHackrfSpectrumTimer(self): self.Plot5.setViewportUpdateMode(QGraphicsView.SmartViewportUpdate) # Not needed # self.Plot5.update() - + if not self.remoteAgentUp: self.hackrfSpectrumTimer.start(self.hackrfSpectrumTimeoutLocal) else: # Slow it down just a bit for remote agents self.hackrfSpectrumTimer.start(self.hackrfSpectrumTimeoutRemote) - + def onGPSTimer(self): self.onGPSStatus(False) self.gpsTimer.start(self.gpsTimerTimeout) - + def onGoogleMap(self): rowPosition = self.networkTable.rowCount() if rowPosition <= 0: QMessageBox.question(self, 'Error',"There's no access points in the table. Please run a scan first or open a saved scan.", QMessageBox.Ok) return - + mapSettings, ok = MapSettingsDialog.getSettings() if not ok: return - + if len(mapSettings.outputfile) == 0: QMessageBox.question(self, 'Error',"Please provide an output file.", QMessageBox.Ok) return - + markerDict = {} markers = [] - + # Range goes to last # - 1 for curRow in range(0, rowPosition): try: curData = self.networkTable.item(curRow, 2).data(Qt.UserRole+1) except: curData = None - + if (curData): newMarker = MapMarker() - + newMarker.label = WirelessEngine.convertUnknownToString(curData.ssid) newMarker.label = newMarker.label[:mapSettings.maxLabelLength] - + if mapSettings.plotstrongest: if curData.strongestgps.isValid: newMarker.gpsValid = True @@ -2073,7 +2078,7 @@ def onGoogleMap(self): newMarker.gpsValid = False newMarker.latitude = 0.0 newMarker.longitude = 0.0 - + newMarker.barCount = WirelessEngine.getSignalQualityFromDB0To5(curData.strongestsignal) else: if curData.gps.isValid: @@ -2084,9 +2089,9 @@ def onGoogleMap(self): newMarker.gpsValid = False newMarker.latitude = 0.0 newMarker.longitude = 0.0 - + newMarker.barCount = WirelessEngine.getSignalQualityFromDB0To5(curData.signal) - + markerKey = newMarker.getKey() if markerKey in markerDict: curMarker = markerDict[markerKey] @@ -2102,10 +2107,10 @@ def onGoogleMap(self): # Now send consolidated list for curKey in markerDict.keys(): markers.append(markerDict[curKey]) - + if len(markers) > 0: retVal = MapEngine.createMap(mapSettings.outputfile,mapSettings.title,markers, connectMarkers=False, openWhenDone=True, mapType=mapSettings.mapType) - + if not retVal: QMessageBox.question(self, 'Error',"Unable to generate map to " + mapSettings.outputfile, QMessageBox.Ok) @@ -2114,90 +2119,90 @@ def onGoogleMapTelemetry(self): if not ok: return - + if len(mapSettings.inputfile) == 0: QMessageBox.question(self, 'Error',"Please provide an input file.", QMessageBox.Ok) return - + if len(mapSettings.outputfile) == 0: QMessageBox.question(self, 'Error',"Please provide an output file.", QMessageBox.Ok) return - + markers = [] raw_list = [] - + try: with open(mapSettings.inputfile, 'r') as f: reader = csv.reader(f) raw_list = list(reader) except: pass - + # remove blank lines while [] in raw_list: raw_list.remove([]) - + if len(raw_list) > 1: # Check header row looks okay if str(raw_list[0]) != "['macAddr', 'SSID', 'Strength', 'Timestamp', 'GPS', 'Latitude', 'Longitude', 'Altitude']": QMessageBox.question(self, 'Error',"File format doesn't look like exported telemetry data saved from the telemetry window.", QMessageBox.Ok) return - + # Ignore header row # Range goes to last # - 1 for i in range (1, len(raw_list)): - + # Plot first, last, and every Nth point if ((i % mapSettings.plotNthPoint == 0) or (i == 1) or (i == (len(raw_list)-1))) and (len(raw_list[i]) > 0): - + if raw_list[i][4] == 'Yes': # The GPS entry is valid newMarker = MapMarker() - + if (i == 1) or (i == (len(raw_list)-1)): # Only put first and last marker labels on. No need to pollute the map if there's a lot of points newMarker.label = WirelessEngine.convertUnknownToString(raw_list[i][1]) newMarker.label = newMarker.label[:mapSettings.maxLabelLength] - + newMarker.latitude = float(raw_list[i][5]) newMarker.longitude = float(raw_list[i][6]) newMarker.barCount = WirelessEngine.getSignalQualityFromDB0To5(int(raw_list[i][2])) - + markers.append(newMarker) - + if len(markers) > 0: retVal = MapEngine.createMap(mapSettings.outputfile,mapSettings.title,markers, connectMarkers=True, openWhenDone=True, mapType=mapSettings.mapType) - + if not retVal: QMessageBox.question(self, 'Error',"Unable to generate map to " + mapSettings.outputfile, QMessageBox.Ok) - + def onGPSCoordinates(self): if not self.gpsCoordWindow: self.gpsCoordWindow = GPSCoordDialog(mainWin=self) - + self.gpsCoordWindow.show() self.gpsCoordWindow.activateWindow() def getCurrentGPS(self): # retVal will be a sparrowGPS object - + if (not self.remoteAgentUp): # Local retVal = self.gpsEngine.getLastCoord() else: # Remote errCode, errMsg, gpsStatus = requestRemoteGPS(self.remoteAgentIP, self.remoteAgentPort) - + if errCode == 0: retVal = gpsStatus.asSparrowGPSObject() else: retVal = None - + if retVal is None: retVal = SparrowGPS() - + return retVal - + def onGPSStatus(self, updateStatusBar=True): if (not self.remoteAgentUp): # Checking local GPS @@ -2212,7 +2217,7 @@ def onGPSStatus(self, updateStatusBar=True): self.btnGPSStatus.setStyleSheet("background-color: yellow; border: 1px;") if updateStatusBar: self.statusBar().showMessage("Local gpsd service is running but it's not synchronized with the satellites yet.") - + else: self.gpsSynchronized = False if updateStatusBar: @@ -2221,7 +2226,7 @@ def onGPSStatus(self, updateStatusBar=True): else: # Checking remote errCode, errMsg, gpsStatus = requestRemoteGPS(self.remoteAgentIP, self.remoteAgentPort) - + if errCode == 0: self.missedAgentCycles = 0 if (gpsStatus.isValid): @@ -2241,10 +2246,10 @@ def onGPSStatus(self, updateStatusBar=True): # Agent may be up but just taking a while to respond. if (not agentUp) or errCode == -1: self.missedAgentCycles += 1 - + if (not agentUp) or (self.missedAgentCycles > self.allowedMissedAgentCycles): # We may let it miss a cycle or two just as a good practice - + # Agent disconnected. # Stop any active scan and transition local self.agentDisconnected() @@ -2253,7 +2258,7 @@ def onGPSStatus(self, updateStatusBar=True): else: self.statusBar().showMessage("Remote GPS Error: " + errMsg) self.btnGPSStatus.setStyleSheet("background-color: red; border: 1px;") - + def onGPSSyncChanged(self): # GPS status has changed @@ -2274,12 +2279,12 @@ def onTableHeadingClicked(self, logical_index): self.networkTableSortOrder = order self.networkTableSortIndex = logical_index self.networkTable.sortItems(logical_index, order ) - + def onNetworkTableSelectionChanged(self): row = self.networkTable.currentRow() if row < 0: return - + if (self.lastSeries is not None): # Change the old one back if (self.lastSeries): @@ -2288,23 +2293,23 @@ def onNetworkTableSelectionChanged(self): self.lastSeries.setPen(pen) self.lastSeries.setVisible(False) self.lastSeries.setVisible(True) - + selectedSeries = self.networkTable.item(row, 2).data(Qt.UserRole) - + if (selectedSeries): pen = selectedSeries.pen() pen.setWidth(6) selectedSeries.setPen(pen) selectedSeries.setVisible(False) selectedSeries.setVisible(True) - + self.lastSeries = selectedSeries else: selectedSeries = None def onTableClicked(self, row, col): pass - + def onGPSStatusIndicatorClicked(self): if self.hasXGPS(): if self.menuRemoteAgent.isChecked(): @@ -2317,26 +2322,26 @@ def onSingleShotScanResults(self, wirelessNetworks, retCode, errString): # Change the GUI controls back self.scanModeCombo.setEnabled(True) self.huntChannels.setEnabled(True) - + self.btnScan.setEnabled(True) self.btnScan.setStyleSheet("background-color: rgba(2,128,192,255); border: none;") self.btnScan.setText('&Scan') - + # Display the data or any errors if they occurred if (retCode == 0): # Good data if len(wirelessNetworks) > 0: self.populateTable(wirelessNetworks) - + self.statusBar().showMessage('Ready') else: # Errors (note, device busy can happen, but for single-shot mode we want to know because no display change would happen) self.errmsg.emit(retCode, errString) - + # reset the shortcut (seems to undo when we change the text) self.btnScan.setShortcut('Ctrl+S') self.btnScan.setChecked(False) - + def onRemoteScanSingleShot(self): # Quick sanity check for interfaces if (self.combo.count() > 0): @@ -2346,16 +2351,16 @@ def onRemoteScanSingleShot(self): # No interfaces, don't do anything and just debounce the scan button self.btnScan.setChecked(False) return - + # Disable the GUI controls to indicate we're scanning self.scanModeCombo.setEnabled(False) self.huntChannels.setEnabled(False) - + self.btnScan.setEnabled(False) self.btnScan.setStyleSheet("background-color: rgba(224,224,224,255); border: none;") self.btnScan.setText('&Scanning') self.btnScan.repaint() - + # Make the call to get the data if self.scanMode == "Normal" or (len(self.huntChannelList) == 0): @@ -2364,24 +2369,24 @@ def onRemoteScanSingleShot(self): else: # retCode, errString, wirelessNetworks = requestRemoteNetworks(self.remoteAgentIP, self.remoteAgentPort, curInterface, self.huntChannelList) self.remoteSingleShotThread = remoteSingleShotThread(curInterface, self, self.remoteAgentIP, self.remoteAgentPort, self.huntChannelList) - + self.remoteSingleShotThread.start() - + # Change the GUI controls back happens when the emit happens def agentDisconnected(self): # Don't try to pull any more GPS status self.remoteAgentUp = False - + # Stop any running scans if self.remoteAutoUpdates and self.btnScan.isChecked(): self.btnScan.setChecked(False) self.onScanClicked(False) - + # Signal disconnect agent internally self.menuRemoteAgent.setChecked(False) self.onRemoteAgent() - + self.menuHackrfSpectrum24.setChecked(False) self.menuHackrfSpectrum5.setChecked(False) if self.hackrf.hasHackrf: @@ -2390,30 +2395,30 @@ def agentDisconnected(self): else: self.menuHackrfSpectrum24.setEnabled(False) self.menuHackrfSpectrum5.setEnabled(False) - + self.setBluetoothMenu() self.checkNotifyBluetoothDiscovery() self.checkNotifyAdvancedScan() - + def onRemoteScanClicked(self, pressed): if not self.remoteAutoUpdates: # Single-shot mode. self.onRemoteScanSingleShot() return - + # Auto update self.remoteScanRunning = pressed - + if not self.remoteScanRunning: if self.remoteScanThread: self.remoteScanThread.signalStop = True - + while (self.remoteScanThread.threadRunning): self.statusBar().showMessage('Waiting for active scan to terminate...') sleep(0.2) - + self.remoteScanThread = None - + self.statusBar().showMessage('Ready') else: if (self.combo.count() > 0): @@ -2423,14 +2428,14 @@ def onRemoteScanClicked(self, pressed): self.remoteScanThread = RemoteScanThread(curInterface, self) else: self.remoteScanThread = RemoteScanThread(curInterface, self, self.huntChannelList) - + self.remoteScanThread.scanDelay = self.remoteScanDelay self.remoteScanThread.start() else: QMessageBox.question(self, 'Error',"No wireless adapters found.", QMessageBox.Ok) self.remoteScanRunning = False self.btnScan.setChecked(False) - + if self.btnScan.isChecked(): # Scanning is on. Turn red to indicate click would stop self.btnScan.setStyleSheet("background-color: rgba(255,0,0,255); border: none;") @@ -2449,25 +2454,25 @@ def onScanClicked(self, pressed): # We're in remote mode. Let's handle it there self.onRemoteScanClicked(pressed) return - + # We're in local mode. self.scanRunning = pressed - + if not self.scanRunning: # Want to stop a running scan (self.scanRunning represents the NEW pressed state) if self.scanThread: self.setWaitCursor() - + self.scanThread.signalStop = True while (self.scanThread.threadRunning): self.statusBar().showMessage('Waiting for active scan to terminate...') sleep(0.2) - + self.scanThread = None - + self.setArrowCursor() - + self.statusBar().showMessage('Ready') else: # Want to start a new scan @@ -2491,7 +2496,7 @@ def onScanClicked(self, pressed): QMessageBox.question(self, 'Error',"No wireless adapters found.", QMessageBox.Ok) self.scanRunning = False self.btnScan.setChecked(False) - + if self.btnScan.isChecked(): # Scanning is on. Turn red to indicate click would stop self.btnScan.setStyleSheet("background-color: rgba(255,0,0,255); border: none;") @@ -2507,13 +2512,13 @@ def onScanClicked(self, pressed): self.scanModeCombo.setEnabled(True) self.huntChannels.setEnabled(True) self.combo.setEnabled(True) - + # Need to reset the shortcut after changing the text self.btnScan.setShortcut('Ctrl+S') - + def scanResultsFromAdvanced(self, wirelessNetworks): self.populateTable(wirelessNetworks, True) - + def scanResults(self, wirelessNetworks): if self.scanRunning: # Running local. If we have a good GPS, update the networks @@ -2523,27 +2528,27 @@ def scanResults(self, wirelessNetworks): curNet = wirelessNetworks[curKey] curNet.gps.copy(self.gpsEngine.lastCoord) curNet.strongestgps.copy(self.gpsEngine.lastCoord) - + if self.menuRemoteAgent.isChecked() or ((not self.menuRemoteAgent.isChecked()) and self.scanRunning): # If is to prevent a messaging issue on last iteration # Scan results will come over from the remote agent with the GPS fields already populated. self.populateTable(wirelessNetworks) - + def onErrMsg(self, errCode, errMsg): self.statusBar().showMessage("Error ["+str(errCode) + "]: " + errMsg) - - if ((errCode == WirelessNetwork.ERR_NETDOWN) or (errCode == WirelessNetwork.ERR_OPNOTSUPPORTED) or + + if ((errCode == WirelessNetwork.ERR_NETDOWN) or (errCode == WirelessNetwork.ERR_OPNOTSUPPORTED) or (errCode == WirelessNetwork.ERR_OPNOTPERMITTED)): if self.scanThread: self.scanThread.signalStop = True - + while (self.scanThread.threadRunning): sleep(0.2) - + self.scanThread = None self.scanRunning = False self.btnScan.setChecked(False) - + # Undo button self.btnScan.setStyleSheet("background-color: rgba(2,128,192,255); border: none;") self.btnScan.setText('&Scan') @@ -2556,13 +2561,13 @@ def plotSSID(self, curSeries, curNet): tmpssid = curNet.ssid if (len(tmpssid) == 0): tmpssid = '' - + curSeries.setName(tmpssid) - + # Both 2.4 and 5 GHz use +-2 channels for 20 MHz channels channelDeltaLow = 2 channelDeltaHigh = 2 - + # For 2.4 GHz Channels: # https://en.wikipedia.org/wiki/List_of_WLAN_channels#5_GHz_.28802.11a.2Fh.2Fj.2Fn.2Fac.29 # See this for 5 GHz channels: @@ -2584,19 +2589,19 @@ def plotSSID(self, curSeries, curNet): elif curNet.bandwidth == 160: channelDeltaLow = 16 channelDeltaHigh = 16 - + lowChannel = curNet.channel - channelDeltaLow highChannel = curNet.channel + channelDeltaHigh - + if curNet.channel <= 16: chartChannelLow = 0 chartChannelHigh = 17 else: chartChannelLow = 30 chartChannelHigh = 171 - + curSeries.clear() - + if lowChannel <=chartChannelLow: # First point on the graph needs to be at dBm rather than -100 if curNet.signal >= -100: @@ -2606,13 +2611,13 @@ def plotSSID(self, curSeries, curNet): else: # Add the zero then our plot # curSeries.append(chartChannelLow, -100) - + if curNet.signal >= -100: curSeries.append( lowChannel, -100) curSeries.append( lowChannel, curNet.signal) else: curSeries.append(lowChannel, -100) - + if highChannel >= chartChannelHigh: # First point on the graph needs to be at dBm rather than -100 if curNet.signal >= -100: @@ -2625,19 +2630,19 @@ def plotSSID(self, curSeries, curNet): curSeries.append( highChannel, -100) else: curSeries.append(highChannel, -100) - + # Add the zero then our plot # curSeries.append(chartChannelHigh, -100) - + def updateNet(self, curSeries, curNet, channelPlotStart, channelPlotEnd): self.plotSSID(curSeries, curNet) - + def updateNetOld(self, curSeries, curNet, channelPlotStart, channelPlotEnd): curSeries.setName(curNet.ssid) - + for i in range(channelPlotStart, channelPlotEnd): graphPoint = False - + if (curNet.bandwidth == 20): if i >= (curNet.channel - 1) and i <=(curNet.channel +1): graphPoint = True @@ -2659,7 +2664,7 @@ def updateNetOld(self, curSeries, curNet, channelPlotStart, channelPlotEnd): elif (curNet.bandwidth == 160): if i >= (curNet.channel - 15) and i <=(curNet.channel +15): graphPoint = True - + if graphPoint: if curNet.signal >= -100: curSeries.replace(i-channelPlotStart, i, curNet.signal) @@ -2667,10 +2672,10 @@ def updateNetOld(self, curSeries, curNet, channelPlotStart, channelPlotEnd): curSeries.replace(i-channelPlotStart, i, -100) else: curSeries.replace(i-channelPlotStart, i, -100) - + def update5Net(self, curSeries, curNet): self.updateNet(curSeries, curNet, 30, 171) - + def update24Net(self, curSeries, curNet): # Loop to channel 14 + 2 for high end, + 1 for range function = 17 self.updateNet(curSeries, curNet, 0, 17) @@ -2690,7 +2695,7 @@ def addNet(self, newSeries, curNet, channelPlotStart, channelPlotEnd, adding5GHz def addNetOld(self, newSeries, curNet, channelPlotStart, channelPlotEnd, adding5GHz): for i in range(channelPlotStart, channelPlotEnd): graphPoint = False - + if (curNet.bandwidth == 20): if i >= (curNet.channel - 1) and i <=(curNet.channel +1): graphPoint = True @@ -2713,7 +2718,7 @@ def addNetOld(self, newSeries, curNet, channelPlotStart, channelPlotEnd, adding5 elif (curNet.bandwidth == 160): if i >= (curNet.channel - 15) and i <=(curNet.channel +15): graphPoint = True - + if graphPoint: if curNet.signal >= -100: newSeries.append( i, curNet.signal) @@ -2721,13 +2726,13 @@ def addNetOld(self, newSeries, curNet, channelPlotStart, channelPlotEnd, adding5 newSeries.append(i, -100) else: newSeries.append(i, -100) - + tmpssid = curNet.ssid if (len(tmpssid) == 0): tmpssid = '' - + newSeries.setName(tmpssid) - + if adding5GHz: self.chart5.addSeries(newSeries) newSeries.attachAxis(self.chart5axisx ) @@ -2736,27 +2741,27 @@ def addNetOld(self, newSeries, curNet, channelPlotStart, channelPlotEnd, adding5 self.chart24.addSeries(newSeries) newSeries.attachAxis(self.chart24axisx) newSeries.attachAxis(self.chart24.axisY()) - + def add5Net(self, newSeries, curNet): self.addNet(newSeries, curNet, 30, 171, True) - + def add24Net(self, newSeries, curNet): self.addNet(newSeries, curNet, 0, 17, False) - + def populateUpdateExisting(self, wirelessNetworks, FromAdvanced=False): numRows = self.networkTable.rowCount() - + if numRows > 0: # Loop through each network in the network table, and compare it against the new networks. # If we find one, then we already know the network. Just update it. - + # Range goes to last # - 1 for curRow in range(0, numRows): try: curData = self.networkTable.item(curRow, 2).data(Qt.UserRole+1) except: curData = None - + if (curData): # We already have the network. just update it for curKey in wirelessNetworks.keys(): @@ -2767,13 +2772,13 @@ def populateUpdateExisting(self, wirelessNetworks, FromAdvanced=False): if clientVendor is None: clientVendor = '' self.networkTable.item(curRow, 1).setText(clientVendor) - + self.networkTable.item(curRow, 3).setText(curNet.security) self.networkTable.item(curRow, 4).setText(curNet.privacy) self.networkTable.item(curRow, 5).setText(str(curNet.getChannelString())) self.networkTable.item(curRow, 6).setText(str(curNet.frequency)) self.networkTable.item(curRow, 7).setText(str(curNet.signal)) - + if FromAdvanced: # There are some fields that are not passed forward from advanced. So let's update our curNet # attributes in the net object that comes from advanced. @@ -2781,15 +2786,15 @@ def populateUpdateExisting(self, wirelessNetworks, FromAdvanced=False): curNet.secondaryChannel = curData.secondaryChannel curNet.thirdChannel = curData.thirdChannel curNet.secondaryChannelLocation = curData.secondaryChannelLocation - + self.networkTable.item(curRow, 8).setText(str(curNet.bandwidth)) self.networkTable.item(curRow, 9).setText(str(curNet.utilization)) self.networkTable.item(curRow, 10).setText(str(curNet.stationcount)) self.networkTable.item(curRow, 11).setText(curNet.lastSeen.strftime("%m/%d/%Y %H:%M:%S")) - + # Carry forward firstSeen curNet.firstSeen = curData.firstSeen # This is one field to carry forward - + # Check strongest signal # If we have a stronger signal, or we have an equal signal but we now have GPS # Note the 0.9. Can be close to store strongest with GPS @@ -2800,25 +2805,25 @@ def populateUpdateExisting(self, wirelessNetworks, FromAdvanced=False): curNet.strongestgps.altitude = curData.gps.altitude curNet.strongestgps.speed = curData.gps.speed curNet.strongestgps.isValid = curData.gps.isValid - + self.networkTable.item(curRow, 12).setText(curNet.firstSeen.strftime("%m/%d/%Y %H:%M:%S")) if curNet.gps.isValid: self.networkTable.item(curRow, 13).setText('Yes') else: self.networkTable.item(curRow, 13).setText('No') - + curNet.foundInList = True self.networkTable.item(curRow, 2).setData(Qt.UserRole+1, curNet) - + # Update series curSeries = self.networkTable.item(curRow, 2).data(Qt.UserRole) - + # Check if we have a telemetry window if curNet.getKey() in self.telemetryWindows.keys(): telemetryWindow = self.telemetryWindows[curNet.getKey()] - telemetryWindow.updateNetworkData(curNet) + telemetryWindow.updateNetworkData(curNet) - # 3 scenarios: + # 3 scenarios: # 20 MHz, 1 channel # 40 MHz, 2nd channel above/below or non-contiguous for 5 GHz # 80/160 MHz, Specified differently. It's allocated as a contiguous block @@ -2829,16 +2834,16 @@ def populateUpdateExisting(self, wirelessNetworks, FromAdvanced=False): else: # 5 GHz self.update5Net(curSeries, curNet) - + break # We found one, so don't bother looping through more def getNextColor(self): nextColor = colors[self.nextColor] self.nextColor += 1 - + if (self.nextColor >= len(colors)): self.nextColor = 0 - + return nextColor def createNewSeries(self, nextColor, parentChart): @@ -2846,18 +2851,18 @@ def createNewSeries(self, nextColor, parentChart): pen = QPen(nextColor) pen.setWidth(2) newSeries.setPen(pen) - + return newSeries def populateTable(self, wirelessNetworks, FromAdvanced=False): self.updateLock.acquire() - + # Update existing if we have it (this will mark the networ's foundInList flag if we did self.populateUpdateExisting(wirelessNetworks, FromAdvanced) addedNetworks = 0 firstTableLoad = False - + for curKey in wirelessNetworks.keys(): # Don't add duplicate curNet = wirelessNetworks[curKey] @@ -2865,11 +2870,11 @@ def populateTable(self, wirelessNetworks, FromAdvanced=False): continue addedNetworks += 1 - + # ----------- Update the plots ------------------- nextColor = self.getNextColor() - - # 3 scenarios: + + # 3 scenarios: # 20 MHz, 1 channel # 40 MHz, 2nd channel above/below or non-contiguous for 5 GHz # 80/160 MHz, Specified differently. It's allocated as a contiguous block @@ -2881,10 +2886,10 @@ def populateTable(self, wirelessNetworks, FromAdvanced=False): # 5 GHz newSeries = self.createNewSeries(nextColor, self.chart5) self.add5Net(newSeries, curNet) - + # ----------- Update the Table ------------------- # Do the table second so we can attach the series to it. - + rowPosition = self.networkTable.rowCount() rowPosition -= 1 addedFirstRow = False @@ -2892,9 +2897,9 @@ def populateTable(self, wirelessNetworks, FromAdvanced=False): addedFirstRow = True rowPosition = 0 firstTableLoad = True - + self.networkTable.insertRow(rowPosition) - + # Just make sure we don't get an extra blank row if (addedFirstRow): self.networkTable.setRowCount(1) @@ -2903,18 +2908,18 @@ def populateTable(self, wirelessNetworks, FromAdvanced=False): tmpssid = curNet.ssid if (len(tmpssid) == 0): tmpssid = '' - + newSeries.setName(tmpssid) newSSID = QTableWidgetItem(tmpssid) ssidBrush = QBrush(nextColor) newSSID.setForeground(ssidBrush) - # You can bind more than one data. See this: + # You can bind more than one data. See this: # https://stackoverflow.com/questions/2579579/qt-how-to-associate-data-with-qtablewidgetitem newSSID.setData(Qt.UserRole, newSeries) newSSID.setData(Qt.UserRole+1, curNet) newSSID.setData(Qt.UserRole+2, None) - + clientVendor = self.ouiLookup(curNet.macAddr) if clientVendor is None: clientVendor = '' @@ -2928,7 +2933,7 @@ def populateTable(self, wirelessNetworks, FromAdvanced=False): self.networkTable.setItem(rowPosition, 8, IntTableWidgetItem(str(curNet.bandwidth))) self.networkTable.setItem(rowPosition, 9, FloatTableWidgetItem(str(curNet.utilization))) self.networkTable.setItem(rowPosition, 10, IntTableWidgetItem(str(curNet.stationcount))) - + self.networkTable.setItem(rowPosition, 11, DateTableWidgetItem(curNet.lastSeen.strftime("%m/%d/%Y %H:%M:%S"))) self.networkTable.setItem(rowPosition, 12, DateTableWidgetItem(curNet.firstSeen.strftime("%m/%d/%Y %H:%M:%S"))) if curNet.gps.isValid: @@ -2940,37 +2945,37 @@ def populateTable(self, wirelessNetworks, FromAdvanced=False): if firstTableLoad: self.networkTable.resizeColumnsToContents() - + if addedNetworks > 0: if self.networkTableSortIndex >=0: self.networkTable.sortItems(self.networkTableSortIndex, self.networkTableSortOrder ) - + self.checkTelemetryWindows() - + # Last formatting tweaks on network table # self.networkTable.resizeColumnsToContents() # self.networkTable.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) - + self.updateLock.release() def checkTelemetryWindows(self): # See if we have any telemetry windows that are no longer in the network table and not visible # Update numRows in case we removed any above numRows = self.networkTable.rowCount() - + if (numRows > 0) and (len(self.telemetryWindows.keys()) > 0): # Build key list just once to cut down # of loops netKeyList = [] for i in range(0, numRows): curNet = self.networkTable.item(i, 2).data(Qt.UserRole+1) netKeyList.append(curNet.getKey()) - + try: # If the length of this dictionary changes it may throw an exception. # We'll just pick it up next pass. This is low-priority cleanup - + keysToRemove = [] - + for curKey in self.telemetryWindows.keys(): # For each telemetry window we have stored, # If it's no longer in the network table and it's not visible @@ -2981,14 +2986,14 @@ def checkTelemetryWindows(self): if not curWin.isVisible(): curWin.close() keysToRemove.append(curKey) - + # Have to separate the iteration and the removal or you'll get a "list changed during iteration" exception for curKey in keysToRemove: del self.telemetryWindows[curKey] except: pass - + def ageOut(self): numRows = self.networkTable.rowCount() @@ -2997,11 +3002,11 @@ def ageOut(self): maxTime = datetime.datetime.now() - datetime.timedelta(minutes=3) rowPosition = numRows - 1 # convert count to index - # range goes to last + # range goes to last for i in range(rowPosition, -1, -1): try: curData = self.networkTable.item(i, 2).data(Qt.UserRole+1) - + # Age out if curData.lastSeen < maxTime: curSeries = self.networkTable.item(i, 2).data(Qt.UserRole) @@ -3009,25 +3014,25 @@ def ageOut(self): self.chart24.removeSeries(curSeries) else: self.chart5.removeSeries(curSeries) - + self.networkTable.removeRow(i) - + except: curData = None self.networkTable.removeRow(i) - + def onInterface(self): pass - + def onClearData(self): self.networkTable.setRowCount(0) self.chart24.removeAllSeries() self.spectrum24Line = None self.chart5.removeAllSeries() - - - def openFileDialog(self, fileSpec="CSV Files (*.csv);;All Files (*)"): + + + def openFileDialog(self, fileSpec="CSV Files (*.csv);;All Files (*)"): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getOpenFileName(self,"QFileDialog.getOpenFileName()", "",fileSpec, options=options) @@ -3035,8 +3040,8 @@ def openFileDialog(self, fileSpec="CSV Files (*.csv);;All Files (*)"): return fileName else: return None - - def saveFileDialog(self, fileSpec="CSV Files (*.csv);;All Files (*)"): + + def saveFileDialog(self, fileSpec="CSV Files (*.csv);;All Files (*)"): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()","",fileSpec, options=options) @@ -3050,99 +3055,99 @@ def onImportIWData(self): if not fileName: return - + wirelessNetworks = {} - + try: f = open(fileName, "r") except: QMessageBox.question(self, 'Error',"Unable to open file " + fileName, QMessageBox.Ok) return - + # this will be a list self.setWaitCursor() - + fileLines = f.readlines() f.close() - + if len(fileLines) > 0: wirelessNetworks = WirelessEngine.parseIWoutput(fileLines) - + if len(wirelessNetworks) > 0: self.populateTable(wirelessNetworks) - + self.setArrowCursor() - + def onImportJSON(self): fileName = self.openFileDialog("JSON Files (*.json);;All Files (*)") if not fileName: return - + if not os.path.isfile(fileName): QMessageBox.question(self, 'Error','File ' + fileName + " doesn't exist.", QMessageBox.Ok) return - + self.setWaitCursor() - + wirelessNetworks = {} json_data = "" - + with open(fileName, 'r') as f: json_data = f.read() - + try: netDict = json.loads(json_data) except: self.setArrowCursor() QMessageBox.question(self, 'Error',"Unable to parse JSON data.", QMessageBox.Ok) return - + if not 'wifi-aps' in netDict: self.setArrowCursor() QMessageBox.question(self, 'Error',"JSON appears to be the wrong format (no wifi-aps tag).", QMessageBox.Ok) return - + netList = netDict['wifi-aps'] - + for curNet in netList: newNet = WirelessNetwork.createFromJsonDict(curNet) wirelessNetworks[newNet.getKey()] = newNet - + if len(wirelessNetworks) > 0: self.onClearData() self.populateTable(wirelessNetworks) self.setArrowCursor() - + def onImportCSV(self): fileName = self.openFileDialog() if not fileName: return - + if not os.path.isfile(fileName): QMessageBox.question(self, 'Error','File ' + fileName + " doesn't exist.", QMessageBox.Ok) return - + wirelessNetworks = {} - + with open(fileName, 'r') as f: self.setWaitCursor() reader = csv.reader(f) raw_list = list(reader) - + # remove blank lines while [] in raw_list: raw_list.remove([]) - + if len(raw_list) > 1: # Check header row looks okay if raw_list[0][0] != 'macAddr' or (len(raw_list[0]) < 22): self.setArrowCursor() QMessageBox.question(self, 'Error',"File format doesn't look like an exported scan.", QMessageBox.Ok) return - + # Ignore header row try: for i in range (1, len(raw_list)): @@ -3153,14 +3158,14 @@ def onImportCSV(self): newNet.ssid = raw_list[i][2].replace('"', '') newNet.security = raw_list[i][3] newNet.privacy = raw_list[i][4] - + # Channel could be primary+secondary channelstr = raw_list[i][5] - + if '+' in channelstr: newNet.channel = int(channelstr.split('+')[0]) newNet.secondaryChannel = int(channelstr.split('+')[1]) - + if newNet.secondaryChannel > newNet.channel: newNet.secondaryChannelLocation = 'above' else: @@ -3169,7 +3174,7 @@ def onImportCSV(self): newNet.channel = int(raw_list[i][5]) newNet.secondaryChannel = 0 newNet.secondaryChannelLocation = 'none' - + newNet.frequency = int(raw_list[i][6]) newNet.signal = int(raw_list[i][7]) newNet.strongestsignal = int(raw_list[i][8]) @@ -3186,7 +3191,7 @@ def onImportCSV(self): newNet.strongestgps.longitude = float(raw_list[i][19]) newNet.strongestgps.altitude = float(raw_list[i][20]) newNet.strongestgps.speed = float(raw_list[i][21]) - + # Added utilization and station count on the end to not mess up any saved files. if (len(raw_list[i]) >= 24): newNet.utilization = float(raw_list[i][22]) @@ -3207,17 +3212,17 @@ def onExportJSON(self): if not fileName: return - + try: outputFile = open(fileName, 'w') except: QMessageBox.question(self, 'Error',"Unable to write to " + fileName, QMessageBox.Ok) return - + self.updateLock.acquire() - + numItems = self.networkTable.rowCount() - + if numItems == 0: outputFile.close() self.updateLock.release() @@ -3226,65 +3231,65 @@ def onExportJSON(self): # This will create a dictionary with an item named 'wifi-aps' which will contain a list of networks outputdict = {} netlist = [] - + self.setWaitCursor() - + for i in range(0, numItems): curData = self.networkTable.item(i, 2).data(Qt.UserRole+1) netlist.append(curData.toJsondict()) - + outputdict['wifi-aps'] = netlist - + outputstr=json.dumps(outputdict) outputFile.write(outputstr) - + outputFile.close() - + self.setArrowCursor() - + self.updateLock.release() - + def onExportCSV(self): fileName = self.saveFileDialog() if not fileName: return - + try: outputFile = open(fileName, 'w') except: QMessageBox.question(self, 'Error',"Unable to write to " + fileName, QMessageBox.Ok) return - + outputFile.write('macAddr,vendor,SSID,Security,Privacy,Channel,Frequency,Signal Strength,Strongest Signal Strength,Bandwidth,Last Seen,First Seen,GPS Valid,Latitude,Longitude,Altitude,Speed,Strongest GPS Valid,Strongest Latitude,Strongest Longitude,Strongest Altitude,Strongest Speed,% Utilization,# of Stations\n') self.updateLock.acquire() numItems = self.networkTable.rowCount() - + if numItems == 0: outputFile.close() self.updateLock.release() return self.setWaitCursor() - + for i in range(0, numItems): curData = self.networkTable.item(i, 2).data(Qt.UserRole+1) outputFile.write(curData.macAddr + ',' + self.networkTable.item(i, 1).text() + ',"' + curData.ssid + '",' + curData.security + ',' + curData.privacy) outputFile.write(',' + curData.getChannelString() + ',' + str(curData.frequency) + ',' + str(curData.signal) + ',' + str(curData.strongestsignal) + ',' + str(curData.bandwidth) + ',' + - curData.lastSeen.strftime("%m/%d/%Y %H:%M:%S") + ',' + curData.firstSeen.strftime("%m/%d/%Y %H:%M:%S") + ',' + - str(curData.gps.isValid) + ',' + str(curData.gps.latitude) + ',' + str(curData.gps.longitude) + ',' + str(curData.gps.altitude) + ',' + str(curData.gps.speed) + ',' + + curData.lastSeen.strftime("%m/%d/%Y %H:%M:%S") + ',' + curData.firstSeen.strftime("%m/%d/%Y %H:%M:%S") + ',' + + str(curData.gps.isValid) + ',' + str(curData.gps.latitude) + ',' + str(curData.gps.longitude) + ',' + str(curData.gps.altitude) + ',' + str(curData.gps.speed) + ',' + str(curData.strongestgps.isValid) + ',' + str(curData.strongestgps.latitude) + ',' + str(curData.strongestgps.longitude) + ',' + str(curData.strongestgps.altitude) + ',' + str(curData.strongestgps.speed) + ',' + str(curData.utilization) + ',' + str(curData.stationcount) + '\n') - + outputFile.close() - + self.setArrowCursor() - + self.updateLock.release() - + def center(self): # Get our geometry qr = self.frameGeometry() @@ -3292,18 +3297,18 @@ def center(self): cp = QDesktopWidget().availableGeometry().center() # Move our center point to the desktop center point qr.moveCenter(cp) - # Move the top-left point of the application window to the top-left point of the qr rectangle, + # Move the top-left point of the application window to the top-left point of the qr rectangle, # basically centering the window self.move(qr.topLeft()) - + def requestRemoteInterfaces(self): url = "http://" + self.remoteAgentIP + ":" + str(self.remoteAgentPort) + "/wireless/interfaces" statusCode, responsestr = makeGetRequest(url) - + if statusCode == 200: try: interfaces = json.loads(responsestr) - + retList = interfaces['interfaces'] return statusCode, retList except: @@ -3314,10 +3319,10 @@ def requestRemoteInterfaces(self): def onRemoteAgentListener(self): if not self.agentListenerWindow: self.agentListenerWindow = AgentListenerDialog(mainWin=self) - + self.agentListenerWindow.show() self.agentListenerWindow.activateWindow() - + def onAgentListenerClosed(self): if self.agentListenerWindow: self.agentListenerWindow.close() @@ -3332,7 +3337,7 @@ def getAgentIPandPort(self): text, okPressed = QInputDialog.getText(self, "Remote Agent","Please provide the : of the remote agent\nor specify 'auto' to launch agent listener\n(auto requires agent to be on the same subnet and started with the --sendannounce flag):", QLineEdit.Normal, "127.0.0.1:8020") if (not okPressed) or text == '': return False, "", 0 - + # Validate the input p = re.compile('^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:[0-9]{1,5})') specIsGood = True @@ -3340,7 +3345,7 @@ def getAgentIPandPort(self): agentSpec = p.search(text).group(1) agentIP = agentSpec.split(':')[0] agentPort = int(agentSpec.split(':')[1]) - + if agentPort < 1 or agentPort > 65535: QMessageBox.question(self, 'Error',"Port must be in an acceptable IP range (1-65535)", QMessageBox.Ok) specIsGood = False @@ -3356,29 +3361,29 @@ def getAgentIPandPort(self): else: QMessageBox.question(self, 'Error',"Please enter it in the format :", QMessageBox.Ok) specIsGood = False - + return specIsGood, agentIP, agentPort - + def onRemoteFiles(self): specIsGood, agentIP, agentPort = self.getAgentIPandPort() - + if not specIsGood: return filesWin = RemoteFilesDialog(self,agentIP, agentPort, self) filesWin.exec() - + def onRemoteAgentConfig(self): specIsGood, agentIP, agentPort = self.getAgentIPandPort() - + if not specIsGood: return - + # Now we can connect. # Request the current config state. If all is well, open the dialog, # if it fails, notify the user retVal, retmsg, startupCfg, runningCfg = requestRemoteConfig(agentIP, agentPort) - + if retVal != 0: QMessageBox.question(self, 'Error',retmsg, QMessageBox.Ok) return @@ -3386,11 +3391,11 @@ def onRemoteAgentConfig(self): # There is both a global and class-based version of this function. # The global can take parameters, the class version uses the remote agent config settings retVal, interfaces = requestRemoteInterfaces(agentIP, agentPort) - + if retVal != 200: QMessageBox.question(self, 'Error','Unable to get remote interfaces.', QMessageBox.Ok) return - + configDialog = AgentConfigDialog(startupCfg, runningCfg, interfaces, agentIP, agentPort) configDialog.exec() @@ -3400,7 +3405,7 @@ def stopSpectrum24Line(self): self.btSpectrumTimer.stop() elif self.hackrfShowSpectrum24: self.hackrfSpectrumTimer.stop() - + self.spectrum24Line.clear() self.chart24.removeSeries(self.spectrum24Line) self.spectrum24Line = None @@ -3420,12 +3425,12 @@ def stopSpectrum5Line(self): self.hackrfShowSpectrum5 = False self.hackrfLastSpectrumState5 = False self.menuHackrfSpectrum5.setChecked(False) - + def onRemoteAgent(self): if (self.menuRemoteAgent.isChecked() == self.lastRemoteState): # There's an extra bounce in this for some reason. return - + if self.menuRemoteAgent.isChecked(): # We're transitioning to a remote agent text, okPressed = QInputDialog.getText(self, "Remote Agent","Please provide the : of the remote agent\nor specify 'auto' to launch agent listener\n(auto requires agent to be on the same subnet and started with the --sendannounce flag):", QLineEdit.Normal, "127.0.0.1:8020") @@ -3437,7 +3442,7 @@ def onRemoteAgent(self): agentSpec = p.search(text).group(1) self.remoteAgentIP = agentSpec.split(':')[0] self.remoteAgentPort = int(agentSpec.split(':')[1]) - + if self.remoteAgentPort < 1 or self.remoteAgentPort > 65535: QMessageBox.question(self, 'Error',"Port must be in an acceptable IP range (1-65535)", QMessageBox.Ok) self.menuRemoteAgent.setChecked(False) @@ -3455,19 +3460,19 @@ def onRemoteAgent(self): QMessageBox.question(self, 'Error',"Please enter it in the format :", QMessageBox.Ok) self.menuRemoteAgent.setChecked(False) specIsGood = False - + if not specIsGood: self.menuRemoteAgent.setChecked(False) self.remoteAgentUp = False return - + # Check that the agent is actually online: if not portOpen(self.remoteAgentIP, self.remoteAgentPort): QMessageBox.question(self, 'Error',"The agent does not appear to be up or is unreachable.", QMessageBox.Ok) self.remoteAgentUp = False self.menuRemoteAgent.setChecked(False) return - + # If we're here we're probably good. reply = QMessageBox.question(self, 'Question',"Would you like to just do 1 scan pass when pressing scan?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) @@ -3479,14 +3484,14 @@ def onRemoteAgent(self): # Configure the GUI. self.lblInterface.setText("Remote Interface") statusCode, interfaces = self.requestRemoteInterfaces() - + if statusCode != 200: QMessageBox.question(self, 'Error',"An error occurred getting the remote interfaces. Please check that the agent is running.", QMessageBox.Ok) self.menuRemoteAgent.setChecked(False) self.lblInterface.setText("Local Interface") self.statusBar().showMessage('An error occurred getting the remote interfaces') return - + # Okay, we have interfaces. Let's load them self.combo.clear() @@ -3498,11 +3503,11 @@ def onRemoteAgent(self): self.statusBar().showMessage('No wireless interfaces found.') # Now we can signal that the remote agent is up. - + self.remoteAgentUp = True self.missedAgentCycles = 0 - - self.lastRemoteState = self.menuRemoteAgent.isChecked() + + self.lastRemoteState = self.menuRemoteAgent.isChecked() # HackRF spectrum errcode, errmsg, hashackrf, scan24running, scan5running = remoteHackrfStatus(self.remoteAgentIP, self.remoteAgentPort) @@ -3511,9 +3516,9 @@ def onRemoteAgent(self): hashackrf = False scan24running = False scan5running = False - + self.remoteHasHackrf = hashackrf - + self.menuHackrfSpectrum24.setEnabled(self.remoteHasHackrf) self.menuHackrfSpectrum5.setEnabled(self.remoteHasHackrf) self.menuHackrfSpectrum24.setChecked(scan24running) @@ -3527,15 +3532,15 @@ def onRemoteAgent(self): elif scan5running: # disable 24 hackrf spectrum self.menuHackrfSpectrum24.setEnabled(False) - + # Deal with bluetooth. # If we're running local spectrum or discovery, stop it. # Then reconfigure based on remote agent state - + # Local Spectrum if self.bluetooth: self.bluetooth.stopScanning() - + errcode, errmsg, self.hasRemoteBluetooth, self.hasRemoteUbertooth, scanRunning, discoveryRunning, beaconRunning = getRemoteBluetoothRunningServices(self.remoteAgentIP, self.remoteAgentPort) if errcode == 0: self.setBluetoothMenu() @@ -3546,7 +3551,7 @@ def onRemoteAgent(self): self.btSpectrumTimer.start(self.btSpectrumTimeoutRemote) else: self.stopSpectrum24Line() - + self.btBeacon = beaconRunning self.menuBtBeacon.setChecked(beaconRunning) self.btLastBeaconState = self.btBeacon @@ -3557,17 +3562,17 @@ def onRemoteAgent(self): self.btShowSpectrum = False self.btLastSpectrumState = False self.stopSpectrum24Line() - + self.menuBtSpectrum.setChecked(False) self.btSpectrumTimer.stop() - + self.btBeacon = False self.btLastBeaconState = self.btBeacon - + self.menuBtBeacon.setEnabled(False) self.menuBtSpectrum.setEnabled(False) # self.menuBtSpectrumGain.setEnabled(False) - + self.onGPSStatus() else: # Stay local. @@ -3581,32 +3586,32 @@ def onRemoteAgent(self): self.lblInterface.setText("Local Interface") self.combo.clear() interfaces=WirelessEngine.getInterfaces() - + if (len(interfaces) > 0): for curInterface in interfaces: self.combo.addItem(curInterface) else: self.statusBar().showMessage('No wireless interfaces found.') - self.lastRemoteState = self.menuRemoteAgent.isChecked() + self.lastRemoteState = self.menuRemoteAgent.isChecked() self.onGPSStatus() - + self.btShowSpectrum = False if self.spectrum24Line: self.stopSpectrum24Line() - + self.menuBtSpectrum.setChecked(False) self.menuHackrfSpectrum24.setChecked(False) self.menuHackrfSpectrum5.setChecked(False) self.menuHackrfSpectrum24.setEnabled(self.hackrf.hasHackrf) self.menuHackrfSpectrum5.setEnabled(self.hackrf.hasHackrf) - + self.btBeacon = self.bluetooth.beaconRunning() self.menuBtBeacon.setChecked(self.btBeacon) self.btLastBeaconState = self.btBeacon self.setBluetoothMenu() - + self.checkNotifyBluetoothDiscovery() self.checkNotifyAdvancedScan() @@ -3616,17 +3621,17 @@ def onAbout(self): aboutMsg += "https://github.com/ghostop14\n\n" aboutMsg += "This application is open source and licensed\n" aboutMsg += "under the terms fo the GPL version 3\n" - + QMessageBox.question(self, 'Message',aboutMsg, QMessageBox.Ok) - + def closeEvent(self, event): # reply = QMessageBox.question(self, 'Message',"Are you sure to quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) # if reply == QMessageBox.Yes: # event.accept() #else: - # event.ignore() - + # event.ignore() + if self.scanRunning: if not self.remoteAgentUp: self.scanThread.signalStop = True @@ -3641,38 +3646,38 @@ def closeEvent(self, event): self.telemetryWindows[curKey] = None except: pass - + if self.advancedScan: self.advancedScan.close() self.advancedScan = None - + if self.bluetoothWin: self.bluetoothWin.close() self.bluetoothWin = None - + if self.btBeacon: self.bluetooth.stopBeacon() - + if self.agentListenerWindow: self.agentListenerWindow.close() self.agentListenerWindow = None - + if self.gpsCoordWindow: self.gpsCoordWindow.close() self.gpsCoordWindow = None - + if self.bluetooth: self.bluetooth.stopScanning() - + self.hackrf.stopScanning() - + event.accept() def onRescanInterfaces(self): self.combo.clear() - + interfaces=WirelessEngine.getInterfaces() - + if (len(interfaces) > 0): for curInterface in interfaces: self.combo.addItem(curInterface) @@ -3681,7 +3686,7 @@ def onRescanInterfaces(self): if __name__ == '__main__': # Code to add paths dirname, filename = os.path.split(os.path.abspath(__file__)) - + if dirname not in sys.path: sys.path.insert(0, dirname) pluginsdir = dirname+'/plugins' @@ -3691,12 +3696,10 @@ def onRescanInterfaces(self): if os.path.isfile(pluginsdir + '/falconwifi.py'): from falconwifidialogs import AdvancedScanDialog hasFalcon = True - + app = QApplication(sys.argv) mainWin = mainWindow() result = app.exec_() sys.exit(result) # Some thread is still blocking... # os._exit(result) - - From 13cfe3390ee2c9e6febe0a47fe2612e510eff1c3 Mon Sep 17 00:00:00 2001 From: stryngs Date: Thu, 20 May 2021 03:07:59 -0400 Subject: [PATCH 3/4] Added regexCompiler and modified selfs so that compilation takes place only once --- wirelessengine.py | 369 +++++++++++++++++++++++----------------------- 1 file changed, 184 insertions(+), 185 deletions(-) diff --git a/wirelessengine.py b/wirelessengine.py index 959647e..1dfa112 100755 --- a/wirelessengine.py +++ b/wirelessengine.py @@ -1,22 +1,22 @@ #!/usr/bin/python3 -# +# # Copyright 2017 ghostop14 -# +# # This is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. -# +# # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this software; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, # Boston, MA 02110-1301, USA. -# +# import os import subprocess @@ -103,7 +103,7 @@ channelToFreq['189'] = '4945' channelToFreq['192'] = '4960' channelToFreq['196'] = '4980' - + # ------------------ WirelessNetwork class ------------------------------------ class WirelessClient(object): def __init__(self): @@ -123,10 +123,10 @@ def __init__(self): self.probedSSIDs = [] # Used for tracking in network table self.foundInList = False - + def __str__(self): retVal = "" - + retVal += "MAC Address: " + self.macAddr + "\n" retVal += "Associated Access Point Mac Address: " + self.apMacAddr + "\n" retVal += "SSID: " + self.ssid + "\n" @@ -136,33 +136,33 @@ def __str__(self): retVal += "First Seen: " + str(self.firstSeen) + "\n" retVal += "Last Seen: " + str(self.lastSeen) + "\n" retVal += "Probed SSIDs:" - + if (len(self.probedSSIDs) > 0): for curSSID in self.probedSSIDs: retVal += " " + curSSID - + retVal += "\n" else: retVal += " No probes observed\n" - + retVal += "Last GPS:\n" retVal += str(self.gps) retVal += "Strongest GPS:\n" retVal += str(self.strongestgps) - + return retVal - + def copy(self): return copy.deepcopy(self) - + def __eq__(self, obj): # This is equivance.... == if not isinstance(obj, WirelessClient): return False - + if self.macAddr != obj.macAddr: return False - + if self.apMacAddr != obj.apMacAddr: return False @@ -170,21 +170,21 @@ def __eq__(self, obj): def __ne__(self, other): return not self.__eq__(other) - + def getKey(self): return self.macAddr - + def associated(self): if len(self.apMacAddr) == 0 or (self.apMacAddr == "(not associated)"): return False - + return True - + def createFromJsonDict(jsondict): retVal = WirelessClient() retVal.fromJsondict(jsondict) return retVal - + def fromJsondict(self, dictjson): # Note: if the json dictionary isn't correct, this will naturally throw an exception that may # need to be caught for error detection @@ -192,7 +192,7 @@ def fromJsondict(self, dictjson): self.apMacAddr = dictjson['apMacAddr'] self.ssid = dictjson['ssid'] self.channel = int(dictjson['channel']) - + self.signal = int(dictjson['signal']) self.strongestsignal = int(dictjson['strongestsignal']) @@ -204,23 +204,23 @@ def fromJsondict(self, dictjson): self.gps.altitude = float(dictjson['alt']) self.gps.speed = float(dictjson['speed']) self.gps.isValid = stringtobool(dictjson['gpsvalid']) - + self.strongestgps.latitude = float(dictjson['strongestlat']) self.strongestgps.longitude = float(dictjson['strongestlon']) self.strongestgps.altitude = float(dictjson['strongestalt']) self.strongestgps.speed = float(dictjson['strongestspeed']) self.strongestgps.isValid = stringtobool(dictjson['strongestgpsvalid']) - + self.probedSSIDs = dictjson['probedssids'] - + def fromJson(self, jsonstr): dictjson = json.loads(jsonstr) self.fromJsondict(dictjson) - + def toJson(self): dictjson = self.toJsondict() return json.dumps(dictjson) - + def toJsondict(self): dictjson = {} dictjson['type'] = 'wifi-client' @@ -236,7 +236,7 @@ def toJsondict(self): dictjson['alt'] = str(self.gps.altitude) dictjson['speed'] = str(self.gps.speed) dictjson['gpsvalid'] = str(self.gps.isValid) - + dictjson['strongestsignal'] = self.strongestsignal dictjson['strongestlat'] = str(self.strongestgps.latitude) dictjson['strongestlon'] = str(self.strongestgps.longitude) @@ -245,15 +245,15 @@ def toJsondict(self): dictjson['strongestgpsvalid'] = str(self.strongestgps.isValid) dictjson['probedssids'] = self.probedSSIDs - + return dictjson - + class WirelessNetwork(object): ERR_NETDOWN = 156 ERR_OPNOTSUPPORTED = 161 ERR_DEVICEBUSY = 240 ERR_OPNOTPERMITTED = 255 - + def __init__(self): self.macAddr = "" self.ssid = "" @@ -276,15 +276,15 @@ def __init__(self): self.gps = SparrowGPS() self.strongestsignal = self.signal self.strongestgps = SparrowGPS() - + # Used for tracking in network table self.foundInList = False - + super().__init__() def __str__(self): retVal = "" - + retVal += "MAC Address: " + self.macAddr + "\n" retVal += "SSID: " + self.ssid + "\n" retVal += "Mode: " + self.mode + "\n" @@ -312,12 +312,12 @@ def __str__(self): def copy(self): return copy.deepcopy(self) - + def __eq__(self, obj): # This is equivance.... == if not isinstance(obj, WirelessNetwork): return False - + if self.macAddr != obj.macAddr: return False if self.ssid != obj.ssid: @@ -325,23 +325,23 @@ def __eq__(self, obj): if self.mode != obj.mode: return False - + if self.security != obj.security: return False - + if self.channel != obj.channel: return False - + return True def __ne__(self, other): return not self.__eq__(other) - + def createFromJsonDict(jsondict): retVal = WirelessNetwork() retVal.fromJsondict(jsondict) return retVal - + def fromJsondict(self, dictjson): # Note: if the json dictionary isn't correct, this will naturally throw an exception that may # need to be caught for error detection @@ -368,17 +368,17 @@ def fromJsondict(self, dictjson): self.gps.altitude = float(dictjson['alt']) self.gps.speed = float(dictjson['speed']) self.gps.isValid = stringtobool(dictjson['gpsvalid']) - + self.strongestgps.latitude = float(dictjson['strongestlat']) self.strongestgps.longitude = float(dictjson['strongestlon']) self.strongestgps.altitude = float(dictjson['strongestalt']) self.strongestgps.speed = float(dictjson['strongestspeed']) self.strongestgps.isValid = stringtobool(dictjson['strongestgpsvalid']) - + def fromJson(self, jsonstr): dictjson = json.loads(jsonstr) self.fromJsondict(dictjson) - + def toJsondict(self): dictjson = {} dictjson['type'] = 'wifi-ap' @@ -406,53 +406,52 @@ def toJsondict(self): dictjson['alt'] = str(self.gps.altitude) dictjson['speed'] = str(self.gps.speed) dictjson['gpsvalid'] = str(self.gps.isValid) - + dictjson['strongestlat'] = str(self.strongestgps.latitude) dictjson['strongestlon'] = str(self.strongestgps.longitude) dictjson['strongestalt'] = str(self.strongestgps.altitude) dictjson['strongestspeed'] = str(self.strongestgps.speed) dictjson['strongestgpsvalid'] = str(self.strongestgps.isValid) - + return dictjson - + def toJson(self): dictjson = self.toJsondict() return json.dumps(dictjson) - + def getChannelString(self): if self.bandwidth == 40 and self.secondaryChannel > 0: retVal = str(self.channel) + '+' + str(self.secondaryChannel) else: retVal = str(self.channel) - + return retVal - + def getKey(self): return self.macAddr + self.ssid+str(self.channel) - + class WirelessEngine(object): def __init__(self): super().__init__() + ## Store the regex compiles + self.regexCompiler() + def getMacAddress(interface): macaddr = "" - + try: f = open('/sys/class/net/'+interface+'/address', 'r') macaddr = f.readline().strip() f.close() except: pass - + return macaddr - + def getFrequencyForChannel(channelNumber): - channelStr = str(channelNumber) - if channelStr in channelToFreq: - return channelToFreq[channelStr] - else: - return None - + return channelToFreq.get(str(channelNumber)) + def getSignalQualityFromDB0To5(dBm): # Based on same scale tha Microsoft uses. # See https://stackoverflow.com/questions/15797920/how-to-convert-wifi-signal-strength-from-quality-percent-to-rssi-dbm @@ -461,8 +460,8 @@ def getSignalQualityFromDB0To5(dBm): elif dBm >= -50: quality = 100 else: - quality = 2 * (dBm + 100) - + quality = 2 * (dBm + 100) + return int(4*quality/100) def getSignalQualityFromDB(dBm): @@ -473,17 +472,17 @@ def getSignalQualityFromDB(dBm): elif dBm >= -50: quality = 100 else: - quality = 2 * (dBm + 100) - + quality = 2 * (dBm + 100) + return quality def convertUnknownToString(ssid): if '\\x00' not in ssid: return ssid - + retVal = ssid.replace('\\x00', '') numblanks = ssid.count('\\x00') - + if len(retVal) == 0: if numblanks > 0: return '' @@ -491,15 +490,15 @@ def convertUnknownToString(ssid): return '' else: return ssid - + def getInterfaces(printResults=False): result = subprocess.run(['iwconfig'], stdout=subprocess.PIPE,stderr=subprocess.DEVNULL) wirelessResult = result.stdout.decode('UTF-8') p = re.compile('^(w.*?) .*', re.MULTILINE) tmpInterfaces = p.findall(wirelessResult) - + retVal = [] - + if (len(tmpInterfaces) > 0): for curInterface in tmpInterfaces: tmpStr=curInterface.replace(' ','') @@ -521,9 +520,9 @@ def getMonitoringModeInterfaces(printResults=False): wirelessResult = result.stdout.decode('UTF-8') p = re.compile('^(.*?) IEEE.*?Mode:Monitor', re.MULTILINE) tmpInterfaces = p.findall(wirelessResult) - + retVal = [] - + if (len(tmpInterfaces) > 0): for curInterface in tmpInterfaces: tmpStr=curInterface.replace(' ','') @@ -541,7 +540,7 @@ def getMonitoringModeInterfaces(printResults=False): monLine = i - 1 break else: - i = i + 1 + i = i + 1 if monLine > -1: p = re.compile('^(.*?) .*', re.MULTILINE) tmpInterfaces = p.findall(lines[monLine]) @@ -563,7 +562,7 @@ def getNetworksAsJson(interfaceName, gpsData, huntChannelList=None): # reports busy (it does happen if we query too fast.) retries = 0 retCode = WirelessNetwork.ERR_DEVICEBUSY - + while (retCode == WirelessNetwork.ERR_DEVICEBUSY) and (retries < 3): # Handle retries in case we get a busy response retCode, errString, wirelessNetworks = WirelessEngine.scanForNetworks(interfaceName) @@ -582,43 +581,43 @@ def getNetworksAsJson(interfaceName, gpsData, huntChannelList=None): retries += 1 if retCode == WirelessNetwork.ERR_DEVICEBUSY: sleep(0.2) - + for curKey in tmpWirelessNetworks.keys(): curNet = tmpWirelessNetworks[curKey] wirelessNetworks[curNet.getKey()] = tmpWirelessNetworks[curNet.getKey()] - + retVal = {} retVal['errCode'] = retCode retVal['errString'] = errString - + netList = [] - + for curKey in wirelessNetworks.keys(): curNet = wirelessNetworks[curKey] if gpsData is not None: curNet.gps.copy(gpsData) netList.append(curNet.toJsondict()) - + gpsdict = {} - + gpsloc = SparrowGPS() if (gpsData is not None): gpsloc.copy(gpsData) - + gpsdict['latitude'] = gpsloc.latitude gpsdict['longitude'] = gpsloc.longitude gpsdict['altitude'] = gpsloc.altitude gpsdict['speed'] = gpsloc.speed retVal['gps'] = gpsdict - + retVal['networks'] = netList - + jsonstr = json.dumps(retVal) - + return retCode, errString, jsonstr - - def scanForNetworks(interfaceName, frequency=0, printResults=False): - + + def scanForNetworks(self, interfaceName, frequency=0, printResults=False): + if frequency == 0: result = subprocess.run(['iw', 'dev', interfaceName, 'scan'], stdout=subprocess.PIPE,stderr=subprocess.STDOUT) else: @@ -627,16 +626,16 @@ def scanForNetworks(interfaceName, frequency=0, printResults=False): retCode = result.returncode errString = "" wirelessResult = result.stdout.decode('UTF-8') - + # debug if (printResults): print('Return Code ' + str(retCode)) print(wirelessResult) - + wirelessNetworks = {} - + if (retCode == 0): - wirelessNetworks = WirelessEngine.parseIWoutput(wirelessResult) + wirelessNetworks = self.parseIWoutput(wirelessResult) else: # errCodes: # 156 = Network is down (i.e. switch may be turned off) @@ -648,58 +647,58 @@ def scanForNetworks(interfaceName, frequency=0, printResults=False): errString = 'Device is busy' elif (retCode == WirelessNetwork.ERR_OPNOTPERMITTED): errString = errString + '. Did you run as root?' - + return retCode, errString, wirelessNetworks - + def getFieldValue(p, curLine): matchobj = p.search(curLine) - + if not matchobj: return "" - + try: retVal = matchobj.group(1) except: retVal = "" - + return retVal - - def parseIWoutput(iwOutput): - - # Define search regexes once: - p_bss = re.compile('^BSS (.*?)\(') - p_ssid = re.compile('^.+?SSID: +(.*)') - p_ess = re.compile('^ capability:.*(ESS)') - p_ess_privacy = re.compile('^ capability:.*(ESS Privacy)') - p_ibss = re.compile('^ capability:.*(IBSS)') - p_ibss_privacy = re.compile('^ capability:.*(IBSS Privacy)') - p_auth_suites = re.compile('.*?Authentication suites: *(.*)') - p_pw_ciphers = re.compile('.*?Pairwise ciphers: *(.*)') - p_param_channel = re.compile('^.*?DS Parameter set: channel +([0-9]+).*') - p_primary_channel = re.compile('^.*?primary channel: +([0-9]+).*') - p_freq = re.compile('^.*?freq:.*?([0-9]+).*') - p_signal = re.compile('^.*?signal:.*?([\-0-9]+).*?dBm') - p_ht = re.compile('.*?HT20/HT40.*') - p_bw = re.compile('.*?\\* channel width:.*?([0-9]+) MHz.*') - p_secondary = re.compile('^.*?secondary channel offset: *([^ \\t]+).*') - p_thirdfreq = re.compile('^.*?center freq segment 1: *([^ \\t]+).*') - p_stationcount = re.compile('.*station count: ([0-9]+)') - p_utilization = re.compile('.*channel utilisation: ([0-9]+)/255') + + def regexCompiler(self): + self.p_bss = re.compile('^BSS (.*?)\(') + self.p_ssid = re.compile('^.+?SSID: +(.*)') + self.p_ess = re.compile('^ capability:.*(ESS)') + self.p_ess_privacy = re.compile('^ capability:.*(ESS Privacy)') + self.p_ibss = re.compile('^ capability:.*(IBSS)') + self.p_ibss_privacy = re.compile('^ capability:.*(IBSS Privacy)') + self.p_auth_suites = re.compile('.*?Authentication suites: *(.*)') + self.p_pw_ciphers = re.compile('.*?Pairwise ciphers: *(.*)') + self.p_param_channel = re.compile('^.*?DS Parameter set: channel +([0-9]+).*') + self.p_primary_channel = re.compile('^.*?primary channel: +([0-9]+).*') + self.p_freq = re.compile('^.*?freq:.*?([0-9]+).*') + self.p_signal = re.compile('^.*?signal:.*?([\-0-9]+).*?dBm') + self.p_ht = re.compile('.*?HT20/HT40.*') + self.p_bw = re.compile('.*?\\* channel width:.*?([0-9]+) MHz.*') + self.p_secondary = re.compile('^.*?secondary channel offset: *([^ \\t]+).*') + self.p_thirdfreq = re.compile('^.*?center freq segment 1: *([^ \\t]+).*') + self.p_stationcount = re.compile('.*station count: ([0-9]+)') + self.p_utilization = re.compile('.*channel utilisation: ([0-9]+)/255') + + def parseIWoutput(self, iwOutput): # start retVal = {} curNetwork = None now=datetime.datetime.now() - + # This now supports direct from STDOUT via scanForNetworks, # and input from a file as f.readlines() which returns a list if type(iwOutput) == str: inputLines = iwOutput.splitlines() else: inputLines = iwOutput - + for curLine in inputLines: - fieldValue = WirelessEngine.getFieldValue(p_bss, curLine) - + fieldValue = WirelessEngine.getFieldValue(self.p_bss, curLine) + if (len(fieldValue) > 0): # New object if curNetwork is not None: @@ -714,119 +713,119 @@ def parseIWoutput(iwOutput): curNetwork.firstSeen = now curNetwork.macAddr = fieldValue continue - + if curNetwork is None: # If we don't have a network object yet, then we haven't # seen a BSSID so just keep going through the lines. continue - fieldValue = WirelessEngine.getFieldValue(p_ssid, curLine) - + fieldValue = WirelessEngine.getFieldValue(self.p_ssid, curLine) + if (len(fieldValue) > 0): curNetwork.ssid = WirelessEngine.convertUnknownToString(fieldValue) - - fieldValue = WirelessEngine.getFieldValue(p_ess, curLine) - + + fieldValue = WirelessEngine.getFieldValue(self.p_ess, curLine) + if (len(fieldValue) > 0): curNetwork.mode = "AP" # Had issue with WEP not showing up. # If capability has "ESS Privacy" there's something there. # If it's PSK, etc. there will be other RSN fields, etc. # So for now start by assuming WEP - + # See: https://wiki.archlinux.org/index.php/Wireless_network_configuration - fieldValue = WirelessEngine.getFieldValue(p_ess_privacy, curLine) - + fieldValue = WirelessEngine.getFieldValue(self.p_ess_privacy, curLine) + if (len(fieldValue) > 0): curNetwork.security = "WEP" curNetwork.privacy = "WEP" - + continue #Found the item - - fieldValue = WirelessEngine.getFieldValue(p_ibss, curLine) - + + fieldValue = WirelessEngine.getFieldValue(self.p_ibss, curLine) + if (len(fieldValue) > 0): curNetwork.mode = "Ad Hoc" curNetwork.security = "[Ad-Hoc] Open" - fieldValue = WirelessEngine.getFieldValue(p_ibss_privacy, curLine) - + fieldValue = WirelessEngine.getFieldValue(self.p_ibss_privacy, curLine) + if (len(fieldValue) > 0): curNetwork.security = "[Ad-Hoc] WEP" curNetwork.privacy = "WEP" - + continue #Found the item # Station count - fieldValue = WirelessEngine.getFieldValue(p_stationcount, curLine) + fieldValue = WirelessEngine.getFieldValue(self.p_stationcount, curLine) if (len(fieldValue) > 0): curNetwork.stationcount = int(fieldValue) continue #Found the item - + # Utilization - fieldValue = WirelessEngine.getFieldValue(p_utilization, curLine) + fieldValue = WirelessEngine.getFieldValue(self.p_utilization, curLine) if (len(fieldValue) > 0): utilization = round(float(fieldValue) / 255.0 * 100.0 * 100.0) / 100.0 curNetwork.utilization = utilization continue #Found the item - + # Auth suites - fieldValue = WirelessEngine.getFieldValue(p_auth_suites, curLine) - + fieldValue = WirelessEngine.getFieldValue(self.p_auth_suites, curLine) + if (len(fieldValue) > 0): curNetwork.security = fieldValue continue #Found the item - + # p = re.compile('.*?Group cipher: *(.*)') - fieldValue = WirelessEngine.getFieldValue(p_pw_ciphers, curLine) + fieldValue = WirelessEngine.getFieldValue(self.p_pw_ciphers, curLine) fieldValue = fieldValue.replace(' ', '/') - + if (len(fieldValue) > 0): curNetwork.privacy = fieldValue curNetwork.cipher = fieldValue continue #Found the item - - fieldValue = WirelessEngine.getFieldValue(p_param_channel, curLine) - + + fieldValue = WirelessEngine.getFieldValue(self.p_param_channel, curLine) + if (len(fieldValue) > 0): curNetwork.channel = int(fieldValue) continue #Found the item - - fieldValue = WirelessEngine.getFieldValue(p_primary_channel, curLine) - + + fieldValue = WirelessEngine.getFieldValue(self.p_primary_channel, curLine) + if (len(fieldValue) > 0): curNetwork.channel = int(fieldValue) continue #Found the item - - fieldValue = WirelessEngine.getFieldValue(p_freq, curLine) - + + fieldValue = WirelessEngine.getFieldValue(self.p_freq, curLine) + if (len(fieldValue) > 0): curNetwork.frequency = int(fieldValue) continue #Found the item - - fieldValue = WirelessEngine.getFieldValue(p_signal, curLine) - + + fieldValue = WirelessEngine.getFieldValue(self.p_signal, curLine) + # This test is different. dBm is negative so can't test > 0. 10dBm is really high so lets use that if (len(fieldValue) > 0): curNetwork.signal = int(fieldValue) curNetwork.strongestsignal = curNetwork.signal continue #Found the item - - fieldValue = WirelessEngine.getFieldValue(p_ht, curLine) - + + fieldValue = WirelessEngine.getFieldValue(self.p_ht, curLine) + if (len(fieldValue) > 0): if (curNetwork.bandwidth == 20): curNetwork.bandwidth = 40 continue #Found the item - - fieldValue = WirelessEngine.getFieldValue(p_bw, curLine) - + + fieldValue = WirelessEngine.getFieldValue(self.p_bw, curLine) + if (len(fieldValue) > 0): curNetwork.bandwidth = int(fieldValue) continue #Found the item - - fieldValue = WirelessEngine.getFieldValue(p_secondary, curLine) - + + fieldValue = WirelessEngine.getFieldValue(self.p_secondary, curLine) + if (len(fieldValue) > 0): curNetwork.secondaryChannelLocation = fieldValue if (fieldValue == 'above'): @@ -834,58 +833,58 @@ def parseIWoutput(iwOutput): elif (fieldValue == 'below'): curNetwork.secondaryChannel = curNetwork.channel - 4 # else it'll say 'no secondary' - + continue #Found the item - - fieldValue = WirelessEngine.getFieldValue(p_thirdfreq, curLine) - + + fieldValue = WirelessEngine.getFieldValue(self.p_thirdfreq, curLine) + if (len(fieldValue) > 0): curNetwork.thirdChannel = int(fieldValue) - + continue #Found the item - + # #### End loop ###### - + # Add the last network if curNetwork is not None: if curNetwork.channel > 0: # I did see incomplete output from iw where not all the data was there retVal[curNetwork.getKey()] = curNetwork - + return retVal - + if __name__ == '__main__': # WirelessEngine.getMacAddress('wlan0mon') if os.geteuid() != 0: print("ERROR: You need to have root privileges to run this script. Please try again, this time using 'sudo'. Exiting.\n") exit(2) # for debugging - + # change this interface name to test it. wirelessInterfaces = WirelessEngine.getInterfaces() - + if len(wirelessInterfaces) == 0: print("ERROR: Unable to find wireless interface.\n") exit(1) - + winterface = wirelessInterfaces[0] print('Scanning for wireless networks on ' + winterface + '...') - + # Testing to/from Json # convert to Json retCode, errString, jsonstr=WirelessEngine.getNetworksAsJson(winterface, None) # Convert back j=json.loads(jsonstr) - + # print results print('Error Code: ' + str(j['errCode']) + '\n') - + if j['errCode'] == 0: for curNetDict in j['networks']: newNet = WirelessNetwork.createFromJsonDict(curNetDict) print(newNet) - - else: + + else: print('Error String: ' + j['errString'] + '\n') - + print('Done.\n') From 2dc4c06870336b73af840e0665f2bd058de08ce5 Mon Sep 17 00:00:00 2001 From: stryngs Date: Fri, 21 May 2021 01:56:18 -0400 Subject: [PATCH 4/4] Moved regex to the class level --- sparrow-wifi.py | 8 +- sparrowbluetooth.py | 473 ++++++++++++++++++++++---------------------- 2 files changed, 246 insertions(+), 235 deletions(-) diff --git a/sparrow-wifi.py b/sparrow-wifi.py index b5f52cd..6d0e945 100755 --- a/sparrow-wifi.py +++ b/sparrow-wifi.py @@ -638,11 +638,11 @@ def checkForBluetooth(self): self.hasRemoteBluetooth = False self.hasRemoteUbertooth = False - numBtAdapters = len(SparrowBluetooth.getBluetoothInterfaces()) + numBtAdapters = len(self.spBth.getBluetoothInterfaces()) if numBtAdapters > 0: self.hasBluetooth = True - if SparrowBluetooth.getNumUbertoothDevices() > 0: + if self.spBth.getNumUbertoothDevices() > 0: #SparrowBluetooth.ubertoothStopSpecan() errcode, errmsg = SparrowBluetooth.hasUbertoothTools() # errcode, errmsg = SparrowBluetooth.ubertoothOnline() @@ -650,7 +650,7 @@ def checkForBluetooth(self): self.hasUbertooth = True if self.hasBluetooth or self.hasUbertooth: - self.bluetooth = SparrowBluetooth() + self.bluetooth = self.spBth else: self.bluetooth = None @@ -673,6 +673,8 @@ def __init__(self): self.spectrum24Line = None self.spectrum5Line = None + ## Instantiate SparrowBluetooth for self purposes + self.spBth = SparrowBluetooth() self.checkForBluetooth() self.bluetoothWin = None diff --git a/sparrowbluetooth.py b/sparrowbluetooth.py index 4b7c249..e7a74ed 100755 --- a/sparrowbluetooth.py +++ b/sparrowbluetooth.py @@ -1,22 +1,22 @@ #!/usr/bin/python3 -# +# # Copyright 2017 ghostop14 -# +# # This is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. -# +# # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this software; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, # Boston, MA 02110-1301, USA. -# +# import os import subprocess @@ -41,12 +41,12 @@ def toHex(val): def hexSplit(string): retVal = ' ' return retVal.join([string[i:i+2] for i in range(0, len(string), 2)]) - + # ------------------ Bluetooth Device Descriptor Class ---------------------------------- class BluetoothDevice(object): BT_CLASSIC = 1 BT_LE = 2 - + def __init__(self): self.uuid="" self.macAddress = "" @@ -65,9 +65,9 @@ def __init__(self): self.gps = SparrowGPS() self.strongestRssi = self.rssi self.strongestgps = SparrowGPS() - + self.foundInList = False - + def __str__(self): retVal = "" retVal += "UUID: " + self.uuid + '\n' @@ -78,7 +78,7 @@ def __str__(self): retVal += 'btType: Bluetooth Classic\n' else: retVal += 'btType: Bluetooth Low Energy (BTLE)\n' - + retVal += "Bluetooth Description: " + self.bluetoothDescription + '\n' retVal += "RSSI: " + str(self.rssi) + '\n' retVal += "TX Power: " + str(self.txPower) + '\n' @@ -86,25 +86,25 @@ def __str__(self): retVal += "Estimated Range (m): " + str(self.iBeaconRange) + '\n' retVal += "Strongest RSSI: " + str(self.strongestRssi) + '\n' - + retVal += "Last GPS:\n" retVal += str(self.gps) retVal += "Strongest GPS:\n" retVal += str(self.strongestgps) - + return retVal def __eq__(self, obj): # This is equivance.... == if not isinstance(obj, BluetoothDevice): return False - + if self.uuid != obj.uuid: return False - + if self.macAddress != obj.macAddress: return False - + if self.btType != obj.btType: return False @@ -112,7 +112,7 @@ def __eq__(self, obj): def __ne__(self, other): return not self.__eq__(other) - + def copy(self, other): self.uuid=other.uuid self.macAddress = other.macAddress @@ -131,14 +131,14 @@ def copy(self, other): self.gps.copy(other.gps) self.strongestRssi = other.strongestRssi self.strongestgps.copy(other.strongestgps) - + self.foundInList = False - + def getKey(self): key = self.macAddress # + "_" + str(self.btType) - + return key - + def calcRange(self): if not self.txPowerValid or self.txPower == 0: self.iBeaconRange = -1 @@ -152,15 +152,15 @@ def calcRange(self): # self.iBeaconRange = ratio ** 10 #else: # self.iBeaconRange = 0.89976* (ratio ** 7.7095) + 0.111 - + #self.iBeaconRange = round(self.iBeaconRange, 2) - + try: ratio_db = float(self.txPower - self.rssi) - + # txPower is supposed to be the RSSI measured at 1m. # In reality that's not quite what I've observed. - + # txPower may be a default/guess so watch the math. # Generally rssi < txPower making ratio_db >= 0 if ratio_db < 0.0: @@ -172,7 +172,7 @@ def calcRange(self): elif ratio_db <= 3.0: self.iBeaconRange = 1.0 return - + #n = 3 # free space n = 2, real-world range is 2.7 - 4.3 # If we don't have an rssi, this could calc wrong #dist = 10 ** (ratio_db / (10*n)) @@ -180,12 +180,12 @@ def calcRange(self): # Safety check on sqrt if dist < 0.0: dist = 0.0 - + dist = math.sqrt(dist) self.iBeaconRange = round(dist, 2) except: self.iBeaconRange = -1 - + # Old: #try: # txPower = int(strTxPower) @@ -198,7 +198,7 @@ def calcRange(self): # dist = -1.0 #except: # dist = -1.0 - + #try: # txPower = int(strTxPower) # gain = 5 # FSPL unknown gain factor @@ -210,15 +210,15 @@ def calcRange(self): # dist = round(dist, 2) #except: # dist = -1 - + def fromJson(self, jsonstr): dictjson = json.loads(jsonstr) self.fromJsondict(dictjson) - + def toJson(self): dictjson = self.toJsondict() return json.dumps(dictjson) - + def fromJsondict(self, dictjson): # Note: if the json dictionary isn't correct, this will naturally throw an exception that may # need to be caught for error detection @@ -243,13 +243,13 @@ def fromJsondict(self, dictjson): self.gps.altitude = float(dictjson['alt']) self.gps.speed = float(dictjson['speed']) self.gps.isValid = stringtobool(dictjson['gpsvalid']) - + self.strongestgps.latitude = float(dictjson['strongestlat']) self.strongestgps.longitude = float(dictjson['strongestlon']) self.strongestgps.altitude = float(dictjson['strongestalt']) self.strongestgps.speed = float(dictjson['strongestspeed']) self.strongestgps.isValid = stringtobool(dictjson['strongestgpsvalid']) - + def toJsondict(self): dictjson = {} dictjson['type'] = 'bluetooth' @@ -265,7 +265,7 @@ def toJsondict(self): dictjson['txpowervalid'] = str(self.txPowerValid) dictjson['strongestrssi'] = self.strongestRssi dictjson['ibeaconrange'] = self.iBeaconRange - + dictjson['firstseen'] = str(self.firstSeen) dictjson['lastseen'] = str(self.lastSeen) @@ -274,7 +274,7 @@ def toJsondict(self): dictjson['alt'] = str(self.gps.altitude) dictjson['speed'] = str(self.gps.speed) dictjson['gpsvalid'] = str(self.gps.isValid) - + dictjson['strongestlat'] = str(self.strongestgps.latitude) dictjson['strongestlon'] = str(self.strongestgps.longitude) dictjson['strongestalt'] = str(self.strongestgps.altitude) @@ -282,8 +282,8 @@ def toJsondict(self): dictjson['strongestgpsvalid'] = str(self.strongestgps.isValid) return dictjson - - + + # ------------------ Ubertooth Specan scanning Thread ---------------------------------- class BtmonThread(BaseThreadClass): def __init__(self, parentBluetooth): @@ -292,28 +292,31 @@ def __init__(self, parentBluetooth): self.hcitoolProc = None self.btmonProc = None self.daemon = True - + + ## Store the regex commpiles + self.regexCompiler() + def getFieldValue(self, p, curLine): matchobj = p.search(curLine) - + if not matchobj: return "" - + try: retVal = matchobj.group(1) except: retVal = "" - + return retVal - + def resetDevice(self): # Have to kill btmon and hcitool if they're running subprocess.run(['pkill', 'btmon'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) subprocess.run(['pkill', '-f','hcitool.*scan'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - + subprocess.run(['hciconfig', 'hci0', 'down'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) subprocess.run(['hciconfig', 'hci0', 'up'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - + def startBTMon(self): self.btmonProc = subprocess.Popen(['btmon'],stdout=subprocess.PIPE,stderr=subprocess.PIPE) @@ -323,118 +326,119 @@ def startHCITool(self): def btMonRunning(self): if not self.btmonProc: return False - + pollrunning = self.btmonProc.poll() is None - + return pollrunning - + def hcitoolRunning(self): if not self.hcitoolProc: return False - + pollrunning = self.hcitoolProc.poll() is None - + return pollrunning - + def stopAndWait(self): super().stopAndWait() if self.threadRunning: # May be stuck at readline if self.btmonProc: self.btmonProc.kill() - + if self.hcitoolProc: self.hcitoolProc.kill() + def regexCompiler(self): + self.p_address = re.compile('Address: ([0-9A-F]{2,2}:[0-9A-F]{2,2}:[0-9A-F]{2,2}:[0-9A-F]{2,2}:[0-9A-F]{2,2}:[0-9A-F]{2,2})') + self.p_company = re.compile('Company: (.*) \(') + # p_type = re.compile('Type: (.*?) (') + self.p_rssi = re.compile('RSSI: (.*?) dB.*') + self.p_txpower = re.compile('TX power: (.*?) dB.*') + self.p_uuid = re.compile('UUID: (.*)') + self.p_name = re.compile('Name.*?: (.*)') + # p_eventType = re.compile('Event type: (.*)') + def run(self): self.threadRunning = True - + # See this for a good example on threading and reading from a streaming proc # https://stackoverflow.com/questions/16768290/understanding-popen-communicate # Reset interface. Have had hcitool lescan fail on bad parameters self.resetDevice() - + # Just a basic sleep for 1 second example loop. # Instantiate and call start() to get it going - + iteration = 0 # Have to start hcitool first because it will set the radio. # If you try to start btmon first, it may lock it and cause hcitool to fail self.startHCITool() self.startBTMon() - - p_address = re.compile('Address: ([0-9A-F]{2,2}:[0-9A-F]{2,2}:[0-9A-F]{2,2}:[0-9A-F]{2,2}:[0-9A-F]{2,2}:[0-9A-F]{2,2})') - p_company = re.compile('Company: (.*) \(') - # p_type = re.compile('Type: (.*?) (') - p_rssi = re.compile('RSSI: (.*?) dB.*') - p_txpower = re.compile('TX power: (.*?) dB.*') - p_uuid = re.compile('UUID: (.*)') - p_name = re.compile('Name.*?: (.*)') - # p_eventType = re.compile('Event type: (.*)') - + curDevice = None # eventType = "" - + while not self.signalStop: if not self.hcitoolRunning(): # May have died # Note: btmon can still keep running even over an HCI reset self.resetDevice() self.startHCITool() - + if not self.btMonRunning(): self.startBTMon() - + curLine = self.btmonProc.stdout.readline().decode('UTF-8').replace('\n', '') # Address - fieldValue = self.getFieldValue(p_address, curLine) - + fieldValue = self.getFieldValue(self.p_address, curLine) + if (len(fieldValue) > 0): curDevice = BluetoothDevice() # eventType = "" # Just doing this scan for LE now. - curDevice.btType = BluetoothDevice.BT_LE + curDevice.btType = BluetoothDevice.BT_LE # This will start a new bluetooth device curDevice.macAddress = fieldValue - + # Name if 'Company' in curLine: pass - - fieldValue = self.getFieldValue(p_name, curLine) - + + fieldValue = self.getFieldValue(self.p_name, curLine) + if (len(fieldValue) > 0): # This will start a new bluetooth device curDevice.name = fieldValue - + # UUID - fieldValue = self.getFieldValue(p_uuid, curLine) - + fieldValue = self.getFieldValue(self.p_uuid, curLine) + if (len(fieldValue) > 0): # This will start a new bluetooth device curDevice.uuid = fieldValue - + # Company - fieldValue = self.getFieldValue(p_company, curLine) - + fieldValue = self.getFieldValue(self.p_company, curLine) + if (len(fieldValue) > 0): # This will start a new bluetooth device curDevice.company = fieldValue - + # Event Type # eventType = self.getFieldValue(p_eventType, curLine) - + # TX Power - fieldValue = self.getFieldValue(p_txpower, curLine) - + fieldValue = self.getFieldValue(self.p_txpower, curLine) + if (len(fieldValue) > 0): # This will start a new bluetooth try: tmpPower = int(fieldValue) - + # If there's an error in the data or pattern rec, # There's no way "Low Energy" would transmit with 0+ dBm. That's not LE. if tmpPower < 0: @@ -442,10 +446,10 @@ def run(self): curDevice.txPowerValid = True except: pass - + # RSSI - Will end the block - fieldValue = self.getFieldValue(p_rssi, curLine) - + fieldValue = self.getFieldValue(self.p_rssi, curLine) + if (len(fieldValue) > 0): # This will start a new bluetooth device try: @@ -454,17 +458,17 @@ def run(self): curDevice.calcRange() except: pass - + if curDevice and len(curDevice.macAddress) > 0: self.parentBluetooth.deviceLock.acquire() - + if curDevice.macAddress in self.parentBluetooth.devices: # We may not always get some fields lastDevice = self.parentBluetooth.devices[curDevice.macAddress] curDevice.firstSeen = lastDevice.firstSeen # copy first seen timestamp curDevice.gps.copy(lastDevice.gps) curDevice.strongestgps.copy(lastDevice.strongestgps) - + if len(lastDevice.name) > 0 and len(curDevice.name) == 0: curDevice.name = lastDevice.name if len(lastDevice.uuid) > 0 and len(curDevice.uuid) == 0: @@ -472,10 +476,10 @@ def run(self): if lastDevice.txPowerValid and not curDevice.txPowerValid: curDevice.txPower = lastDevice.txPower curDevice.txPowerValid = lastDevice.txPowerValid - + self.parentBluetooth.devices[curDevice.macAddress] = curDevice self.parentBluetooth.deviceLock.release() - + # Just give the thread a chance to release resources iteration += 1 if iteration > 50000: @@ -486,14 +490,14 @@ def run(self): self.hcitoolProc.kill() except: pass - + try: self.btmonProc.kill() except: pass - + self.resetDevice() - + self.threadRunning = False # ------------------ Ubertooth Specan scanning Thread ---------------------------------- @@ -502,19 +506,19 @@ def __init__(self, parentBluetooth): super().__init__() self.parentBluetooth= parentBluetooth self.daemon = True - + def run(self): self.threadRunning = True - + # See this for a good example on threading and reading from a streaming proc # https://stackoverflow.com/questions/16768290/understanding-popen-communicate specanProc = subprocess.Popen(['ubertooth-specan'],stdout=subprocess.PIPE,stderr=subprocess.DEVNULL) # Just a basic sleep for 1 second example loop. # Instantiate and call start() to get it going - + iteration = 0 rssi_offset = -54 # Note: This is direct from the Ubertooth bluetooth module. I thought the RSSI's looked really high. - + while not specanProc.poll() and not self.signalStop: dataline = specanProc.stdout.readline().decode('UTF-8').replace('\n', '') dataline = dataline.replace(' ', '') @@ -527,102 +531,105 @@ def run(self): # print('Frequency: ' + str(frequency) + ', RSSI: ' + str(rssi)) except: pass - + # Just give the thread a chance to release resources iteration += 1 if iteration > 50000: iteration = 0 sleep(0.01) - + try: specanProc.kill() except: pass - + self.threadRunning = False # ------------------ Sparrow Bluetooth Class ---------------------------------- class SparrowBluetooth(object): SCANTYPE_BLUEHYDRA = 1 SCANTYPE_ADVERTISEMENT = 2 - + def __init__(self): self.spectrum = {} for i in range(2402, 2495): self.spectrum[i] = -100 - + self.spectrumLock = Lock() self.deviceLock = Lock() - + # This scan thread is for the spectrum self.spectrumScanThread = None - + self.blueHydraProc = None self.btmonThread = None self.devices = {} self.scanType = SparrowBluetooth.SCANTYPE_BLUEHYDRA - + self.beaconActive = False - + self.hasBluetooth = False self.hasUbertooth = False self.hasBlueHydra = False - numBtAdapters = len(SparrowBluetooth.getBluetoothInterfaces()) + ## Store the regex compiles + self.regexCompiler() + + numBtAdapters = len(self.getBluetoothInterfaces()) if numBtAdapters > 0: self.hasBluetooth = True - - if SparrowBluetooth.getNumUbertoothDevices() > 0: + + if self.getNumUbertoothDevices() > 0: #SparrowBluetooth.ubertoothStopSpecan() errcode, errmsg = SparrowBluetooth.hasUbertoothTools() # errcode, errmsg = SparrowBluetooth.ubertoothOnline() if errcode == 0: self.hasUbertooth = True - + if os.path.isfile('/opt/bluetooth/blue_hydra/bin/blue_hydra'): self.hasBlueHydra = True - + def __str__(self): retVal = "" - + retVal += "Has Bluetooth Hardware: " + str(self.hasBluetooth) + '\n' retVal += "Has Ubertooth Hardware and Software: " + str(self.hasUbertooth) + '\n' retVal += "Has Blue Hydra: " + str(self.hasBlueHydra) + '\n' - + if self.scanRunning(): retVal += "Scan Running: Yes"+ '\n' else: retVal += "Scan Running: No"+ '\n' - + return retVal - + def startBeacon(self, uuidOverride=""): # Can gen a uuid with uuid.uuid4().hex - + if len(uuidOverride) > 0: struuid = uuidOverride else: # The UUID below is the same as the iOS "Beacon Toolkit" app uses # Can pass it any UUID as a parameter - + # uuid = 'E20A39F473F54BC4A12F17D1AD07A961' struuid = uuid.uuid4().hex # First reset subprocess.run(['hciconfig', 'hci0', 'down'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) subprocess.run(['hciconfig', 'hci0', 'up'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - + # Turn on LE advertising. 3 says we're not connectable, just like a true iBeacon subprocess.run(['hciconfig', 'hci0', 'leadv', '3'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - + # Turn off scanning subprocess.run(['hciconfig', 'hci0', 'noscan'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - + # Issue command majorhex = toHex(0) minorhex = toHex(0) powerhex = toHex(200) - + uuid_bytes = hexSplit(struuid).split(' ') params = ['hcitool', '-i','hci0', 'cmd', '0x08','0x0008', '1E', '02', '01', '1A', '1A', 'FF', '4C', '00', '02', '15'] params = params + uuid_bytes @@ -630,41 +637,41 @@ def startBeacon(self, uuidOverride=""): params.append(minorhex) params.append(powerhex) params.append('00') - + result = subprocess.run(params, stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - + if result.returncode == 0: self.beaconActive = True else: self.beaconActive = False - + return self.beaconActive - - + + def stopBeacon(self): # Stop LE advertisement subprocess.run(['hciconfig', 'hci0', 'noleadv'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - + # Re-enable scan subprocess.run(['hciconfig', 'hci0', 'piscan'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - + # Reset device subprocess.run(['hciconfig', 'hci0', 'down'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) subprocess.run(['hciconfig', 'hci0', 'up'], stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - + self.beaconActive = False - + def beaconRunning(self): return self.beaconActive - + def discoveryRunning(self): if self.blueHydraProc: pollrunning = self.blueHydraProc.poll() is None - + # If the process stopped let's update our records if not pollrunning: self.blueHydraProc = None - + # return pollrunning true/false return pollrunning else: @@ -672,18 +679,18 @@ def discoveryRunning(self): return True else: return False - + def stopDiscovery(self): if self.blueHydraProc: try: self.blueHydraProc.kill() except: pass - + self.blueHydraProc = None - + SparrowBluetooth.resetUbertooth() - + if self.btmonThread and self.btmonThread.threadRunning: self.btmonThread.stopAndWait() self.btmonThread = None @@ -691,14 +698,14 @@ def stopDiscovery(self): def clearDeviceList(self): self.deviceLock.acquire() self.devices = {} - self.deviceLock.release() - + self.deviceLock.release() + def updateDeviceList(self): # Because GPS comes from further up the stack, we maintain a local class list and have to # be sure to copy some fields like firstseen forward on updates if self.scanType == SparrowBluetooth.SCANTYPE_BLUEHYDRA: - errcode, retList = SparrowBluetooth.getBlueHydraBluetoothDevices() - + errcode, retList = SparrowBluetooth.getBlueHydraBluetoothDevices() + if errcode == 0: self.deviceLock.acquire() for curDevice in retList: @@ -711,33 +718,33 @@ def updateDeviceList(self): curDevice.strongestRssi = dev.strongestRssi curDevice.strongestgps.copy(dev.strongestgps) self.devices[curDevice.macAddress] = curDevice - self.deviceLock.release() + self.deviceLock.release() else: errcode = 0 - + return errcode - + def getDiscoveredDevices(self): errcode = self.updateDeviceList() - + retList = [] - + # Now copy to return list self.deviceLock.acquire() - + for curKey in self.devices.keys(): curEntry = self.devices[curKey] newDevice = BluetoothDevice() newDevice.copy(curEntry) retList.append(newDevice) - + self.deviceLock.release() - + return errcode, retList - + def startDiscovery(self, useBlueHydra=True): self.devices.clear() - + if useBlueHydra: # Make sure we don't have a discovery scan running if self.btmonThread and self.btmonThread.threadRunning: @@ -753,11 +760,11 @@ def startDiscovery(self, useBlueHydra=True): else: # Already running return - + self.scanType = SparrowBluetooth.SCANTYPE_BLUEHYDRA # Clear the sqlite table SparrowBluetooth.blueHydraClearDevices() - + # -d says daemonize self.blueHydraProc = subprocess.Popen(['bin/blue_hydra', '-d'],cwd='/opt/bluetooth/blue_hydra', stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) else: @@ -771,47 +778,47 @@ def startDiscovery(self, useBlueHydra=True): self.blueHydraProc.kill() except: pass - + self.blueHydraProc = None - - + + self.scanType = SparrowBluetooth.SCANTYPE_ADVERTISEMENT self.btmonThread = BtmonThread(self) self.btmonThread.start() - - + + def blueHydraClearDevices(filepath='/opt/bluetooth/blue_hydra/blue_hydra.db'): if not os.path.isfile(filepath): return -1 - + try: blueHydraDB = sqlite3.connect(filepath) except: return -2 - + cursor = blueHydraDB.cursor() - + try: cursor.execute('''delete FROM blue_hydra_devices''') blueHydraDB.commit() except: pass - + return 0 def getBlueHydraBluetoothDevices(filepath='/opt/bluetooth/blue_hydra/blue_hydra.db'): if not os.path.isfile(filepath): return -1, None - + try: blueHydraDB = sqlite3.connect(filepath) except: return -2, None - + cursor = blueHydraDB.cursor() - + deviceList = [] - + try: # 0 1 2 3 4 5 6 7 8 9 10 11 12 cursor.execute('''SELECT uuid,address,name,company,classic_mode,classic_rssi,lmp_version,le_mode,le_rssi,ibeacon_range,last_seen,le_tx_power,classic_tx_power FROM blue_hydra_devices''') @@ -824,7 +831,7 @@ def getBlueHydraBluetoothDevices(filepath='/opt/bluetooth/blue_hydra/blue_hydra. btDevice.name = curDevice[2] if curDevice[3]: btDevice.company = curDevice[3] - + if curDevice[4] == 't': btDevice.btType = BluetoothDevice.BT_CLASSIC # parse [5] for RSSI @@ -860,7 +867,7 @@ def getBlueHydraBluetoothDevices(filepath='/opt/bluetooth/blue_hydra/blue_hydra. strRssi = strRssi.replace(' dBm', '') btDevice.rssi = int(strRssi) highesttimestamp = curEntry['t'] - + if curDevice[11]: # Have tx power strTxPower = curDevice[11].replace(' dB', '') @@ -870,23 +877,23 @@ def getBlueHydraBluetoothDevices(filepath='/opt/bluetooth/blue_hydra/blue_hydra. btDevice.calcRange() except: btDevice.txPower = -60 - + if curDevice[6]: btDevice.bluetoothDescription = curDevice[6] - + if curDevice[9]: btDevice.iBeaconRange = int(curDevice[9]) - + btDevice.lastSeen = datetime.datetime.fromtimestamp(curDevice[10]) deviceList.append(btDevice) except: return -3, None - + return 0, deviceList - + def spectrumToChannels(self): retVal = {} - + for curKey in self.spectrum.keys(): # curKey is frequency channel = SparrowBluetooth.fFreqToChannel(curKey) @@ -894,98 +901,100 @@ def spectrumToChannels(self): if rssi > -10.0: rssi = -10.0 retVal[channel] = rssi - + return retVal - + def fFreqToChannel(frequency): # Note: This function returns a float for partial channels # ch1 center freq is 2412. +- 1 channel is 5 MHz # 2402 = Ch -1 - - + + # Map bluetooth frequency to 2.4 GHz wifi channel # ch 1 starts at 2401 MHz and ch 14 tops out at 2495 if frequency < 2402: return float(-1.0) elif frequency > 2494: return float(16.0) - + channel = -1.0 + (float(frequency) - 2402)/5 return channel - + # Frequency range of 2.4 GHz channels 1 (low end 2402) to 14 (high end 2494) #frange = 2494.0 - 2402.0 # The top end of 14 is 2494 but that would map to 16 on the chart #crange = 16.0 #channel = float((float(frequency) - 2402.0) / frange * crange) - + #return channel - + + def regexCompiler(self): + self.numUber = re.compile('^.*(1d50)', re.MULTILINE) + self.blueIntfs = re.compile('^.*(hci[0-9])', re.MULTILINE) + def startScanning(self): if self.spectrumScanThread: self.stopScanning() - + self.spectrumScanThread = specanThread(self) self.spectrumScanThread.start() - + def scanRunning(self): if self.spectrumScanThread and self.spectrumScanThread.threadRunning: return True else: return False - + def scanInitializing(self): if len(self.spectrum) < 79: return True else: return False - + def stopScanning(self): if self.spectrumScanThread: self.spectrumScanThread.stopAndWait() self.spectrumScanThread = None SparrowBluetooth.resetUbertooth() - + def resetUbertooth(): result = subprocess.run(['ubertooth-util', '-r'], stdout=subprocess.PIPE,stderr=subprocess.DEVNULL) - + return result.returncode - - def getNumUbertoothDevices(): + + def getNumUbertoothDevices(self): result = subprocess.run(['lsusb', '-d', '1d50:6002'], stdout=subprocess.PIPE,stderr=subprocess.DEVNULL) if result.returncode != 0: return 0 - + hciResult = result.stdout.decode('UTF-8') - p = re.compile('^.*(1d50)', re.MULTILINE) - tmpInterfaces = p.findall(hciResult) - + tmpInterfaces = self.numUber.findall(hciResult) + retVal = 0 - + if (len(tmpInterfaces) > 0): for curInterface in tmpInterfaces: retVal += 1 return retVal - - - def getBluetoothInterfaces(printResults=False): + + + def getBluetoothInterfaces(self, printResults = False): try: result = subprocess.run(['hcitool', 'dev'], stdout=subprocess.PIPE,stderr=subprocess.DEVNULL) except: print('ERROR: Unable to run hcitool. Reporting no bluetooth devices.') return [] - + if result.returncode != 0: return [] - + hciResult = result.stdout.decode('UTF-8') - p = re.compile('^.*(hci[0-9])', re.MULTILINE) - tmpInterfaces = p.findall(hciResult) - + tmpInterfaces = self.blueIntfs.findall(hciResult) + retVal = [] - + if (len(tmpInterfaces) > 0): for curInterface in tmpInterfaces: tmpStr=curInterface.replace(' ','') @@ -999,7 +1008,7 @@ def getBluetoothInterfaces(printResults=False): print("Error: No wireless interfaces found.") return retVal - + def getUbertoothSpecanProcesses(): # Returns a list of process id's result = subprocess.run(['pgrep', '-f','ubertooth-specan'], stdout=subprocess.PIPE,stderr=subprocess.DEVNULL) @@ -1008,14 +1017,14 @@ def getUbertoothSpecanProcesses(): retVal = [] if result.returncode != 0: return retVal - + procList = testResult.split('\n') for curLine in procList: if len(curLine) > 0: retVal.append(int(curLine)) - + return retVal - + def ubertoothSpecanRunning(): result = subprocess.run(['pgrep', '-f','ubertooth-specan'], stdout=subprocess.PIPE,stderr=subprocess.DEVNULL) @@ -1024,10 +1033,10 @@ def ubertoothSpecanRunning(): return True else: return False - + def ubertoothStopSpecan(): procList = SparrowBluetooth.getUbertoothSpecanProcesses() - + for curProc in procList: try: os.kill(curProc, signal.SIGINT) @@ -1037,36 +1046,36 @@ def ubertoothStopSpecan(): def hasBluetoothHardware(): if SparrowBluetooth.getNumUbertoothDevices() == 0: return False - + numBtAdapters = SparrowBluetooth.getBluetoothInterfaces() - + if len(numBtAdapters) == 0: return False - + return True - + def hasUbertoothTools(): if not os.path.isfile('/usr/local/bin/ubertooth-specan') and not os.path.isfile('/usr/bin/ubertooth-specan'): return -1, 'ubertooth tools not found.' - + return 0, '' - + def ubertoothOnline(): # Check if ubertooth-specan is installed and that the ubertooth is actually present if not os.path.isfile('/usr/local/bin/ubertooth-specan') and not os.path.isfile('/usr/bin/ubertooth-specan'): return -1, 'ubertooth tools not found.' - + if SparrowBluetooth.ubertoothSpecanRunning(): return -2, 'Ubertooth-specan is running. Please stop it before continuing.' - + # aircrack-ng -a2 -b D8:EB:97:2F:DD:CE -w /opt/wordlists/TopPasswords3-2.txt falconcap-01.cap params = ['ubertooth-util', '-v'] - + result = subprocess.run(params, stdout=subprocess.PIPE,stderr=subprocess.PIPE) testResult = result.stdout.decode('UTF-8') if 'could not open Ubertooth device' in testResult: return -3, 'Unable to find Ubertooth device' - + if result.returncode == 0: return 0, "" else: @@ -1074,7 +1083,7 @@ def ubertoothOnline(): def testSpectrum(): bt.startScanning() - + try: while True: print('Spectrum Size: ' + str(len(bt.spectrum))) @@ -1083,34 +1092,34 @@ def testSpectrum(): print('Scan initializing...') sleep(1) - + except KeyboardInterrupt: print('Shutting down...') bt.stopScanning() print('Done') - + if __name__ == '__main__': errcode, devices=SparrowBluetooth.getBlueHydraBluetoothDevices() - + btInterfaces = SparrowBluetooth.getBluetoothInterfaces() - + if len(btInterfaces) > 0: print('Bluetooth (hci) interfaces:') for curInterface in btInterfaces: print(curInterface) - + errcode, errmsg = SparrowBluetooth.ubertoothOnline() - + if errcode == 0: print('Ubertooth tools found and device is online') else: print('Error: ' + errmsg) specanProcesses = SparrowBluetooth.getUbertoothSpecanProcesses() - + for curProc in specanProcesses: print(curProc) - + bt=SparrowBluetooth() print(bt)