Merge branch 'master' into plugin.on-callback

bring the current branch up to evilsocket/pwnagotchi
Signed Off: Casey Diemel <diemelcw@gmail.com>
This commit is contained in:
Casey Diemel 2019-10-13 13:45:11 -04:00
commit d648f7cdf5
16 changed files with 649 additions and 371 deletions

View File

@ -2,10 +2,10 @@
if __name__ == '__main__':
import argparse
import time
import os
import logging
import pwnagotchi
import pwnagotchi.grid as grid
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
@ -64,7 +64,7 @@ if __name__ == '__main__':
display.on_manual_mode(agent.last_session)
time.sleep(1)
if Agent.is_connected():
if grid.is_connected():
plugins.on('internet_available', agent)
else:
@ -102,7 +102,7 @@ if __name__ == '__main__':
# affect ours ... neat ^_^
agent.next_epoch()
if Agent.is_connected():
if grid.is_connected():
plugins.on('internet_available', agent)
except Exception as e:

View File

@ -34,7 +34,7 @@
url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip"
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
pwngrid:
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.6.3/pwngrid_linux_armv6l_v1.6.3.zip"
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.3/pwngrid_linux_armv6l_1.7.3.zip"
apt:
hold:
- firmware-atheros
@ -497,12 +497,12 @@
Description=pwngrid peer service.
Documentation=https://pwnagotchi.ai
Wants=network.target
After=network.target
After=bettercap.service
[Service]
Type=simple
PermissionsStartOnly=true
ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait -log /var/log/pwngrid-peer.log
ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait -log /var/log/pwngrid-peer.log -iface mon0
Restart=always
RestartSec=30

View File

@ -4,7 +4,7 @@ import logging
import time
import pwnagotchi.ui.view as view
version = '1.0.0RC2'
version = '1.0.0RC4'
_name = None
@ -17,6 +17,11 @@ def name():
return _name
def uptime():
with open('/proc/uptime') as fp:
return int(fp.read().split('.')[0])
def mem_usage():
out = subprocess.getoutput("free -m")
for line in out.split("\n"):

View File

@ -2,11 +2,10 @@ import time
import json
import os
import re
import socket
from datetime import datetime
import logging
import _thread
import pwnagotchi
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
from pwnagotchi.log import LastSession
@ -41,15 +40,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
if not os.path.exists(config['bettercap']['handshakes']):
os.makedirs(config['bettercap']['handshakes'])
@staticmethod
def is_connected():
try:
socket.create_connection(("www.google.com", 80))
return True
except OSError:
pass
return False
def config(self):
return self._config
@ -192,7 +182,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
def set_access_points(self, aps):
self._access_points = aps
plugins.on('wifi_update', self, aps)
self._epoch.observe(aps, self._advertiser.peers() if self._advertiser is not None else ())
self._epoch.observe(aps, list(self._peers.values()))
return self._access_points
def get_access_points(self):
@ -241,9 +231,9 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
return None
def _update_uptime(self, s):
secs = time.time() - self._started_at
secs = pwnagotchi.uptime()
self._view.set('uptime', utils.secs_to_hhmmss(secs))
self._view.set('epoch', '%04d' % self._epoch.epoch)
# self._view.set('epoch', '%04d' % self._epoch.epoch)
def _update_counters(self):
tot_aps = len(self._access_points)
@ -273,22 +263,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
if new_shakes > 0:
self._view.on_handshakes(new_shakes)
def _update_advertisement(self, s):
run_handshakes = len(self._handshakes)
tot_handshakes = utils.total_unique_handshakes(self._config['bettercap']['handshakes'])
started = s['started_at'].split('.')[0]
started = datetime.strptime(started, '%Y-%m-%dT%H:%M:%S')
started = time.mktime(started.timetuple())
self._advertiser.update({ \
'pwnd_run': run_handshakes,
'pwnd_tot': tot_handshakes,
'uptime': time.time() - started,
'epoch': self._epoch.epoch})
def _update_peers(self):
peer = self._advertiser.closest_peer()
tot = self._advertiser.num_peers()
self._view.set_closest_peer(peer, tot)
self._view.set_closest_peer(self._closest_peer, len(self._peers))
def _save_recovery_data(self):
logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
@ -333,10 +309,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
s = self.session()
self._update_uptime(s)
if self._advertiser is not None:
self._update_advertisement(s)
self._update_peers()
self._update_advertisement(s)
self._update_peers()
self._update_counters()
try:

View File

@ -27,6 +27,7 @@ main:
files:
- /root/brain.nn
- /root/brain.json
- /root/.api-report.json
- /root/handshakes/
- /etc/pwnagotchi/
- /etc/hostname
@ -66,6 +67,12 @@ main:
wordlist_folder: /opt/wordlists/
AircrackOnly:
enabled: false
bt-tether:
enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0
mac: ~ # mac of your bluetooth device
ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
netmask: 24
interval: 1 # check every x minutes for device
# monitor interface to use
iface: mon0
# command to run to bring the mon interface up in case it's not up already
@ -167,7 +174,7 @@ ui:
color: 'black'
video:
enabled: true
address: '10.0.0.2'
address: '0.0.0.0'
port: 8080

97
pwnagotchi/grid.py Normal file
View File

@ -0,0 +1,97 @@
import subprocess
import socket
import requests
import json
import logging
import pwnagotchi
# pwngrid-peer is running on port 8666
API_ADDRESS = "http://127.0.0.1:8666/api/v1"
def is_connected():
try:
socket.create_connection(("www.google.com", 80))
return True
except OSError:
pass
return False
def call(path, obj=None):
url = '%s%s' % (API_ADDRESS, path)
if obj is None:
r = requests.get(url, headers=None)
else:
r = requests.post(url, headers=None, json=obj)
if r.status_code != 200:
raise Exception("(status %d) %s" % (r.status_code, r.text))
return r.json()
def advertise(enabled=True):
return call("/mesh/%s" % 'true' if enabled else 'false')
def set_advertisement_data(data):
return call("/mesh/data", obj=data)
def peers():
return call("/mesh/peers")
def closest_peer():
all = peers()
return all[0] if len(all) else None
def update_data(last_session):
brain = {}
try:
with open('/root/brain.json') as fp:
brain = json.load(fp)
except:
pass
data = {
'session': {
'duration': last_session.duration,
'epochs': last_session.epochs,
'train_epochs': last_session.train_epochs,
'avg_reward': last_session.avg_reward,
'min_reward': last_session.min_reward,
'max_reward': last_session.max_reward,
'deauthed': last_session.deauthed,
'associated': last_session.associated,
'handshakes': last_session.handshakes,
'peers': last_session.peers,
},
'uname': subprocess.getoutput("uname -a"),
'brain': brain,
'version': pwnagotchi.version
}
logging.debug("updating grid data: %s" % data)
call("/data", data)
def report_ap(essid, bssid):
try:
call("/report/ap", {
'essid': essid,
'bssid': bssid,
})
return True
except Exception as e:
logging.exception("error while reporting ap %s(%s)" % (essid, bssid))
return False
def inbox(page=1, with_pager=False):
obj = call("/inbox?p=%d" % page)
return obj["messages"] if not with_pager else obj

View File

@ -47,7 +47,7 @@ class KeyPair(object):
self.fingerprint = hashlib.sha256(pem_ascii).hexdigest()
# no exception, keys loaded correctly.
self._view.on_normal()
self._view.on_starting()
return
except Exception as e:

View File

@ -129,10 +129,15 @@ class LastSession(object):
if m:
name, pubkey, rssi, sid, pwnd_tot, uptime = m[0]
if pubkey not in cache:
self.last_peer = Peer(sid, 1, int(rssi),
{'name': name,
'identity': pubkey,
'pwnd_tot': int(pwnd_tot)})
self.last_peer = Peer({
'session_id': sid,
'channel': 1,
'rssi': int(rssi),
'identity': pubkey,
'advertisement':{
'name': name,
'pwnd_tot': int(pwnd_tot)
}})
self.peers += 1
cache[pubkey] = self.last_peer
else:

View File

@ -1,4 +0,0 @@
import os
def new_session_id():
return ':'.join(['%02x' % b for b in os.urandom(6)])

View File

@ -1,176 +0,0 @@
import time
import json
import _thread
import threading
import logging
from scapy.all import Dot11, Dot11Elt, RadioTap, sendp, sniff
import pwnagotchi.ui.faces as faces
import pwnagotchi.mesh.wifi as wifi
from pwnagotchi.mesh import new_session_id
from pwnagotchi.mesh.peer import Peer
def _dummy_peer_cb(peer):
pass
class Advertiser(object):
MAX_STALE_TIME = 300
def __init__(self, iface, name, version, identity, period=0.3, data={}):
self._iface = iface
self._period = period
self._running = False
self._stopped = threading.Event()
self._peers_lock = threading.Lock()
self._adv_lock = threading.Lock()
self._new_peer_cb = _dummy_peer_cb
self._lost_peer_cb = _dummy_peer_cb
self._peers = {}
self._frame = None
self._me = Peer(new_session_id(), 0, 0, {
'name': name,
'version': version,
'identity': identity,
'face': faces.FRIEND,
'pwnd_run': 0,
'pwnd_tot': 0,
'uptime': 0,
'epoch': 0,
'data': data
})
self.update()
def update(self, values={}):
with self._adv_lock:
for field, value in values.items():
self._me.adv[field] = value
self._frame = wifi.encapsulate(payload=json.dumps(self._me.adv), addr_from=self._me.session_id)
def on_peer(self, new_cb, lost_cb):
self._new_peer_cb = new_cb
self._lost_peer_cb = lost_cb
def on_face_change(self, old, new):
self.update({'face': new})
def start(self):
self._running = True
_thread.start_new_thread(self._sender, ())
_thread.start_new_thread(self._listener, ())
_thread.start_new_thread(self._pruner, ())
def num_peers(self):
with self._peers_lock:
return len(self._peers)
def peers(self):
with self._peers_lock:
return list(self._peers.values())
def closest_peer(self):
closest = None
with self._peers_lock:
for ident, peer in self._peers.items():
if closest is None or peer.is_closer(closest):
closest = peer
return closest
def stop(self):
self._running = False
self._stopped.set()
def _sender(self):
logging.info("started advertiser thread (period:%s sid:%s) ..." % (str(self._period), self._me.session_id))
while self._running:
try:
sendp(self._frame, iface=self._iface, verbose=False, count=1, inter=self._period)
except OSError as ose:
logging.warning("non critical issue while sending advertising packet: %s" % ose)
except Exception as e:
logging.exception("error")
time.sleep(self._period)
def _on_advertisement(self, src_session_id, channel, rssi, adv):
ident = adv['identity']
with self._peers_lock:
if ident not in self._peers:
peer = Peer(src_session_id, channel, rssi, adv)
logging.info("detected unit %s (v%s) on channel %d (%s dBm) [sid:%s pwnd_tot:%d uptime:%d]" % ( \
peer.full_name(),
peer.version(),
channel,
rssi,
src_session_id,
peer.pwnd_total(),
peer.uptime()))
self._peers[ident] = peer
self._new_peer_cb(peer)
else:
self._peers[ident].update(src_session_id, channel, rssi, adv)
def _parse_identity(self, radio, dot11, dot11elt):
payload = b''
while dot11elt:
payload += dot11elt.info
dot11elt = dot11elt.payload.getlayer(Dot11Elt)
if payload != b'':
adv = json.loads(payload)
self._on_advertisement( \
dot11.addr3,
wifi.freq_to_channel(radio.Channel),
radio.dBm_AntSignal,
adv)
def _is_broadcasted_advertisement(self, dot11):
# dst bcast + protocol signature + not ours
return dot11 is not None and \
dot11.addr1 == wifi.BroadcastAddress and \
dot11.addr2 == wifi.SignatureAddress and \
dot11.addr3 != self._me.session_id
def _is_frame_for_us(self, dot11):
# dst is us + protocol signature + not ours (why would we send a frame to ourself anyway?)
return dot11 is not None and \
dot11.addr1 == self._me.session_id and \
dot11.addr2 == wifi.SignatureAddress and \
dot11.addr3 != self._me.session_id
def _on_packet(self, p):
dot11 = p.getlayer(Dot11)
if self._is_broadcasted_advertisement(dot11):
try:
dot11elt = p.getlayer(Dot11Elt)
if dot11elt.ID == wifi.Dot11ElemID_Whisper:
self._parse_identity(p[RadioTap], dot11, dot11elt)
else:
raise Exception("unknown frame id %d" % dot11elt.ID)
except Exception as e:
logging.exception("error decoding packet from %s" % dot11.addr3)
def _listener(self):
# logging.info("started advertisements listener ...")
expr = "type mgt subtype beacon and ether src %s" % wifi.SignatureAddress
sniff(iface=self._iface, filter=expr, prn=self._on_packet, store=0, stop_filter=lambda x: self._stopped.isSet())
def _pruner(self):
while self._running:
time.sleep(10)
with self._peers_lock:
stale = []
for ident, peer in self._peers.items():
inactive_for = peer.inactive_for()
if inactive_for >= Advertiser.MAX_STALE_TIME:
logging.info("peer %s lost (inactive for %ds)" % (peer.full_name(), inactive_for))
self._lost_peer_cb(peer)
stale.append(ident)
for ident in stale:
del self._peers[ident]

View File

@ -1,32 +1,28 @@
import time
import logging
import pwnagotchi.mesh.wifi as wifi
import pwnagotchi.ui.faces as faces
class Peer(object):
def __init__(self, sid, channel, rssi, adv):
def __init__(self, obj):
self.first_seen = time.time()
self.last_seen = self.first_seen
self.session_id = sid
self.last_channel = channel
self.presence = [0] * wifi.NumChannels
self.adv = adv
self.rssi = rssi
self.presence[channel - 1] = 1
self.session_id = obj['session_id']
self.last_channel = obj['channel']
self.rssi = obj['rssi']
self.adv = obj['advertisement']
def update(self, sid, channel, rssi, adv):
if self.name() != adv['name']:
logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), adv['name']))
def update(self, new):
if self.name() != new.name():
logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), new.name()))
if self.session_id != sid:
logging.info("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, sid))
if self.session_id != new.session_id:
logging.info("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, new.session_id))
self.presence[channel - 1] += 1
self.adv = adv
self.rssi = rssi
self.session_id = sid
self.adv = new.adv
self.rssi = new.rssi
self.session_id = new.session_id
self.last_seen = time.time()
def inactive_for(self):

View File

@ -1,8 +1,13 @@
import _thread
import logging
import time
import pwnagotchi
import pwnagotchi.utils as utils
import pwnagotchi.ui.faces as faces
import pwnagotchi.plugins as plugins
import pwnagotchi.grid as grid
from pwnagotchi.mesh.peer import Peer
class AsyncAdvertiser(object):
@ -10,38 +15,78 @@ class AsyncAdvertiser(object):
self._config = config
self._view = view
self._keypair = keypair
self._advertiser = None
self._advertisement = {
'name': pwnagotchi.name(),
'version': pwnagotchi.version,
'identity': self._keypair.fingerprint,
'face': faces.FRIEND,
'pwnd_run': 0,
'pwnd_tot': 0,
'uptime': 0,
'epoch': 0,
'policy': self._config['personality']
}
self._peers = {}
self._closest_peer = None
def keypair(self):
return self._keypair
def _update_advertisement(self, s):
self._advertisement['pwnd_run'] = len(self._handshakes)
self._advertisement['pwnd_tot'] = utils.total_unique_handshakes(self._config['bettercap']['handshakes'])
self._advertisement['uptime'] = pwnagotchi.uptime()
self._advertisement['epoch'] = self._epoch.epoch
grid.set_advertisement_data(self._advertisement)
def start_advertising(self):
_thread.start_new_thread(self._adv_worker, ())
def _adv_worker(self):
# this will take some time due to scapy being slow to be imported ...
from pwnagotchi.mesh.advertise import Advertiser
self._advertiser = Advertiser(
self._config['main']['iface'],
pwnagotchi.name(),
pwnagotchi.version,
self._keypair.fingerprint,
period=0.3,
data=self._config['personality'])
self._advertiser.on_peer(self._on_new_unit, self._on_lost_unit)
if self._config['personality']['advertise']:
self._advertiser.start()
self._view.on_state_change('face', self._advertiser.on_face_change)
_thread.start_new_thread(self._adv_poller, ())
grid.set_advertisement_data(self._advertisement)
grid.advertise(True)
self._view.on_state_change('face', self._on_face_change)
else:
logging.warning("advertising is disabled")
def _on_new_unit(self, peer):
self._view.on_new_peer(peer)
plugins.on('peer_detected', self, peer)
def _on_face_change(self, old, new):
self._advertisement['face'] = new
grid.set_advertisement_data(self._advertisement)
def _on_lost_unit(self, peer):
self._view.on_lost_peer(peer)
plugins.on('peer_lost', self, peer)
def _adv_poller(self):
while True:
logging.debug("polling pwngrid-peer for peers ...")
try:
grid_peers = grid.peers()
new_peers = {}
self._closest_peer = None
for obj in grid_peers:
peer = Peer(obj)
new_peers[peer.identity()] = peer
if self._closest_peer is None:
self._closest_peer = peer
# check who's gone
to_delete = []
for ident, peer in self._peers.items():
if ident not in new_peers:
self._view.on_lost_peer(peer)
plugins.on('peer_lost', self, peer)
to_delete.append(ident)
for ident in to_delete:
del self._peers[ident]
for ident, peer in new_peers.items():
# check who's new
if ident not in self._peers:
self._peers[ident] = peer
self._view.on_new_peer(peer)
plugins.on('peer_detected', self, peer)
# update the rest
else:
self._peers[ident].update(peer)
except Exception as e:
logging.exception("error while polling pwngrid-peer")
time.sleep(1)

View File

@ -1,8 +1,6 @@
SignatureAddress = 'de:ad:be:ef:de:ad'
BroadcastAddress = 'ff:ff:ff:ff:ff:ff'
Dot11ElemID_Whisper = 222
NumChannels = 140
def freq_to_channel(freq):
if freq <= 2472:
return int(((freq - 2412) / 5) + 1)
@ -12,26 +10,3 @@ def freq_to_channel(freq):
return int(((freq - 5035) / 5) + 7)
else:
return 0
def encapsulate(payload, addr_from, addr_to=BroadcastAddress):
from scapy.all import Dot11, Dot11Beacon, Dot11Elt, RadioTap
radio = RadioTap()
dot11 = Dot11(type=0, subtype=8, addr1=addr_to, addr2=SignatureAddress, addr3=addr_from)
beacon = Dot11Beacon(cap='ESS')
frame = radio / dot11 / beacon
data_size = len(payload)
data_left = data_size
data_off = 0
chunk_size = 255
while data_left > 0:
sz = min(chunk_size, data_left)
chunk = payload[data_off: data_off + sz]
frame /= Dot11Elt(ID=Dot11ElemID_Whisper, info=chunk, len=sz)
data_off += sz
data_left -= sz
return frame

View File

@ -0,0 +1,408 @@
__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 time
import re
import logging
import subprocess
import dbus
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.utils import StatusFile
READY = False
INTERVAL = StatusFile('/root/.bt-tether')
OPTIONS = dict()
class BTError(Exception):
"""
Custom bluetooth exception
"""
pass
class BTNap:
"""
This class creates a bluetooth connection to the specified bt-mac
see https://github.com/bablokb/pi-btnap/blob/master/files/usr/local/sbin/btnap.service.py
"""
IFACE_BASE = 'org.bluez'
IFACE_DEV = 'org.bluez.Device1'
IFACE_ADAPTER = 'org.bluez.Adapter1'
IFACE_PROPS = 'org.freedesktop.DBus.Properties'
def __init__(self, mac):
self._mac = mac
@staticmethod
def get_bus():
"""
Get systembus obj
"""
bus = getattr(BTNap.get_bus, 'cached_obj', None)
if not bus:
bus = BTNap.get_bus.cached_obj = dbus.SystemBus()
return bus
@staticmethod
def get_manager():
"""
Get manager obj
"""
manager = getattr(BTNap.get_manager, 'cached_obj', None)
if not manager:
manager = BTNap.get_manager.cached_obj = dbus.Interface(
BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'),
'org.freedesktop.DBus.ObjectManager' )
return manager
@staticmethod
def prop_get(obj, k, iface=None):
"""
Get a property of the obj
"""
if iface is None:
iface = obj.dbus_interface
return obj.Get(iface, k, dbus_interface=BTNap.IFACE_PROPS)
@staticmethod
def prop_set(obj, k, v, iface=None):
"""
Set a property of the obj
"""
if iface is None:
iface = obj.dbus_interface
return obj.Set(iface, k, v, dbus_interface=BTNap.IFACE_PROPS)
@staticmethod
def find_adapter(pattern=None):
"""
Find the bt adapter
"""
return BTNap.find_adapter_in_objects(BTNap.get_manager().GetManagedObjects(), pattern)
@staticmethod
def find_adapter_in_objects(objects, pattern=None):
"""
Finds the obj with a pattern
"""
bus, obj = BTNap.get_bus(), None
for path, ifaces in objects.items():
adapter = ifaces.get(BTNap.IFACE_ADAPTER)
if adapter is None:
continue
if not pattern or pattern == adapter['Address'] or path.endswith(pattern):
obj = bus.get_object(BTNap.IFACE_BASE, path)
yield dbus.Interface(obj, BTNap.IFACE_ADAPTER)
if obj is None:
raise BTError('Bluetooth adapter not found')
@staticmethod
def find_device(device_address, adapter_pattern=None):
"""
Finds the device
"""
return BTNap.find_device_in_objects(BTNap.get_manager().GetManagedObjects(),
device_address, adapter_pattern)
@staticmethod
def find_device_in_objects(objects, device_address, adapter_pattern=None):
"""
Finds the device in objects
"""
bus = BTNap.get_bus()
path_prefix = ''
if adapter_pattern:
if not isinstance(adapter_pattern, str):
adapter = adapter_pattern
else:
adapter = BTNap.find_adapter_in_objects(objects, adapter_pattern)
path_prefix = adapter.object_path
for path, ifaces in objects.items():
device = ifaces.get(BTNap.IFACE_DEV)
if device is None:
continue
if device['Address'] == device_address and path.startswith(path_prefix):
obj = bus.get_object(BTNap.IFACE_BASE, path)
return dbus.Interface(obj, BTNap.IFACE_DEV)
raise BTError('Bluetooth device not found')
def power(self, on=True):
"""
Set power of devices to on/off
"""
devs = list(BTNap.find_adapter())
devs = dict((BTNap.prop_get(dev, 'Address'), dev) for dev in devs)
for dev_addr, dev in devs.items():
BTNap.prop_set(dev, 'Powered', on)
logging.debug('Set power of %s (addr %s) to %s', dev.object_path, dev_addr, str(on))
if devs:
return list(devs.values())[0]
return None
def wait_for_device(self, timeout=30):
"""
Wait for device
returns device if found None if not
"""
bt_dev = self.power(True)
if not bt_dev:
return None
# could be set to 0, so check if > -1
while timeout > -1:
try:
dev_remote = BTNap.find_device(self._mac, bt_dev)
logging.debug('Using remote device (addr: %s): %s',
BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path )
return dev_remote
except BTError:
pass
time.sleep(1)
timeout -= 1
# Device not found :(
return None
def connect(self, reconnect=False):
"""
Connect to device
return True if connected; False if failed
"""
# power up devices
bt_dev = self.power(True)
if not bt_dev:
return False
# check if device is close
dev_remote = self.wait_for_device()
if not dev_remote:
return False
#wait_iter = lambda: time.sleep(3600)
# signal.signal(signal.SIGTERM, lambda sig,frm: sys.exit(0))
try:
dev_remote.ConnectProfile('nap')
except Exception:
pass
net = dbus.Interface(dev_remote, 'org.bluez.Network1')
try:
net.Connect('nap')
except dbus.exceptions.DBusException as err:
if err.get_dbus_name() != 'org.bluez.Error.Failed':
raise
connected = BTNap.prop_get(net, 'Connected')
if not connected:
return False
if reconnect:
net.Disconnect()
return self.connect(reconnect=False)
return True
#################################################
#################################################
#################################################
class SystemdUnitWrapper:
"""
systemd wrapper
"""
def __init__(self, unit):
self.unit = unit
@staticmethod
def _action_on_unit(action, unit):
process = subprocess.Popen(f"systemctl {action} {unit}", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
if process.returncode > 0:
return False
return True
@staticmethod
def daemon_reload():
"""
Calls systemctl daemon-reload
"""
process = subprocess.Popen("systemctl daemon-reload", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
if process.returncode > 0:
return False
return True
def is_active(self):
"""
Checks if unit is active
"""
return SystemdUnitWrapper._action_on_unit('is-active', self.unit)
def is_enabled(self):
"""
Checks if unit is enabled
"""
return SystemdUnitWrapper._action_on_unit('is-enabled', self.unit)
def is_failed(self):
"""
Checks if unit is failed
"""
return SystemdUnitWrapper._action_on_unit('is-failed', self.unit)
def enable(self):
"""
Enables the unit
"""
return SystemdUnitWrapper._action_on_unit('enable', self.unit)
def disable(self):
"""
Disables the unit
"""
return SystemdUnitWrapper._action_on_unit('disable', self.unit)
def start(self):
"""
Starts the unit
"""
return SystemdUnitWrapper._action_on_unit('start', self.unit)
def stop(self):
"""
Stops the unit
"""
return SystemdUnitWrapper._action_on_unit('stop', self.unit)
def restart(self):
"""
Restarts the unit
"""
return SystemdUnitWrapper._action_on_unit('restart', self.unit)
class IfaceWrapper:
"""
Small wrapper to check and manage ifaces
see: https://github.com/rlisagor/pynetlinux/blob/master/pynetlinux/ifconfig.py
"""
def __init__(self, iface):
self.iface = iface
self.path = f"/sys/class/net/{iface}"
def exists(self):
"""
Checks if iface exists
"""
return os.path.exists(self.path)
def is_up(self):
"""
Checks if iface is ip
"""
return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up'
def set_addr(self, addr):
"""
Set the netmask
"""
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")
process.wait()
if process.returncode == 2 or process.returncode == 0: # 2 = already set
return True
return False
def on_loaded():
"""
Gets called when the plugin gets loaded
"""
global READY
global INTERVAL
for opt in ['mac', 'ip', 'netmask', 'interval']:
if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None):
logging.error("BT-TET: Pleace 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
INTERVAL.update()
READY = True
def on_ui_update(ui):
"""
Try to connect to device
"""
if READY:
global INTERVAL
if INTERVAL.newer_then_minutes(OPTIONS['interval']):
return
INTERVAL.update()
bt = BTNap(OPTIONS['mac'])
if bt.connect():
btnap_iface = IfaceWrapper('bnep0')
if btnap_iface.exists():
# check ip
addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}"
if not btnap_iface.set_addr(addr):
ui.set('bluetooth', 'ERR1')
logging.error("Could not set ip of bnep0 to %s", addr)
return
ui.set('bluetooth', 'CON')
else:
ui.set('bluetooth', 'ERR2')
else:
ui.set('bluetooth', 'NF')
def on_ui_setup(ui):
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 30, 0),
label_font=fonts.Bold, text_font=fonts.Medium))

View File

@ -6,11 +6,9 @@ __description__ = 'This plugin signals the unit cryptographic identity and list
import os
import logging
import requests
import glob
import json
import subprocess
import pwnagotchi
import pwnagotchi.grid as grid
import pwnagotchi.utils as utils
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
@ -65,74 +63,13 @@ def is_excluded(what):
return False
def grid_call(path, obj=None):
# pwngrid-peer is running on port 8666
api_address = 'http://127.0.0.1:8666/api/v1%s' % path
if obj is None:
r = requests.get(api_address, headers=None)
else:
r = requests.post(api_address, headers=None, json=obj)
if r.status_code != 200:
raise Exception("(status %d) %s" % (r.status_code, r.text))
return r.json()
def grid_update_data(last_session):
brain = {}
try:
with open('/root/brain.json') as fp:
brain = json.load(fp)
except:
pass
data = {
'session': {
'duration': last_session.duration,
'epochs': last_session.epochs,
'train_epochs': last_session.train_epochs,
'avg_reward': last_session.avg_reward,
'min_reward': last_session.min_reward,
'max_reward': last_session.max_reward,
'deauthed': last_session.deauthed,
'associated': last_session.associated,
'handshakes': last_session.handshakes,
'peers': last_session.peers,
},
'uname': subprocess.getoutput("uname -a"),
'brain': brain,
'version': pwnagotchi.version
}
logging.debug("updating grid data: %s" % data)
grid_call("/data", data)
def grid_report_ap(essid, bssid):
try:
grid_call("/report/ap", {
'essid': essid,
'bssid': bssid,
})
return True
except Exception as e:
logging.exception("error while reporting ap %s(%s)" % (essid, bssid))
return False
def grid_inbox():
return grid_call("/inbox")["messages"]
def on_ui_update(ui):
new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES)
if not ui.has_element('mailbox') and TOTAL_MESSAGES > 0:
if ui.is_inky():
pos=(80, 0)
pos = (80, 0)
else:
pos=(100,0)
pos = (100, 0)
ui.add_element('mailbox',
LabeledValue(color=BLACK, label='MSG', value=new_value,
position=pos,
@ -141,13 +78,19 @@ def on_ui_update(ui):
ui.set('mailbox', new_value)
def set_reported(reported, net_id):
global REPORT
reported.append(net_id)
REPORT.update(data={'reported': reported})
def on_internet_available(agent):
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
logging.debug("internet available")
try:
grid_update_data(agent.last_session)
grid.update_data(agent.last_session)
except Exception as e:
logging.error("error connecting to the pwngrid-peer service: %s" % e)
return
@ -155,13 +98,13 @@ def on_internet_available(agent):
try:
logging.debug("checking mailbox ...")
messages = grid_inbox()
messages = grid.inbox()
TOTAL_MESSAGES = len(messages)
UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None])
if TOTAL_MESSAGES:
on_ui_update(agent.view())
logging.debug( " %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES))
logging.debug(" %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES))
logging.debug("checking pcaps")
@ -181,17 +124,19 @@ def on_internet_available(agent):
net_id = os.path.basename(pcap_file).replace('.pcap', '')
if net_id not in reported:
if is_excluded(net_id):
logging.info("skipping %s due to exclusion filter" % pcap_file)
logging.debug("skipping %s due to exclusion filter" % pcap_file)
set_reported(reported, net_id)
continue
essid, bssid = parse_pcap(pcap_file)
if bssid:
add_as_reported = False
if is_excluded(essid) or is_excluded(bssid):
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
elif grid_report_ap(essid, bssid):
reported.append(net_id)
REPORT.update(data={'reported': reported})
set_reported(reported, net_id)
else:
if grid.report_ap(essid, bssid):
set_reported(reported, net_id)
else:
logging.warning("no bssid found?!")
else:

View File

@ -10,6 +10,7 @@ TEMP_BACKUP_FOLDER=/tmp/pwnagotchi_backup
FILES_TO_BACKUP=(
/root/brain.nn
/root/brain.json
/root/.api-report.json
/root/handshakes
/etc/pwnagotchi/
/etc/hostname