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,16 +48,12 @@ 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")):
plugin_name = os.path.basename(filename.replace(".py", ""))
if plugin_name in enabled:
try: try:
name, plugin = load_from_file(filename) load_from_file(filename)
if name in loaded:
raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__))
elif name not in enabled:
# print("plugin %s is not enabled" % name)
pass
else:
loaded[name] = plugin
except Exception as e: except Exception as e:
logging.warning("error while loading %s: %s" % (filename, e)) logging.warning("error while loading %s: %s" % (filename, e))
logging.debug(e, exc_info=True) logging.debug(e, exc_info=True)
@ -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
# propagate options
for name, plugin in loaded.items(): for name, plugin in loaded.items():
plugin.__dict__['OPTIONS'] = config['main']['plugins'][name] plugin.options = config['main']['plugins'][name]
on('loaded') on('loaded')

View File

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

View File

@ -1,47 +1,45 @@
__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):
if 'files' not in self.options or ('files' in self.options and self.options['files'] is None):
logging.error("AUTO-BACKUP: No files to backup.") logging.error("AUTO-BACKUP: No files to backup.")
return return
if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None): 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.") logging.error("AUTO-BACKUP: Interval is not set.")
return return
if 'commands' not in OPTIONS or ('commands' in OPTIONS and OPTIONS['commands'] is None): if 'commands' not in self.options or ('commands' in self.options and self.options['commands'] is None):
logging.error("AUTO-BACKUP: No commands given.") logging.error("AUTO-BACKUP: No commands given.")
return return
READY = True self.ready = True
logging.info("AUTO-BACKUP: Successfully loaded.") logging.info("AUTO-BACKUP: Successfully loaded.")
def on_internet_available(self, agent):
if not self.ready:
return
def on_internet_available(agent): if self.status.newer_then_days(self.options['interval']):
global STATUS
if READY:
if STATUS.newer_then_days(OPTIONS['interval']):
return 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:
@ -51,7 +49,7 @@ 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")
@ -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():
""" """
@ -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):
""" """
@ -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
@ -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 ...')
@ -387,7 +374,6 @@ 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
@ -413,16 +399,20 @@ 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
def on_loaded(self):
for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None): 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) logging.error("BT-TET: Please specify the %s in your config.yml.", opt)
return return
@ -433,24 +423,23 @@ def on_loaded():
logging.error("BT-TET: Can't start bluetooth.service") logging.error("BT-TET: Can't start bluetooth.service")
return return
INTERVAL.update() self.interval.update()
READY = True 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(ui): def on_ui_update(self, ui):
""" if not self.ready:
Try to connect to device
"""
if READY:
global INTERVAL
global NETWORK
if INTERVAL.newer_then_minutes(OPTIONS['interval']):
return return
INTERVAL.update() if self.interval.newer_then_minutes(self.options['interval']):
return
bt = BTNap(OPTIONS['mac']) self.interval.update()
bt = BTNap(self.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,21 +1,26 @@
__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.'
def __init__(self):
logging.debug("example plugin created")
# called when the plugin is loaded
def on_loaded(self):
logging.warning("WARNING: this plugin should be disabled! options = " % self.options)
# called when <host>:<port>/plugins/<pluginname> is opened # called when <host>:<port>/plugins/<pluginname> is opened
def on_webhook(response, path): def on_webhook(self, response, path):
res = "<html><body><a>Hook triggered</a></body></html>" res = "<html><body><a>Hook triggered</a></body></html>"
response.send_response(200) response.send_response(200)
response.send_header('Content-type', 'text/html') response.send_header('Content-type', 'text/html')
@ -26,157 +31,124 @@ def on_webhook(response, path):
except Exception as ex: except Exception as ex:
logging.error(ex) logging.error(ex)
# called when the plugin is loaded
def on_loaded():
logging.warning("WARNING: plugin %s should be disabled!" % __name__)
# 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 # called to setup the ui elements
def on_ui_setup(ui): def on_ui_setup(self, ui):
# add custom UI elements # add custom UI elements
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0), 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)) label_font=fonts.Bold, text_font=fonts.Medium))
# called when the ui is updated # called when the ui is updated
def on_ui_update(ui): def on_ui_update(self, ui):
# update those elements # update those elements
some_voltage = 0.1 some_voltage = 0.1
some_capacity = 100.0 some_capacity = 100.0
ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity)) ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity))
# called when the hardware display setup is done, display is an hardware specific object # called when the hardware display setup is done, display is an hardware specific object
def on_display_setup(display): def on_display_setup(self, display):
pass pass
# called when everything is ready and the main loop is about to start # called when everything is ready and the main loop is about to start
def on_ready(agent): def on_ready(self, agent):
logging.info("unit is ready") logging.info("unit is ready")
# you can run custom bettercap commands if you want # you can run custom bettercap commands if you want
# agent.run('ble.recon on') # agent.run('ble.recon on')
# or set a custom state # or set a custom state
# agent.set_bored() # agent.set_bored()
# called when the AI finished loading # called when the AI finished loading
def on_ai_ready(agent): def on_ai_ready(self, agent):
pass pass
# called when the AI finds a new set of parameters # called when the AI finds a new set of parameters
def on_ai_policy(agent, policy): def on_ai_policy(self, agent, policy):
pass pass
# called when the AI starts training for a given number of epochs # called when the AI starts training for a given number of epochs
def on_ai_training_start(agent, epochs): def on_ai_training_start(self, agent, epochs):
pass pass
# called after the AI completed a training epoch # called after the AI completed a training epoch
def on_ai_training_step(agent, _locals, _globals): def on_ai_training_step(self, agent, _locals, _globals):
pass pass
# called when the AI has done training # called when the AI has done training
def on_ai_training_end(agent): def on_ai_training_end(self, agent):
pass pass
# called when the AI got the best reward so far # called when the AI got the best reward so far
def on_ai_best_reward(agent, reward): def on_ai_best_reward(self, agent, reward):
pass pass
# called when the AI got the worst reward so far # called when the AI got the worst reward so far
def on_ai_worst_reward(agent, reward): def on_ai_worst_reward(self, agent, reward):
pass pass
# called when a non overlapping wifi channel is found to be free # called when a non overlapping wifi channel is found to be free
def on_free_channel(agent, channel): def on_free_channel(self, agent, channel):
pass pass
# called when the status is set to bored # called when the status is set to bored
def on_bored(agent): def on_bored(self, agent):
pass pass
# called when the status is set to sad # called when the status is set to sad
def on_sad(agent): def on_sad(self, agent):
pass pass
# called when the status is set to excited # called when the status is set to excited
def on_excited(agent): def on_excited(aself, gent):
pass pass
# called when the status is set to lonely # called when the status is set to lonely
def on_lonely(agent): def on_lonely(self, agent):
pass pass
# called when the agent is rebooting the board # called when the agent is rebooting the board
def on_rebooting(agent): def on_rebooting(self, agent):
pass pass
# called when the agent is waiting for t seconds # called when the agent is waiting for t seconds
def on_wait(agent, t): def on_wait(self, agent, t):
pass pass
# called when the agent is sleeping for t seconds # called when the agent is sleeping for t seconds
def on_sleep(agent, t): def on_sleep(self, agent, t):
pass pass
# called when the agent refreshed its access points list # called when the agent refreshed its access points list
def on_wifi_update(agent, access_points): def on_wifi_update(self, agent, access_points):
pass pass
# called when the agent is sending an association frame # called when the agent is sending an association frame
def on_association(agent, access_point): def on_association(self, agent, access_point):
pass pass
# called when the agent is deauthenticating a client station from an AP
# callend when the agent is deauthenticating a client station from an AP def on_deauthentication(self, agent, access_point, client_station):
def on_deauthentication(agent, access_point, client_station):
pass pass
# callend when the agent is tuning on a specific channel # callend when the agent is tuning on a specific channel
def on_channel_hop(agent, channel): def on_channel_hop(self, agent, channel):
pass pass
# called when a new handshake is captured, access_point and client_station are json objects # 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 # 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): def on_handshake(self, agent, filename, access_point, client_station):
pass pass
# called when an epoch is over (where an epoch is a single loop of the main algorithm) # called when an epoch is over (where an epoch is a single loop of the main algorithm)
def on_epoch(agent, epoch, epoch_data): def on_epoch(self, agent, epoch, epoch_data):
pass pass
# called when a new peer is detected # called when a new peer is detected
def on_peer_detected(agent, peer): def on_peer_detected(self, agent, peer):
pass pass
# called when a known peer is lost # called when a known peer is lost
def on_peer_lost(agent, peer): def on_peer_lost(self, agent, peer):
pass pass

View File

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

View File

@ -1,41 +1,38 @@
__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']):
logging.info("enabling gps bettercap's module for %s" % self.options['device'])
try: try:
agent.run('gps off') agent.run('gps off')
except: except:
pass 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):
def on_handshake(agent, filename, access_point, client_station): if self.running:
if running:
info = agent.session() info = agent.session()
gps = info['gps'] gps = info['gps']
gps_filename = filename.replace('.pcap', '.gps.json') gps_filename = filename.replace('.pcap', '.gps.json')

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,76 +41,83 @@ 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'
__version__ = '1.0.1'
__license__ = 'GPL3'
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
'networks to api.pwnagotchi.ai '
def __init__(self):
self.options = dict()
self.report = StatusFile('/root/.api-report.json', data_format='json')
self.unread_messages = 0
self.total_messages = 0
def is_excluded(self, what):
for skip in self.options['exclude']:
skip = skip.lower() skip = skip.lower()
what = what.lower() what = what.lower()
if skip in what or skip.replace(':', '') in what: if skip in what or skip.replace(':', '') in what:
return True return True
return False return False
def on_loaded(self):
logging.info("grid plugin loaded.")
def set_reported(reported, net_id): def set_reported(self, reported, net_id):
global REPORT
reported.append(net_id) reported.append(net_id)
REPORT.update(data={'reported': reported}) self.report.update(data={'reported': reported})
def check_inbox(agent):
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
def check_inbox(self, agent):
logging.debug("checking mailbox ...") logging.debug("checking mailbox ...")
messages = grid.inbox() messages = grid.inbox()
TOTAL_MESSAGES = len(messages) self.total_messages = len(messages)
UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None]) 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):
def check_handshakes(agent):
logging.debug("checking pcaps") logging.debug("checking pcaps")
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
num_networks = len(pcap_files) num_networks = len(pcap_files)
reported = REPORT.data_field_or('reported', default=[]) reported = self.report.data_field_or('reported', default=[])
num_reported = len(reported) num_reported = len(reported)
num_new = num_networks - num_reported num_new = num_networks - num_reported
if num_new > 0: if num_new > 0:
if OPTIONS['report']: if self.options['report']:
logging.info("grid: %d new networks to report" % num_new) logging.info("grid: %d new networks to report" % num_new)
logging.debug("OPTIONS: %s" % OPTIONS) logging.debug("self.options: %s" % self.options)
logging.debug(" exclude: %s" % OPTIONS['exclude']) logging.debug(" exclude: %s" % self.options['exclude'])
for pcap_file in pcap_files: for pcap_file in pcap_files:
net_id = os.path.basename(pcap_file).replace('.pcap', '') net_id = os.path.basename(pcap_file).replace('.pcap', '')
if net_id not in reported: if net_id not in reported:
if is_excluded(net_id): if self.is_excluded(net_id):
logging.debug("skipping %s due to exclusion filter" % pcap_file) logging.debug("skipping %s due to exclusion filter" % pcap_file)
set_reported(reported, net_id) self.set_reported(reported, net_id)
continue continue
essid, bssid = parse_pcap(pcap_file) essid, bssid = parse_pcap(pcap_file)
if bssid: if bssid:
if is_excluded(essid) or is_excluded(bssid): if self.is_excluded(essid) or self.is_excluded(bssid):
logging.debug("not reporting %s due to exclusion filter" % pcap_file) logging.debug("not reporting %s due to exclusion filter" % pcap_file)
set_reported(reported, net_id) self.set_reported(reported, net_id)
else: else:
if grid.report_ap(essid, bssid): if grid.report_ap(essid, bssid):
set_reported(reported, net_id) self.set_reported(reported, net_id)
time.sleep(1.5) time.sleep(1.5)
else: else:
logging.warning("no bssid found?!") logging.warning("no bssid found?!")
else: else:
logging.debug("grid: reporting disabled") logging.debug("grid: reporting disabled")
def on_internet_available(self, agent):
def on_internet_available(agent):
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
logging.debug("internet available") logging.debug("internet available")
try: try:
@ -137,13 +128,13 @@ def on_internet_available(agent):
return return
try: try:
check_inbox(agent) self.check_inbox(agent)
except Exception as e: except Exception as e:
logging.error("[grid] error while checking inbox: %s" % e) logging.error("[grid] error while checking inbox: %s" % e)
logging.debug(e, exc_info=True) logging.debug(e, exc_info=True)
try: try:
check_handshakes(agent) self.check_handshakes(agent)
except Exception as e: except Exception as e:
logging.error("[grid] error while checking pcaps: %s" % e) logging.error("[grid] error while checking pcaps: %s" % e)
logging.debug(e, exc_info=True) 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):
def mem_usage():
return int(pwnagotchi.mem_usage() * 100) return int(pwnagotchi.mem_usage() * 100)
def cpu_load(self):
def cpu_load():
return int(pwnagotchi.cpu_load() * 100) return int(pwnagotchi.cpu_load() * 100)
def on_ui_setup(self, ui):
def on_ui_setup(ui): if self.options['orientation'] == "horizontal":
if OPTIONS['orientation'] == "horizontal":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -', ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
position=(ui.width() / 2 + 30, ui.height() / 2 + 15), position=(ui.width() / 2 + 30, ui.height() / 2 + 15),
label_font=fonts.Small, text_font=fonts.Small)) label_font=fonts.Small, text_font=fonts.Small))
elif OPTIONS['orientation'] == "vertical": elif self.options['orientation'] == "vertical":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-', ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
position=(ui.width() / 2 + 55, ui.height() / 2), position=(ui.width() / 2 + 55, ui.height() / 2),
label_font=fonts.Small, text_font=fonts.Small)) label_font=fonts.Small, text_font=fonts.Small))
def on_ui_update(self, ui):
if self.options['orientation'] == "horizontal":
ui.set('memtemp',
" mem cpu temp\n %s%% %s%% %sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
def on_ui_update(ui): elif self.options['orientation'] == "vertical":
if OPTIONS['orientation'] == "horizontal": ui.set('memtemp',
ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature())) " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (self.mem_usage(), self.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,37 +1,36 @@
import logging
import json
import os
import requests
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
class NetPos(plugins.Plugin):
__author__ = 'zenzen san' __author__ = 'zenzen san'
__version__ = '2.0.0' __version__ = '2.0.0'
__name__ = 'net-pos'
__license__ = 'GPL3' __license__ = 'GPL3'
__description__ = """Saves a json file with the access points with more signal __description__ = """Saves a json file with the access points with more signal
whenever a handshake is captured. whenever a handshake is captured.
When internet is available the files are converted in geo locations When internet is available the files are converted in geo locations
using Mozilla LocationService """ using Mozilla LocationService """
import logging def __init__(self):
import json self.report = StatusFile('/root/.net_pos_saved', data_format='json')
import os self.skip = list()
import requests self.ready = False
from pwnagotchi.utils import StatusFile
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}' def on_loaded(self):
REPORT = StatusFile('/root/.net_pos_saved', data_format='json') if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
SKIP = list()
READY = False
OPTIONS = dict()
def on_loaded():
global READY
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.") logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
return return
READY = True self.ready = True
logging.info("net-pos plugin loaded.") 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)
@ -44,21 +43,18 @@ def _append_saved(path):
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
if READY:
config = agent.config() config = agent.config()
display = agent.view() display = agent.view()
reported = REPORT.data_field_or('reported', default=list()) reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes'] handshake_dir = config['bettercap']['handshakes']
all_files = os.listdir(handshake_dir) all_files = os.listdir(handshake_dir)
all_np_files = [os.path.join(handshake_dir, filename) all_np_files = [os.path.join(handshake_dir, filename)
for filename in all_files for filename in all_files
if filename.endswith('.net-pos.json')] if filename.endswith('.net-pos.json')]
new_np_files = set(all_np_files) - set(reported) - set(SKIP) new_np_files = set(all_np_files) - set(reported) - set(self.skip)
if new_np_files: if new_np_files:
logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files)) logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files))
@ -70,36 +66,35 @@ def on_internet_available(agent):
if os.path.exists(geo_file): if os.path.exists(geo_file):
# got already the position # got already the position
reported.append(np_file) reported.append(np_file)
REPORT.update(data={'reported': reported}) self.report.update(data={'reported': reported})
continue continue
try: try:
geo_data = _get_geo_data(np_file) # returns json obj geo_data = self._get_geo_data(np_file) # returns json obj
except requests.exceptions.RequestException as req_e: except requests.exceptions.RequestException as req_e:
logging.error("NET-POS: %s", req_e) logging.error("NET-POS: %s", req_e)
SKIP += np_file self.skip += np_file
continue continue
except json.JSONDecodeError as js_e: except json.JSONDecodeError as js_e:
logging.error("NET-POS: %s", js_e) logging.error("NET-POS: %s", js_e)
SKIP += np_file self.skip += np_file
continue continue
except OSError as os_e: except OSError as os_e:
logging.error("NET-POS: %s", os_e) logging.error("NET-POS: %s", os_e)
SKIP += np_file self.skip += np_file
continue continue
with open(geo_file, 'w+t') as sf: with open(geo_file, 'w+t') as sf:
json.dump(geo_data, sf) json.dump(geo_data, sf)
reported.append(np_file) reported.append(np_file)
REPORT.update(data={'reported': reported}) self.report.update(data={'reported': reported})
display.set('status', f"Fetching positions ({idx + 1}/{len(new_np_files)})") display.set('status', f"Fetching positions ({idx + 1}/{len(new_np_files)})")
display.update(force=True) display.update(force=True)
def on_handshake(self, agent, filename, access_point, client_station):
def on_handshake(agent, filename, access_point, client_station): netpos = self._get_netpos(agent)
netpos = _get_netpos(agent)
netpos_filename = filename.replace('.pcap', '.net-pos.json') netpos_filename = filename.replace('.pcap', '.net-pos.json')
logging.info("NET-POS: Saving net-location to %s", netpos_filename) logging.info("NET-POS: Saving net-location to %s", netpos_filename)
@ -109,8 +104,7 @@ def on_handshake(agent, filename, access_point, client_station):
except OSError as os_e: except OSError as os_e:
logging.error("NET-POS: %s", os_e) logging.error("NET-POS: %s", os_e)
def _get_netpos(self, agent):
def _get_netpos(agent):
aps = agent.get_access_points() aps = agent.get_access_points()
netpos = dict() netpos = dict()
netpos['wifiAccessPoints'] = list() netpos['wifiAccessPoints'] = list()
@ -120,8 +114,8 @@ def _get_netpos(agent):
'signalStrength': access_point['rssi']}) 'signalStrength': access_point['rssi']})
return netpos return netpos
def _get_geo_data(path, timeout=30): def _get_geo_data(self, path, timeout=30):
geourl = MOZILLA_API_URL.format(api=OPTIONS['api_key']) geourl = MOZILLA_API_URL.format(api=self.options['api_key'])
try: try:
with open(path, "r") as json_file: with open(path, "r") as json_file:

View File

@ -1,39 +1,37 @@
__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'
__version__ = '2.0.0'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
def __init__(self):
self.ready = False
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
self.skip = list()
def on_loaded(self):
""" """
Gets called when the plugin gets loaded Gets called when the plugin gets loaded
""" """
global READY if 'email' not in self.options or ('email' in self.options and self.options['email'] is None):
if 'email' not in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None):
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com") logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
return return
READY = True self.ready = True
def _upload_to_ohc(self, path, timeout=30):
def _upload_to_ohc(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:
@ -47,40 +45,38 @@ def _upload_to_ohc(path, timeout=30):
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):
def on_internet_available(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
if READY:
display = agent.view() display = agent.view()
config = agent.config() config = agent.config()
reported = REPORT.data_field_or('reported', default=list()) reported = self.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("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com") logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com")
for idx, handshake in enumerate(handshake_new): for idx, handshake in enumerate(handshake_new):
display.set('status', f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})") display.set('status',
f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
display.update(force=True) display.update(force=True)
try: try:
_upload_to_ohc(handshake) self._upload_to_ohc(handshake)
reported.append(handshake) reported.append(handshake)
REPORT.update(data={'reported': reported}) self.report.update(data={'reported': reported})
logging.info(f"OHC: Successfully uploaded {handshake}") logging.info(f"OHC: Successfully uploaded {handshake}")
except requests.exceptions.RequestException as req_e: except requests.exceptions.RequestException as req_e:
SKIP.append(handshake) self.skip.append(handshake)
logging.error("OHC: %s", req_e) logging.error("OHC: %s", req_e)
continue continue
except OSError as os_e: except OSError as os_e:
SKIP.append(handshake) self.skip.append(handshake)
logging.error("OHC: %s", os_e) logging.error("OHC: %s", os_e)
continue 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):
def on_loaded():
logging.info("PAW-GPS loaded") logging.info("PAW-GPS loaded")
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None): 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)") 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): def on_handshake(self, agent, filename, access_point, client_station):
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None): if 'ip' not in self.options or ('ip' in self.options and self.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):
self.text_to_set = ""
def on_loaded(self):
logging.info("Quick dictionary check plugin loaded") logging.info("Quick dictionary check plugin loaded")
def on_handshake(agent, filename, access_point, client_station): def on_handshake(self, agent, filename, access_point, client_station):
display = agent._view display = agent.view()
result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''),
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) shell=True, stdout=subprocess.PIPE)
result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace}) result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
if not result: if not result:
logging.info("[quickdic] No handshake") logging.info("[quickdic] No handshake")
else: else:
logging.info("[quickdic] Handshake confirmed") logging.info("[quickdic] Handshake confirmed")
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) 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() result2 = result2.stdout.decode('utf-8').strip()
logging.info("[quickdic] " + result2) logging.info("[quickdic] " + result2)
if result2 != "KEY NOT FOUND": if result2 != "KEY NOT FOUND":
key = re.search('\[(.*)\]', result2) key = re.search('\[(.*)\]', result2)
pwd = str(key.group(1)) pwd = str(key.group(1))
set_text("Cracked password: "+pwd) self.text_to_set = "Cracked password: " + pwd
display.update(force=True) 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
text_to_set = text
def on_ui_update(ui):
global text_to_set
if text_to_set:
ui.set('face', "(·ω·)") ui.set('face', "(·ω·)")
ui.set('status', text_to_set) ui.set('status', self.text_to_set)
text_to_set = "" self.text_to_set = ""

View File

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

View File

@ -1,20 +1,19 @@
__author__ = '33197631+dadav@users.noreply.github.com' import logging
from pwnagotchi.voice import Voice
import pwnagotchi.plugins as plugins
class Twitter(plugins.Plugin):
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0' __version__ = '1.0.0'
__name__ = 'twitter'
__license__ = 'GPL3' __license__ = 'GPL3'
__description__ = 'This plugin creates tweets about the recent activity of pwnagotchi' __description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
import logging def on_loaded(self):
from pwnagotchi.voice import Voice
OPTIONS = dict()
def on_loaded():
logging.info("twitter plugin loaded.") logging.info("twitter plugin loaded.")
# 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):
config = agent.config() config = agent.config()
display = agent.view() display = agent.view()
last_session = agent.last_session last_session = agent.last_session
@ -28,7 +27,7 @@ def on_internet_available(agent):
logging.info("detected a new session and internet connectivity!") logging.info("detected a new session and internet connectivity!")
picture = '/dev/shm/pwnagotchi.png' picture = '/root/pwnagotchi.png'
display.on_manual_mode(last_session) display.on_manual_mode(last_session)
display.update(force=True) display.update(force=True)
@ -37,8 +36,8 @@ def on_internet_available(agent):
display.update(force=True) display.update(force=True)
try: try:
auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret']) auth = tweepy.OAuthHandler(self.options['consumer_key'], self.options['consumer_secret'])
auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret']) auth.set_access_token(self.options['access_token_key'], self.options['access_token_secret'])
api = tweepy.API(auth) api = tweepy.API(auth)
tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session) tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session)

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):
def on_ui_setup(ui):
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0), 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)) label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_update(self, ui):
def on_ui_update(ui): ui.set('ups', "%4.2fV/%2i%%" % (self.ups.voltage(), self.ups.capacity()))
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([
@ -79,6 +59,7 @@ def _transform_wigle_entry(gps_data, pcap_data):
'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,25 +90,39 @@ def _send_to_wigle(lines, api_key, timeout=30):
raise re_e raise re_e
def on_internet_available(agent): class Wigle(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '2.0.0'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads collected wifis to wigle.net'
def __init__(self):
self.ready = False
self.report = StatusFile('/root/.wigle_uploads', data_format='json')
self.skip = list()
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("WIGLE: api_key isn't set. Can't upload to wigle.net")
return
self.ready = True
def on_internet_available(self, agent):
from scapy.all import Scapy_Exception from scapy.all import Scapy_Exception
""" """
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
if READY:
config = agent.config() config = agent.config()
display = agent.view() display = agent.view()
reported = REPORT.data_field_or('reported', default=list()) reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes'] handshake_dir = config['bettercap']['handshakes']
all_files = os.listdir(handshake_dir) all_files = os.listdir(handshake_dir)
all_gps_files = [os.path.join(handshake_dir, filename) all_gps_files = [os.path.join(handshake_dir, filename)
for filename in all_files for filename in all_files
if filename.endswith('.gps.json')] if filename.endswith('.gps.json')]
new_gps_files = set(all_gps_files) - set(reported) - set(SKIP) new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
if new_gps_files: if new_gps_files:
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net") logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
@ -140,26 +135,25 @@ def on_internet_available(agent):
if not os.path.exists(pcap_filename): if not os.path.exists(pcap_filename):
logging.error("WIGLE: Can't find pcap for %s", 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: try:
gps_data = _extract_gps_data(gps_file) gps_data = _extract_gps_data(gps_file)
except OSError as os_err: except OSError as os_err:
logging.error("WIGLE: %s", os_err) logging.error("WIGLE: %s", os_err)
SKIP.append(gps_file) self.skip.append(gps_file)
continue continue
except json.JSONDecodeError as json_err: except json.JSONDecodeError as json_err:
logging.error("WIGLE: %s", json_err) logging.error("WIGLE: %s", json_err)
SKIP.append(gps_file) self.skip.append(gps_file)
continue continue
if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0: if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file) logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
SKIP.append(gps_file) self.skip.append(gps_file)
continue continue
try: try:
pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID, pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID,
WifiInfo.ESSID, WifiInfo.ESSID,
@ -168,11 +162,11 @@ def on_internet_available(agent):
WifiInfo.RSSI]) WifiInfo.RSSI])
except FieldNotFoundError: except FieldNotFoundError:
logging.error("WIGLE: Could not extract all information. Skip %s", gps_file) logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
SKIP.append(gps_file) self.skip.append(gps_file)
continue continue
except Scapy_Exception as sc_e: except Scapy_Exception as sc_e:
logging.error("WIGLE: %s", sc_e) logging.error("WIGLE: %s", sc_e)
SKIP.append(gps_file) self.skip.append(gps_file)
continue continue
new_entry = _transform_wigle_entry(gps_data, pcap_data) new_entry = _transform_wigle_entry(gps_data, pcap_data)
@ -183,13 +177,13 @@ def on_internet_available(agent):
display.set('status', "Uploading gps-data to wigle.net ...") display.set('status', "Uploading gps-data to wigle.net ...")
display.update(force=True) display.update(force=True)
try: try:
_send_to_wigle(csv_entries, OPTIONS['api_key']) _send_to_wigle(csv_entries, self.options['api_key'])
reported += no_err_entries reported += no_err_entries
REPORT.update(data={'reported': reported}) self.report.update(data={'reported': reported})
logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries)) logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries))
except requests.exceptions.RequestException as re_e: except requests.exceptions.RequestException as re_e:
SKIP += no_err_entries self.skip += no_err_entries
logging.error("WIGLE: Got an exception while uploading %s", re_e) logging.error("WIGLE: Got an exception while uploading %s", re_e)
except OSError as os_e: except OSError as os_e:
SKIP += no_err_entries self.skip += no_err_entries
logging.error("WIGLE: Got the following error: %s", os_e) logging.error("WIGLE: Got the following error: %s", os_e)

View File

@ -1,47 +1,32 @@
__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
READY = True
def _upload_to_wpasec(path, timeout=30):
""" """
Uploads the file to https://wpa-sec.stanev.org, or another endpoint. Uploads the file to https://wpa-sec.stanev.org, or another endpoint.
""" """
with open(path, 'rb') as file_to_upload: with open(path, 'rb') as file_to_upload:
cookie = {'key': OPTIONS['api_key']} cookie = {'key': self.options['api_key']}
payload = {'file': file_to_upload} payload = {'file': file_to_upload}
try: try:
result = requests.post(OPTIONS['api_url'], result = requests.post(self.options['api_url'],
cookies=cookie, cookies=cookie,
files=payload, files=payload,
timeout=timeout) timeout=timeout)
@ -50,22 +35,34 @@ def _upload_to_wpasec(path, timeout=30):
except requests.exceptions.RequestException as req_e: except requests.exceptions.RequestException as req_e:
raise req_e raise req_e
def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org")
return
def on_internet_available(agent): if 'api_url' not in self.options or ('api_url' in self.options and self.options['api_url'] is None):
logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
return
self.ready = True
def on_internet_available(self, agent):
""" """
Called in manual mode when there's internet connectivity Called in manual mode when there's internet connectivity
""" """
global REPORT if self.ready:
global SKIP
if READY:
config = agent.config() config = agent.config()
display = agent.view() display = agent.view()
reported = REPORT.data_field_or('reported', default=list()) reported = self.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")
@ -74,12 +71,12 @@ def on_internet_available(agent):
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: