mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
misc: refactored plugin system to use classes
This commit is contained in:
@ -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')
|
||||||
|
@ -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 = ""
|
|
||||||
|
@ -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!')
|
||||||
|
@ -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 ...'})
|
||||||
|
@ -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))
|
|
||||||
|
@ -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
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
|
||||||
|
@ -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)
|
|
||||||
|
@ -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()))
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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')
|
||||||
|
@ -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 = ""
|
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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 ##
|
|
@ -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()))
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user