fix: on_internet_available plugins callback is now called for both MANU and AUTO mode (fixes #210)

This commit is contained in:
Simone Margaritelli 2019-10-08 14:54:03 +02:00
parent 1c526b0bf1
commit 55d99836e7
14 changed files with 139 additions and 102 deletions

View File

@ -9,7 +9,6 @@ if __name__ == '__main__':
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
from pwnagotchi.log import SessionParser
from pwnagotchi.identity import KeyPair
from pwnagotchi.agent import Agent
from pwnagotchi.ui.display import Display
@ -51,22 +50,23 @@ if __name__ == '__main__':
elif args.do_manual:
logging.info("entering manual mode ...")
log = SessionParser(config)
agent.last_session.parse()
logging.info(
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
log.duration_human,
log.epochs,
log.train_epochs,
log.avg_reward,
log.min_reward,
log.max_reward))
agent.last_session.duration_human,
agent.last_session.epochs,
agent.last_session.train_epochs,
agent.last_session.avg_reward,
agent.last_session.min_reward,
agent.last_session.max_reward))
while True:
display.on_manual_mode(log)
display.on_manual_mode(agent.last_session)
time.sleep(1)
if Agent.is_connected():
plugins.on('internet_available', display, keypair, config, log)
plugins.on('internet_available', agent)
else:
logging.info("entering auto mode ...")
@ -104,5 +104,9 @@ if __name__ == '__main__':
# WiFi electromagnetic fields affect time like gravitational fields
# affect ours ... neat ^_^
agent.next_epoch()
if Agent.is_connected():
plugins.on('internet_available', agent)
except Exception as e:
logging.exception("main loop exception")

View File

@ -9,6 +9,7 @@ import _thread
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
from pwnagotchi.log import LastSession
from pwnagotchi.bettercap import Client
from pwnagotchi.mesh.utils import AsyncAdvertiser
from pwnagotchi.ai.train import AsyncTrainer
@ -35,6 +36,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
self._last_pwnd = None
self._history = {}
self._handshakes = {}
self.last_session = LastSession(self._config)
@staticmethod
def is_connected():
@ -48,6 +50,9 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
def config(self):
return self._config
def view(self):
return self._view
def supported_channels(self):
return self._supported_channels

View File

@ -11,7 +11,7 @@ from file_read_backwards import FileReadBackwards
LAST_SESSION_FILE = '/root/.pwnagotchi-last-session'
class SessionParser(object):
class LastSession(object):
EPOCH_TOKEN = '[epoch '
EPOCH_PARSER = re.compile(r'^.+\[epoch (\d+)\] (.+)')
EPOCH_DATA_PARSER = re.compile(r'([a-z_]+)=([^\s]+)')
@ -70,27 +70,27 @@ class SessionParser(object):
if started_at is None:
started_at = stopped_at
if SessionParser.DEAUTH_TOKEN in line and line not in cache:
if LastSession.DEAUTH_TOKEN in line and line not in cache:
self.deauthed += 1
cache[line] = 1
elif SessionParser.ASSOC_TOKEN in line and line not in cache:
elif LastSession.ASSOC_TOKEN in line and line not in cache:
self.associated += 1
cache[line] = 1
elif SessionParser.HANDSHAKE_TOKEN in line and line not in cache:
elif LastSession.HANDSHAKE_TOKEN in line and line not in cache:
self.handshakes += 1
cache[line] = 1
elif SessionParser.TRAINING_TOKEN in line:
elif LastSession.TRAINING_TOKEN in line:
self.train_epochs += 1
elif SessionParser.EPOCH_TOKEN in line:
elif LastSession.EPOCH_TOKEN in line:
self.epochs += 1
m = SessionParser.EPOCH_PARSER.findall(line)
m = LastSession.EPOCH_PARSER.findall(line)
if m:
epoch_num, epoch_data = m[0]
m = SessionParser.EPOCH_DATA_PARSER.findall(epoch_data)
m = LastSession.EPOCH_DATA_PARSER.findall(epoch_data)
for key, value in m:
if key == 'reward':
reward = float(value)
@ -101,7 +101,7 @@ class SessionParser(object):
elif reward > self.max_reward:
self.max_reward = reward
elif SessionParser.PEER_TOKEN in line:
elif LastSession.PEER_TOKEN in line:
m = self._peer_parser.findall(line)
if m:
name, pubkey, rssi, sid, pwnd_tot, uptime = m[0]
@ -134,6 +134,30 @@ class SessionParser(object):
self.duration_human = ', '.join(self.duration_human)
self.avg_reward /= (self.epochs if self.epochs else 1)
def parse(self):
lines = []
if os.path.exists(self.path):
with FileReadBackwards(self.path, encoding="utf-8") as fp:
for line in fp:
line = line.strip()
if line != "" and line[0] != '[':
continue
lines.append(line)
if LastSession.START_TOKEN in line:
break
lines.reverse()
if len(lines) == 0:
lines.append("Initial Session");
self.last_session = lines
self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest()
self.last_saved_session_id = self._get_last_saved_session_id()
self._parse_stats()
self.parsed = True
def __init__(self, config):
self.config = config
self.voice = Voice(lang=config['main']['lang'])
@ -150,28 +174,10 @@ class SessionParser(object):
self.last_peer = None
self._peer_parser = re.compile(
'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]')
lines = []
if os.path.exists(self.path):
with FileReadBackwards(self.path, encoding="utf-8") as fp:
for line in fp:
line = line.strip()
if line != "" and line[0] != '[':
continue
lines.append(line)
if SessionParser.START_TOKEN in line:
break
lines.reverse()
if len(lines) == 0:
lines.append("Initial Session");
self.last_session = lines
self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest()
self.last_saved_session_id = self._get_last_saved_session_id()
self._parse_stats()
self.last_session = []
self.last_session_id = None
self.last_saved_session_id = None
self.parsed = False
def is_new(self):
return self.last_session_id != self.last_saved_session_id

View File

@ -12,6 +12,9 @@ class AsyncAdvertiser(object):
self._keypair = keypair
self._advertiser = None
def keypair(self):
return self._keypair
def start_advertising(self):
_thread.start_new_thread(self._adv_worker, ())

View File

@ -33,7 +33,7 @@ def on_loaded():
logging.info("AUTO-BACKUP: Successfuly loaded.")
def on_internet_available(display, keypair, config, log):
def on_internet_available(agent):
global STATUS
if READY:
@ -42,6 +42,8 @@ def on_internet_available(display, keypair, config, log):
files_to_backup = " ".join(OPTIONS['files'])
try:
display = agent.view()
logging.info("AUTO-BACKUP: Backing up ...")
display.set('status', 'Backing up ...')
display.update()

View File

@ -23,13 +23,15 @@ def on_loaded():
READY = True
def on_internet_available(display, keypair, config, log):
def on_internet_available(agent):
global STATUS
if READY:
if STATUS.newer_then_days(OPTIONS['interval']):
return
display = agent.view()
try:
display.set('status', 'Updating ...')
display.update()

View File

@ -20,7 +20,7 @@ def on_loaded():
# called in manual mode when there's internet connectivity
def on_internet_available(ui, keypair, config, log):
def on_internet_available(agent):
pass

View File

@ -22,16 +22,16 @@ def on_loaded():
logging.info("api plugin loaded.")
def get_api_token(log, keys):
def get_api_token(last_session, keys):
global AUTH
if AUTH.newer_then_minutes(25) and AUTH.data is not None and 'token' in AUTH.data:
return AUTH.data['token']
if AUTH.data is None:
logging.info("api: enrolling unit ...")
logging.info("grid: enrolling unit ...")
else:
logging.info("api: refreshing token ...")
logging.info("grid: refreshing token ...")
identity = "%s@%s" % (pwnagotchi.name(), keys.fingerprint)
# sign the identity string to prove we own both keys
@ -43,16 +43,16 @@ def get_api_token(log, keys):
'public_key': keys.pub_key_pem_b64,
'signature': signature_b64,
'data': {
'duration': log.duration,
'epochs': log.epochs,
'train_epochs': log.train_epochs,
'avg_reward': log.avg_reward,
'min_reward': log.min_reward,
'max_reward': log.max_reward,
'deauthed': log.deauthed,
'associated': log.associated,
'handshakes': log.handshakes,
'peers': log.peers,
'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")
}
}
@ -63,13 +63,13 @@ def get_api_token(log, keys):
AUTH.update(data=r.json())
logging.info("api: done")
logging.info("grid: done")
return AUTH.data["token"]
def parse_pcap(filename):
logging.info("api: parsing %s ..." % filename)
logging.info("grid: parsing %s ..." % filename)
net_id = os.path.basename(filename).replace('.pcap', '')
@ -91,15 +91,15 @@ def parse_pcap(filename):
try:
info = extract_from_pcap(filename, [WifiInfo.BSSID, WifiInfo.ESSID])
except Exception as e:
logging.error("api: %s" % e)
logging.error("grid: %s" % e)
return info[WifiInfo.ESSID], info[WifiInfo.BSSID]
def api_report_ap(log, keys, token, essid, bssid):
def api_report_ap(last_session, keys, token, essid, bssid):
while True:
token = AUTH.data['token']
logging.info("api: reporting %s (%s)" % (essid, bssid))
logging.info("grid: reporting %s (%s)" % (essid, bssid))
try:
api_address = 'https://api.pwnagotchi.ai/api/v1/unit/report/ap'
headers = {'Authorization': 'access_token %s' % token}
@ -111,21 +111,23 @@ def api_report_ap(log, keys, token, essid, bssid):
if r.status_code != 200:
if r.status_code == 401:
logging.warning("token expired")
token = get_api_token(log, keys)
token = get_api_token(last_session, keys)
continue
else:
raise Exception("(status %d) %s" % (r.status_code, r.text))
else:
return True
except Exception as e:
logging.error("api: %s" % e)
logging.error("grid: %s" % e)
return False
def on_internet_available(ui, keys, config, log):
def on_internet_available(agent):
global REPORT
try:
config = agent.config()
keys = agent.keypair()
pcap_files = glob.glob(os.path.join(config['bettercap']['handshakes'], "*.pcap"))
num_networks = len(pcap_files)
@ -134,10 +136,10 @@ def on_internet_available(ui, keys, config, log):
num_new = num_networks - num_reported
if num_new > 0:
logging.info("api: %d new networks to report" % num_new)
token = get_api_token(log, keys)
if OPTIONS['report']:
logging.info("grid: %d new networks to report" % num_new)
token = get_api_token(agent.last_session, agent.keypair())
for pcap_file in pcap_files:
net_id = os.path.basename(pcap_file).replace('.pcap', '')
do_skip = False
@ -151,11 +153,11 @@ def on_internet_available(ui, keys, config, log):
if net_id not in reported and not do_skip:
essid, bssid = parse_pcap(pcap_file)
if bssid:
if api_report_ap(log, keys, token, essid, bssid):
if api_report_ap(agent.last_session, keys, token, essid, bssid):
reported.append(net_id)
REPORT.update(data={'reported': reported})
else:
logging.info("api: reporting disabled")
logging.debug("grid: reporting disabled")
except Exception as e:
logging.exception("error while enrolling the unit")

View File

@ -55,11 +55,14 @@ def _upload_to_ohc(path, timeout=30):
raise e
def on_internet_available(display, keypair, config, log):
def on_internet_available(agent):
"""
Called in manual mode when there's internet connectivity
"""
if READY:
display = agent.view()
config = agent.config()
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]

View File

@ -14,8 +14,12 @@ def on_loaded():
# called in manual mode when there's internet connectivity
def on_internet_available(ui, keypair, config, log):
if log.is_new() and log.handshakes > 0:
def on_internet_available(agent):
config = agent.config()
display = agent.view()
last_session = agent.last_session
if last_session.is_new() and last_session.handshakes > 0:
try:
import tweepy
except ImportError:
@ -26,20 +30,20 @@ def on_internet_available(ui, keypair, config, log):
picture = '/dev/shm/pwnagotchi.png'
ui.on_manual_mode(log)
ui.update(force=True)
ui.image().save(picture, 'png')
ui.set('status', 'Tweeting...')
ui.update(force=True)
display.on_manual_mode(last_session)
display.update(force=True)
display.image().save(picture, 'png')
display.set('status', 'Tweeting...')
display.update(force=True)
try:
auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret'])
auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret'])
api = tweepy.API(auth)
tweet = Voice(lang=config['main']['lang']).on_log_tweet(log)
tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session)
api.update_with_media(filename=picture, status=tweet)
log.save_session_id()
last_session.save_session_id()
logging.info("tweeted: %s" % tweet)
except Exception as e:

View File

@ -196,7 +196,7 @@ def _send_to_wigle(lines, api_key, timeout=30):
raise re_e
def on_internet_available(display, keypair, config, log):
def on_internet_available(agent):
from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \
Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA
"""
@ -206,6 +206,9 @@ def on_internet_available(display, keypair, config, log):
global SKIP
if READY:
config = agent.config()
display = agent.view()
handshake_dir = config['bettercap']['handshakes']
all_files = os.listdir(handshake_dir)
all_gps_files = [os.path.join(handshake_dir, filename)

View File

@ -54,11 +54,14 @@ def _upload_to_wpasec(path, timeout=30):
raise e
def on_internet_available(display, keypair, config, log):
def on_internet_available(agent):
"""
Called in manual mode when there's internet connectivity
"""
if READY:
config = agent.config()
display = agent.view()
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]

View File

@ -163,17 +163,17 @@ class View(object):
self.set('status', self._voice.on_ai_ready())
self.update()
def on_manual_mode(self, log):
def on_manual_mode(self, last_session):
self.set('mode', 'MANU')
self.set('face', faces.SAD if log.handshakes == 0 else faces.HAPPY)
self.set('status', self._voice.on_log(log))
self.set('epoch', "%04d" % log.epochs)
self.set('uptime', log.duration)
self.set('face', faces.SAD if last_session.handshakes == 0 else faces.HAPPY)
self.set('status', self._voice.on_last_session_data(last_session))
self.set('epoch', "%04d" % last_session.epochs)
self.set('uptime', last_session.duration)
self.set('channel', '-')
self.set('aps', "%d" % log.associated)
self.set('shakes', '%d (%s)' % (log.handshakes, \
self.set('aps', "%d" % last_session.associated)
self.set('shakes', '%d (%s)' % (last_session.handshakes, \
utils.total_unique_handshakes(self._config['bettercap']['handshakes'])))
self.set_closest_peer(log.last_peer, log.peers)
self.set_closest_peer(last_session.last_peer, last_session.peers)
def is_normal(self):
return self._state.get('face') not in (

View File

@ -125,23 +125,23 @@ class Voice:
def on_rebooting(self):
return self._("Ops, something went wrong ... Rebooting ...")
def on_log(self, log):
status = self._('Kicked {num} stations\n').format(num=log.deauthed)
status += self._('Made {num} new friends\n').format(num=log.associated)
status += self._('Got {num} handshakes\n').format(num=log.handshakes)
if log.peers == 1:
def on_last_session_data(self, last_session):
status = self._('Kicked {num} stations\n').format(num=last_session.deauthed)
status += self._('Made {num} new friends\n').format(num=last_session.associated)
status += self._('Got {num} handshakes\n').format(num=last_session.handshakes)
if last_session.peers == 1:
status += self._('Met 1 peer')
elif log.peers > 0:
status += self._('Met {num} peers').format(num=log.peers)
elif last_session.peers > 0:
status += self._('Met {num} peers').format(num=last_session.peers)
return status
def on_log_tweet(self, log):
def on_last_session_tweet(self, last_session):
return self._(
'I\'ve been pwning for {duration} and kicked {deauthed} clients! I\'ve also met {associated} new friends and ate {handshakes} handshakes! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet').format(
duration=log.duration_human,
deauthed=log.deauthed,
associated=log.associated,
handshakes=log.handshakes)
duration=last_session.duration_human,
deauthed=last_session.deauthed,
associated=last_session.associated,
handshakes=last_session.handshakes)
def hhmmss(self, count, fmt):
if count > 1: