new: using pwngrid for mesh advertising (we got rid of scapy loading times)

This commit is contained in:
Simone Margaritelli
2019-10-13 17:24:47 +02:00
parent 77c16c38f4
commit 5d28557608
10 changed files with 197 additions and 366 deletions

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

@ -2,7 +2,11 @@ import _thread
import logging
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 +14,76 @@ 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
_thread.start_new_thread(self._adv_poller, ())
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)
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:
# 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.error("error while polling pwngrid-peer: %s" % e)

View File

@ -1,37 +0,0 @@
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)
elif freq == 2484:
return int(14)
elif 5035 <= freq <= 5865:
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