Big update

This commit is contained in:
dadav
2020-04-03 19:01:40 +02:00
parent 5d8d86204a
commit 1f2dd73976
36 changed files with 1590 additions and 515 deletions

View File

@ -4,9 +4,7 @@ import _thread
import threading
import importlib, importlib.util
import logging
import pwnagotchi
from pwnagotchi.ui import view
from pwnagotchi.utils import save_config
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
@ -39,6 +37,10 @@ def toggle_plugin(name, enable=True):
returns True if changed, otherwise False
"""
import pwnagotchi
from pwnagotchi.ui import view
from pwnagotchi.utils import save_config
global loaded, database
if pwnagotchi.config:
@ -55,6 +57,8 @@ def toggle_plugin(name, enable=True):
if enable and name in database and name not in loaded:
load_from_file(database[name])
one(name, 'loaded')
if pwnagotchi.config:
one(name, 'config_changed', pwnagotchi.config)
one(name, 'ui_setup', view.ROOT)
one(name, 'ready', view.ROOT._agent)
return True
@ -63,7 +67,7 @@ def toggle_plugin(name, enable=True):
def on(event_name, *args, **kwargs):
for plugin_name, plugin in loaded.items():
for plugin_name in loaded.keys():
one(plugin_name, event_name, *args, **kwargs)
@ -136,3 +140,4 @@ def load(config):
plugin.options = config['main']['plugins'][name]
on('loaded')
on('config_changed', config)

389
pwnagotchi/plugins/cmd.py Normal file
View File

@ -0,0 +1,389 @@
# Handles the commandline stuff
import sys
import os
import logging
import glob
import re
import shutil
from fnmatch import fnmatch
from pwnagotchi.utils import download_file, unzip, save_config, parse_version, md5
from pwnagotchi.plugins import default_path
REPO_URL = 'https://github.com/evilsocket/pwnagotchi-plugins-contrib/archive/master.zip'
SAVE_DIR = '/usr/local/share/pwnagotchi/availaible-plugins/'
DEFAULT_INSTALL_PATH = '/usr/local/share/pwnagotchi/installed-plugins/'
def add_parsers(parser):
"""
Adds the plugins subcommand to a given argparse.ArgumentParser
"""
subparsers = parser.add_subparsers()
## pwnagotchi plugins
parser_plugins = subparsers.add_parser('plugins')
plugin_subparsers = parser_plugins.add_subparsers(dest='plugincmd')
## pwnagotchi plugins search
parser_plugins_search = plugin_subparsers.add_parser('search', help='Search for pwnagotchi plugins')
parser_plugins_search.add_argument('pattern', type=str, help="Search expression (wildcards allowed)")
## pwnagotchi plugins list
parser_plugins_list = plugin_subparsers.add_parser('list', help='List available pwnagotchi plugins')
parser_plugins_list.add_argument('-i', '--installed', action='store_true', required=False, help='List also installed plugins')
## pwnagotchi plugins update
parser_plugins_update = plugin_subparsers.add_parser('update', help='Updates the database')
## pwnagotchi plugins upgrade
parser_plugins_upgrade = plugin_subparsers.add_parser('upgrade', help='Upgrades plugins')
parser_plugins_upgrade.add_argument('pattern', type=str, nargs='?', default='*', help="Filter expression (wildcards allowed)")
## pwnagotchi plugins enable
parser_plugins_enable = plugin_subparsers.add_parser('enable', help='Enables a plugin')
parser_plugins_enable.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins disable
parser_plugins_disable = plugin_subparsers.add_parser('disable', help='Disables a plugin')
parser_plugins_disable.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins install
parser_plugins_install = plugin_subparsers.add_parser('install', help='Installs a plugin')
parser_plugins_install.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins uninstall
parser_plugins_uninstall = plugin_subparsers.add_parser('uninstall', help='Uninstalls a plugin')
parser_plugins_uninstall.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins edit
parser_plugins_edit = plugin_subparsers.add_parser('edit', help='Edit the options')
parser_plugins_edit.add_argument('name', type=str, help='Name of the plugin')
return parser
def used_plugin_cmd(args):
"""
Checks if the plugins subcommand was used
"""
return hasattr(args, 'plugincmd')
def handle_cmd(args, config):
"""
Parses the arguments and does the thing the user wants
"""
if args.plugincmd == 'update':
return update()
elif args.plugincmd == 'search':
args.installed = True # also search in installed plugins
return list_plugins(args, config, args.pattern)
elif args.plugincmd == 'install':
return install(args, config)
elif args.plugincmd == 'uninstall':
return uninstall(args, config)
elif args.plugincmd == 'list':
return list_plugins(args, config)
elif args.plugincmd == 'enable':
return enable(args, config)
elif args.plugincmd == 'disable':
return disable(args, config)
elif args.plugincmd == 'upgrade':
return upgrade(args, config, args.pattern)
elif args.plugincmd == 'edit':
return edit(args, config)
raise NotImplementedError()
def edit(args, config):
"""
Edit the config of the plugin
"""
plugin = args.name
editor = os.environ.get('EDITOR', 'vim') # because vim is the best
if plugin not in config['main']['plugins']:
return 1
plugin_config = {'main': {'plugins': {plugin: config['main']['plugins'][plugin]}}}
import toml
from subprocess import call
from tempfile import NamedTemporaryFile
from pwnagotchi.utils import DottedTomlEncoder
new_plugin_config = None
with NamedTemporaryFile(suffix=".tmp", mode='r+t') as tmp:
tmp.write(toml.dumps(plugin_config, encoder=DottedTomlEncoder()))
tmp.flush()
rc = call([editor, tmp.name])
if rc != 0:
return rc
tmp.seek(0)
new_plugin_config = toml.load(tmp)
config['main']['plugins'][plugin] = new_plugin_config['main']['plugins'][plugin]
save_config(config, args.user_config)
return 0
def enable(args, config):
"""
Enables the given plugin and saves the config to disk
"""
if args.name not in config['main']['plugins']:
config['main']['plugins'][args.name] = dict()
config['main']['plugins'][args.name]['enabled'] = True
save_config(config, args.user_config)
return 0
def disable(args, config):
"""
Disables the given plugin and saves the config to disk
"""
if args.name not in config['main']['plugins']:
config['main']['plugins'][args.name] = dict()
config['main']['plugins'][args.name]['enabled'] = False
save_config(config, args.user_config)
return 0
def upgrade(args, config, pattern='*'):
"""
Upgrades the given plugin
"""
available = _get_available()
installed = _get_installed(config)
for plugin, filename in installed.items():
if not fnmatch(plugin, pattern) or plugin not in available:
continue
available_version = _extract_version(available[plugin])
installed_version = _extract_version(filename)
if installed_version and available_version:
if available_version <= installed_version:
continue
else:
continue
logging.info('Upgrade %s from %s to %s', plugin, '.'.join(installed_version), '.'.join(available_version))
shutil.copyfile(available[plugin], installed[plugin])
# maybe has config
for conf in glob.glob(available[plugin].replace('.py', '.y?ml')):
dst = os.path.join(os.path.dirname(installed[plugin]), os.path.basename(conf))
if os.path.exists(dst) and md5(dst) != md5(conf):
# backup
logging.info('Backing up config: %s', os.path.basename(conf))
shutil.move(dst, dst + '.bak')
shutil.copyfile(conf, dst)
return 0
def list_plugins(args, config, pattern='*'):
"""
Lists the available and installed plugins
"""
found = False
line = "|{name:^{width}}|{version:^9}|{enabled:^10}|{status:^15}|"
available = _get_available()
installed = _get_installed(config)
available_and_installed = set(list(available.keys()) + list(installed.keys()))
available_not_installed = set(available.keys()) - set(installed.keys())
max_len_list = available_and_installed if args.installed else available_not_installed
max_len = max(map(len, max_len_list))
header = line.format(name='Plugin', width=max_len, version='Version', enabled='Active', status='Status')
line_length = max(max_len, len('Plugin')) + len(header) - len('Plugin') - 12 # lol
print('-' * line_length)
print(header)
print('-' * line_length)
if args.installed:
# only installed (maybe update available?)
for plugin, filename in sorted(installed.items()):
if not fnmatch(plugin, pattern):
continue
found = True
installed_version = _extract_version(filename)
available_version = None
if plugin in available:
available_version = _extract_version(available[plugin])
status = "installed"
if installed_version and available_version:
if available_version > installed_version:
status = "installed (^)"
enabled = 'enabled' if plugin in config['main']['plugins'] and \
'enabled' in config['main']['plugins'][plugin] and \
config['main']['plugins'][plugin]['enabled'] \
else 'disabled'
print(line.format(name=plugin, width=max_len, version='.'.join(installed_version), enabled=enabled, status=status))
for plugin in sorted(available_not_installed):
if not fnmatch(plugin, pattern):
continue
found = True
available_version = _extract_version(available[plugin])
print(line.format(name=plugin, width=max_len, version='.'.join(available_version), enabled='-', status='available'))
print('-' * line_length)
if not found:
logging.info('Maybe try: pwnagotchi plugins update')
return 1
return 0
def _extract_version(filename):
"""
Extracts the version from a python file
"""
plugin_content = open(filename, 'rt').read()
m = re.search(r'__version__[\t ]*=[\t ]*[\'\"]([^\"\']+)', plugin_content)
if m:
return parse_version(m.groups()[0])
return None
def _get_available():
"""
Get all availaible plugins
"""
available = dict()
for filename in glob.glob(os.path.join(SAVE_DIR, "*.py")):
plugin_name = os.path.basename(filename.replace(".py", ""))
available[plugin_name] = filename
return available
def _get_installed(config):
"""
Get all installed plugins
"""
installed = dict()
search_dirs = [ default_path, config['main']['custom_plugins'] ]
for search_dir in search_dirs:
if search_dir:
for filename in glob.glob(os.path.join(search_dir, "*.py")):
plugin_name = os.path.basename(filename.replace(".py", ""))
installed[plugin_name] = filename
return installed
def uninstall(args, config):
"""
Uninstalls a plugin
"""
plugin_name = args.name
installed = _get_installed(config)
if plugin_name not in installed:
logging.error('Plugin %s is not installed.', plugin_name)
return 1
os.remove(installed[plugin_name])
return 0
def install(args, config):
"""
Installs the given plugin
"""
global DEFAULT_INSTALL_PATH
plugin_name = args.name
available = _get_available()
installed = _get_installed(config)
if plugin_name not in available:
logging.error('%s not found.', plugin_name)
return 1
if plugin_name in installed:
logging.error('%s already installed.', plugin_name)
# install into custom_plugins path
install_path = config['main']['custom_plugins']
if not install_path:
install_path = DEFAULT_INSTALL_PATH
config['main']['custom_plugins'] = install_path
save_config(config, args.user_config)
os.makedirs(install_path, exist_ok=True)
shutil.copyfile(available[plugin_name], os.path.join(install_path, os.path.basename(available[plugin_name])))
# maybe has config
for conf in glob.glob(available[plugin_name].replace('.py', '.y?ml')):
dst = os.path.join(install_path, os.path.basename(conf))
if os.path.exists(dst) and md5(dst) != md5(conf):
# backup
logging.info('Backing up config: %s', os.path.basename(conf))
shutil.move(dst, dst + '.bak')
shutil.copyfile(conf, dst)
return 0
def _analyse_dir(path):
results = dict()
path += '*' if path.endswith('/') else '/*'
for filename in glob.glob(path, recursive=True):
if not os.path.isfile(filename):
continue
try:
results[filename] = md5(filename)
except OSError:
continue
return results
def update():
"""
Updates the database
"""
global REPO_URL, SAVE_DIR
DEST = os.path.join(SAVE_DIR, 'plugins.zip')
logging.info('Downloading plugins to %s', DEST)
try:
os.makedirs(SAVE_DIR, exist_ok=True)
before_update = _analyse_dir(SAVE_DIR)
download_file(REPO_URL, os.path.join(SAVE_DIR, DEST))
logging.info('Unzipping...')
unzip(DEST, SAVE_DIR, strip_dirs=1)
after_update = _analyse_dir(SAVE_DIR)
b_len = len(before_update)
a_len = len(after_update)
if a_len > b_len:
logging.info('Found %d new file(s).', a_len - b_len)
changed = 0
for filename, filehash in after_update.items():
if filename in before_update and filehash != before_update[filename]:
changed += 1
if changed:
logging.info('%d file(s) were changed.', changed)
return 0
except Exception as ex:
logging.error('Error while updating plugins %s', ex)
return 1

View File

@ -6,12 +6,11 @@ import requests
import platform
import shutil
import glob
import pkg_resources
from threading import Lock
import pwnagotchi
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile
from pwnagotchi.utils import StatusFile, parse_version as version_to_tuple
def check(version, repo, native=True):
@ -30,8 +29,8 @@ def check(version, repo, native=True):
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
is_arm = info['arch'].startswith('arm')
local = pkg_resources.parse_version(info['current'])
remote = pkg_resources.parse_version(latest_ver)
local = version_to_tuple(info['current'])
remote = version_to_tuple(latest_ver)
if remote > local:
if not native:
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
@ -161,6 +160,9 @@ class AutoUpdate(plugins.Plugin):
logging.info("[update] plugin loaded.")
def on_internet_available(self, agent):
if self.lock.locked():
return
with self.lock:
logging.debug("[update] internet connectivity is available (ready %s)" % self.ready)

View File

@ -7,6 +7,7 @@ import re
import pwnagotchi.grid as grid
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
from threading import Lock
def parse_pcap(filename):
@ -54,6 +55,7 @@ class Grid(plugins.Plugin):
self.unread_messages = 0
self.total_messages = 0
self.lock = Lock()
def is_excluded(self, what):
for skip in self.options['exclude']:
@ -122,21 +124,25 @@ class Grid(plugins.Plugin):
def on_internet_available(self, agent):
logging.debug("internet available")
try:
grid.update_data(agent.last_session)
except Exception as e:
logging.error("error connecting to the pwngrid-peer service: %s" % e)
logging.debug(e, exc_info=True)
if self.lock.locked():
return
try:
self.check_inbox(agent)
except Exception as e:
logging.error("[grid] error while checking inbox: %s" % e)
logging.debug(e, exc_info=True)
with self.lock:
try:
grid.update_data(agent.last_session)
except Exception as e:
logging.error("error connecting to the pwngrid-peer service: %s" % e)
logging.debug(e, exc_info=True)
return
try:
self.check_handshakes(agent)
except Exception as e:
logging.error("[grid] error while checking pcaps: %s" % e)
logging.debug(e, exc_info=True)
try:
self.check_inbox(agent)
except Exception as e:
logging.error("[grid] error while checking inbox: %s" % e)
logging.debug(e, exc_info=True)
try:
self.check_handshakes(agent)
except Exception as e:
logging.error("[grid] error while checking pcaps: %s" % e)
logging.debug(e, exc_info=True)

View File

@ -0,0 +1,282 @@
import os
import logging
import threading
from time import sleep
from datetime import datetime,timedelta
from pwnagotchi import plugins
from pwnagotchi.utils import StatusFile
from flask import render_template_string
from flask import jsonify
from flask import abort
from flask import Response
TEMPLATE = """
{% extends "base.html" %}
{% set active_page = "plugins" %}
{% block title %}
Logtail
{% endblock %}
{% block styles %}
{{ super() }}
<style>
* {
box-sizing: border-box;
}
#filter {
width: 100%;
font-size: 16px;
padding: 12px 20px 12px 40px;
border: 1px solid #ddd;
margin-bottom: 12px;
}
table {
border-collapse: collapse;
width: 100%;
border: 1px solid #ddd;
}
th, td {
text-align: left;
padding: 12px;
width: 1px;
white-space: nowrap;
}
td:nth-child(2) {
text-align: center;
}
thead, tr:hover {
background-color: #f1f1f1;
}
tr {
border-bottom: 1px solid #ddd;
}
div.sticky {
position: -webkit-sticky;
position: sticky;
top: 0;
display: table;
width: 100%;
}
div.sticky > * {
display: table-cell;
}
div.sticky > span {
width: 1%;
}
div.sticky > input {
width: 100%;
}
tr.default {
color: black;
}
tr.info {
color: black;
}
tr.warning {
color: darkorange;
}
tr.error {
color: crimson;
}
tr.debug {
color: blueviolet;
}
.ui-mobile .ui-page-active {
overflow: visible;
overflow-x: visible;
}
</style>
{% endblock %}
{% block script %}
var content = document.getElementById('content');
var filter = document.getElementById('filter');
var filterVal = filter.value.toUpperCase();
var xhr = new XMLHttpRequest();
xhr.open('GET', '{{ url_for('plugins') }}/logtail/stream');
xhr.send();
var position = 0;
var data;
var time;
var level;
var msg;
var colorClass;
function handleNewData() {
var messages = xhr.responseText.split('\\n');
filterVal = filter.value.toUpperCase();
messages.slice(position, -1).forEach(function(value) {
if (value.charAt(0) != '[') {
msg = value;
time = '';
level = '';
} else {
data = value.split(']');
time = data.shift() + ']';
level = data.shift() + ']';
msg = data.join(']');
switch(level) {
case ' [INFO]':
colorClass = 'info';
break;
case ' [WARNING]':
colorClass = 'warning';
break;
case ' [ERROR]':
colorClass = 'error';
break;
case ' [DEBUG]':
colorClass = 'debug';
break;
default:
colorClass = 'default';
break;
}
}
var tr = document.createElement('tr');
var td1 = document.createElement('td');
var td2 = document.createElement('td');
var td3 = document.createElement('td');
td1.textContent = time;
td2.textContent = level;
td3.textContent = msg;
tr.appendChild(td1);
tr.appendChild(td2);
tr.appendChild(td3);
tr.className = colorClass;
if (filterVal.length > 0 && value.toUpperCase().indexOf(filterVal) == -1) {
tr.style.visibility = "collapse";
}
content.appendChild(tr);
});
position = messages.length - 1;
}
var scrollingElement = (document.scrollingElement || document.body)
function scrollToBottom () {
scrollingElement.scrollTop = scrollingElement.scrollHeight;
}
var timer;
var scrollElm = document.getElementById('autoscroll');
timer = setInterval(function() {
handleNewData();
if (scrollElm.checked) {
scrollToBottom();
}
if (xhr.readyState == XMLHttpRequest.DONE) {
clearInterval(timer);
}
}, 1000);
var typingTimer;
var doneTypingInterval = 1000;
filter.onkeyup = function() {
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
}
filter.onkeydown = function() {
clearTimeout(typingTimer);
}
function doneTyping() {
document.body.style.cursor = 'progress';
var table, tr, tds, td, i, txtValue;
filterVal = filter.value.toUpperCase();
table = document.getElementById("content");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
tds = tr[i].getElementsByTagName("td");
if (tds) {
for (l = 0; l < tds.length; l++) {
td = tds[l];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filterVal) > -1) {
tr[i].style.visibility = "visible";
break;
} else {
tr[i].style.visibility = "collapse";
}
}
}
}
}
document.body.style.cursor = 'default';
}
{% endblock %}
{% block content %}
<div class="sticky">
<input type="text" id="filter" placeholder="Search for ..." title="Type in a filter">
<span><input checked type="checkbox" id="autoscroll"></span>
<span><label for="autoscroll"> Autoscroll to bottom</label><br></span>
</div>
<table id="content">
<thead>
<th>
Time
</th>
<th>
Level
</th>
<th>
Message
</th>
</thead>
</table>
{% endblock %}
"""
class Logtail(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '0.1.0'
__license__ = 'GPL3'
__description__ = 'This plugin tails the logfile.'
def __init__(self):
self.lock = threading.Lock()
self.options = dict()
self.ready = False
def on_config_changed(self, config):
self.config = config
self.ready = True
def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
logging.info("Logtail plugin loaded.")
def on_webhook(self, path, request):
if not self.ready:
return "Plugin not ready"
if not path or path == "/":
return render_template_string(TEMPLATE)
if path == 'stream':
def generate():
with open(self.config['main']['log']['path']) as f:
yield f.read()
while True:
yield f.readline()
return Response(generate(), mimetype='text/plain')
abort(404)

View File

@ -49,6 +49,8 @@ class NetPos(plugins.Plugin):
saved_file.write(x + "\n")
def on_internet_available(self, agent):
if self.lock.locked():
return
with self.lock:
if self.ready:
config = agent.config()

View File

@ -5,7 +5,7 @@ import re
import requests
from datetime import datetime
from threading import Lock
from pwnagotchi.utils import StatusFile
from pwnagotchi.utils import StatusFile, remove_whitelisted
import pwnagotchi.plugins as plugins
from json.decoder import JSONDecodeError
@ -20,7 +20,7 @@ class OnlineHashCrack(plugins.Plugin):
self.ready = False
try:
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
except JSONDecodeError as json_err:
except JSONDecodeError:
os.remove('/root/.ohc_uploads')
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
self.skip = list()
@ -35,25 +35,11 @@ class OnlineHashCrack(plugins.Plugin):
return
if 'whitelist' not in self.options:
self.options['whitelist'] = []
# remove special characters from whitelist APs to match on-disk format
self.options['whitelist'] = set(map(lambda x: re.sub(r'[^a-zA-Z0-9]', '', x), self.options['whitelist']))
self.options['whitelist'] = list()
self.ready = True
logging.info("OHC: OnlineHashCrack plugin loaded.")
def _filter_handshake_file(self, handshake_filename):
try:
basename = os.path.basename(handshake_filename)
ssid, bssid = basename.split('_')
# remove the ".pcap" from the bssid (which is really just the end of the filename)
bssid = bssid[:-5]
except:
# something failed in our parsing of the filename. let the file through
return True
return ssid not in self.options['whitelist'] and bssid not in self.options['whitelist']
def _upload_to_ohc(self, path, timeout=30):
"""
@ -96,64 +82,59 @@ class OnlineHashCrack(plugins.Plugin):
"""
Called in manual mode when there's internet connectivity
"""
if not self.ready or self.lock.locked():
return
with self.lock:
if self.ready:
display = agent.view()
config = agent.config()
reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
filename.endswith('.pcap')]
# pull out whitelisted APs
handshake_paths = filter(lambda path: self._filter_handshake_file(path), handshake_paths)
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
if handshake_new:
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com")
for idx, handshake in enumerate(handshake_new):
display.set('status',
f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
display.update(force=True)
try:
self._upload_to_ohc(handshake)
if handshake not in reported:
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.info(f"OHC: Successfully uploaded {handshake}")
except requests.exceptions.RequestException as req_e:
self.skip.append(handshake)
logging.error("OHC: %s", req_e)
continue
except OSError as os_e:
self.skip.append(handshake)
logging.error("OHC: %s", os_e)
continue
if 'dashboard' in self.options and self.options['dashboard']:
cracked_file = os.path.join(handshake_dir, 'onlinehashcrack.cracked')
if os.path.exists(cracked_file):
last_check = datetime.fromtimestamp(os.path.getmtime(cracked_file))
if last_check is not None and ((datetime.now() - last_check).seconds / (60 * 60)) < 1:
return
display = agent.view()
config = agent.config()
reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
filename.endswith('.pcap')]
# pull out whitelisted APs
handshake_paths = remove_whitelisted(handshake_paths, self.options['whitelist'])
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
if handshake_new:
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com")
for idx, handshake in enumerate(handshake_new):
display.set('status',
f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
display.update(force=True)
try:
self._download_cracked(cracked_file)
logging.info("OHC: Downloaded cracked passwords.")
self._upload_to_ohc(handshake)
if handshake not in reported:
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.info(f"OHC: Successfully uploaded {handshake}")
except requests.exceptions.RequestException as req_e:
logging.debug("OHC: %s", req_e)
self.skip.append(handshake)
logging.error("OHC: %s", req_e)
continue
except OSError as os_e:
logging.debug("OHC: %s", os_e)
if 'single_files' in self.options and self.options['single_files']:
with open(cracked_file, 'r') as cracked_list:
for row in csv.DictReader(cracked_list):
if row['password']:
filename = re.sub(r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace(':','')
if os.path.exists( os.path.join(handshake_dir, filename+'.pcap') ):
with open(os.path.join(handshake_dir, filename+'.pcap.cracked'), 'w') as f:
f.write(row['password'])
self.skip.append(handshake)
logging.error("OHC: %s", os_e)
continue
if 'dashboard' in self.options and self.options['dashboard']:
cracked_file = os.path.join(handshake_dir, 'onlinehashcrack.cracked')
if os.path.exists(cracked_file):
last_check = datetime.fromtimestamp(os.path.getmtime(cracked_file))
if last_check is not None and ((datetime.now() - last_check).seconds / (60 * 60)) < 1:
return
try:
self._download_cracked(cracked_file)
logging.info("OHC: Downloaded cracked passwords.")
except requests.exceptions.RequestException as req_e:
logging.debug("OHC: %s", req_e)
except OSError as os_e:
logging.debug("OHC: %s", os_e)
if 'single_files' in self.options and self.options['single_files']:
with open(cracked_file, 'r') as cracked_list:
for row in csv.DictReader(cracked_list):
if row['password']:
filename = re.sub(r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace(':','')
if os.path.exists( os.path.join(handshake_dir, filename+'.pcap') ):
with open(os.path.join(handshake_dir, filename+'.pcap.cracked'), 'w') as f:
f.write(row['password'])

View File

@ -16,11 +16,22 @@ TEMPLATE = """
{% endblock %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" href="/css/jquery.jqplot.min.css"/>
<link rel="stylesheet" href="/css/jquery.jqplot.css"/>
<style>
div.chart {
height: 400px;
width: 100%;
}
div#session {
width: 100%;
}
</style>
{% endblock %}
{% block scripts %}
{{ super() }}
<script type="text/javascript" src="/js/jquery.jqplot.min.js"></script>
<script type="text/javascript" src="/js/jquery.jqplot.js"></script>
<script type="text/javascript" src="/js/plugins/jqplot.mobile.js"></script>
@ -83,7 +94,6 @@ TEMPLATE = """
tickOptions:{formatString:'%H:%M:%S'}
},
yaxis:{
min: 0,
tickOptions:{formatString:'%.2f'}
}
},
@ -102,7 +112,6 @@ TEMPLATE = """
tickOptions:{formatString:'%H:%M:%S'}
},
yaxis:{
min: 0,
tickOptions:{formatString:'%.2f'}
}
}
@ -121,8 +130,9 @@ TEMPLATE = """
var session = x.options[x.selectedIndex].text;
loadData('/plugins/session-stats/os' + '?session=' + session, 'chart_os', 'OS', false)
loadData('/plugins/session-stats/temp' + '?session=' + session, 'chart_temp', 'Temp', false)
loadData('/plugins/session-stats/nums' + '?session=' + session, 'chart_nums', 'Wifi', true)
loadData('/plugins/session-stats/wifi' + '?session=' + session, 'chart_wifi', 'Wifi', true)
loadData('/plugins/session-stats/duration' + '?session=' + session, 'chart_duration', 'Sleeping', true)
loadData('/plugins/session-stats/reward' + '?session=' + session, 'chart_reward', 'Reward', false)
loadData('/plugins/session-stats/epoch' + '?session=' + session, 'chart_epoch', 'Epochs', false)
}
@ -134,14 +144,15 @@ TEMPLATE = """
{% endblock %}
{% block content %}
<select id="session" style="width:100%;">
<select id="session">
<option selected>Current</option>
</select>
<div id="chart_os" style="height:400px;width:100%; "></div>
<div id="chart_temp" style="height:400px;width:100%; "></div>
<div id="chart_nums" style="height:400px;width:100%; "></div>
<div id="chart_duration" style="height:400px;width:100%; "></div>
<div id="chart_epoch" style="height:400px;width:100%; "></div>
<div id="chart_os" class="chart"></div>
<div id="chart_temp" class="chart"></div>
<div id="chart_wifi" class="chart"></div>
<div id="chart_duration" class="chart"></div>
<div id="chart_reward" class="chart"></div>
<div id="chart_epoch" class="chart"></div>
{% endblock %}
"""
@ -189,9 +200,6 @@ class SessionStats(plugins.Plugin):
data_format='json')
logging.info("Session-stats plugin loaded.")
def on_unloaded(self, ui):
pass
def on_epoch(self, agent, epoch, epoch_data):
"""
Save the epoch_data to self.stats
@ -220,7 +228,7 @@ class SessionStats(plugins.Plugin):
extract_keys = ['cpu_load','mem_usage',]
elif path == "temp":
extract_keys = ['temperature']
elif path == "nums":
elif path == "wifi":
extract_keys = [
'missed_interactions',
'num_hops',
@ -236,10 +244,13 @@ class SessionStats(plugins.Plugin):
'duration_secs',
'slept_for_secs',
]
elif path == "reward":
extract_keys = [
'reward',
]
elif path == "epoch":
extract_keys = [
'active_for_epochs',
'blind_for_epochs',
]
elif path == "session":
return jsonify({'files': os.listdir(self.options['save_directory'])})

View File

@ -139,7 +139,7 @@ class Switcher(plugins.Plugin):
'bored', 'sad', 'excited', 'lonely', 'rebooting', 'wait',
'sleep', 'wifi_update', 'unfiltered_ap_list', 'association',
'deauthentication', 'channel_hop', 'handshake', 'epoch',
'peer_detected', 'peer_lost']
'peer_detected', 'peer_lost', 'config_changed']
for m in methods:
setattr(Switcher, 'on_%s' % m, partial(self.trigger, m))

View File

@ -8,174 +8,183 @@ from flask import abort
from flask import render_template_string
INDEX = """
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=0" />
<title>
webcfg
</title>
<style>
#divTop {
position: -webkit-sticky;
position: sticky;
top: 0px;
width: 100%;
font-size: 16px;
padding: 5px;
border: 1px solid #ddd;
margin-bottom: 5px;
}
{% extends "base.html" %}
{% set active_page = "plugins" %}
{% block title %}
Webcfg
{% endblock %}
#searchText {
width: 100%;
}
{% block meta %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=0" />
{% endblock %}
table {
table-layout: auto;
width: 100%;
}
{% block styles %}
{{ super() }}
<style>
#divTop {
position: -webkit-sticky;
position: sticky;
top: 0px;
width: 100%;
font-size: 16px;
padding: 5px;
border: 1px solid #ddd;
margin-bottom: 5px;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
#searchText {
width: 100%;
}
th, td {
padding: 15px;
text-align: left;
}
table {
table-layout: auto;
width: 100%;
}
table tr:nth-child(even) {
background-color: #eee;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
table tr:nth-child(odd) {
background-color: #fff;
}
th, td {
padding: 15px;
text-align: left;
}
table th {
background-color: black;
color: white;
}
table tr:nth-child(even) {
background-color: #eee;
}
.remove {
background-color: #f44336;
color: white;
border: 2px solid #f44336;
padding: 4px 8px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 12px;
margin: 4px 2px;
-webkit-transition-duration: 0.4s; /* Safari */
transition-duration: 0.4s;
cursor: pointer;
}
table tr:nth-child(odd) {
background-color: #fff;
}
.remove:hover {
background-color: white;
color: black;
}
table th {
background-color: black;
color: white;
}
#btnSave {
position: -webkit-sticky;
position: sticky;
bottom: 0px;
width: 100%;
background-color: #0061b0;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
cursor: pointer;
float: right;
}
.remove {
background-color: #f44336;
color: white;
border: 2px solid #f44336;
padding: 4px 8px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 12px;
margin: 4px 2px;
-webkit-transition-duration: 0.4s; /* Safari */
transition-duration: 0.4s;
cursor: pointer;
}
#divTop {
display: table;
width: 100%;
}
#divTop > * {
display: table-cell;
}
#divTop > span {
width: 1%;
}
#divTop > input {
width: 100%;
}
.remove:hover {
background-color: white;
color: black;
}
@media screen and (max-width:700px) {
table, tr, td {
padding:0;
border:1px solid black;
}
#btnSave {
position: -webkit-sticky;
position: sticky;
bottom: 0px;
width: 100%;
background-color: #0061b0;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
cursor: pointer;
float: right;
}
table {
border:none;
}
#divTop {
display: table;
width: 100%;
}
#divTop > * {
display: table-cell;
}
#divTop > span {
width: 1%;
}
#divTop > input {
width: 100%;
}
tr:first-child, thead, th {
display:none;
border:none;
}
@media screen and (max-width:700px) {
table, tr, td {
padding:0;
border:1px solid black;
}
tr {
float: left;
width: 100%;
margin-bottom: 2em;
}
table {
border:none;
}
table tr:nth-child(odd) {
background-color: #eee;
}
tr:first-child, thead, th {
display:none;
border:none;
}
td {
float: left;
width: 100%;
padding:1em;
}
tr {
float: left;
width: 100%;
margin-bottom: 2em;
}
td::before {
content:attr(data-label);
word-wrap: break-word;
background: #eee;
border-right:2px solid black;
width: 20%;
float:left;
padding:1em;
font-weight: bold;
margin:-1em 1em -1em -1em;
}
table tr:nth-child(odd) {
background-color: #eee;
}
.del_btn_wrapper {
content:attr(data-label);
word-wrap: break-word;
background: #eee;
border-right:2px solid black;
width: 20%;
float:left;
padding:1em;
font-weight: bold;
margin:-1em 1em -1em -1em;
}
}
</style>
</head>
<body>
<div id="divTop">
<input type="text" id="searchText" onkeyup="filterTable()" placeholder="Search for options ..." title="Type an option name">
<span><select id="selAddType"><option value="text">Text</option><option value="number">Number</option></select></span>
<span><button id="btnAdd" type="button" onclick="addOption()">+</button></span>
</div>
<div id="content"></div>
<button id="btnSave" type="button" onclick="saveConfig()">Save</button>
</body>
<script type="text/javascript">
td {
float: left;
width: 100%;
padding:1em;
}
td::before {
content:attr(data-label);
word-wrap: break-word;
background: #eee;
border-right:2px solid black;
width: 20%;
float:left;
padding:1em;
font-weight: bold;
margin:-1em 1em -1em -1em;
}
.del_btn_wrapper {
content:attr(data-label);
word-wrap: break-word;
background: #eee;
border-right:2px solid black;
width: 20%;
float:left;
padding:1em;
font-weight: bold;
margin:-1em 1em -1em -1em;
}
}
</style>
{% endblock %}
{% block content %}
<div id="divTop">
<input type="text" id="searchText" placeholder="Search for options ..." title="Type an option name">
<span><select id="selAddType"><option value="text">Text</option><option value="number">Number</option></select></span>
<span><button id="btnAdd" type="button" onclick="addOption()">+</button></span>
</div>
<button id="btnSave" type="button" onclick="saveConfig()">Save and restart</button>
<div id="content"></div>
{% endblock %}
{% block script %}
function addOption() {
var input, table, tr, td, divDelBtn, btnDel, selType, selTypeVal;
input = document.getElementById("searchText");
@ -231,11 +240,10 @@ INDEX = """
});
}
}
function filterTable(){
var input, filter, table, tr, td, i, txtValue;
input = document.getElementById("searchText");
filter = input.value.toUpperCase();
var searchInput = document.getElementById("searchText");
searchInput.onkeyup = function() {
var filter, table, tr, td, i, txtValue;
filter = searchInput.value.toUpperCase();
table = document.getElementById("tableOptions");
if (table) {
tr = table.getElementsByTagName("tr");
@ -446,8 +454,7 @@ INDEX = """
divContent.innerHTML = "";
divContent.appendChild(table);
});
</script>
</html>
{% endblock %}
"""
def serializer(obj):
@ -463,16 +470,17 @@ class WebConfig(plugins.Plugin):
def __init__(self):
self.ready = False
self.mode = 'MANU'
def on_config_changed(self, config):
self.config = config
self.ready = True
def on_ready(self, agent):
self.config = agent.config()
self.mode = "MANU" if agent.mode == "manual" else "AUTO"
self.ready = True
self.mode = 'MANU' if agent.mode == 'manual' else 'AUTO'
def on_internet_available(self, agent):
self.config = agent.config()
self.mode = "MANU" if agent.mode == "manual" else "AUTO"
self.ready = True
self.mode = 'MANU' if agent.mode == 'manual' else 'AUTO'
def on_loaded(self):
"""

View File

@ -10,12 +10,12 @@ from dateutil.parser import parse
'''
webgpsmap shows existing position data stored in your /handshakes/ directory
the plugin does the following:
- search for *.pcap files in your /handshakes/ dir
- for every found .pcap file it looks for a .geo.json or .gps.json or .paw-gps.json file with
latitude+longitude data inside and shows this position on the map
- if also an .cracked file with a plaintext password inside exist, it reads the content and shows the
- if also an .cracked file with a plaintext password inside exist, it reads the content and shows the
position as green instead of red and the password inside the infopox of the position
special:
you can save the html-map as one file for offline use or host on your own webspace with "/plugins/webgpsmap/offlinemap"
@ -35,8 +35,8 @@ class Webgpsmap(plugins.Plugin):
def __init__(self):
self.ready = False
def on_ready(self, agent):
self.config = agent.config()
def on_config_changed(self, config):
self.config = config
self.ready = True
def on_loaded(self):

View File

@ -1,12 +1,14 @@
import os
import logging
import json
from io import StringIO
import csv
from datetime import datetime
import requests
from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile
import pwnagotchi.plugins as plugins
from io import StringIO
from datetime import datetime
from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile, remove_whitelisted
from threading import Lock
from pwnagotchi import plugins
def _extract_gps_data(path):
@ -100,90 +102,90 @@ class Wigle(plugins.Plugin):
self.ready = False
self.report = StatusFile('/root/.wigle_uploads', data_format='json')
self.skip = list()
self.lock = Lock()
def on_loaded(self):
if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net")
return
if not 'whitelist' in self.options:
self.options['whitelist'] = list()
self.ready = True
def on_internet_available(self, agent):
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())
if not self.ready or self.lock.locked():
return
handshake_dir = config['bettercap']['handshakes']
all_files = os.listdir(handshake_dir)
all_gps_files = [os.path.join(handshake_dir, filename)
for filename in all_files
if filename.endswith('.gps.json')]
new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
from scapy.all import Scapy_Exception
if new_gps_files:
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
config = agent.config()
display = agent.view()
reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
all_files = os.listdir(handshake_dir)
all_gps_files = [os.path.join(handshake_dir, filename)
for filename in all_files
if filename.endswith('.gps.json')]
csv_entries = list()
no_err_entries = list()
for gps_file in new_gps_files:
pcap_filename = gps_file.replace('.gps.json', '.pcap')
if not os.path.exists(pcap_filename):
logging.error("WIGLE: Can't find pcap for %s", gps_file)
self.skip.append(gps_file)
continue
try:
gps_data = _extract_gps_data(gps_file)
except OSError as os_err:
logging.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
if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
self.skip.append(gps_file)
continue
try:
pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID,
WifiInfo.ESSID,
WifiInfo.ENCRYPTION,
WifiInfo.CHANNEL,
WifiInfo.RSSI])
except FieldNotFoundError:
logging.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
new_entry = _transform_wigle_entry(gps_data, pcap_data)
csv_entries.append(new_entry)
no_err_entries.append(gps_file)
if csv_entries:
display.set('status', "Uploading gps-data to wigle.net ...")
display.update(force=True)
try:
_send_to_wigle(csv_entries, self.options['api_key'])
reported += no_err_entries
self.report.update(data={'reported': reported})
logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries))
except requests.exceptions.RequestException as re_e:
self.skip += no_err_entries
logging.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)
all_gps_files = remove_whitelisted(all_gps_files, self.options['whitelist'])
new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
if new_gps_files:
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
csv_entries = list()
no_err_entries = list()
for gps_file in new_gps_files:
pcap_filename = gps_file.replace('.gps.json', '.pcap')
if not os.path.exists(pcap_filename):
logging.error("WIGLE: Can't find pcap for %s", gps_file)
self.skip.append(gps_file)
continue
try:
gps_data = _extract_gps_data(gps_file)
except OSError as os_err:
logging.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
if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
self.skip.append(gps_file)
continue
try:
pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID,
WifiInfo.ESSID,
WifiInfo.ENCRYPTION,
WifiInfo.CHANNEL,
WifiInfo.RSSI])
except FieldNotFoundError:
logging.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
new_entry = _transform_wigle_entry(gps_data, pcap_data)
csv_entries.append(new_entry)
no_err_entries.append(gps_file)
if csv_entries:
display.set('status', "Uploading gps-data to wigle.net ...")
display.update(force=True)
try:
_send_to_wigle(csv_entries, self.options['api_key'])
reported += no_err_entries
self.report.update(data={'reported': reported})
logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries))
except requests.exceptions.RequestException as re_e:
self.skip += no_err_entries
logging.error("WIGLE: Got an exception while uploading %s", re_e)
except OSError as os_e:
self.skip += no_err_entries
logging.error("WIGLE: Got the following error: %s", os_e)

View File

@ -3,7 +3,7 @@ import logging
import requests
from datetime import datetime
from threading import Lock
from pwnagotchi.utils import StatusFile
from pwnagotchi.utils import StatusFile, remove_whitelisted
from pwnagotchi import plugins
from json.decoder import JSONDecodeError
@ -19,7 +19,7 @@ class WpaSec(plugins.Plugin):
self.lock = Lock()
try:
self.report = StatusFile('/root/.wpa_sec_uploads', data_format='json')
except JSONDecodeError as json_err:
except JSONDecodeError:
os.remove("/root/.wpa_sec_uploads")
self.report = StatusFile('/root/.wpa_sec_uploads', data_format='json')
self.options = dict()
@ -78,54 +78,57 @@ class WpaSec(plugins.Plugin):
logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
return
if 'whitelist' not in self.options:
self.options['whitelist'] = list()
self.ready = True
def on_internet_available(self, agent):
"""
Called in manual mode when there's internet connectivity
"""
if not self.ready or self.lock.locked():
return
with self.lock:
if self.ready:
config = agent.config()
display = agent.view()
reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
filename.endswith('.pcap')]
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
if handshake_new:
logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org")
for idx, handshake in enumerate(handshake_new):
display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})")
display.update(force=True)
try:
self._upload_to_wpasec(handshake)
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.info("WPA_SEC: Successfully uploaded %s", handshake)
except requests.exceptions.RequestException as req_e:
self.skip.append(handshake)
logging.error("WPA_SEC: %s", req_e)
continue
except OSError as os_e:
logging.error("WPA_SEC: %s", os_e)
continue
if 'download_results' in self.options and self.options['download_results']:
cracked_file = os.path.join(handshake_dir, 'wpa-sec.cracked.potfile')
if os.path.exists(cracked_file):
last_check = datetime.fromtimestamp(os.path.getmtime(cracked_file))
if last_check is not None and ((datetime.now() - last_check).seconds / (60 * 60)) < 1:
return
config = agent.config()
display = agent.view()
reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
filename.endswith('.pcap')]
handshake_paths = remove_whitelisted(handshake_paths, self.options['whitelist'])
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
if handshake_new:
logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org")
for idx, handshake in enumerate(handshake_new):
display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})")
display.update(force=True)
try:
self._download_from_wpasec(os.path.join(handshake_dir, 'wpa-sec.cracked.potfile'))
logging.info("WPA_SEC: Downloaded cracked passwords.")
self._upload_to_wpasec(handshake)
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.info("WPA_SEC: Successfully uploaded %s", handshake)
except requests.exceptions.RequestException as req_e:
logging.debug("WPA_SEC: %s", req_e)
self.skip.append(handshake)
logging.error("WPA_SEC: %s", req_e)
continue
except OSError as os_e:
logging.debug("WPA_SEC: %s", os_e)
logging.error("WPA_SEC: %s", os_e)
continue
if 'download_results' in self.options and self.options['download_results']:
cracked_file = os.path.join(handshake_dir, 'wpa-sec.cracked.potfile')
if os.path.exists(cracked_file):
last_check = datetime.fromtimestamp(os.path.getmtime(cracked_file))
if last_check is not None and ((datetime.now() - last_check).seconds / (60 * 60)) < 1:
return
try:
self._download_from_wpasec(os.path.join(handshake_dir, 'wpa-sec.cracked.potfile'))
logging.info("WPA_SEC: Downloaded cracked passwords.")
except requests.exceptions.RequestException as req_e:
logging.debug("WPA_SEC: %s", req_e)
except OSError as os_e:
logging.debug("WPA_SEC: %s", os_e)