def __init__(self): self.ready = False self.tries = 0 logging.debug("GIT BACKUP") self.status = StatusFile('/root/.git_backup')
class AutoUpdate(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '1.1.1' __name__ = 'auto-update' __license__ = 'GPL3' __description__ = 'This plugin checks when updates are available and applies them when internet is available.' def __init__(self): self.ready = False self.status = StatusFile('/root/.auto-update') def on_loaded(self): if 'interval' not in self.options or ( 'interval' in self.options and self.options['interval'] is None): logging.error( "[update] main.plugins.auto-update.interval is not set") return self.ready = True logging.info("[update] plugin loaded.") def on_internet_available(self, agent): logging.debug( "[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( "[update] last check happened less than %d hours ago" % self.options['interval']) return logging.info("[update] checking for updates ...") display = agent.view() prev_status = display.get('status') try: display.update(force=True, new_data={'status': 'Checking for updates ...'}) to_install = [] to_check = [ ('bettercap/bettercap', parse_version('bettercap -version'), True, 'bettercap'), ('evilsocket/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'), ('evilsocket/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( "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: if install(display, update): num_installed += 1 else: prev_status = '%d new update%c available!' % ( num_updates, 's' if num_updates > 1 else '') logging.info("[update] done") self.status.update() if num_installed > 0: display.update(force=True, new_data={'status': 'Rebooting ...'}) pwnagotchi.reboot() except Exception as e: logging.error("[update] %s" % e) display.update(force=True, new_data={ 'status': prev_status if prev_status is not None else '' })
def __init__(self): self.ready = False self.report = StatusFile('/root/.wigle_uploads', data_format='json') self.skip = list() self.lock = Lock() self.shutdown = False
__version__ = '2.0.0' __name__ = 'wigle' __license__ = 'GPL3' __description__ = 'This plugin automatically uploades collected wifis to wigle.net' import os import logging import json from io import StringIO import csv from datetime import datetime import requests from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile READY = False REPORT = StatusFile('/root/.wigle_uploads', data_format='json') SKIP = list() OPTIONS = dict() def on_loaded(): """ Gets called when the plugin gets loaded """ global READY if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net") return READY = True
def __init__(self): self.ready = False self.status = StatusFile('/root/.auto-backup')
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 logging.info("OHC: OnlineHashCrack plugin loaded.") 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 _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 """ 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 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 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 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): 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.1.5' __license__ = 'GPL3' __description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com' __dependencies__ = {'pip': ['requests']} __defaults__ = { 'enabled': False, 'email': '', 'dashboard': '', 'single_files': False, 'whitelist': [], } 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() self.shutdown = False def on_config_changed(self, config): with self.lock: self.options['whitelist'] = list( set(self.options['whitelist'] + config['main']['whitelist'])) def on_before_shutdown(self): self.shutdown = True def on_loaded(self): """ Gets called when the plugin gets loaded """ if not self.options['email']: logging.error( "[ohc] Email isn't set. Can't upload to onlinehashcrack.com") return 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.debug(f"[ohc] {path} was already uploaded.") except requests.exceptions.RequestException as e: logging.debug( 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() 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_webhook(self, path, request): import requests from flask import redirect s = requests.Session() s.get('https://www.onlinehashcrack.com/dashboard') r = s.post('https://www.onlinehashcrack.com/dashboard', data={ 'emailTasks': self.options['email'], 'submit': '' }) return redirect(r.url, code=302) def on_internet_available(self, agent): """ Called in manual mode when there's internet connectivity """ if not self.ready or self.lock.locked() or self.shutdown: 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): if self.shutdown: return 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.debug( f"[ohc] Successfully uploaded {handshake}") except requests.exceptions.RequestException as req_e: self.skip.append(handshake) logging.debug("[ohc] %s", req_e) continue except OSError as os_e: self.skip.append(handshake) logging.debug("[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'])
def __init__(self): self.ready = False self.report = StatusFile('/root/.ohc_uploads', data_format='json') self.skip = list()
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
def __init__(self): self.ready = False self.report = StatusFile('/root/.wpa_sec_uploads', data_format='json') self.options = dict() self.skip = list()
class GitBackup(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '1.0.0' __license__ = 'GPL3' __description__ = 'git_backup' def __init__(self): self.ready = False self.tries = 0 logging.debug("GIT BACKUP") self.status = StatusFile('/root/.git_backup') # called when http://<host>:<port>/plugins/<plugin>/ is called # must return a html page # IMPORTANT: If you use "POST"s, add a csrf-token (via csrf_token() and render_template_string) def on_webhook(self, path, request): pass # called when the plugin is loaded def on_loaded(self): self.ready = True logging.info("GIT-BACKUP: Successfully loaded.") # called before the plugin is unloaded def on_unload(self, ui): pass # called hen there's internet connectivity def on_internet_available(self, agent): if not self.ready: return if self.tries >= 2: return if self.status.newer_then_days(self.options['interval']): return try: f = open("/home/pi/git_err.txt", "a+") display = agent.view() logging.info("GITBACKUP: Backing up ...") display.set('status', 'Backing up ...') display.update() logging.info(f"GIT-BACKUP: Running") process = subprocess.Popen([ 'sudo cp -r /root/brain.nn /root/brain.json /root/.api-report.json /root/handshakes/ /root/peers/ /etc/pwnagotchi/ /var/log/pwnagotchi.log /root/Pwnagotchi/ && cd /root/Pwnagotchi/ && sudo git pull && sudo git add . && sudo git commit -am "backup" && sudo git push' ], shell=True, stderr=f, stdin=f) process.wait() f.close() logging.info("GIT-BACKUP: backup done") display.set('status', 'GIT-Backup done!') display.update() self.status.update() except OSError as os_e: self.tries += 1 logging.info(f"GIT-BACKUP: Error: {os_e}") display.set('status', 'Backup failed!') display.update() # called to setup the ui elements def on_ui_setup(self, ui): pass # called when the ui is updated def on_ui_update(self, ui): pass # update those elements # called when the hardware display setup is done, display is an hardware specific object def on_display_setup(self, display): pass # called when everything is ready and the main loop is about to start def on_ready(self, agent): logging.info("unit is ready") # you can run custom bettercap commands if you want # agent.run('ble.recon on') # or set a custom state # agent.set_bored() # called when the AI finished loading def on_ai_ready(self, agent): pass # called when the AI finds a new set of parameters def on_ai_policy(self, agent, policy): pass # called when the AI starts training for a given number of epochs def on_ai_training_start(self, agent, epochs): pass # called after the AI completed a training epoch def on_ai_training_step(self, agent, _locals, _globals): pass # called when the AI has done training def on_ai_training_end(self, agent): pass # called when the AI got the best reward so far def on_ai_best_reward(self, agent, reward): pass # called when the AI got the worst reward so far def on_ai_worst_reward(self, agent, reward): pass # called when a non overlapping wifi channel is found to be free def on_free_channel(self, agent, channel): pass # called when the status is set to bored def on_bored(self, agent): pass # called when the status is set to sad def on_sad(self, agent): pass # called when the status is set to excited def on_excited(self, agent): pass # called when the status is set to lonely def on_lonely(self, agent): pass # called when the agent is rebooting the board def on_rebooting(self, agent): pass # called when the agent is waiting for t seconds def on_wait(self, agent, t): pass # called when the agent is sleeping for t seconds def on_sleep(self, agent, t): pass # called when the agent refreshed its access points list def on_wifi_update(self, agent, access_points): pass # called when the agent refreshed an unfiltered access point list # this list contains all access points that were detected BEFORE filtering def on_unfiltered_ap_list(self, agent, access_points): pass # called when the agent is sending an association frame def on_association(self, agent, access_point): pass # called when the agent is deauthenticating a client station from an AP def on_deauthentication(self, agent, access_point, client_station): pass # callend when the agent is tuning on a specific channel def on_channel_hop(self, agent, channel): pass # called when a new handshake is captured, access_point and client_station are json objects # if the agent could match the BSSIDs to the current list, otherwise they are just the strings of the BSSIDs def on_handshake(self, agent, filename, access_point, client_station): pass # called when an epoch is over (where an epoch is a single loop of the main algorithm) def on_epoch(self, agent, epoch, epoch_data): pass # called when a new peer is detected def on_peer_detected(self, agent, peer): pass # called when a known peer is lost def on_peer_lost(self, agent, peer): pass
def __init__(self): self.ready = False self.status = StatusFile('/root/.auto-update')
class SessionStats(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '0.1.0' __license__ = 'GPL3' __description__ = 'This plugin displays stats of the current session.' def __init__(self): self.lock = threading.Lock() self.options = dict() self.stats = dict() self.clock = GhettoClock() def on_loaded(self): """ Gets called when the plugin gets loaded """ # this has to happen in "loaded" because the options are not yet # available in the __init__ os.makedirs(self.options['save_directory'], exist_ok=True) self.session_name = "stats_{}.json".format( self.clock.now().strftime("%Y_%m_%d_%H_%M")) self.session = StatusFile(os.path.join(self.options['save_directory'], self.session_name), data_format='json') logging.info("[session-stats] Plugin loaded.") def on_epoch(self, agent, epoch, epoch_data): """ Save the epoch_data to self.stats """ with self.lock: self.stats[self.clock.now().strftime("%H:%M:%S")] = epoch_data self.session.update(data={'data': self.stats}) @staticmethod def extract_key_values(data, subkeys): result = dict() result['values'] = list() result['labels'] = subkeys for plot_key in subkeys: v = [[ts, d[plot_key]] for ts, d in data.items()] result['values'].append(v) return result 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 AutoBackup(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '2.0.0' __license__ = 'GPL3' __description__ = 'This plugin backups files when internet is available.' __defaults__ = { 'enabled': False, 'interval': 1, 'max_tries': 0, 'files': [ '/root/brain.nn', '/root/brain.json', '/root/.api-report.json', '/root/handshakes/', '/etc/pwnagotchi/', '/var/log/pwnagotchi.log', ], 'commands': ['tar czf /root/pwnagotchi-backup.tar.gz {files}'], } def __init__(self): self.ready = False self.tries = 0 self.status = StatusFile('/root/.auto-backup') def on_loaded(self): for opt in ['files', 'interval', 'commands', 'max_tries']: if opt not in self.options or (opt in self.options and self.options[opt] is None): logging.error(f"[autobackup] Option {opt} is not set.") return self.ready = True logging.info('[autobackup] Successfully loaded.') def on_internet_available(self, agent): if not self.ready: return if self.options[ 'max_tries'] and self.tries >= self.options['max_tries']: return if self.status.newer_then_days(self.options['interval']): return # Only backup existing files to prevent errors existing_files = list( filter(lambda f: os.path.exists(f), self.options['files'])) files_to_backup = " ".join(existing_files) try: display = agent.view() logging.info('[autobackup] Backing up ...') display.set('status', 'Backing up ...') display.update() for cmd in self.options['commands']: logging.info( f"[autobackup] Running {cmd.format(files=files_to_backup)}" ) process = subprocess.Popen(cmd.format(files=files_to_backup), shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") process.wait() if process.returncode > 0: raise OSError(f"Command failed (rc: {process.returncode})") logging.info('[autobackup] backup done') display.set('status', 'Backup done!') display.update() self.status.update() except OSError as os_e: self.tries += 1 logging.info(f"[autobackup] Error: {os_e}") display.set('status', 'Backup failed!') display.update()
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: 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.debug("%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 not self.options['api_key']): 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 not self.options['api_url']): logging.error( "WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured." ) return if 'whitelist' not in self.options: self.options['whitelist'] = list() self.ready = True def on_webhook(self, path, request): from flask import make_response, redirect response = make_response(redirect(self.options['api_url'], code=302)) response.set_cookie('key', self.options['api_key']) return response 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: 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_paths = remove_whitelisted(handshake_paths, self.options['whitelist']) 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.debug("WPA_SEC: Successfully uploaded %s", handshake) except requests.exceptions.RequestException as req_e: self.skip.append(handshake) logging.debug("WPA_SEC: %s", req_e) continue except OSError as os_e: logging.debug("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)
def __init__(self): self.options = dict() self.report = StatusFile('/root/.api-report.json', data_format='json') self.unread_messages = 0 self.total_messages = 0
__author__ = '*****@*****.**' __version__ = '1.0.0' __name__ = 'wpa-sec' __license__ = 'GPL3' __description__ = 'This plugin automatically uploades handshakes to https://wpa-sec.stanev.org' import os import logging import requests from pwnagotchi.utils import StatusFile READY = False REPORT = StatusFile('/root/.wpa_sec_uploads', data_format='json') OPTIONS = dict() SKIP = list() def on_loaded(): """ Gets called when the plugin gets loaded """ global READY if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): logging.error( "WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org") return READY = True
class AutoBackup(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '1.0.0' __license__ = 'GPL3' __description__ = 'This plugin backups files when internet is available.' def __init__(self): self.ready = False self.status = StatusFile('/root/.auto-backup') def on_loaded(self): if 'files' not in self.options or ('files' in self.options and self.options['files'] is None): logging.error("AUTO-BACKUP: No files to backup.") return if 'interval' not in self.options or ( 'interval' in self.options and self.options['interval'] is None): logging.error("AUTO-BACKUP: Interval is not set.") return if 'commands' not in self.options or ( 'commands' in self.options and self.options['commands'] is None): logging.error("AUTO-BACKUP: No commands given.") return self.ready = True logging.info("AUTO-BACKUP: Successfully loaded.") def on_internet_available(self, agent): if not self.ready: return if self.status.newer_then_days(self.options['interval']): return # Only backup existing files to prevent errors existing_files = list( filter(lambda f: os.path.exists(f), self.options['files'])) files_to_backup = " ".join(existing_files) try: display = agent.view() logging.info("AUTO-BACKUP: Backing up ...") display.set('status', 'Backing up ...') display.update() for cmd in self.options['commands']: logging.info( f"AUTO-BACKUP: Running {cmd.format(files=files_to_backup)}" ) process = subprocess.Popen(cmd.format(files=files_to_backup), shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") process.wait() if process.returncode > 0: raise OSError(f"Command failed (rc: {process.returncode})") logging.info("AUTO-BACKUP: backup done") display.set('status', 'Backup done!') display.update() self.status.update() except OSError as os_e: logging.info(f"AUTO-BACKUP: Error: {os_e}") display.set('status', 'Backup failed!') display.update()
__author__ = '*****@*****.**' __version__ = '1.0.0' __name__ = 'auto-backup' __license__ = 'GPL3' __description__ = 'This plugin backups files when internet is availaible.' from pwnagotchi.utils import StatusFile import logging import os import subprocess OPTIONS = dict() READY = False STATUS = StatusFile('/root/.auto-backup') def on_loaded(): global READY if 'files' not in OPTIONS or ('files' in OPTIONS and OPTIONS['files'] is None): logging.error("AUTO-BACKUP: No files to backup.") return if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None): logging.error("AUTO-BACKUP: Interval is not set.") return if 'commands' not in OPTIONS or ('commands' in OPTIONS and OPTIONS['commands'] is None):
class Wigle(plugins.Plugin): __author__ = '*****@*****.**' __version__ = '3.0.1' __license__ = 'GPL3' __description__ = 'This plugin automatically uploads collected wifis to wigle.net' __dependencies__ = {'pip': ['requests']} __defaults__ = { 'enabled': False, 'api_key': '', 'whitelist': [], } def __init__(self): self.ready = False self.report = StatusFile('/root/.wigle_uploads', data_format='json') self.skip = list() self.lock = Lock() self.shutdown = False def on_config_changed(self, config): with self.lock: self.options['whitelist'] = list( set(self.options['whitelist'] + config['main']['whitelist'])) def on_before_shutdown(self): self.shutdown = True def on_loaded(self): if not self.options['api_key']: logging.debug( "WIGLE: api_key isn't set. Can't upload to wigle.net") return if 'whitelist' not in self.options: self.options['whitelist'] = list() self.ready = True def on_internet_available(self, agent): """ Called in manual mode when there's internet connectivity """ if not self.ready or self.lock.locked() or self.shutdown: 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: if self.shutdown: return pcap_filename = gps_file.replace('.gps.json', '.pcap') if not os.path.exists(pcap_filename): logging.debug("WIGLE: Can't find pcap for %s", gps_file) self.skip.append(gps_file) continue try: gps_data = _extract_gps_data(gps_file) except OSError as os_err: logging.debug("WIGLE: %s", os_err) self.skip.append(gps_file) continue except json.JSONDecodeError as json_err: logging.debug("WIGLE: %s", json_err) self.skip.append(gps_file) continue if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0: logging.debug( "WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file) 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( "WIGLE: Could not extract all information. Skip %s", gps_file) self.skip.append(gps_file) continue except Scapy_Exception as sc_e: logging.debug("WIGLE: %s", 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("WIGLE: Successfully uploaded %d files", len(no_err_entries)) except requests.exceptions.RequestException as re_e: self.skip += no_err_entries logging.debug("WIGLE: Got an exception while uploading %s", re_e) except OSError as os_e: self.skip += no_err_entries logging.debug("WIGLE: Got the following error: %s", os_e)
class NetPos(plugins.Plugin): __author__ = 'zenzen san' __version__ = '2.0.3' __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 """ 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() 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 if 'api_url' in self.options: self.API_URL = self.options['api_url'] self.ready = True logging.info("net-pos plugin loaded.") logging.debug(f"net-pos: use api_url: {self.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): 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'] 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): 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.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
__license__ = 'GPL3' __description__ = 'This makes the display reachable over bluetooth' import os import time import re import logging import subprocess import dbus from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.view import BLACK import pwnagotchi.ui.fonts as fonts from pwnagotchi.utils import StatusFile READY = False INTERVAL = StatusFile('/root/.bt-tether') OPTIONS = dict() class BTError(Exception): """ Custom bluetooth exception """ pass class BTNap: """ This class creates a bluetooth connection to the specified bt-mac see https://github.com/bablokb/pi-btnap/blob/master/files/usr/local/sbin/btnap.service.py
def __init__(self): self.report = StatusFile('/root/.net_pos_saved', data_format='json') self.skip = list() self.ready = False self.lock = threading.Lock()
import os import logging import subprocess import requests import platform import shutil import glob import pkg_resources import pwnagotchi from pwnagotchi.utils import StatusFile OPTIONS = dict() READY = False STATUS = StatusFile('/root/.auto-update') def on_loaded(): global READY if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None): logging.error("[update] main.plugins.auto-update.interval is not set") return READY = True logging.info("[update] plugin loaded.") def check(version, repo, native=True): logging.debug("checking remote version for %s, local is %s" % (repo, version))
__name__ = 'grid' __license__ = 'GPL3' __description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \ 'networks to api.pwnagotchi.ai ' import os import logging import time import glob import re import pwnagotchi.grid as grid from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap OPTIONS = dict() REPORT = StatusFile('/root/.api-report.json', data_format='json') UNREAD_MESSAGES = 0 TOTAL_MESSAGES = 0 def on_loaded(): logging.info("grid plugin loaded.") def parse_pcap(filename): logging.info("grid: parsing %s ..." % filename) net_id = os.path.basename(filename).replace('.pcap', '') if '_' in net_id: