misc: refactored plugin system to use classes

This commit is contained in:
Simone Margaritelli
2019-11-01 13:51:45 +01:00
parent ae330dc0b5
commit 2f948306eb
20 changed files with 943 additions and 1049 deletions

View File

@ -7,20 +7,20 @@ default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "defaul
loaded = {} loaded = {}
def dummy_callback(): class Plugin:
pass @classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
global loaded
plugin_name = cls.__module__.split('.')[0]
plugin_instance = cls()
logging.debug("loaded plugin %s as %s" % (plugin_name, plugin_instance))
loaded[plugin_name] = plugin_instance
def on(event_name, *args, **kwargs): def on(event_name, *args, **kwargs):
global loaded
cb_name = 'on_%s' % event_name
for plugin_name, plugin in loaded.items(): for plugin_name, plugin in loaded.items():
if cb_name in plugin.__dict__: one(plugin_name, event_name, *args, **kwargs)
try:
plugin.__dict__[cb_name](*args, **kwargs)
except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True)
def one(plugin_name, event_name, *args, **kwargs): def one(plugin_name, event_name, *args, **kwargs):
@ -28,15 +28,17 @@ def one(plugin_name, event_name, *args, **kwargs):
if plugin_name in loaded: if plugin_name in loaded:
plugin = loaded[plugin_name] plugin = loaded[plugin_name]
cb_name = 'on_%s' % event_name cb_name = 'on_%s' % event_name
if cb_name in plugin.__dict__: callback = getattr(plugin, cb_name, None)
if callback is not None and callable(callback):
try: try:
plugin.__dict__[cb_name](*args, **kwargs) callback(*args, **kwargs)
except Exception as e: except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True) logging.error(e, exc_info=True)
def load_from_file(filename): def load_from_file(filename):
logging.debug("loading %s" % filename)
plugin_name = os.path.basename(filename.replace(".py", "")) plugin_name = os.path.basename(filename.replace(".py", ""))
spec = importlib.util.spec_from_file_location(plugin_name, filename) spec = importlib.util.spec_from_file_location(plugin_name, filename)
instance = importlib.util.module_from_spec(spec) instance = importlib.util.module_from_spec(spec)
@ -46,19 +48,15 @@ def load_from_file(filename):
def load_from_path(path, enabled=()): def load_from_path(path, enabled=()):
global loaded global loaded
logging.debug("loading plugins from %s - enabled: %s" % (path, enabled))
for filename in glob.glob(os.path.join(path, "*.py")): for filename in glob.glob(os.path.join(path, "*.py")):
try: plugin_name = os.path.basename(filename.replace(".py", ""))
name, plugin = load_from_file(filename) if plugin_name in enabled:
if name in loaded: try:
raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__)) load_from_file(filename)
elif name not in enabled: except Exception as e:
# print("plugin %s is not enabled" % name) logging.warning("error while loading %s: %s" % (filename, e))
pass logging.debug(e, exc_info=True)
else:
loaded[name] = plugin
except Exception as e:
logging.warning("error while loading %s: %s" % (filename, e))
logging.debug(e, exc_info=True)
return loaded return loaded
@ -66,17 +64,17 @@ def load_from_path(path, enabled=()):
def load(config): def load(config):
enabled = [name for name, options in config['main']['plugins'].items() if enabled = [name for name, options in config['main']['plugins'].items() if
'enabled' in options and options['enabled']] 'enabled' in options and options['enabled']]
custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
# load default plugins # load default plugins
loaded = load_from_path(default_path, enabled=enabled) load_from_path(default_path, enabled=enabled)
# set the options
for name, plugin in loaded.items():
plugin.__dict__['OPTIONS'] = config['main']['plugins'][name]
# load custom ones # load custom ones
custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
if custom_path is not None: if custom_path is not None:
loaded = load_from_path(custom_path, enabled=enabled) load_from_path(custom_path, enabled=enabled)
# set the options
for name, plugin in loaded.items(): # propagate options
plugin.__dict__['OPTIONS'] = config['main']['plugins'][name] for name, plugin in loaded.items():
plugin.options = config['main']['plugins'][name]
on('loaded') on('loaded')

View File

@ -1,56 +1,57 @@
__author__ = 'pwnagotchi [at] rossmarks [dot] uk' import pwnagotchi.plugins as plugins
__version__ = '1.0.1'
__name__ = 'AircrackOnly'
__license__ = 'GPL3'
__description__ = 'confirm pcap contains handshake/PMKID or delete it'
'''
Aircrack-ng needed, to install:
> apt-get install aircrack-ng
'''
import logging import logging
import subprocess import subprocess
import string import string
import os import os
OPTIONS = dict() '''
Aircrack-ng needed, to install:
> apt-get install aircrack-ng
'''
def on_loaded():
logging.info("aircrackonly plugin loaded")
def on_handshake(agent, filename, access_point, client_station): class AircrackOnly(plugins.Plugin):
display = agent._view __author__ = 'pwnagotchi [at] rossmarks [dot] uk'
todelete = 0 __version__ = '1.0.1'
__license__ = 'GPL3'
__description__ = 'confirm pcap contains handshake/PMKID or delete it'
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) def __init__(self):
result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) super().__init__(self)
if result: self.text_to_set = ""
logging.info("[AircrackOnly] contains handshake")
else:
todelete = 1
if todelete == 0: def on_loaded(self):
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "PMKID" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) logging.info("aircrackonly plugin loaded")
result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace})
def on_handshake(self, agent, filename, access_point, client_station):
display = agent._view
todelete = 0
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 result: if result:
logging.info("[AircrackOnly] contains PMKID") logging.info("[AircrackOnly] contains handshake")
else: else:
todelete = 1 todelete = 1
if todelete == 1: if todelete == 0:
os.remove(filename) result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "PMKID" | awk \'{print $2}\''),
set_text("Removed an uncrackable pcap") shell=True, stdout=subprocess.PIPE)
display.update(force=True) result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
if result:
logging.info("[AircrackOnly] contains PMKID")
else:
todelete = 1
text_to_set = ""; if todelete == 1:
def set_text(text): os.remove(filename)
global text_to_set self.text_to_set = "Removed an uncrackable pcap"
text_to_set = text display.update(force=True)
def on_ui_update(ui): def on_ui_update(self, ui):
global text_to_set if self.text_to_set:
if text_to_set: ui.set('face', "(>.<)")
ui.set('face', "(>.<)") ui.set('status', self.text_to_set)
ui.set('status', text_to_set) self.text_to_set = ""
text_to_set = ""

View File

@ -1,49 +1,47 @@
__author__ = '33197631+dadav@users.noreply.github.com' import pwnagotchi.plugins as plugins
__version__ = '1.0.0'
__name__ = 'auto-backup'
__license__ = 'GPL3'
__description__ = 'This plugin backups files when internet is available.'
from pwnagotchi.utils import StatusFile from pwnagotchi.utils import StatusFile
import logging import logging
import os import os
import subprocess import subprocess
OPTIONS = dict()
READY = False
STATUS = StatusFile('/root/.auto-backup')
class AutoBackup(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'This plugin backups files when internet is available.'
def on_loaded(): def __init__(self):
global READY self.ready = False
self.status = StatusFile('/root/.auto-backup')
if 'files' not in OPTIONS or ('files' in OPTIONS and OPTIONS['files'] is None): def on_loaded(self):
logging.error("AUTO-BACKUP: No files to backup.") if 'files' not in self.options or ('files' in self.options and self.options['files'] is None):
return logging.error("AUTO-BACKUP: No files to backup.")
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):
logging.error("AUTO-BACKUP: No commands given.")
return
READY = True
logging.info("AUTO-BACKUP: Successfully loaded.")
def on_internet_available(agent):
global STATUS
if READY:
if STATUS.newer_then_days(OPTIONS['interval']):
return 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 # Only backup existing files to prevent errors
existing_files = list(filter(lambda f: os.path.exists(f), OPTIONS['files'])) existing_files = list(filter(lambda f: os.path.exists(f), self.options['files']))
files_to_backup = " ".join(existing_files) files_to_backup = " ".join(existing_files)
try: try:
display = agent.view() display = agent.view()
@ -51,10 +49,10 @@ def on_internet_available(agent):
display.set('status', 'Backing up ...') display.set('status', 'Backing up ...')
display.update() display.update()
for cmd in OPTIONS['commands']: for cmd in self.options['commands']:
logging.info(f"AUTO-BACKUP: Running {cmd.format(files=files_to_backup)}") 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, process = subprocess.Popen(cmd.format(files=files_to_backup), shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait() process.wait()
if process.returncode > 0: if process.returncode > 0:
raise OSError(f"Command failed (rc: {process.returncode})") raise OSError(f"Command failed (rc: {process.returncode})")
@ -62,7 +60,7 @@ def on_internet_available(agent):
logging.info("AUTO-BACKUP: backup done") logging.info("AUTO-BACKUP: backup done")
display.set('status', 'Backup done!') display.set('status', 'Backup done!')
display.update() display.update()
STATUS.update() self.status.update()
except OSError as os_e: except OSError as os_e:
logging.info(f"AUTO-BACKUP: Error: {os_e}") logging.info(f"AUTO-BACKUP: Error: {os_e}")
display.set('status', 'Backup failed!') display.set('status', 'Backup failed!')

View File

@ -1,9 +1,3 @@
__author__ = 'evilsocket@gmail.com'
__version__ = '1.1.1'
__name__ = 'auto-update'
__license__ = 'GPL3'
__description__ = 'This plugin checks when updates are available and applies them when internet is available.'
import os import os
import re import re
import logging import logging
@ -15,21 +9,9 @@ import glob
import pkg_resources import pkg_resources
import pwnagotchi import pwnagotchi
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile 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): def check(version, repo, native=True):
logging.debug("checking remote version for %s, local is %s" % (repo, version)) logging.debug("checking remote version for %s, local is %s" % (repo, version))
@ -158,14 +140,32 @@ def parse_version(cmd):
raise Exception('could not parse version from "%s": output=\n%s' % (cmd, out)) raise Exception('could not parse version from "%s": output=\n%s' % (cmd, out))
def on_internet_available(agent): class AutoUpdate(plugins.Plugin):
global STATUS __author__ = 'evilsocket@gmail.com'
__version__ = '1.1.1'
__name__ = 'auto-update'
__license__ = 'GPL3'
__description__ = 'This plugin checks when updates are available and applies them when internet is available.'
logging.debug("[update] internet connectivity is available (ready %s)" % READY) def __init__(self):
self.ready = False
self.status = StatusFile('/root/.auto-update')
if READY: def on_loaded(self):
if STATUS.newer_then_hours(OPTIONS['interval']): if 'interval' not in self.options or ('interval' in self.options and self.options['interval'] is None):
logging.debug("[update] last check happened less than %d hours ago" % OPTIONS['interval']) 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 return
logging.info("[update] checking for updates ...") logging.info("[update] checking for updates ...")
@ -187,7 +187,8 @@ def on_internet_available(agent):
info = check(local_version, repo, is_native) info = check(local_version, repo, is_native)
if info['url'] is not None: if info['url'] is not None:
logging.warning( logging.warning(
"update for %s available (local version is '%s'): %s" % (repo, info['current'], info['url'])) "update for %s available (local version is '%s'): %s" % (
repo, info['current'], info['url']))
info['service'] = svc_name info['service'] = svc_name
to_install.append(info) to_install.append(info)
@ -195,7 +196,7 @@ def on_internet_available(agent):
num_installed = 0 num_installed = 0
if num_updates > 0: if num_updates > 0:
if OPTIONS['install']: if self.options['install']:
for update in to_install: for update in to_install:
if install(display, update): if install(display, update):
num_installed += 1 num_installed += 1
@ -204,7 +205,7 @@ def on_internet_available(agent):
logging.info("[update] done") logging.info("[update] done")
STATUS.update() self.status.update()
if num_installed > 0: if num_installed > 0:
display.update(force=True, new_data={'status': 'Rebooting ...'}) display.update(force=True, new_data={'status': 'Rebooting ...'})

View File

@ -1,9 +1,3 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__name__ = 'bt-tether'
__license__ = 'GPL3'
__description__ = 'This makes the display reachable over bluetooth'
import os import os
import time import time
import re import re
@ -14,11 +8,8 @@ from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
from pwnagotchi.utils import StatusFile from pwnagotchi.utils import StatusFile
import pwnagotchi.plugins as plugins
READY = False
INTERVAL = StatusFile('/root/.bt-tether')
OPTIONS = dict()
NETWORK = None
class BTError(Exception): class BTError(Exception):
""" """
@ -26,6 +17,7 @@ class BTError(Exception):
""" """
pass pass
class BTNap: class BTNap:
""" """
This class creates a bluetooth connection to the specified bt-mac This class creates a bluetooth connection to the specified bt-mac
@ -41,7 +33,6 @@ class BTNap:
def __init__(self, mac): def __init__(self, mac):
self._mac = mac self._mac = mac
@staticmethod @staticmethod
def get_bus(): def get_bus():
""" """
@ -59,9 +50,9 @@ class BTNap:
""" """
manager = getattr(BTNap.get_manager, 'cached_obj', None) manager = getattr(BTNap.get_manager, 'cached_obj', None)
if not manager: if not manager:
manager = BTNap.get_manager.cached_obj = dbus.Interface( manager = BTNap.get_manager.cached_obj = dbus.Interface(
BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'), BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'),
'org.freedesktop.DBus.ObjectManager' ) 'org.freedesktop.DBus.ObjectManager')
return manager return manager
@staticmethod @staticmethod
@ -82,7 +73,6 @@ class BTNap:
iface = obj.dbus_interface iface = obj.dbus_interface
return obj.Set(iface, k, v, dbus_interface=BTNap.IFACE_PROPS) return obj.Set(iface, k, v, dbus_interface=BTNap.IFACE_PROPS)
@staticmethod @staticmethod
def find_adapter(pattern=None): def find_adapter(pattern=None):
""" """
@ -98,14 +88,14 @@ class BTNap:
""" """
bus, obj = BTNap.get_bus(), None bus, obj = BTNap.get_bus(), None
for path, ifaces in objects.items(): for path, ifaces in objects.items():
adapter = ifaces.get(BTNap.IFACE_ADAPTER) adapter = ifaces.get(BTNap.IFACE_ADAPTER)
if adapter is None: if adapter is None:
continue continue
if not pattern or pattern == adapter['Address'] or path.endswith(pattern): if not pattern or pattern == adapter['Address'] or path.endswith(pattern):
obj = bus.get_object(BTNap.IFACE_BASE, path) obj = bus.get_object(BTNap.IFACE_BASE, path)
yield dbus.Interface(obj, BTNap.IFACE_ADAPTER) yield dbus.Interface(obj, BTNap.IFACE_ADAPTER)
if obj is None: if obj is None:
raise BTError('Bluetooth adapter not found') raise BTError('Bluetooth adapter not found')
@staticmethod @staticmethod
def find_device(device_address, adapter_pattern=None): def find_device(device_address, adapter_pattern=None):
@ -178,7 +168,6 @@ class BTNap:
logging.debug("BT-TETHER: Device is not connected.") logging.debug("BT-TETHER: Device is not connected.")
return None, False return None, False
def is_paired(self): def is_paired(self):
""" """
Check if already connected Check if already connected
@ -198,7 +187,6 @@ class BTNap:
logging.debug("BT-TETHER: Device is not paired.") logging.debug("BT-TETHER: Device is not paired.")
return False return False
def wait_for_device(self, timeout=15): def wait_for_device(self, timeout=15):
""" """
Wait for device Wait for device
@ -227,7 +215,7 @@ class BTNap:
try: try:
dev_remote = BTNap.find_device(self._mac, bt_dev) dev_remote = BTNap.find_device(self._mac, bt_dev)
logging.debug("BT-TETHER: Using remote device (addr: %s): %s", logging.debug("BT-TETHER: Using remote device (addr: %s): %s",
BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path ) BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path)
break break
except BTError: except BTError:
logging.debug("BT-TETHER: Not found yet ...") logging.debug("BT-TETHER: Not found yet ...")
@ -259,7 +247,6 @@ class BTNap:
pass pass
return False return False
@staticmethod @staticmethod
def nap(device): def nap(device):
logging.debug('BT-TETHER: Trying to nap ...') logging.debug('BT-TETHER: Trying to nap ...')
@ -267,7 +254,7 @@ class BTNap:
try: try:
logging.debug('BT-TETHER: Connecting to profile ...') logging.debug('BT-TETHER: Connecting to profile ...')
device.ConnectProfile('nap') device.ConnectProfile('nap')
except Exception: # raises exception, but still works except Exception: # raises exception, but still works
pass pass
net = dbus.Interface(device, 'org.bluez.Network1') net = dbus.Interface(device, 'org.bluez.Network1')
@ -297,7 +284,7 @@ class SystemdUnitWrapper:
@staticmethod @staticmethod
def _action_on_unit(action, unit): def _action_on_unit(action, unit):
process = subprocess.Popen(f"systemctl {action} {unit}", shell=True, stdin=None, process = subprocess.Popen(f"systemctl {action} {unit}", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait() process.wait()
if process.returncode > 0: if process.returncode > 0:
return False return False
@ -309,7 +296,7 @@ class SystemdUnitWrapper:
Calls systemctl daemon-reload Calls systemctl daemon-reload
""" """
process = subprocess.Popen("systemctl daemon-reload", shell=True, stdin=None, process = subprocess.Popen("systemctl daemon-reload", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait() process.wait()
if process.returncode > 0: if process.returncode > 0:
return False return False
@ -387,16 +374,15 @@ class IfaceWrapper:
""" """
return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up' return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up'
def set_addr(self, addr): def set_addr(self, addr):
""" """
Set the netmask Set the netmask
""" """
process = subprocess.Popen(f"ip addr add {addr} dev {self.iface}", shell=True, stdin=None, process = subprocess.Popen(f"ip addr add {addr} dev {self.iface}", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait() process.wait()
if process.returncode == 2 or process.returncode == 0: # 2 = already set if process.returncode == 2 or process.returncode == 0: # 2 = already set
return True return True
return False return False
@ -404,7 +390,7 @@ class IfaceWrapper:
@staticmethod @staticmethod
def set_route(addr): def set_route(addr):
process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None, process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait() process.wait()
if process.returncode > 0: if process.returncode > 0:
@ -413,44 +399,47 @@ class IfaceWrapper:
return True return True
class BTTether(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'This makes the display reachable over bluetooth'
def on_loaded(): def __init__(self):
""" self.ready = False
Gets called when the plugin gets loaded self.interval = StatusFile('/root/.bt-tether')
""" self.network = None
global READY
global INTERVAL
for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: def on_loaded(self):
if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None): for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
logging.error("BT-TET: Please specify the %s in your config.yml.", opt) if opt not in self.options or (opt in self.options and self.options[opt] is None):
logging.error("BT-TET: Please specify the %s in your config.yml.", opt)
return
# ensure bluetooth is running
bt_unit = SystemdUnitWrapper('bluetooth.service')
if not bt_unit.is_active():
if not bt_unit.start():
logging.error("BT-TET: Can't start bluetooth.service")
return
self.interval.update()
self.ready = True
def on_ui_setup(self, ui):
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_update(self, ui):
if not self.ready:
return return
# ensure bluetooth is running if self.interval.newer_then_minutes(self.options['interval']):
bt_unit = SystemdUnitWrapper('bluetooth.service')
if not bt_unit.is_active():
if not bt_unit.start():
logging.error("BT-TET: Can't start bluetooth.service")
return return
INTERVAL.update() self.interval.update()
READY = True
bt = BTNap(self.options['mac'])
def on_ui_update(ui):
"""
Try to connect to device
"""
if READY:
global INTERVAL
global NETWORK
if INTERVAL.newer_then_minutes(OPTIONS['interval']):
return
INTERVAL.update()
bt = BTNap(OPTIONS['mac'])
logging.debug('BT-TETHER: Check if already connected and paired') logging.debug('BT-TETHER: Check if already connected and paired')
dev_remote, connected = bt.is_connected() dev_remote, connected = bt.is_connected()
@ -483,14 +472,13 @@ def on_ui_update(ui):
else: else:
logging.debug('BT-TETHER: Already paired.') logging.debug('BT-TETHER: Already paired.')
btnap_iface = IfaceWrapper('bnep0') btnap_iface = IfaceWrapper('bnep0')
logging.debug('BT-TETHER: Check interface') logging.debug('BT-TETHER: Check interface')
if not btnap_iface.exists(): if not btnap_iface.exists():
# connected and paired but not napping # connected and paired but not napping
logging.debug('BT-TETHER: Try to connect to nap ...') logging.debug('BT-TETHER: Try to connect to nap ...')
network, status = BTNap.nap(dev_remote) network, status = BTNap.nap(dev_remote)
NETWORK = network self.network = network
if status: if status:
logging.info('BT-TETHER: Napping!') logging.info('BT-TETHER: Napping!')
ui.set('bluetooth', 'C') ui.set('bluetooth', 'C')
@ -504,7 +492,7 @@ def on_ui_update(ui):
logging.debug('BT-TETHER: Interface found') logging.debug('BT-TETHER: Interface found')
# check ip # check ip
addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}" addr = f"{self.options['ip']}/{self.options['netmask']}"
logging.debug('BT-TETHER: Try to set ADDR to interface') logging.debug('BT-TETHER: Try to set ADDR to interface')
if not btnap_iface.set_addr(addr): if not btnap_iface.set_addr(addr):
@ -515,9 +503,10 @@ def on_ui_update(ui):
logging.debug('BT-TETHER: Set ADDR to interface') logging.debug('BT-TETHER: Set ADDR to interface')
# change route if sharking # change route if sharking
if OPTIONS['share_internet']: if self.options['share_internet']:
logging.debug('BT-TETHER: Set routing and change resolv.conf') logging.debug('BT-TETHER: Set routing and change resolv.conf')
IfaceWrapper.set_route(".".join(OPTIONS['ip'].split('.')[:-1] + ['1'])) # im not proud about that IfaceWrapper.set_route(
".".join(self.options['ip'].split('.')[:-1] + ['1'])) # im not proud about that
# fix resolv.conf; dns over https ftw! # fix resolv.conf; dns over https ftw!
with open('/etc/resolv.conf', 'r+') as resolv: with open('/etc/resolv.conf', 'r+') as resolv:
nameserver = resolv.read() nameserver = resolv.read()
@ -530,8 +519,3 @@ def on_ui_update(ui):
else: else:
logging.error('BT-TETHER: bnep0 not found') logging.error('BT-TETHER: bnep0 not found')
ui.set('bluetooth', 'BE') ui.set('bluetooth', 'BE')
def on_ui_setup(ui):
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0),
label_font=fonts.Bold, text_font=fonts.Medium))

View File

@ -1,182 +1,154 @@
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__name__ = 'hello_world'
__license__ = 'GPL3'
__description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.'
import logging import logging
import pwnagotchi.plugins as plugins
from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
# Will be set with the options in config.yml config['main']['plugins'][__name__] class Example(plugins.Plugin):
OPTIONS = dict() __author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.'
# called when <host>:<port>/plugins/<pluginname> is opened def __init__(self):
def on_webhook(response, path): logging.debug("example plugin created")
res = "<html><body><a>Hook triggered</a></body></html>"
response.send_response(200)
response.send_header('Content-type', 'text/html')
response.end_headers()
try: # called when the plugin is loaded
response.wfile.write(bytes(res, "utf-8")) def on_loaded(self):
except Exception as ex: logging.warning("WARNING: this plugin should be disabled! options = " % self.options)
logging.error(ex)
# called when the plugin is loaded # called when <host>:<port>/plugins/<pluginname> is opened
def on_loaded(): def on_webhook(self, response, path):
logging.warning("WARNING: plugin %s should be disabled!" % __name__) res = "<html><body><a>Hook triggered</a></body></html>"
response.send_response(200)
response.send_header('Content-type', 'text/html')
response.end_headers()
try:
response.wfile.write(bytes(res, "utf-8"))
except Exception as ex:
logging.error(ex)
# called in manual mode when there's internet connectivity # called in manual mode when there's internet connectivity
def on_internet_available(agent): def on_internet_available(self, agent):
pass pass
# called to setup the ui elements
def on_ui_setup(self, ui):
# add custom UI elements
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
label_font=fonts.Bold, text_font=fonts.Medium))
# called to setup the ui elements # called when the ui is updated
def on_ui_setup(ui): def on_ui_update(self, ui):
# add custom UI elements # update those elements
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0), some_voltage = 0.1
label_font=fonts.Bold, text_font=fonts.Medium)) some_capacity = 100.0
ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity))
# called when the hardware display setup is done, display is an hardware specific object
def on_display_setup(self, display):
pass
# called when the ui is updated # called when everything is ready and the main loop is about to start
def on_ui_update(ui): def on_ready(self, agent):
# update those elements logging.info("unit is ready")
some_voltage = 0.1 # you can run custom bettercap commands if you want
some_capacity = 100.0 # agent.run('ble.recon on')
# or set a custom state
# agent.set_bored()
ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity)) # 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 hardware display setup is done, display is an hardware specific object # called when the AI starts training for a given number of epochs
def on_display_setup(display): def on_ai_training_start(self, agent, epochs):
pass pass
# called after the AI completed a training epoch
def on_ai_training_step(self, agent, _locals, _globals):
pass
# called when everything is ready and the main loop is about to start # called when the AI has done training
def on_ready(agent): def on_ai_training_end(self, agent):
logging.info("unit is ready") pass
# 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 got the best reward so far
def on_ai_best_reward(self, agent, reward):
pass
# called when the AI finished loading # called when the AI got the worst reward so far
def on_ai_ready(agent): def on_ai_worst_reward(self, agent, reward):
pass pass
# called when a non overlapping wifi channel is found to be free
def on_free_channel(self, agent, channel):
pass
# called when the AI finds a new set of parameters # called when the status is set to bored
def on_ai_policy(agent, policy): def on_bored(self, agent):
pass pass
# called when the status is set to sad
def on_sad(self, agent):
pass
# called when the AI starts training for a given number of epochs # called when the status is set to excited
def on_ai_training_start(agent, epochs): def on_excited(aself, gent):
pass pass
# called when the status is set to lonely
def on_lonely(self, agent):
pass
# called after the AI completed a training epoch # called when the agent is rebooting the board
def on_ai_training_step(agent, _locals, _globals): def on_rebooting(self, agent):
pass pass
# called when the agent is waiting for t seconds
def on_wait(self, agent, t):
pass
# called when the AI has done training # called when the agent is sleeping for t seconds
def on_ai_training_end(agent): def on_sleep(self, agent, t):
pass pass
# called when the agent refreshed its access points list
def on_wifi_update(self, agent, access_points):
pass
# called when the AI got the best reward so far # called when the agent is sending an association frame
def on_ai_best_reward(agent, reward): def on_association(self, agent, access_point):
pass pass
# called when the agent is deauthenticating a client station from an AP
def on_deauthentication(self, agent, access_point, client_station):
pass
# called when the AI got the worst reward so far # callend when the agent is tuning on a specific channel
def on_ai_worst_reward(agent, reward): def on_channel_hop(self, agent, channel):
pass 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 a non overlapping wifi channel is found to be free # called when an epoch is over (where an epoch is a single loop of the main algorithm)
def on_free_channel(agent, channel): def on_epoch(self, agent, epoch, epoch_data):
pass pass
# called when a new peer is detected
def on_peer_detected(self, agent, peer):
pass
# called when the status is set to bored # called when a known peer is lost
def on_bored(agent): def on_peer_lost(self, agent, peer):
pass pass
# called when the status is set to sad
def on_sad(agent):
pass
# called when the status is set to excited
def on_excited(agent):
pass
# called when the status is set to lonely
def on_lonely(agent):
pass
# called when the agent is rebooting the board
def on_rebooting(agent):
pass
# called when the agent is waiting for t seconds
def on_wait(agent, t):
pass
# called when the agent is sleeping for t seconds
def on_sleep(agent, t):
pass
# called when the agent refreshed its access points list
def on_wifi_update(agent, access_points):
pass
# called when the agent is sending an association frame
def on_association(agent, access_point):
pass
# callend when the agent is deauthenticating a client station from an AP
def on_deauthentication(agent, access_point, client_station):
pass
# callend when the agent is tuning on a specific channel
def on_channel_hop(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(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(agent, epoch, epoch_data):
pass
# called when a new peer is detected
def on_peer_detected(agent, peer):
pass
# called when a known peer is lost
def on_peer_lost(agent, peer):
pass

View File

@ -1,38 +1,40 @@
__author__ = 'ratmandu@gmail.com'
__version__ = '1.0.0'
__name__ = 'gpio_buttons'
__license__ = 'GPL3'
__description__ = 'GPIO Button support plugin'
import logging import logging
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
import subprocess import subprocess
import pwnagotchi.plugins as plugins
running = False
OPTIONS = dict()
GPIOs = {}
COMMANDs = None
def runCommand(channel):
command = GPIOs[channel]
logging.info(f"Button Pressed! Running command: {command}")
process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
def on_loaded(): class GPIOButtons(plugins.Plugin):
logging.info("GPIO Button plugin loaded.") __author__ = 'ratmandu@gmail.com'
__version__ = '1.0.0'
#get list of GPIOs __license__ = 'GPL3'
gpios = OPTIONS['gpios'] __description__ = 'GPIO Button support plugin'
#set gpio numbering def __init__(self):
GPIO.setmode(GPIO.BCM) self.running = False
self.ports = {}
self.commands = None
for i in gpios: def runCommand(self, channel):
gpio = list(i)[0] command = self.ports[channel]
command = i[gpio] logging.info(f"Button Pressed! Running command: {command}")
GPIOs[gpio] = command process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None,
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP) executable="/bin/bash")
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=runCommand, bouncetime=250) process.wait()
logging.info("Added command: %s to GPIO #%d", command, gpio)
def on_loaded(self):
logging.info("GPIO Button plugin loaded.")
# get list of GPIOs
gpios = self.options['gpios']
# set gpio numbering
GPIO.setmode(GPIO.BCM)
for i in gpios:
gpio = list(i)[0]
command = i[gpio]
self.ports[gpio] = command
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runCommand, bouncetime=250)
logging.info("Added command: %s to GPIO #%d", command, gpio)

View File

@ -1,45 +1,42 @@
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__name__ = 'gps'
__license__ = 'GPL3'
__description__ = 'Save GPS coordinates whenever an handshake is captured.'
import logging import logging
import json import json
import os import os
import pwnagotchi.plugins as plugins
running = False
OPTIONS = dict()
def on_loaded(): class GPS(plugins.Plugin):
logging.info("gps plugin loaded for %s" % OPTIONS['device']) __author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'Save GPS coordinates whenever an handshake is captured.'
def __init__(self):
self.running = False
def on_ready(agent): def on_loaded(self):
global running logging.info("gps plugin loaded for %s" % self.options['device'])
if os.path.exists(OPTIONS['device']): def on_ready(self, agent):
logging.info("enabling gps bettercap's module for %s" % OPTIONS['device']) if os.path.exists(self.options['device']):
try: logging.info("enabling gps bettercap's module for %s" % self.options['device'])
agent.run('gps off') try:
except: agent.run('gps off')
pass except:
pass
agent.run('set gps.device %s' % OPTIONS['device']) agent.run('set gps.device %s' % self.options['device'])
agent.run('set gps.speed %d' % OPTIONS['speed']) agent.run('set gps.speed %d' % self.options['speed'])
agent.run('gps on') agent.run('gps on')
running = True running = True
else: else:
logging.warning("no GPS detected") logging.warning("no GPS detected")
def on_handshake(self, agent, filename, access_point, client_station):
if self.running:
info = agent.session()
gps = info['gps']
gps_filename = filename.replace('.pcap', '.gps.json')
def on_handshake(agent, filename, access_point, client_station): logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
if running: with open(gps_filename, 'w+t') as fp:
info = agent.session() json.dump(gps, fp)
gps = info['gps']
gps_filename = filename.replace('.pcap', '.gps.json')
logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
with open(gps_filename, 'w+t') as fp:
json.dump(gps, fp)

View File

@ -1,10 +1,3 @@
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.1'
__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 os
import logging import logging
import time import time
@ -12,18 +5,9 @@ import glob
import re import re
import pwnagotchi.grid as grid import pwnagotchi.grid as grid
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap 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): def parse_pcap(filename):
logging.info("grid: parsing %s ..." % filename) logging.info("grid: parsing %s ..." % filename)
@ -57,93 +41,100 @@ def parse_pcap(filename):
return info[WifiInfo.ESSID], info[WifiInfo.BSSID] return info[WifiInfo.ESSID], info[WifiInfo.BSSID]
def is_excluded(what): class Grid(plugins.Plugin):
for skip in OPTIONS['exclude']: __author__ = 'evilsocket@gmail.com'
skip = skip.lower() __version__ = '1.0.1'
what = what.lower() __license__ = 'GPL3'
if skip in what or skip.replace(':', '') in what: __description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
return True 'networks to api.pwnagotchi.ai '
return False
def __init__(self):
self.options = dict()
self.report = StatusFile('/root/.api-report.json', data_format='json')
def set_reported(reported, net_id): self.unread_messages = 0
global REPORT self.total_messages = 0
reported.append(net_id)
REPORT.update(data={'reported': reported})
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 check_inbox(agent): def on_loaded(self):
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES logging.info("grid plugin loaded.")
logging.debug("checking mailbox ...") def set_reported(self, reported, net_id):
reported.append(net_id)
self.report.update(data={'reported': reported})
messages = grid.inbox() def check_inbox(self, agent):
TOTAL_MESSAGES = len(messages) logging.debug("checking mailbox ...")
UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None]) messages = grid.inbox()
self.total_messages = len(messages)
self.unread_messages = len([m for m in messages if m['seen_at'] is None])
if UNREAD_MESSAGES: if self.unread_messages:
logging.debug("[grid] unread:%d total:%d" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) logging.debug("[grid] unread:%d total:%d" % (self.unread_messages, self.total_messages))
agent.view().on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES) agent.view().on_unread_messages(self.unread_messages, self.total_messages)
def check_handshakes(self, agent):
logging.debug("checking pcaps")
def check_handshakes(agent): pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
logging.debug("checking pcaps") num_networks = len(pcap_files)
reported = self.report.data_field_or('reported', default=[])
num_reported = len(reported)
num_new = num_networks - num_reported
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) if num_new > 0:
num_networks = len(pcap_files) if self.options['report']:
reported = REPORT.data_field_or('reported', default=[]) logging.info("grid: %d new networks to report" % num_new)
num_reported = len(reported) logging.debug("self.options: %s" % self.options)
num_new = num_networks - num_reported logging.debug(" exclude: %s" % self.options['exclude'])
if num_new > 0: for pcap_file in pcap_files:
if OPTIONS['report']: net_id = os.path.basename(pcap_file).replace('.pcap', '')
logging.info("grid: %d new networks to report" % num_new) if net_id not in reported:
logging.debug("OPTIONS: %s" % OPTIONS) if self.is_excluded(net_id):
logging.debug(" exclude: %s" % OPTIONS['exclude']) logging.debug("skipping %s due to exclusion filter" % pcap_file)
self.set_reported(reported, net_id)
continue
for pcap_file in pcap_files: essid, bssid = parse_pcap(pcap_file)
net_id = os.path.basename(pcap_file).replace('.pcap', '') if bssid:
if net_id not in reported: if self.is_excluded(essid) or self.is_excluded(bssid):
if is_excluded(net_id): logging.debug("not reporting %s due to exclusion filter" % pcap_file)
logging.debug("skipping %s due to exclusion filter" % pcap_file) self.set_reported(reported, net_id)
set_reported(reported, net_id) else:
continue if grid.report_ap(essid, bssid):
self.set_reported(reported, net_id)
essid, bssid = parse_pcap(pcap_file) time.sleep(1.5)
if bssid:
if is_excluded(essid) or is_excluded(bssid):
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
set_reported(reported, net_id)
else: else:
if grid.report_ap(essid, bssid): logging.warning("no bssid found?!")
set_reported(reported, net_id) else:
time.sleep(1.5) logging.debug("grid: reporting disabled")
else:
logging.warning("no bssid found?!")
else:
logging.debug("grid: reporting disabled")
def on_internet_available(self, agent):
logging.debug("internet available")
def on_internet_available(agent): try:
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES 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
logging.debug("internet available") 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: try:
grid.update_data(agent.last_session) self.check_handshakes(agent)
except Exception as e: except Exception as e:
logging.error("error connecting to the pwngrid-peer service: %s" % e) logging.error("[grid] error while checking pcaps: %s" % e)
logging.debug(e, exc_info=True) logging.debug(e, exc_info=True)
return
try:
check_inbox(agent)
except Exception as e:
logging.error("[grid] error while checking inbox: %s" % e)
logging.debug(e, exc_info=True)
try:
check_handshakes(agent)
except Exception as e:
logging.error("[grid] error while checking pcaps: %s" % e)
logging.debug(e, exc_info=True)

View File

@ -17,48 +17,44 @@
# - Added horizontal and vertical orientation # - Added horizontal and vertical orientation
# #
############################################################### ###############################################################
__author__ = 'https://github.com/xenDE'
__version__ = '1.0.1'
__name__ = 'memtemp'
__license__ = 'GPL3'
__description__ = 'A plugin that will display memory/cpu usage and temperature'
from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
import pwnagotchi.plugins as plugins
import pwnagotchi import pwnagotchi
import logging import logging
OPTIONS = dict()
class MemTemp(plugins.Plugin):
__author__ = 'https://github.com/xenDE'
__version__ = '1.0.1'
__license__ = 'GPL3'
__description__ = 'A plugin that will display memory/cpu usage and temperature'
def on_loaded(): def on_loaded(self):
logging.info("memtemp plugin loaded.") logging.info("memtemp plugin loaded.")
def mem_usage(self):
return int(pwnagotchi.mem_usage() * 100)
def mem_usage(): def cpu_load(self):
return int(pwnagotchi.mem_usage() * 100) return int(pwnagotchi.cpu_load() * 100)
def on_ui_setup(self, ui):
if self.options['orientation'] == "horizontal":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
position=(ui.width() / 2 + 30, ui.height() / 2 + 15),
label_font=fonts.Small, text_font=fonts.Small))
elif self.options['orientation'] == "vertical":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
position=(ui.width() / 2 + 55, ui.height() / 2),
label_font=fonts.Small, text_font=fonts.Small))
def cpu_load(): def on_ui_update(self, ui):
return int(pwnagotchi.cpu_load() * 100) if self.options['orientation'] == "horizontal":
ui.set('memtemp',
" mem cpu temp\n %s%% %s%% %sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
elif self.options['orientation'] == "vertical":
def on_ui_setup(ui): ui.set('memtemp',
if OPTIONS['orientation'] == "horizontal": " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
position=(ui.width() / 2 + 30, ui.height() / 2 + 15),
label_font=fonts.Small, text_font=fonts.Small))
elif OPTIONS['orientation'] == "vertical":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
position=(ui.width() / 2 + 55, ui.height() / 2),
label_font=fonts.Small, text_font=fonts.Small))
def on_ui_update(ui):
if OPTIONS['orientation'] == "horizontal":
ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))
elif OPTIONS['orientation'] == "vertical":
ui.set('memtemp', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))

View File

@ -1,140 +1,134 @@
__author__ = 'zenzen san'
__version__ = '2.0.0'
__name__ = 'net-pos'
__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 """
import logging import logging
import json import json
import os import os
import requests import requests
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile from pwnagotchi.utils import StatusFile
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}' MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
REPORT = StatusFile('/root/.net_pos_saved', data_format='json')
SKIP = list()
READY = False
OPTIONS = dict()
def on_loaded(): class NetPos(plugins.Plugin):
global READY __author__ = 'zenzen san'
__version__ = '2.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 """
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): def __init__(self):
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.") self.report = StatusFile('/root/.net_pos_saved', data_format='json')
return self.skip = list()
self.ready = False
READY = True 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.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
return
logging.info("net-pos plugin loaded.") self.ready = True
logging.info("net-pos plugin loaded.")
def _append_saved(path): def _append_saved(self, path):
to_save = list() to_save = list()
if isinstance(path, str): if isinstance(path, str):
to_save.append(path) to_save.append(path)
elif isinstance(path, list): elif isinstance(path, list):
to_save += path to_save += path
else: else:
raise TypeError("Expected list or str, got %s" % type(path)) raise TypeError("Expected list or str, got %s" % type(path))
with open('/root/.net_pos_saved', 'a') as saved_file: with open('/root/.net_pos_saved', 'a') as saved_file:
for x in to_save: for x in to_save:
saved_file.write(x + "\n") saved_file.write(x + "\n")
def on_internet_available(agent): def on_internet_available(self, agent):
global SKIP if self.ready:
global REPORT config = agent.config()
display = agent.view()
reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
if READY: all_files = os.listdir(handshake_dir)
config = agent.config() all_np_files = [os.path.join(handshake_dir, filename)
display = agent.view() for filename in all_files
reported = REPORT.data_field_or('reported', default=list()) if filename.endswith('.net-pos.json')]
handshake_dir = config['bettercap']['handshakes'] new_np_files = set(all_np_files) - set(reported) - set(self.skip)
all_files = os.listdir(handshake_dir) if new_np_files:
all_np_files = [os.path.join(handshake_dir, filename) logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files))
for filename in all_files display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...")
if filename.endswith('.net-pos.json')]
new_np_files = set(all_np_files) - set(reported) - set(SKIP)
if new_np_files:
logging.info("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)
REPORT.update(data={'reported': reported})
continue
try:
geo_data = _get_geo_data(np_file) # returns json obj
except requests.exceptions.RequestException as req_e:
logging.error("NET-POS: %s", req_e)
SKIP += np_file
continue
except json.JSONDecodeError as js_e:
logging.error("NET-POS: %s", js_e)
SKIP += np_file
continue
except OSError as os_e:
logging.error("NET-POS: %s", os_e)
SKIP += np_file
continue
with open(geo_file, 'w+t') as sf:
json.dump(geo_data, sf)
reported.append(np_file)
REPORT.update(data={'reported': reported})
display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)})")
display.update(force=True) 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
def on_handshake(agent, filename, access_point, client_station): try:
netpos = _get_netpos(agent) geo_data = self._get_geo_data(np_file) # returns json obj
netpos_filename = filename.replace('.pcap', '.net-pos.json') except requests.exceptions.RequestException as req_e:
logging.info("NET-POS: Saving net-location to %s", netpos_filename) logging.error("NET-POS: %s", req_e)
self.skip += np_file
continue
except json.JSONDecodeError as js_e:
logging.error("NET-POS: %s", js_e)
self.skip += np_file
continue
except OSError as os_e:
logging.error("NET-POS: %s", os_e)
self.skip += np_file
continue
try: with open(geo_file, 'w+t') as sf:
with open(netpos_filename, 'w+t') as net_pos_file: json.dump(geo_data, sf)
json.dump(netpos, net_pos_file)
except OSError as os_e:
logging.error("NET-POS: %s", os_e)
reported.append(np_file)
self.report.update(data={'reported': reported})
def _get_netpos(agent): display.set('status', f"Fetching positions ({idx + 1}/{len(new_np_files)})")
aps = agent.get_access_points() display.update(force=True)
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(path, timeout=30): def on_handshake(self, agent, filename, access_point, client_station):
geourl = MOZILLA_API_URL.format(api=OPTIONS['api_key']) netpos = self._get_netpos(agent)
netpos_filename = filename.replace('.pcap', '.net-pos.json')
logging.info("NET-POS: Saving net-location to %s", netpos_filename)
try: try:
with open(path, "r") as json_file: with open(netpos_filename, 'w+t') as net_pos_file:
data = json.load(json_file) json.dump(netpos, net_pos_file)
except json.JSONDecodeError as js_e: except OSError as os_e:
raise js_e logging.error("NET-POS: %s", os_e)
except OSError as os_e:
raise os_e
try: def _get_netpos(self, agent):
result = requests.post(geourl, aps = agent.get_access_points()
json=data, netpos = dict()
timeout=timeout) netpos['wifiAccessPoints'] = list()
return result.json() # 6 seems a good number to save a wifi networks location
except requests.exceptions.RequestException as req_e: for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]:
raise req_e netpos['wifiAccessPoints'].append({'macAddress': access_point['mac'],
'signalStrength': access_point['rssi']})
return netpos
def _get_geo_data(self, path, timeout=30):
geourl = MOZILLA_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 result.json()
except requests.exceptions.RequestException as req_e:
raise req_e

View File

@ -1,86 +1,82 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '2.0.0'
__name__ = 'onlinehashcrack'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
import os import os
import logging import logging
import requests import requests
from pwnagotchi.utils import StatusFile from pwnagotchi.utils import StatusFile
import pwnagotchi.plugins as plugins
READY = False
REPORT = StatusFile('/root/.ohc_uploads', data_format='json')
SKIP = list()
OPTIONS = dict()
def on_loaded(): class OnlineHashCrack(plugins.Plugin):
""" __author__ = '33197631+dadav@users.noreply.github.com'
Gets called when the plugin gets loaded __version__ = '2.0.0'
""" __license__ = 'GPL3'
global READY __description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
if 'email' not in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None): def __init__(self):
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com") self.ready = False
return self.report = StatusFile('/root/.ohc_uploads', data_format='json')
self.skip = list()
READY = True 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(path, timeout=30): def _upload_to_ohc(self, path, timeout=30):
""" """
Uploads the file to onlinehashcrack.com Uploads the file to onlinehashcrack.com
""" """
with open(path, 'rb') as file_to_upload: with open(path, 'rb') as file_to_upload:
data = {'email': OPTIONS['email']} data = {'email': self.options['email']}
payload = {'file': file_to_upload} payload = {'file': file_to_upload}
try: try:
result = requests.post('https://api.onlinehashcrack.com', result = requests.post('https://api.onlinehashcrack.com',
data=data, data=data,
files=payload, files=payload,
timeout=timeout) timeout=timeout)
if 'already been sent' in result.text: if 'already been sent' in result.text:
logging.warning(f"{path} was already uploaded.") logging.warning(f"{path} was already uploaded.")
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logging.error(f"OHC: Got an exception while uploading {path} -> {e}") logging.error(f"OHC: Got an exception while uploading {path} -> {e}")
raise 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())
def on_internet_available(agent): handshake_dir = config['bettercap']['handshakes']
""" handshake_filenames = os.listdir(handshake_dir)
Called in manual mode when there's internet connectivity handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
""" filename.endswith('.pcap')]
global REPORT handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
global SKIP
if READY:
display = agent.view()
config = agent.config()
reported = REPORT.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes'] if handshake_new:
handshake_filenames = os.listdir(handshake_dir) logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com")
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(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:
_upload_to_ohc(handshake)
reported.append(handshake)
REPORT.update(data={'reported': reported})
logging.info(f"OHC: Successfully uploaded {handshake}")
except requests.exceptions.RequestException as req_e:
SKIP.append(handshake)
logging.error("OHC: %s", req_e)
continue
except OSError as os_e:
SKIP.append(handshake)
logging.error("OHC: %s", os_e)
continue
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

View File

@ -1,27 +1,27 @@
__author__ = 'leont' import logging
__version__ = '1.0.0' import requests
__name__ = 'pawgps' import pwnagotchi.plugins as plugins
__license__ = 'GPL3'
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android '
''' '''
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemic: You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemic:
https://raw.githubusercontent.com/systemik/pwnagotchi-bt-tether/master/GPS-via-PAW https://raw.githubusercontent.com/systemik/pwnagotchi-bt-tether/master/GPS-via-PAW
''' '''
import logging
import requests
OPTIONS = dict() class PawGPS(plugins.Plugin):
__author__ = 'leont'
__version__ = '1.0.0'
__name__ = 'pawgps'
__license__ = 'GPL3'
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android '
def on_loaded(self):
logging.info("PAW-GPS loaded")
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None):
logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)")
def on_loaded(): def on_handshake(self, agent, filename, access_point, client_station):
logging.info("PAW-GPS loaded") if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None):
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)")
def on_handshake(agent, filename, access_point, client_station):
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
ip = "192.168.44.1" ip = "192.168.44.1"
gps = requests.get('http://' + ip + '/gps.xhtml') gps = requests.get('http://' + ip + '/gps.xhtml')

View File

@ -1,8 +1,8 @@
__author__ = 'pwnagotchi [at] rossmarks [dot] uk' import logging
__version__ = '1.0.0' import subprocess
__name__ = 'quickdic' import string
__license__ = 'GPL3' import re
__description__ = 'Run a quick dictionary scan against captured handshakes' import pwnagotchi.plugins as plugins
''' '''
Aircrack-ng needed, to install: Aircrack-ng needed, to install:
@ -11,42 +11,41 @@ Upload wordlist files in .txt format to folder in config file (Default: /opt/wor
Cracked handshakes stored in handshake folder as [essid].pcap.cracked Cracked handshakes stored in handshake folder as [essid].pcap.cracked
''' '''
import logging
import subprocess
import string
import re
OPTIONS = dict() class QuickDic(plugins.Plugin):
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'Run a quick dictionary scan against captured handshakes'
def on_loaded(): def __init__(self):
logging.info("Quick dictionary check plugin loaded") self.text_to_set = ""
def on_handshake(agent, filename, access_point, client_station): def on_loaded(self):
display = agent._view logging.info("Quick dictionary check plugin loaded")
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) def on_handshake(self, agent, filename, access_point, client_station):
result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) display = agent.view()
if not result: result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''),
logging.info("[quickdic] No handshake") shell=True, stdout=subprocess.PIPE)
else: result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
logging.info("[quickdic] Handshake confirmed") if not result:
result2 = subprocess.run(('aircrack-ng -w `echo '+OPTIONS['wordlist_folder']+'*.txt | sed \'s/\ /,/g\'` -l '+filename+'.cracked -q -b '+result+' '+filename+' | grep KEY'),shell=True,stdout=subprocess.PIPE) logging.info("[quickdic] No handshake")
result2 = result2.stdout.decode('utf-8').strip() else:
logging.info("[quickdic] "+result2) logging.info("[quickdic] Handshake confirmed")
if result2 != "KEY NOT FOUND": result2 = subprocess.run(('aircrack-ng -w `echo ' + self.options[
key = re.search('\[(.*)\]', result2) 'wordlist_folder'] + '*.txt | sed \'s/\ /,/g\'` -l ' + filename + '.cracked -q -b ' + result + ' ' + filename + ' | grep KEY'),
pwd = str(key.group(1)) shell=True, stdout=subprocess.PIPE)
set_text("Cracked password: "+pwd) result2 = result2.stdout.decode('utf-8').strip()
display.update(force=True) logging.info("[quickdic] " + result2)
if result2 != "KEY NOT FOUND":
key = re.search('\[(.*)\]', result2)
pwd = str(key.group(1))
self.text_to_set = "Cracked password: " + pwd
display.update(force=True)
text_to_set = ""; def on_ui_update(self, ui):
def set_text(text): if self.text_to_set:
global text_to_set ui.set('face', "(·ω·)")
text_to_set = text ui.set('status', self.text_to_set)
self.text_to_set = ""
def on_ui_update(ui):
global text_to_set
if text_to_set:
ui.set('face', "(·ω·)")
ui.set('status', text_to_set)
text_to_set = ""

View File

@ -1,24 +1,23 @@
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
__version__ = '1.0.0'
__name__ = 'screen_refresh'
__license__ = 'GPL3'
__description__ = 'Refresh he e-ink display after X amount of updates'
import logging import logging
import pwnagotchi.plugins as plugins
OPTIONS = dict()
update_count = 0;
def on_loaded(): class ScreenRefresh(plugins.Plugin):
logging.info("Screen refresh plugin loaded") __author__ = 'pwnagotchi [at] rossmarks [dot] uk'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'Refresh he e-ink display after X amount of updates'
def __init__(self):
self.update_count = 0;
def on_ui_update(ui): def on_loaded(self):
global update_count logging.info("Screen refresh plugin loaded")
update_count += 1
if update_count == OPTIONS['refresh_interval']: def on_ui_update(self, ui):
ui.init_display() self.update_count += 1
ui.set('status', "Screen cleaned") if self.update_count == self.options['refresh_interval']:
logging.info("Screen refreshing") ui.init_display()
update_count = 0 ui.set('status', "Screen cleaned")
logging.info("Screen refreshing")
self.update_count = 0

View File

@ -1,50 +1,49 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__name__ = 'twitter'
__license__ = 'GPL3'
__description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
import logging import logging
from pwnagotchi.voice import Voice from pwnagotchi.voice import Voice
import pwnagotchi.plugins as plugins
OPTIONS = dict()
def on_loaded():
logging.info("twitter plugin loaded.")
# called in manual mode when there's internet connectivity class Twitter(plugins.Plugin):
def on_internet_available(agent): __author__ = 'evilsocket@gmail.com'
config = agent.config() __version__ = '1.0.0'
display = agent.view() __license__ = 'GPL3'
last_session = agent.last_session __description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
if last_session.is_new() and last_session.handshakes > 0: def on_loaded(self):
try: logging.info("twitter plugin loaded.")
import tweepy
except ImportError:
logging.error("Couldn't import tweepy")
return
logging.info("detected a new session and internet connectivity!") # called in manual mode when there's internet connectivity
def on_internet_available(self, agent):
config = agent.config()
display = agent.view()
last_session = agent.last_session
picture = '/dev/shm/pwnagotchi.png' if last_session.is_new() and last_session.handshakes > 0:
try:
import tweepy
except ImportError:
logging.error("Couldn't import tweepy")
return
display.on_manual_mode(last_session) logging.info("detected a new session and internet connectivity!")
display.update(force=True)
display.image().save(picture, 'png')
display.set('status', 'Tweeting...')
display.update(force=True)
try: picture = '/root/pwnagotchi.png'
auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret'])
auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret'])
api = tweepy.API(auth)
tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session) display.on_manual_mode(last_session)
api.update_with_media(filename=picture, status=tweet) display.update(force=True)
last_session.save_session_id() display.image().save(picture, 'png')
display.set('status', 'Tweeting...')
display.update(force=True)
logging.info("tweeted: %s" % tweet) try:
except Exception as e: auth = tweepy.OAuthHandler(self.options['consumer_key'], self.options['consumer_secret'])
logging.exception("error while tweeting") auth.set_access_token(self.options['access_token_key'], self.options['access_token_secret'])
api = tweepy.API(auth)
tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session)
api.update_with_media(filename=picture, status=tweet)
last_session.save_session_id()
logging.info("tweeted: %s" % tweet)
except Exception as e:
logging.exception("error while tweeting")

View File

@ -1,22 +0,0 @@
__author__ = 'diemelcw@gmail.com'
__version__ = '1.0.0'
__name__ = 'unfiltered_example'
__license__ = 'GPL3'
__description__ = 'An example plugin for pwnagotchi that implements on_unfiltered_ap_list(agent,aps)'
import logging
# Will be set with the options in config.yml config['main']['plugins'][__name__]
OPTIONS = dict()
# called when the plugin is loaded
def on_loaded():
logging.warning("%s plugin loaded" % __name__)
# called when AP list is ready, before whitelist filtering has occurred
def on_unfiltered_ap_list(agent,aps):
logging.info("Unfiltered AP list to follow")
for ap in aps:
logging.info(ap['hostname'])
## Additional logic here ##

View File

@ -7,17 +7,12 @@
# For Raspberry Pi Zero Ups Power Expansion Board with Integrated Serial Port S3U4 # For Raspberry Pi Zero Ups Power Expansion Board with Integrated Serial Port S3U4
# https://www.ebay.de/itm/For-Raspberry-Pi-Zero-Ups-Power-Expansion-Board-with-Integrated-Serial-Port-S3U4/323873804310 # https://www.ebay.de/itm/For-Raspberry-Pi-Zero-Ups-Power-Expansion-Board-with-Integrated-Serial-Port-S3U4/323873804310
# https://www.aliexpress.com/item/32888533624.html # https://www.aliexpress.com/item/32888533624.html
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__name__ = 'ups_lite'
__license__ = 'GPL3'
__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1'
import struct import struct
from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
import pwnagotchi.plugins as plugins
# TODO: add enable switch in config.yml an cleanup all to the best place # TODO: add enable switch in config.yml an cleanup all to the best place
@ -47,18 +42,21 @@ class UPS:
return 0.0 return 0.0
ups = None class UPSLite(plugins.Plugin):
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1'
def __init__(self):
self.ups = None
def on_loaded(): def on_loaded(self):
global ups self.ups = UPS()
ups = UPS()
def on_ui_setup(self, ui):
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_setup(ui): def on_ui_update(self, ui):
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0), ui.set('ups', "%4.2fV/%2i%%" % (self.ups.voltage(), self.ups.capacity()))
label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_update(ui):
ui.set('ups', "%4.2fV/%2i%%" % (ups.voltage(), ups.capacity()))

View File

@ -1,9 +1,3 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '2.0.0'
__name__ = 'wigle'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads collected wifis to wigle.net'
import os import os
import logging import logging
import json import json
@ -12,24 +6,7 @@ import csv
from datetime import datetime from datetime import datetime
import requests import requests
from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile
import pwnagotchi.plugins as plugins
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 _extract_gps_data(path): def _extract_gps_data(path):
@ -54,14 +31,17 @@ def _format_auth(data):
out = f"{out}[{auth}]" out = f"{out}[{auth}]"
return out return out
def _transform_wigle_entry(gps_data, pcap_data): def _transform_wigle_entry(gps_data, pcap_data):
""" """
Transform to wigle entry in file Transform to wigle entry in file
""" """
dummy = StringIO() dummy = StringIO()
# write kismet header # write kismet header
dummy.write("WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n") dummy.write(
dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type") "WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n")
dummy.write(
"MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type")
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\") writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\")
writer.writerow([ writer.writerow([
@ -75,10 +55,11 @@ def _transform_wigle_entry(gps_data, pcap_data):
gps_data['Latitude'], gps_data['Latitude'],
gps_data['Longitude'], gps_data['Longitude'],
gps_data['Altitude'], gps_data['Altitude'],
0, # accuracy? 0, # accuracy?
'WIFI']) 'WIFI'])
return dummy.getvalue() return dummy.getvalue()
def _send_to_wigle(lines, api_key, timeout=30): def _send_to_wigle(lines, api_key, timeout=30):
""" """
Uploads the file to wigle-net Uploads the file to wigle-net
@ -109,87 +90,100 @@ def _send_to_wigle(lines, api_key, timeout=30):
raise re_e raise re_e
def on_internet_available(agent): class Wigle(plugins.Plugin):
from scapy.all import Scapy_Exception __author__ = '33197631+dadav@users.noreply.github.com'
""" __version__ = '2.0.0'
Called in manual mode when there's internet connectivity __license__ = 'GPL3'
""" __description__ = 'This plugin automatically uploads collected wifis to wigle.net'
global REPORT
global SKIP
if READY: def __init__(self):
config = agent.config() self.ready = False
display = agent.view() self.report = StatusFile('/root/.wigle_uploads', data_format='json')
reported = REPORT.data_field_or('reported', default=list()) self.skip = list()
handshake_dir = config['bettercap']['handshakes'] def on_loaded(self):
all_files = os.listdir(handshake_dir) if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
all_gps_files = [os.path.join(handshake_dir, filename) logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net")
for filename in all_files return
if filename.endswith('.gps.json')] self.ready = True
new_gps_files = set(all_gps_files) - set(reported) - set(SKIP)
if new_gps_files: def on_internet_available(self, agent):
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net") from scapy.all import Scapy_Exception
"""
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())
csv_entries = list() handshake_dir = config['bettercap']['handshakes']
no_err_entries = list() 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')]
new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
for gps_file in new_gps_files: if new_gps_files:
pcap_filename = gps_file.replace('.gps.json', '.pcap') logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
if not os.path.exists(pcap_filename): csv_entries = list()
logging.error("WIGLE: Can't find pcap for %s", gps_file) no_err_entries = list()
SKIP.append(gps_file)
continue
try: for gps_file in new_gps_files:
gps_data = _extract_gps_data(gps_file) pcap_filename = gps_file.replace('.gps.json', '.pcap')
except OSError as os_err:
logging.error("WIGLE: %s", os_err)
SKIP.append(gps_file)
continue
except json.JSONDecodeError as json_err:
logging.error("WIGLE: %s", json_err)
SKIP.append(gps_file)
continue
if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0: if not os.path.exists(pcap_filename):
logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file) logging.error("WIGLE: Can't find pcap for %s", gps_file)
SKIP.append(gps_file) self.skip.append(gps_file)
continue continue
try:
gps_data = _extract_gps_data(gps_file)
except OSError as os_err:
logging.error("WIGLE: %s", os_err)
self.skip.append(gps_file)
continue
except json.JSONDecodeError as json_err:
logging.error("WIGLE: %s", json_err)
self.skip.append(gps_file)
continue
try: if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID, logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
WifiInfo.ESSID, self.skip.append(gps_file)
WifiInfo.ENCRYPTION, continue
WifiInfo.CHANNEL,
WifiInfo.RSSI])
except FieldNotFoundError:
logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
SKIP.append(gps_file)
continue
except Scapy_Exception as sc_e:
logging.error("WIGLE: %s", sc_e)
SKIP.append(gps_file)
continue
new_entry = _transform_wigle_entry(gps_data, pcap_data) try:
csv_entries.append(new_entry) pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID,
no_err_entries.append(gps_file) WifiInfo.ESSID,
WifiInfo.ENCRYPTION,
WifiInfo.CHANNEL,
WifiInfo.RSSI])
except FieldNotFoundError:
logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
self.skip.append(gps_file)
continue
except Scapy_Exception as sc_e:
logging.error("WIGLE: %s", sc_e)
self.skip.append(gps_file)
continue
if csv_entries: new_entry = _transform_wigle_entry(gps_data, pcap_data)
display.set('status', "Uploading gps-data to wigle.net ...") csv_entries.append(new_entry)
display.update(force=True) no_err_entries.append(gps_file)
try:
_send_to_wigle(csv_entries, OPTIONS['api_key']) if csv_entries:
reported += no_err_entries display.set('status', "Uploading gps-data to wigle.net ...")
REPORT.update(data={'reported': reported}) display.update(force=True)
logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries)) try:
except requests.exceptions.RequestException as re_e: _send_to_wigle(csv_entries, self.options['api_key'])
SKIP += no_err_entries reported += no_err_entries
logging.error("WIGLE: Got an exception while uploading %s", re_e) self.report.update(data={'reported': reported})
except OSError as os_e: logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries))
SKIP += no_err_entries except requests.exceptions.RequestException as re_e:
logging.error("WIGLE: Got the following error: %s", os_e) self.skip += no_err_entries
logging.error("WIGLE: Got an exception while uploading %s", re_e)
except OSError as os_e:
self.skip += no_err_entries
logging.error("WIGLE: Got the following error: %s", os_e)

View File

@ -1,87 +1,84 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '2.0.1'
__name__ = 'wpa-sec'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org'
import os import os
import logging import logging
import requests import requests
from pwnagotchi.utils import StatusFile from pwnagotchi.utils import StatusFile
import pwnagotchi.plugins as plugins
READY = False
REPORT = StatusFile('/root/.wpa_sec_uploads', data_format='json')
OPTIONS = dict()
SKIP = list()
def on_loaded(): class WpaSec(plugins.Plugin):
""" __author__ = '33197631+dadav@users.noreply.github.com'
Gets called when the plugin gets loaded __version__ = '2.0.1'
""" __license__ = 'GPL3'
global READY __description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org'
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): def __init__(self):
logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org") self.ready = False
return self.report = StatusFile('/root/.wpa_sec_uploads', data_format='json')
self.options = dict()
self.skip = list()
if 'api_url' not in OPTIONS or ('api_url' in OPTIONS and OPTIONS['api_url'] is None): def _upload_to_wpasec(self, path, timeout=30):
logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.") """
return Uploads the file to https://wpa-sec.stanev.org, or another endpoint.
"""
READY = True 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 _upload_to_wpasec(path, timeout=30): def on_loaded(self):
""" """
Uploads the file to https://wpa-sec.stanev.org, or another endpoint. Gets called when the plugin gets loaded
""" """
with open(path, 'rb') as file_to_upload: if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
cookie = {'key': OPTIONS['api_key']} logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org")
payload = {'file': file_to_upload} return
try: if 'api_url' not in self.options or ('api_url' in self.options and self.options['api_url'] is None):
result = requests.post(OPTIONS['api_url'], logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
cookies=cookie, return
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
self.ready = True
def on_internet_available(agent): def on_internet_available(self, agent):
""" """
Called in manual mode when there's internet connectivity Called in manual mode when there's internet connectivity
""" """
global REPORT if self.ready:
global SKIP config = agent.config()
if READY: display = agent.view()
config = agent.config() reported = self.report.data_field_or('reported', default=list())
display = agent.view()
reported = REPORT.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes'] handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir) 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 = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
handshake_new = set(handshake_paths) - set(reported) - set(SKIP) filename.endswith('.pcap')]
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
if handshake_new: if handshake_new:
logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org") logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org")
for idx, handshake in enumerate(handshake_new): for idx, handshake in enumerate(handshake_new):
display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})") display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})")
display.update(force=True) display.update(force=True)
try: try:
_upload_to_wpasec(handshake) self._upload_to_wpasec(handshake)
reported.append(handshake) reported.append(handshake)
REPORT.update(data={'reported': reported}) self.report.update(data={'reported': reported})
logging.info("WPA_SEC: Successfully uploaded %s", handshake) logging.info("WPA_SEC: Successfully uploaded %s", handshake)
except requests.exceptions.RequestException as req_e: except requests.exceptions.RequestException as req_e:
SKIP.append(handshake) self.skip.append(handshake)
logging.error("WPA_SEC: %s", req_e) logging.error("WPA_SEC: %s", req_e)
continue continue
except OSError as os_e: except OSError as os_e:
logging.error("WPA_SEC: %s", os_e) logging.error("WPA_SEC: %s", os_e)
continue continue