async def _on_event(self, msg): found_handshake = False jmsg = json.loads(msg) if jmsg['tag'] == 'wifi.client.handshake': filename = jmsg['data']['file'] sta_mac = jmsg['data']['station'] ap_mac = jmsg['data']['ap'] key = "%s -> %s" % (sta_mac, ap_mac) if key not in self._handshakes: self._handshakes[key] = jmsg s = self.session() ap_and_station = self._find_ap_sta_in(sta_mac, ap_mac, s) if ap_and_station is None: logging.warning("!!! captured new handshake: %s !!!", key) self._last_pwnd = ap_mac plugins.on('handshake', self, filename, ap_mac, sta_mac) else: (ap, sta) = ap_and_station self._last_pwnd = ap[ 'hostname'] if ap['hostname'] != '' and ap[ 'hostname'] != '<hidden>' else ap_mac logging.warning( "!!! captured new handshake on channel %d, %d dBm: %s (%s) -> %s [%s (%s)] !!!", ap['channel'], ap['rssi'], sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor']) plugins.on('handshake', self, filename, ap, sta) found_handshake = True self._update_handshakes(1 if found_handshake else 0)
def set_channel(self, channel, verbose=True): if self.is_stale(): logging.debug("recon is stale, skipping set_channel(%d)", channel) return # if in the previous loop no client stations has been deauthenticated # and only association frames have been sent, we don't need to wait # very long before switching channel as we don't have to wait for # such client stations to reconnect in order to sniff the handshake. wait = 0 if self._epoch.did_deauth: wait = self._config['personality']['hop_recon_time'] elif self._epoch.did_associate: wait = self._config['personality']['min_recon_time'] if channel != self._current_channel: if self._current_channel != 0 and wait > 0: if verbose: logging.info("waiting for %ds on channel %d ...", wait, self._current_channel) else: logging.debug("waiting for %ds on channel %d ...", wait, self._current_channel) self.wait_for(wait) if verbose and self._epoch.any_activity: logging.info("CHANNEL %d", channel) try: self.run('wifi.recon.channel %d' % channel) self._current_channel = channel self._epoch.track(hop=True) self._view.set('channel', '%d' % channel) plugins.on('channel_hop', self, channel) except Exception as e: logging.error("Error while setting channel (%s)", e)
def init_display(self): if self._enabled: self._implementation.initialize() plugins.on('display_setup', self._implementation) else: logging.warning("display module is disabled") self.on_render(self._on_view_rendered)
def next_epoch(self): was_stale = self.is_stale() did_miss = self._epoch.num_missed self._epoch.next() # after X misses during an epoch, set the status to lonely if was_stale: logging.warning("agent missed %d interactions -> lonely" % did_miss) self.set_lonely() # after X times being bored, the status is set to sad elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']: logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for) self.set_sad() # after X times being inactive, the status is set to bored elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']: logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for) self.set_bored() # after X times being active, the status is set to happy / excited elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']: logging.warning("%d epochs with activity -> excited" % self._epoch.active_for) self.set_excited() plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data()) if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']: logging.critical("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for) self._reboot() self._epoch.blind_for = 0
def update(self, force=False, new_data={}): for key, val in new_data.items(): self.set(key, val) with self._lock: if self._frozen: return state = self._state changes = state.changes(ignore=self._ignore_changes) if force or len(changes): self._canvas = Image.new('1', (self._width, self._height), WHITE) drawer = ImageDraw.Draw(self._canvas) plugins.on('ui_update', self) for key, lv in state.items(): lv.draw(self._canvas, drawer) web.update_frame(self._canvas) for cb in self._render_cbs: cb(self._canvas) self._state.reset()
def set_access_points(self, aps): self._access_points = aps plugins.on('wifi_update', self, aps) self._epoch.observe( aps, self._advertiser.peers() if self._advertiser is not None else ()) return self._access_points
def update(self, force=False, new_data={}, with_lock=True): for key, val in new_data.items(): self.set(key, val) maybe_lock = self._lock if with_lock else nullcontext() with maybe_lock: if self._frozen: return state = self._state changes = state.changes(ignore=self._ignore_changes) min_changes = 2 if self._config['ui']['fps'] == 0.0 else 0 if force or len(changes) > min_changes: logging.debug("Update screen because %s", 'it was forced.' if force else f"{changes} triggered it.") self._canvas = Image.new('1', (self._width, self._height), WHITE) drawer = ImageDraw.Draw(self._canvas) plugins.on('ui_update', self) for key, lv in state.items(): lv.draw(self._canvas, drawer) if self._config['ui']['web']['dark']: print(self._canvas.mode) self._canvas = ImageOps.invert(self._canvas.convert('L')).convert('1') web.update_frame(self._canvas) for cb in self._render_cbs: cb(self._canvas) self._state.reset()
def on_handshake(self, agent, filename, access_point, client_station): display = agent.view() result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''), shell=True, stdout=subprocess.PIPE) result = result.stdout.decode('utf-8').translate( {ord(c): None for c in string.whitespace}) if not result: logging.info('[quickdic] No handshake') else: logging.info('[quickdic] Handshake confirmed') result2 = subprocess.run( ('aircrack-ng -w `echo ' + self.options['wordlist_folder'] + '*.txt | sed \'s/ /,/g\'` -l ' + filename + '.cracked -q -b ' + result + ' ' + filename + ' | grep KEY'), shell=True, stdout=subprocess.PIPE) result2 = result2.stdout.decode('utf-8').strip() logging.info('[quickdic] %s', result2) if result2 != "KEY NOT FOUND": key = re.search(r'\[(.*)\]', result2) pwd = str(key.group(1)) self.text_to_set = "Cracked password: " + pwd display.update(force=True) plugins.on('cracked', access_point, pwd)
def get_access_points(self): whitelist = self._config['main']['whitelist'] restrict_to = self._config['main']['restrict_to'] aps = [] try: s = self.session() plugins.on("unfiltered_ap_list", self, s['wifi']['aps']) for ap in s['wifi']['aps']: if ap['encryption'] == '' or ap['encryption'] == 'OPEN': continue elif len(restrict_to) > 0 and \ (ap['mac'].lower() in restrict_to or ap['mac'][:8].lower() in restrict_to): if self._filter_included(ap): aps.append(ap) elif len(restrict_to) > 0: # Fall through for above case # We are restricting aps and this one is not wanted continue elif ap['hostname'] not in whitelist \ and ap['mac'].lower() not in whitelist \ and ap['mac'][:8].lower() not in whitelist: if self._filter_included(ap): aps.append(ap) except Exception as e: logging.exception("Error while getting acces points (%s)", e) aps.sort(key=lambda ap: ap['channel']) return self.set_access_points(aps)
def on_internet_available(self, agent): with self.lock: 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: 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 '') 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 set_training(self, training, for_epochs=0): self._is_training = training self._training_epochs = for_epochs if training: plugins.on('ai_training_start', self, for_epochs) else: plugins.on('ai_training_end', self)
def set_lonely(self): if not self._has_support_network_for(1.0): logging.info("unit is lonely") self._view.on_lonely() plugins.on('lonely', self) else: logging.info("unit is grateful instead of lonely") self.set_grateful()
def _init_display(self): if self.is_inky(): logging.info("initializing inky display") from pwnagotchi.ui.inkyphat.inkyphatfast import InkyPHATFast self._display = InkyPHATFast(self._display_color) self._display.set_border(InkyPHATFast.BLACK) self._render_cb = self._inky_render elif self.is_papirus(): logging.info("initializing papirus display") from pwnagotchi.ui.papirus.epd import EPD os.environ['EPD_SIZE'] = '2.0' self._display = EPD() self._display.clear() self._render_cb = self._papirus_render elif self.is_waveshare_v1(): if self._display_color == 'black': logging.info( "initializing waveshare v1 display in monochromatic mode") from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD self._display = EPD() self._display.init(self._display.lut_full_update) self._display.Clear(0xFF) self._display.init(self._display.lut_partial_update) self._render_cb = self._waveshare_render else: logging.info("initializing waveshare v1 display 3-color mode") from pwnagotchi.ui.waveshare.v1.epd2in13bc import EPD self._display = EPD() self._display.init() self._display.Clear() self._render_cb = self._waveshare_bc_render elif self.is_waveshare_v2(): logging.info("initializing waveshare v2 display") from pwnagotchi.ui.waveshare.v2.waveshare import EPD self._display = EPD() self._display.init(self._display.FULL_UPDATE) self._display.Clear(WHITE) self._display.init(self._display.PART_UPDATE) self._render_cb = self._waveshare_render elif self.is_oledhat(): logging.info("initializing oledhat display") from pwnagotchi.ui.waveshare.oledhat.epd import EPD self._display = EPD() self._display.init() self._display.Clear() self._render_cb = self._oledhat_render else: logging.critical("unknown display type %s" % self._display_type) plugins.on('display_setup', self._display) self.on_render(self._on_view_rendered)
def set_angry(self, factor): if not self._has_support_network_for(factor): logging.warning("%d epochs with no activity -> angry", self._epoch.inactive_for) self._view.on_angry() plugins.on('angry', self) else: logging.info("unit is grateful instead of angry") self.set_grateful()
def _event_poller(self): self._load_recovery_data() self.run('events.clear') while True: time.sleep(1) new_shakes = 0 logging.debug("polling events ...") try: s = self.session() self._update_uptime(s) self._update_advertisement(s) self._update_peers() self._update_counters() for h in [ e for e in self.events() if e['tag'] == 'wifi.client.handshake' ]: filename = h['data']['file'] sta_mac = h['data']['station'] ap_mac = h['data']['ap'] key = "%s -> %s" % (sta_mac, ap_mac) if key not in self._handshakes: self._handshakes[key] = h new_shakes += 1 ap_and_station = self._find_ap_sta_in( sta_mac, ap_mac, s) if ap_and_station is None: logging.warning( "!!! captured new handshake: %s !!!", key) self._last_pwnd = ap_mac plugins.on('handshake', self, filename, ap_mac, sta_mac) else: (ap, sta) = ap_and_station self._last_pwnd = ap[ 'hostname'] if ap['hostname'] != '' and ap[ 'hostname'] != '<hidden>' else ap_mac logging.warning( "!!! captured new handshake on channel %d, %d dBm: %s (%s) -> %s [%s (%s)] !!!", ap['channel'], ap['rssi'], sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor']) plugins.on('handshake', self, filename, ap, sta) except Exception as e: logging.error("error: %s", e) finally: self._update_handshakes(new_shakes)
def set_sad(self): factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs'] if not self._has_support_network_for(factor): logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for) self._view.on_sad() plugins.on('sad', self) else: logging.info("unit is grateful instead of sad") self.set_grateful()
def check_inbox(self, agent): logging.debug("[grid] 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(f"[grid] unread:{self.unread_messages} total:{self.total_messages}") agent.view().on_unread_messages(self.unread_messages, self.total_messages)
def set_bored(self): factor = self._epoch.inactive_for / self._config['personality'][ 'bored_num_epochs'] if not self._has_support_network_for(factor): logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for) self._view.on_bored() plugins.on('bored', self) else: self.set_grateful()
def custom_get_access_points(self): aps = [] try: s = self.agent.session() plugins.on("unfiltered_ap_list", self.agent, s['wifi']['aps']) for ap in s['wifi']['aps']: if ap['encryption'] == '' or ap['encryption'] == 'OPEN': continue if self.is_whitelisted(ap): aps.append(ap) except Exception as e: logging.exception(f"Error while getting access points ({e})") aps.sort(key=lambda ap: ap['channel']) return self.agent.set_access_points(aps)
def get_access_points(self): whitelist = self._config['main']['whitelist'] aps = [] try: s = self.session() plugins.on("unfiltered_ap_list", self, s['wifi']['aps']) for ap in s['wifi']['aps']: if ap['hostname'] not in whitelist: if self._filter_included(ap): aps.append(ap) except Exception as e: logging.exception("error") aps.sort(key=lambda ap: ap['channel']) return self.set_access_points(aps)
def on_ai_policy(self, new_params): plugins.on('ai_policy', self, new_params) logging.info("[ai] setting new policy:") for name, value in new_params.items(): if name in self._config['personality']: curr_value = self._config['personality'][name] if curr_value != value: logging.info("[ai] ! %s: %s -> %s" % (name, curr_value, value)) self._config['personality'][name] = value else: logging.error("[ai] param %s not in personality configuration!" % name) self.run('set wifi.ap.ttl %d' % self._config['personality']['ap_ttl']) self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl']) self.run('set wifi.rssi.min %d' % self._config['personality']['min_rssi'])
def next_epoch(self): logging.debug("agent.next_epoch()") was_stale = self.is_stale() did_miss = self._epoch.num_missed self._epoch.next() # after X misses during an epoch, set the status to lonely or angry if was_stale: factor = did_miss / self._config['personality'][ 'max_misses_for_recon'] if factor >= 2.0: self.set_angry(factor) else: logging.warning("agent missed %d interactions -> lonely" % did_miss) self.set_lonely() # after X times being bored, the status is set to sad or angry elif self._epoch.inactive_for >= self._config['personality'][ 'sad_num_epochs']: factor = self._epoch.inactive_for / self._config['personality'][ 'sad_num_epochs'] if factor >= 2.0: self.set_angry(factor) else: self.set_sad() # after X times being inactive, the status is set to bored elif self._epoch.inactive_for >= self._config['personality'][ 'bored_num_epochs']: self.set_bored() # after X times being active, the status is set to happy / excited elif self._epoch.active_for >= self._config['personality'][ 'excited_num_epochs']: self.set_excited() elif self._epoch.active_for >= 5 and self._has_support_network_for( 5.0): self.set_grateful() plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data()) if self._epoch.blind_for >= self._config['main'][ 'mon_max_blind_epochs']: logging.critical( "%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for) self._reboot() self._epoch.blind_for = 0
def update(self, force=False): with self._lock: changes = self._state.changes(ignore=self._ignore_changes) if force or len(changes): self._canvas = Image.new('1', (self._width, self._height), WHITE) drawer = ImageDraw.Draw(self._canvas) plugins.on('ui_update', self) for key, lv in self._state.items(): lv.draw(self._canvas, drawer) for cb in self._render_cbs: cb(self._canvas) self._state.reset()
def associate(self, ap, throttle=0): if self.is_stale(): logging.debug(f"Recon is stale, skipping assoc({ap['mac']}).") return if self._config['personality']['associate'] and self._should_interact(ap['mac']): self._view.on_assoc(ap) try: logging.info(f"Sending association frame to {ap['hostname']} ({ap['mac']} {ap['vendor']}) on channel {ap['channel']} [{len(ap['clients'])} clients], {ap['rssi']} dBm...") self.run(f"wifi.assoc {ap['mac']}") self._epoch.track(assoc=True) except Exception as e: self._on_error(ap['mac'], e) plugins.on('association', self, ap) if throttle > 0: time.sleep(throttle) self._view.on_normal()
def deauth(self, ap, sta, throttle=0): if self.is_stale(): logging.debug(f"Recon is stale, skipping deauth({sta['mac']}).") return if self._config['personality']['deauth'] and self._should_interact(sta['mac']): self._view.on_deauth(sta) try: logging.info(f"Deauthing {sta['mac']} ({sta['vendor']}) from {ap['hostname']} ({ap['mac']} {ap['vendor']}) on channel {ap['channel']}, {ap['rssi']} dBm...") self.run(f"wifi.deauth {sta['mac']}") self._epoch.track(deauth=True) except Exception as e: self._on_error(sta['mac'], e) plugins.on('deauthentication', self, ap, sta) if throttle > 0: time.sleep(throttle) self._view.on_normal()
def get_access_points(self): whitelist = self._config['main']['whitelist'] aps = [] try: s = self.session() plugins.on("unfiltered_ap_list", self, s['wifi']['aps']) for ap in s['wifi']['aps']: if ap['encryption'] == '' or ap['encryption'] == 'OPEN': continue elif ap['hostname'] not in whitelist \ and ap['mac'].lower() not in whitelist \ and ap['mac'][:8].lower() not in whitelist: if self._filter_included(ap): aps.append(ap) except Exception as e: logging.exception("Error while getting acces points (%s)", e) aps.sort(key=lambda ap: ap['channel']) return self.set_access_points(aps)
def associate(self, ap, throttle=0): if self.is_stale(): logging.debug("recon is stale, skipping assoc(%s)", ap['mac']) return if self._config['personality']['associate'] and self._should_interact(ap['mac']): self._view.on_assoc(ap) try: logging.info("sending association frame to %s (%s %s) on channel %d [%d clients], %d dBm...", ap['hostname'], ap['mac'], ap['vendor'], ap['channel'], len(ap['clients']), ap['rssi']) self.run('wifi.assoc %s' % ap['mac']) self._epoch.track(assoc=True) except Exception as e: self._on_error(ap['mac'], e) plugins.on('association', self, ap) if throttle > 0: time.sleep(throttle) self._view.on_normal()
def deauth(self, ap, sta, throttle=0): if self.is_stale(): logging.debug("recon is stale, skipping deauth(%s)", sta['mac']) return if self._config['personality']['deauth'] and self._should_interact(sta['mac']): self._view.on_deauth(sta) try: logging.info("deauthing %s (%s) from %s (%s %s) on channel %d, %d dBm ...", sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor'], ap['channel'], ap['rssi']) self.run('wifi.deauth %s' % sta['mac']) self._epoch.track(deauth=True) except Exception as e: self._on_error(sta['mac'], e) plugins.on('deauthentication', self, ap, sta) if throttle > 0: time.sleep(throttle) self._view.on_normal()
def _init_display(self): if self._is_inky(): logging.info("initializing inky display") from inky import InkyPHAT self._display = InkyPHAT(self._display_color) self._display.set_border(InkyPHAT.BLACK) self._render_cb = self._inky_render elif self._is_papirus(): logging.info("initializing papirus display") from pwnagotchi.ui.papirus.epd import EPD os.environ['EPD_SIZE'] = '2.0' self._display = EPD() self._display.clear() self._render_cb = self._papirus_render elif self._is_waveshare1(): logging.info("initializing waveshare v1 display") from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD self._display = EPD() self._display.init(self._display.lut_full_update) self._display.Clear(0xFF) self._display.init(self._display.lut_partial_update) self._render_cb = self._waveshare_render elif self._is_waveshare2(): logging.info("initializing waveshare v2 display") from pwnagotchi.ui.waveshare.v2.waveshare import EPD self._display = EPD() self._display.init(self._display.FULL_UPDATE) self._display.Clear(WHITE) self._display.init(self._display.PART_UPDATE) self._render_cb = self._waveshare_render else: logging.critical("unknown display type %s" % self._display_type) plugins.on('display_setup', self._display) self.on_render(self._on_view_rendered)
def _adv_poller(self): while True: logging.debug("polling pwngrid-peer for peers ...") try: grid_peers = grid.peers() new_peers = {} self._closest_peer = None for obj in grid_peers: peer = Peer(obj) new_peers[peer.identity()] = peer if self._closest_peer is None: self._closest_peer = peer # check who's gone to_delete = [] for ident, peer in self._peers.items(): if ident not in new_peers: self._view.on_lost_peer(peer) plugins.on('peer_lost', self, peer) to_delete.append(ident) for ident in to_delete: del self._peers[ident] for ident, peer in new_peers.items(): # check who's new if ident not in self._peers: self._peers[ident] = peer self._view.on_new_peer(peer) plugins.on('peer_detected', self, peer) # update the rest else: self._peers[ident].update(peer) except Exception as e: logging.exception("error while polling pwngrid-peer") time.sleep(1)