def on_webhook(self, path, request): if not path or path == "/": return render_template_string(TEMPLATE) session_param = request.args.get('session') if path == "os": extract_keys = [ 'cpu_load', 'mem_usage', ] elif path == "temp": extract_keys = ['temperature'] elif path == "wifi": extract_keys = [ 'missed_interactions', 'num_hops', 'num_peers', 'tot_bond', 'avg_bond', 'num_deauths', 'num_associations', 'num_handshakes', ] elif path == "duration": extract_keys = [ 'duration_secs', 'slept_for_secs', ] elif path == "reward": extract_keys = [ 'reward', ] elif path == "epoch": extract_keys = [ 'active_for_epochs', ] elif path == "session": return jsonify( {'files': os.listdir(self.options['save_directory'])}) with self.lock: data = self.stats if session_param and session_param != 'Current': file_stats = StatusFile(os.path.join( self.options['save_directory'], session_param), data_format='json') data = file_stats.data_field_or('data', default=dict()) return jsonify(SessionStats.extract_key_values(data, extract_keys))
class NetPos(plugins.Plugin): __author__ = 'zenzen san' __version__ = '3.0.0' __license__ = 'GPL3' __description__ = """Saves a json file with the access points with more signal whenever a handshake is captured. When internet is available the files are converted in geo locations using Mozilla LocationService """ __defaults__ = { 'enabled': False, 'api_key': 'test', 'api_url': 'https://location.services.mozilla.com/v1/geolocate?key={api}', } def __init__(self): self.report = StatusFile('/root/.net_pos_saved', data_format='json') self.skip = list() self.ready = False self.lock = threading.Lock() self.shutdown = False def on_before_shutdown(self): self.shutdown = True def on_loaded(self): if 'api_key' not in self.options or ('api_key' in self.options and not self.options['api_key']): logging.error( '[net-pos] api_key isn\'t set. Can\'t use mozilla\'s api.') return self.ready = True logging.info('[net-pos] plugin loaded.') logging.debug(f"[net-pos] use api_url: {self.options['api_url']}") def _append_saved(self, path): to_save = list() if isinstance(path, str): to_save.append(path) elif isinstance(path, list): to_save += path else: raise TypeError("Expected list or str, got %s" % type(path)) with open('/root/.net_pos_saved', 'a') as saved_file: for x in to_save: saved_file.write(x + "\n") def on_internet_available(self, agent): if not self.ready or self.lock.locked() or self.shutdown: return with self.lock: config = agent.config() display = agent.view() reported = self.report.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] all_files = os.listdir(handshake_dir) all_np_files = [ os.path.join(handshake_dir, filename) for filename in all_files if filename.endswith('.net-pos.json') ] new_np_files = set(all_np_files) - set(reported) - set(self.skip) if new_np_files: logging.debug( '[net-pos] Found %d new net-pos files. Fetching positions ...', len(new_np_files)) display.set( 'status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ..." ) display.update(force=True) for idx, np_file in enumerate(new_np_files): if self.shutdown: return geo_file = np_file.replace('.net-pos.json', '.geo.json') if os.path.exists(geo_file): # got already the position reported.append(np_file) self.report.update(data={'reported': reported}) continue try: geo_data = self._get_geo_data( np_file) # returns json obj except requests.exceptions.RequestException as req_e: logging.error('[net-pos] %s - RequestException: %s', np_file, req_e) self.skip += np_file continue except json.JSONDecodeError as js_e: logging.error( '[net-pos] %s - JSONDecodeError: %s, removing it...', np_file, js_e) os.remove(np_file) continue except OSError as os_e: logging.error('[net-pos] %s - OSError: %s', np_file, os_e) self.skip += np_file continue with open(geo_file, 'w+t') as sf: json.dump(geo_data, sf) reported.append(np_file) self.report.update(data={'reported': reported}) display.set( 'status', f"Fetching positions ({idx + 1}/{len(new_np_files)})") display.update(force=True) def on_handshake(self, agent, filename, access_point, client_station): netpos = self._get_netpos(agent) if not netpos['wifiAccessPoints']: return netpos["ts"] = int("%.0f" % time.time()) netpos_filename = filename.replace('.pcap', '.net-pos.json') logging.debug('[net-pos] Saving net-location to %s', netpos_filename) try: with open(netpos_filename, 'w+t') as net_pos_file: json.dump(netpos, net_pos_file) except OSError as os_e: logging.error('[net-pos] %s', os_e) def _get_netpos(self, agent): aps = agent.get_access_points() netpos = dict() netpos['wifiAccessPoints'] = list() # 6 seems a good number to save a wifi networks location for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]: netpos['wifiAccessPoints'].append({ 'macAddress': access_point['mac'], 'signalStrength': access_point['rssi'] }) return netpos def _get_geo_data(self, path, timeout=30): geourl = self.options['api_url'].format(api=self.options['api_key']) try: with open(path, "r") as json_file: data = json.load(json_file) except json.JSONDecodeError as js_e: raise js_e except OSError as os_e: raise os_e try: result = requests.post(geourl, json=data, timeout=timeout) return_geo = result.json() if data["ts"]: return_geo["ts"] = data["ts"] return return_geo except requests.exceptions.RequestException as req_e: raise req_e
class WpaSec(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '2.1.0' __license__ = 'GPL3' __description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org' def __init__(self): self.ready = False self.lock = Lock() try: self.report = StatusFile('/root/.wpa_sec_uploads', data_format='json') except JSONDecodeError as json_err: os.remove("/root/.wpa_sec_uploads") self.report = StatusFile('/root/.wpa_sec_uploads', data_format='json') self.options = dict() self.skip = list() def _upload_to_wpasec(self, path, timeout=30): """ Uploads the file to https://wpa-sec.stanev.org, or another endpoint. """ with open(path, 'rb') as file_to_upload: cookie = {'key': self.options['api_key']} payload = {'file': file_to_upload} try: result = requests.post(self.options['api_url'], cookies=cookie, files=payload, timeout=timeout) if ' already submitted' in result.text: logging.warning("%s was already submitted.", path) except requests.exceptions.RequestException as req_e: raise req_e def _download_from_wpasec(self, output, timeout=30): """ Downloads the results from wpasec and safes them to output Output-Format: bssid, station_mac, ssid, password """ api_url = self.options['api_url'] if not api_url.endswith('/'): api_url = f"{api_url}/" api_url = f"{api_url}?api&dl=1" cookie = {'key': self.options['api_key']} try: result = requests.get(api_url, cookies=cookie, timeout=timeout) with open(output, 'wb') as output_file: output_file.write(result.content) except requests.exceptions.RequestException as req_e: raise req_e except OSError as os_e: raise os_e def on_loaded(self): """ Gets called when the plugin gets loaded """ if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None): logging.error( "WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org" ) return if 'api_url' not in self.options or ('api_url' in self.options and self.options['api_url'] is None): logging.error( "WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured." ) return self.ready = True def on_internet_available(self, agent): """ Called in manual mode when there's internet connectivity """ with self.lock: if self.ready: config = agent.config() display = agent.view() reported = self.report.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) handshake_paths = [ os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap') ] handshake_new = set(handshake_paths) - set(reported) - set( self.skip) if handshake_new: logging.info( "WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org" ) for idx, handshake in enumerate(handshake_new): display.set( 'status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})" ) display.update(force=True) try: self._upload_to_wpasec(handshake) reported.append(handshake) self.report.update(data={'reported': reported}) logging.info("WPA_SEC: Successfully uploaded %s", handshake) except requests.exceptions.RequestException as req_e: self.skip.append(handshake) logging.error("WPA_SEC: %s", req_e) continue except OSError as os_e: logging.error("WPA_SEC: %s", os_e) continue if 'download_results' in self.options and self.options[ 'download_results']: cracked_file = os.path.join(handshake_dir, 'wpa-sec.cracked.potfile') if os.path.exists(cracked_file): last_check = datetime.fromtimestamp( os.path.getmtime(cracked_file)) if last_check is not None and ( (datetime.now() - last_check).seconds / (60 * 60)) < 1: return try: self._download_from_wpasec( os.path.join(handshake_dir, 'wpa-sec.cracked.potfile')) logging.info("WPA_SEC: Downloaded cracked passwords.") except requests.exceptions.RequestException as req_e: logging.debug("WPA_SEC: %s", req_e) except OSError as os_e: logging.debug("WPA_SEC: %s", os_e)
class Grid(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '1.0.1' __license__ = 'GPL3' __description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \ 'networks to api.pwnagotchi.ai ' def __init__(self): self.options = dict() self.report = StatusFile('/root/.api-report.json', data_format='json') self.unread_messages = 0 self.total_messages = 0 def is_excluded(self, what): for skip in self.options['exclude']: skip = skip.lower() what = what.lower() if skip in what or skip.replace(':', '') in what: return True return False def on_loaded(self): logging.info("grid plugin loaded.") def set_reported(self, reported, net_id): if net_id not in reported: reported.append(net_id) self.report.update(data={'reported': reported}) def check_inbox(self, agent): logging.debug("checking mailbox ...") messages = grid.inbox() self.total_messages = len(messages) self.unread_messages = len( [m for m in messages if m['seen_at'] is None]) if self.unread_messages: plugins.on('unread_inbox', self.unread_messages) logging.debug("[grid] unread:%d total:%d" % (self.unread_messages, self.total_messages)) agent.view().on_unread_messages(self.unread_messages, self.total_messages) def check_handshakes(self, agent): logging.debug("checking pcaps") pcap_files = glob.glob( os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) num_networks = len(pcap_files) reported = self.report.data_field_or('reported', default=[]) num_reported = len(reported) num_new = num_networks - num_reported if num_new > 0: if self.options['report']: logging.info("grid: %d new networks to report" % num_new) logging.debug("self.options: %s" % self.options) logging.debug(" exclude: %s" % self.options['exclude']) for pcap_file in pcap_files: net_id = os.path.basename(pcap_file).replace('.pcap', '') if net_id not in reported: if self.is_excluded(net_id): logging.debug( "skipping %s due to exclusion filter" % pcap_file) self.set_reported(reported, net_id) continue essid, bssid = parse_pcap(pcap_file) if bssid: if self.is_excluded(essid) or self.is_excluded( bssid): logging.debug( "not reporting %s due to exclusion filter" % pcap_file) self.set_reported(reported, net_id) else: if grid.report_ap(essid, bssid): self.set_reported(reported, net_id) time.sleep(1.5) else: logging.warning("no bssid found?!") else: logging.debug("grid: reporting disabled") def on_internet_available(self, agent): logging.debug("internet available") try: grid.update_data(agent.last_session) except Exception as e: logging.error("error connecting to the pwngrid-peer service: %s" % e) logging.debug(e, exc_info=True) return try: self.check_inbox(agent) except Exception as e: logging.error("[grid] error while checking inbox: %s" % e) logging.debug(e, exc_info=True) try: self.check_handshakes(agent) except Exception as e: logging.error("[grid] error while checking pcaps: %s" % e) logging.debug(e, exc_info=True)
class OnlineHashCrack(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '2.0.0' __license__ = 'GPL3' __description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com' def __init__(self): self.ready = False self.report = StatusFile('/root/.ohc_uploads', data_format='json') self.skip = list() def on_loaded(self): """ Gets called when the plugin gets loaded """ if 'email' not in self.options or ('email' in self.options and self.options['email'] is None): logging.error( "OHC: Email isn't set. Can't upload to onlinehashcrack.com") return self.ready = True def _upload_to_ohc(self, path, timeout=30): """ Uploads the file to onlinehashcrack.com """ with open(path, 'rb') as file_to_upload: data = {'email': self.options['email']} payload = {'file': file_to_upload} try: result = requests.post('https://api.onlinehashcrack.com', data=data, files=payload, timeout=timeout) if 'already been sent' in result.text: logging.warning(f"{path} was already uploaded.") except requests.exceptions.RequestException as e: logging.error( f"OHC: Got an exception while uploading {path} -> {e}") raise e def on_internet_available(self, agent): """ Called in manual mode when there's internet connectivity """ if self.ready: display = agent.view() config = agent.config() reported = self.report.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) handshake_paths = [ os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap') ] handshake_new = set(handshake_paths) - set(reported) - set( self.skip) if handshake_new: logging.info( "OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com" ) for idx, handshake in enumerate(handshake_new): display.set( 'status', f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})" ) display.update(force=True) try: self._upload_to_ohc(handshake) reported.append(handshake) self.report.update(data={'reported': reported}) logging.info(f"OHC: Successfully uploaded {handshake}") except requests.exceptions.RequestException as req_e: self.skip.append(handshake) logging.error("OHC: %s", req_e) continue except OSError as os_e: self.skip.append(handshake) logging.error("OHC: %s", os_e) continue
class Wigle(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '2.0.0' __license__ = 'GPL3' __description__ = 'This plugin automatically uploads collected wifis to wigle.net' def __init__(self): self.ready = False self.report = StatusFile('/root/.wigle_uploads', data_format='json') self.skip = list() self.lock = Lock() def on_loaded(self): if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None): logging.debug( "[wigle] api_key isn't set, can't upload to wigle.net.") return if not 'whitelist' in self.options: self.options['whitelist'] = list() self.ready = True logging.info("[wigle] Plugin loaded.") def on_internet_available(self, agent): """ Called in manual mode when there's internet connectivity """ if not self.ready or self.lock.locked(): return from scapy.all import Scapy_Exception config = agent.config() display = agent.view() reported = self.report.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] all_files = os.listdir(handshake_dir) all_gps_files = [ os.path.join(handshake_dir, filename) for filename in all_files if filename.endswith('.gps.json') ] all_gps_files = remove_whitelisted(all_gps_files, self.options['whitelist']) new_gps_files = set(all_gps_files) - set(reported) - set(self.skip) if new_gps_files: logging.info( "[wigle] Internet connectivity detected, Uploading new handshakes to wigle.net..." ) csv_entries = list() no_err_entries = list() for gps_file in new_gps_files: pcap_filename = gps_file.replace('.gps.json', '.pcap') if not os.path.exists(pcap_filename): logging.debug(f"[wigle] Can't find pcap for {gps_file}.") self.skip.append(gps_file) continue try: gps_data = _extract_gps_data(gps_file) except OSError as os_err: logging.debug(f"[wigle] {os_err}") self.skip.append(gps_file) continue except json.JSONDecodeError as json_err: logging.debug(f"[wigle]: {json_err}") self.skip.append(gps_file) continue if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0: logging.debug( f"[wigle] Not enough gps-information for {gps_file}. Trying again next time." ) self.skip.append(gps_file) continue try: pcap_data = extract_from_pcap(pcap_filename, [ WifiInfo.BSSID, WifiInfo.ESSID, WifiInfo.ENCRYPTION, WifiInfo.CHANNEL, WifiInfo.RSSI ]) except FieldNotFoundError: logging.debug( f"[wigle] Could not extract all information. Skip {gps_file}." ) self.skip.append(gps_file) continue except Scapy_Exception as sc_e: logging.debug(f"[wigle]: {sc_e}") self.skip.append(gps_file) continue new_entry = _transform_wigle_entry(gps_data, pcap_data) csv_entries.append(new_entry) no_err_entries.append(gps_file) if csv_entries: display.set('status', "Uploading gps-data to wigle.net...") display.update(force=True) try: _send_to_wigle(csv_entries, self.options['api_key']) reported += no_err_entries self.report.update(data={'reported': reported}) logging.info( f"[wigle] Successfully uploaded {len(no_err_entries)} files." ) except requests.exceptions.RequestException as re_e: self.skip += no_err_entries logging.debug( f"[wigle] Got an exception while uploading {re_e}") except OSError as os_e: self.skip += no_err_entries logging.debug(f"[wigle] Got the following error: {os_e}")
class OnlineHashCrack(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '2.0.1' __license__ = 'GPL3' __description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com' def __init__(self): self.ready = False try: self.report = StatusFile('/root/.ohc_uploads', data_format='json') except JSONDecodeError: os.remove('/root/.ohc_uploads') self.report = StatusFile('/root/.ohc_uploads', data_format='json') self.skip = list() self.lock = Lock() def on_loaded(self): """ Gets called when the plugin gets loaded """ if 'email' not in self.options or ('email' in self.options and not self.options['email']): logging.error( "OHC: Email isn't set. Can't upload to onlinehashcrack.com") return if 'whitelist' not in self.options: self.options['whitelist'] = list() self.ready = True logging.info("OHC: OnlineHashCrack plugin loaded.") def _upload_to_ohc(self, path, timeout=30): """ Uploads the file to onlinehashcrack.com """ with open(path, 'rb') as file_to_upload: data = {'email': self.options['email']} payload = {'file': file_to_upload} try: result = requests.post('https://api.onlinehashcrack.com', data=data, files=payload, timeout=timeout) if 'already been sent' in result.text: logging.warning(f"{path} was already uploaded.") except requests.exceptions.RequestException as e: logging.error( f"OHC: Got an exception while uploading {path} -> {e}") raise e def _download_cracked(self, save_file, timeout=120): """ Downloads the cracked passwords and saves them returns the number of downloaded passwords """ try: s = requests.Session() dashboard = s.get(self.options['dashboard'], timeout=timeout) result = s.get('https://www.onlinehashcrack.com/wpa-exportcsv', timeout=timeout) result.raise_for_status() with open(save_file, 'wb') as output_file: output_file.write(result.content) except requests.exceptions.RequestException as req_e: raise req_e except OSError as os_e: raise os_e def on_internet_available(self, agent): """ Called in manual mode when there's internet connectivity """ if not self.ready or self.lock.locked(): return with self.lock: display = agent.view() config = agent.config() reported = self.report.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) handshake_paths = [ os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap') ] # pull out whitelisted APs handshake_paths = remove_whitelisted(handshake_paths, self.options['whitelist']) handshake_new = set(handshake_paths) - set(reported) - set( self.skip) if handshake_new: logging.info( "OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com" ) for idx, handshake in enumerate(handshake_new): display.set( 'status', f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})" ) display.update(force=True) try: self._upload_to_ohc(handshake) if handshake not in reported: reported.append(handshake) self.report.update(data={'reported': reported}) logging.info( f"OHC: Successfully uploaded {handshake}") except requests.exceptions.RequestException as req_e: self.skip.append(handshake) logging.error("OHC: %s", req_e) continue except OSError as os_e: self.skip.append(handshake) logging.error("OHC: %s", os_e) continue if 'dashboard' in self.options and self.options['dashboard']: cracked_file = os.path.join(handshake_dir, 'onlinehashcrack.cracked') if os.path.exists(cracked_file): last_check = datetime.fromtimestamp( os.path.getmtime(cracked_file)) if last_check is not None and ( (datetime.now() - last_check).seconds / (60 * 60)) < 1: return try: self._download_cracked(cracked_file) logging.info("OHC: Downloaded cracked passwords.") except requests.exceptions.RequestException as req_e: logging.debug("OHC: %s", req_e) except OSError as os_e: logging.debug("OHC: %s", os_e) if 'single_files' in self.options and self.options[ 'single_files']: with open(cracked_file, 'r') as cracked_list: for row in csv.DictReader(cracked_list): if row['password']: filename = re.sub( r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace( ':', '') if os.path.exists( os.path.join(handshake_dir, filename + '.pcap')): with open( os.path.join( handshake_dir, filename + '.pcap.cracked'), 'w') as f: f.write(row['password'])
class nextcloud(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '0.0.1' __license__ = 'GPL3' __description__ = 'This plugin automatically uploads handshakes to a nextcloud webdav endpoint' def __init__(self): self.ready = False self.lock = threading.Lock() try: self.report = StatusFile('/root/.nextcloud_uploads', data_format='json') except JSONDecodeError as json_err: os.remove("/root/.nextcloud_uploads") self.report = StatusFile('/root/.nextcloud_uploads', data_format='json') self.options = dict() self.skip = list() self.session = None self.full_url = None def _make_path(self, dir): return f"{self.options['baseurl']}/remote.php/dav/files/{self.options['user']}/{dir}" def _make_session(self): s = requests.Session() s.auth = (self.options["user"], self.options["pass"]) logging.info("[nextcloud] >> send first req!") # check if creds correct try: r = s.request("PROPFIND", self._make_path("./")) logging.info("[nextcloud] >> send first req 2!") if r.status_code == 401: logging.info("[nextcloud] >> wrong creds!") return False elif r.status_code == 404: logging.info("[nextcloud] >> path does not exist!") return False self.session = s except requests.exceptions.RequestException as e: logging.error("nextcloud: Got an exception checking credentials!") raise e def _make_dir(self, path): try: r = self.session.request("MKCOL", path) if r.status_code == 201: print("created new directory") return True else: return False except requests.exceptions.RequestException as e: logging.error("nextcloud: Got an exception while creating a dir.") raise e def _exists_dir(self, path): try: r = self.session.request("PROPFIND", path) if r.status_code == 404: return False else: return True except requests.exceptions.RequestException as e: logging.error( "nextcloud: Got an exception while checking if a dir exists.") raise e def _upload_to_nextcloud(self, path, timeout=30): head, tail = os.path.split(path) destFile = self.full_url + '/' + tail with open(path, 'rb') as fp: try: r = self.session.put(destFile, data=fp.read()) except requests.exceptions.RequestException as e: logging.error( f"nextcloud: Got an exception while uploading {path} -> {e}" ) raise e def on_loaded(self): for opt in ['baseurl', 'user', 'pass', 'path']: if opt not in self.options or (opt in self.options and self.options[opt] is None): logging.error(f"NEXTCLOUD: Option {opt} is not set.") return self.ready = True logging.info("NEXTCLOUD: Successfully loaded.") def on_internet_available(self, agent): with self.lock: if self.ready: config = agent.config() display = agent.view() reported = self.report.data_field_or('reported', default=dict()) handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) handshake_paths = [ os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap') ] handshake_new = set(handshake_paths) - set(reported) - set( self.skip) # filter for new files handshake_new = [] for hs in set(handshake_paths) - set(self.skip): if hs not in reported: handshake_new.append(hs) else: if os.path.getmtime(hs) > reported[hs]: handshake_new.append(hs) logging.info( f"[nx]: found {len(handshake_new)} new handshakes") if handshake_new: logging.info( "nextcloud: Internet connectivity detected. Uploading new handshakes" ) logging.info("[nextcloud] create session START") self._make_session() logging.info("[nextcloud] check for dir Half") self.full_url = self._make_path(f"{self.options['path']}/") logging.info("[nextcloud] create session DONE") logging.info("[nextcloud] check for dir START") if not self._exists_dir(self.full_url): if not self._make_dir(self.full_url): logging.info("[nextcloud] check for dir Fail") logging.error( "nextcloud: couldn't create necessary directory" ) return logging.info("[nextcloud] check for dir DONE") for idx, handshake in enumerate(handshake_new): logging.info( f"[nextcloud] uploading hs {handshake}, nr {idx + 1} out of {len(handshake_new)}" ) display.set( 'status', f"Uploading handshake to nextcloud ({idx + 1}/{len(handshake_new)})" ) display.update(force=True) try: logging.info("[nx] upload 1") self._upload_to_nextcloud(handshake) logging.info("[nx] upload 2") reported[handshake] = os.path.getmtime(handshake) logging.info("[nx] upload 3") self.report.update(data={'reported': reported}) logging.info("nextcloud: Successfully uploaded %s", handshake) except requests.exceptions.RequestException as req_e: self.skip.append(handshake) logging.error("nextcloud: %s", req_e) continue except OSError as os_e: logging.error("nextcloud: %s", os_e) continue
class AutoUpdate(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '2.0.0' __name__ = 'auto-update' __license__ = 'GPL3' __description__ = 'This plugin checks when updates are available and applies them when internet is available.' __defaults__ = { 'enabled': True, 'install': True, 'interval': 1, } def __init__(self): self.ready = False self.status = StatusFile('/root/.auto-update', data_format='json') self.lock = Lock() self.done_caplets_check = False # only check once def on_loaded(self): self.ready = True logging.info("[auto-update] plugin loaded.") def on_internet_available(self, agent): if self.lock.locked(): return with self.lock: logging.debug("[auto-update] internet connectivity is available (ready %s)" % self.ready) if not self.ready: return if self.status.newer_then_hours(self.options['interval']): logging.debug("[auto-update] last check happened less than %d hours ago" % self.options['interval']) return logging.info("[auto-update] checking for updates ...") config = agent.config() display = agent.view() prev_status = display.get('status') try: display.update(force=True, new_data={'status': 'Checking for updates ...'}) to_install = [] to_check = [ ('dadav/bettercap', parse_version('bettercap -version'), True, 'bettercap'), ('dadav/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi') ] for repo, local_version, is_native, svc_name in to_check: info = check(local_version, repo, is_native) if info['url'] is not None: logging.warning( "[auto-update] for %s available (local version is '%s'): %s" % ( repo, info['current'], info['url'])) info['service'] = svc_name to_install.append(info) num_updates = len(to_install) num_installed = 0 if num_updates > 0: if self.options['install']: for update in to_install: plugins.on('updating') if install(display, update): num_installed += 1 else: prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '') if not self.done_caplets_check: prev_commit = self.status.data_field_or('caplets_version', '') try: logging.info('[auto-update] Checking for new caplets.') current_commit = fetch_last_commit('bettercap/caplets') if prev_commit != current_commit: logging.info('[auto-update] Updating caplets.') rc = os.system('bettercap -eval "caplets.update;q"') if rc == 0: self.status.update(data={'caplets_version': current_commit}) except Exception as ex: logging.error("[auto-update] %s", ex) finally: self.done_caplets_check = True # update plugins logging.info("[auto-update] Checking for new plugins") if plugin_update(config) == 0: logging.info("[auto-update] Upgrading plugins") plugin_upgrade(None, config) logging.info("[auto-update] done") self.status.update(data=self.status.data) if num_installed > 0: display.update(force=True, new_data={'status': 'Rebooting ...'}) pwnagotchi.reboot() except Exception as e: logging.error("[auto-update] %s", e) display.update(force=True, new_data={'status': prev_status if prev_status is not None else ''})
class dropbox(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '0.0.1' __license__ = 'GPL3' __description__ = 'This plugin automatically uploads handshakes to a dropbox app' def __init__(self): self.ready = False self.lock = threading.Lock() try: self.report = StatusFile('/root/.dropbox_ul_uploads', data_format='json') except JSONDecodeError as json_err: os.remove("/root/.dropbox_ul_uploads") self.report = StatusFile('/root/.dropbox_ul_uploads', data_format='json') self.options = dict() self.skip = list() def _upload_to_dropbox(self, path, timeout=30): """ Uploads the file to dropbox """ head, tail = os.path.split(path) destFile = self.options['path'] + '/' + tail dbOpts = { 'path': destFile, 'mode': 'add', 'autorename': True, 'mute': False, 'strict_conflict': False } headers = { 'Authorization': 'Bearer ' + self.options['app_token'], 'Dropbox-API-Arg': json.dumps(dbOpts), 'Content-Type': 'application/octet-stream', } data = open(path, 'rb').read() try: response = requests.post( 'https://content.dropboxapi.com/2/files/upload', headers=headers, data=data) logging.error(response) except requests.exceptions.RequestException as e: logging.error( f"OHC: Got an exception while uploading {path} -> {e}") raise e def on_loaded(self): """ Gets called when the plugin gets loaded """ if 'app_token' not in self.options or ( 'app_token' in self.options and self.options['app_token'] is None): logging.error("dropbox_ul: APP-TOKEN isn't set.") return logging.info(" [dropbox_ul] plugin loaded") self.ready = True def on_internet_available(self, agent): """ Called in manual mode when there's internet connectivity """ with self.lock: if self.ready: config = agent.config() display = agent.view() reported = self.report.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) handshake_paths = [ os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap') ] handshake_new = set(handshake_paths) - set(reported) - set( self.skip) if handshake_new: logging.info( "dropbox_ul: Internet connectivity detected. Uploading new handshakes" ) for idx, handshake in enumerate(handshake_new): display.set( 'status', f"Uploading handshake to dropbox ({idx + 1}/{len(handshake_new)})" ) display.update(force=True) try: self._upload_to_dropbox(handshake) reported.append(handshake) self.report.update(data={'reported': reported}) logging.info( "dropbox_ul: Successfully uploaded %s", handshake) except requests.exceptions.RequestException as req_e: self.skip.append(handshake) logging.error("dropbox_ul: %s", req_e) continue except OSError as os_e: logging.error("dropbox_ul: %s", os_e) continue
class OnlineHashCrack(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '2.0.1' __license__ = 'GPL3' __description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com' def __init__(self): self.ready = False try: self.report = StatusFile('/root/.ohc_uploads', data_format='json') except JSONDecodeError as json_err: os.remove('/root/.ohc_uploads') self.report = StatusFile('/root/.ohc_uploads', data_format='json') self.skip = list() self.lock = Lock() def on_loaded(self): """ Gets called when the plugin gets loaded """ if 'email' not in self.options or ('email' in self.options and not self.options['email']): logging.error( "OHC: Email isn't set. Can't upload to onlinehashcrack.com") return if 'whitelist' not in self.options: self.options['whitelist'] = [] # remove special characters from whitelist APs to match on-disk format self.options['whitelist'] = set( map(lambda x: re.sub(r'[^a-zA-Z0-9]', '', x), self.options['whitelist'])) self.ready = True def _filter_handshake_file(self, handshake_filename): try: basename = os.path.basename(handshake_filename) ssid, bssid = basename.split('_') # remove the ".pcap" from the bssid (which is really just the end of the filename) bssid = bssid[:-5] except: # something failed in our parsing of the filename. let the file through return True return ssid not in self.options[ 'whitelist'] and bssid not in self.options['whitelist'] def _upload_to_ohc(self, path, timeout=30): """ Uploads the file to onlinehashcrack.com """ with open(path, 'rb') as file_to_upload: data = {'email': self.options['email']} payload = {'file': file_to_upload} try: result = requests.post('https://api.onlinehashcrack.com', data=data, files=payload, timeout=timeout) if 'already been sent' in result.text: logging.warning(f"{path} was already uploaded.") except requests.exceptions.RequestException as e: logging.error( f"OHC: Got an exception while uploading {path} -> {e}") raise e def on_internet_available(self, agent): """ Called in manual mode when there's internet connectivity """ with self.lock: if self.ready: display = agent.view() config = agent.config() reported = self.report.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) handshake_paths = [ os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap') ] # pull out whitelisted APs handshake_paths = filter( lambda path: self._filter_handshake_file(path), handshake_paths) handshake_new = set(handshake_paths) - set(reported) - set( self.skip) if handshake_new: logging.info( "OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com" ) for idx, handshake in enumerate(handshake_new): display.set( 'status', f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})" ) display.update(force=True) try: self._upload_to_ohc(handshake) if handshake not in reported: reported.append(handshake) self.report.update(data={'reported': reported}) logging.info( f"OHC: Successfully uploaded {handshake}") except requests.exceptions.RequestException as req_e: self.skip.append(handshake) logging.error("OHC: %s", req_e) continue except OSError as os_e: self.skip.append(handshake) logging.error("OHC: %s", os_e) continue
class WpaSec(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '2.0.1' __license__ = 'GPL3' __description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org' def __init__(self): self.ready = False self.report = StatusFile('/root/.wpa_sec_uploads', data_format='json') self.options = dict() self.skip = list() def _upload_to_wpasec(self, path, timeout=30): """ Uploads the file to https://wpa-sec.stanev.org, or another endpoint. """ with open(path, 'rb') as file_to_upload: cookie = {'key': self.options['api_key']} payload = {'file': file_to_upload} try: result = requests.post(self.options['api_url'], cookies=cookie, files=payload, timeout=timeout) if ' already submitted' in result.text: logging.warning("%s was already submitted.", path) except requests.exceptions.RequestException as req_e: raise req_e def on_loaded(self): """ Gets called when the plugin gets loaded """ if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None): logging.error( "WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org" ) return if 'api_url' not in self.options or ('api_url' in self.options and self.options['api_url'] is None): logging.error( "WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured." ) return self.ready = True def on_internet_available(self, agent): """ Called in manual mode when there's internet connectivity """ if self.ready: config = agent.config() display = agent.view() reported = self.report.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) handshake_paths = [ os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap') ] handshake_new = set(handshake_paths) - set(reported) - set( self.skip) if handshake_new: logging.info( "WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org" ) for idx, handshake in enumerate(handshake_new): display.set( 'status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})" ) display.update(force=True) try: self._upload_to_wpasec(handshake) reported.append(handshake) self.report.update(data={'reported': reported}) logging.info("WPA_SEC: Successfully uploaded %s", handshake) except requests.exceptions.RequestException as req_e: self.skip.append(handshake) logging.error("WPA_SEC: %s", req_e) continue except OSError as os_e: logging.error("WPA_SEC: %s", os_e) continue