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:
commit
d648f7cdf5
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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"):
|
||||||
|
@ -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:
|
||||||
|
@ -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
97
pwnagotchi/grid.py
Normal 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
|
@ -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:
|
||||||
|
@ -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,
|
||||||
|
'channel': 1,
|
||||||
|
'rssi': int(rssi),
|
||||||
'identity': pubkey,
|
'identity': pubkey,
|
||||||
'pwnd_tot': int(pwnd_tot)})
|
'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:
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
def new_session_id():
|
|
||||||
return ':'.join(['%02x' % b for b in os.urandom(6)])
|
|
@ -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]
|
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
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)
|
self._view.on_lost_peer(peer)
|
||||||
plugins.on('peer_lost', self, 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)
|
||||||
|
@ -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
|
|
||||||
|
408
pwnagotchi/plugins/default/bt-tether.py
Normal file
408
pwnagotchi/plugins/default/bt-tether.py
Normal 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))
|
@ -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,67 +63,6 @@ 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:
|
||||||
@ -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,7 +98,7 @@ 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])
|
||||||
|
|
||||||
@ -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:
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user