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__': if __name__ == '__main__':
import argparse import argparse
import time import time
import os
import logging import logging
import pwnagotchi import pwnagotchi
import pwnagotchi.grid as grid
import pwnagotchi.utils as utils import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
@ -64,7 +64,7 @@ if __name__ == '__main__':
display.on_manual_mode(agent.last_session) display.on_manual_mode(agent.last_session)
time.sleep(1) time.sleep(1)
if Agent.is_connected(): if grid.is_connected():
plugins.on('internet_available', agent) plugins.on('internet_available', agent)
else: else:
@ -102,7 +102,7 @@ if __name__ == '__main__':
# affect ours ... neat ^_^ # affect ours ... neat ^_^
agent.next_epoch() agent.next_epoch()
if Agent.is_connected(): if grid.is_connected():
plugins.on('internet_available', agent) plugins.on('internet_available', agent)
except Exception as e: 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" 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" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
pwngrid: 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: apt:
hold: hold:
- firmware-atheros - firmware-atheros
@ -497,12 +497,12 @@
Description=pwngrid peer service. Description=pwngrid peer service.
Documentation=https://pwnagotchi.ai Documentation=https://pwnagotchi.ai
Wants=network.target Wants=network.target
After=network.target After=bettercap.service
[Service] [Service]
Type=simple Type=simple
PermissionsStartOnly=true 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 Restart=always
RestartSec=30 RestartSec=30

View File

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

View File

@ -2,11 +2,10 @@ import time
import json import json
import os import os
import re import re
import socket
from datetime import datetime
import logging import logging
import _thread import _thread
import pwnagotchi
import pwnagotchi.utils as utils import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
from pwnagotchi.log import LastSession from pwnagotchi.log import LastSession
@ -41,15 +40,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
if not os.path.exists(config['bettercap']['handshakes']): if not os.path.exists(config['bettercap']['handshakes']):
os.makedirs(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): def config(self):
return self._config return self._config
@ -192,7 +182,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
def set_access_points(self, aps): def set_access_points(self, aps):
self._access_points = aps self._access_points = aps
plugins.on('wifi_update', self, 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 return self._access_points
def get_access_points(self): def get_access_points(self):
@ -241,9 +231,9 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
return None return None
def _update_uptime(self, s): 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('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): def _update_counters(self):
tot_aps = len(self._access_points) tot_aps = len(self._access_points)
@ -273,22 +263,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
if new_shakes > 0: if new_shakes > 0:
self._view.on_handshakes(new_shakes) 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): def _update_peers(self):
peer = self._advertiser.closest_peer() self._view.set_closest_peer(self._closest_peer, len(self._peers))
tot = self._advertiser.num_peers()
self._view.set_closest_peer(peer, tot)
def _save_recovery_data(self): def _save_recovery_data(self):
logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE) logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
@ -333,10 +309,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
s = self.session() s = self.session()
self._update_uptime(s) self._update_uptime(s)
if self._advertiser is not None: self._update_advertisement(s)
self._update_advertisement(s) self._update_peers()
self._update_peers()
self._update_counters() self._update_counters()
try: try:

View File

@ -27,6 +27,7 @@ main:
files: files:
- /root/brain.nn - /root/brain.nn
- /root/brain.json - /root/brain.json
- /root/.api-report.json
- /root/handshakes/ - /root/handshakes/
- /etc/pwnagotchi/ - /etc/pwnagotchi/
- /etc/hostname - /etc/hostname
@ -66,6 +67,12 @@ main:
wordlist_folder: /opt/wordlists/ wordlist_folder: /opt/wordlists/
AircrackOnly: AircrackOnly:
enabled: false 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 # monitor interface to use
iface: mon0 iface: mon0
# command to run to bring the mon interface up in case it's not up already # command to run to bring the mon interface up in case it's not up already
@ -167,7 +174,7 @@ ui:
color: 'black' color: 'black'
video: video:
enabled: true enabled: true
address: '10.0.0.2' address: '0.0.0.0'
port: 8080 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() self.fingerprint = hashlib.sha256(pem_ascii).hexdigest()
# no exception, keys loaded correctly. # no exception, keys loaded correctly.
self._view.on_normal() self._view.on_starting()
return return
except Exception as e: except Exception as e:

View File

@ -129,10 +129,15 @@ class LastSession(object):
if m: if m:
name, pubkey, rssi, sid, pwnd_tot, uptime = m[0] name, pubkey, rssi, sid, pwnd_tot, uptime = m[0]
if pubkey not in cache: if pubkey not in cache:
self.last_peer = Peer(sid, 1, int(rssi), self.last_peer = Peer({
{'name': name, 'session_id': sid,
'identity': pubkey, 'channel': 1,
'pwnd_tot': int(pwnd_tot)}) 'rssi': int(rssi),
'identity': pubkey,
'advertisement':{
'name': name,
'pwnd_tot': int(pwnd_tot)
}})
self.peers += 1 self.peers += 1
cache[pubkey] = self.last_peer cache[pubkey] = self.last_peer
else: 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 time
import logging import logging
import pwnagotchi.mesh.wifi as wifi
import pwnagotchi.ui.faces as faces import pwnagotchi.ui.faces as faces
class Peer(object): class Peer(object):
def __init__(self, sid, channel, rssi, adv): def __init__(self, obj):
self.first_seen = time.time() self.first_seen = time.time()
self.last_seen = self.first_seen self.last_seen = self.first_seen
self.session_id = sid self.session_id = obj['session_id']
self.last_channel = channel self.last_channel = obj['channel']
self.presence = [0] * wifi.NumChannels self.rssi = obj['rssi']
self.adv = adv self.adv = obj['advertisement']
self.rssi = rssi
self.presence[channel - 1] = 1
def update(self, sid, channel, rssi, adv): def update(self, new):
if self.name() != adv['name']: if self.name() != new.name():
logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), adv['name'])) logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), new.name()))
if 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, sid)) 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 = new.adv
self.adv = adv self.rssi = new.rssi
self.rssi = rssi self.session_id = new.session_id
self.session_id = sid
self.last_seen = time.time() self.last_seen = time.time()
def inactive_for(self): def inactive_for(self):

View File

@ -1,8 +1,13 @@
import _thread import _thread
import logging import logging
import time
import pwnagotchi import pwnagotchi
import pwnagotchi.utils as utils
import pwnagotchi.ui.faces as faces
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
import pwnagotchi.grid as grid
from pwnagotchi.mesh.peer import Peer
class AsyncAdvertiser(object): class AsyncAdvertiser(object):
@ -10,38 +15,78 @@ class AsyncAdvertiser(object):
self._config = config self._config = config
self._view = view self._view = view
self._keypair = keypair 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): def _update_advertisement(self, s):
return self._keypair 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): 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']: if self._config['personality']['advertise']:
self._advertiser.start() _thread.start_new_thread(self._adv_poller, ())
self._view.on_state_change('face', self._advertiser.on_face_change)
grid.set_advertisement_data(self._advertisement)
grid.advertise(True)
self._view.on_state_change('face', self._on_face_change)
else: else:
logging.warning("advertising is disabled") logging.warning("advertising is disabled")
def _on_new_unit(self, peer): def _on_face_change(self, old, new):
self._view.on_new_peer(peer) self._advertisement['face'] = new
plugins.on('peer_detected', self, peer) grid.set_advertisement_data(self._advertisement)
def _on_lost_unit(self, peer): def _adv_poller(self):
self._view.on_lost_peer(peer) while True:
plugins.on('peer_lost', self, peer) 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 NumChannels = 140
def freq_to_channel(freq): def freq_to_channel(freq):
if freq <= 2472: if freq <= 2472:
return int(((freq - 2412) / 5) + 1) return int(((freq - 2412) / 5) + 1)
@ -12,26 +10,3 @@ def freq_to_channel(freq):
return int(((freq - 5035) / 5) + 7) return int(((freq - 5035) / 5) + 7)
else: else:
return 0 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 os
import logging import logging
import requests
import glob import glob
import json
import subprocess import pwnagotchi.grid as grid
import pwnagotchi
import pwnagotchi.utils as utils import pwnagotchi.utils as utils
from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
@ -65,74 +63,13 @@ def is_excluded(what):
return False 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): def on_ui_update(ui):
new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES) new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES)
if not ui.has_element('mailbox') and TOTAL_MESSAGES > 0: if not ui.has_element('mailbox') and TOTAL_MESSAGES > 0:
if ui.is_inky(): if ui.is_inky():
pos=(80, 0) pos = (80, 0)
else: else:
pos=(100,0) pos = (100, 0)
ui.add_element('mailbox', ui.add_element('mailbox',
LabeledValue(color=BLACK, label='MSG', value=new_value, LabeledValue(color=BLACK, label='MSG', value=new_value,
position=pos, position=pos,
@ -141,13 +78,19 @@ def on_ui_update(ui):
ui.set('mailbox', new_value) 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): def on_internet_available(agent):
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
logging.debug("internet available") logging.debug("internet available")
try: try:
grid_update_data(agent.last_session) grid.update_data(agent.last_session)
except Exception as e: except Exception as e:
logging.error("error connecting to the pwngrid-peer service: %s" % e) logging.error("error connecting to the pwngrid-peer service: %s" % e)
return return
@ -155,13 +98,13 @@ def on_internet_available(agent):
try: try:
logging.debug("checking mailbox ...") logging.debug("checking mailbox ...")
messages = grid_inbox() messages = grid.inbox()
TOTAL_MESSAGES = len(messages) TOTAL_MESSAGES = len(messages)
UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None]) UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None])
if TOTAL_MESSAGES: if TOTAL_MESSAGES:
on_ui_update(agent.view()) 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") logging.debug("checking pcaps")
@ -181,17 +124,19 @@ def on_internet_available(agent):
net_id = os.path.basename(pcap_file).replace('.pcap', '') net_id = os.path.basename(pcap_file).replace('.pcap', '')
if net_id not in reported: if net_id not in reported:
if is_excluded(net_id): if 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 continue
essid, bssid = parse_pcap(pcap_file) essid, bssid = parse_pcap(pcap_file)
if bssid: if bssid:
add_as_reported = False
if is_excluded(essid) or is_excluded(bssid): if is_excluded(essid) or is_excluded(bssid):
logging.debug("not reporting %s due to exclusion filter" % pcap_file) logging.debug("not reporting %s due to exclusion filter" % pcap_file)
set_reported(reported, net_id)
elif grid_report_ap(essid, bssid): else:
reported.append(net_id) if grid.report_ap(essid, bssid):
REPORT.update(data={'reported': reported}) set_reported(reported, net_id)
else: else:
logging.warning("no bssid found?!") logging.warning("no bssid found?!")
else: else:

View File

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