Merge remote-tracking branch 'origin/pwnagotchi-torch' into pwnagotchi-torch

# Conflicts:
#	builder/pwnagotchi.yml
This commit is contained in:
Jeroen Oudshoorn
2023-08-31 05:29:10 +02:00
11 changed files with 36 additions and 476 deletions

2
.idea/misc.xml generated
View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (pwnagotchi)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11" project-jdk-type="Python SDK" />
</project> </project>

2
.idea/pwnagotchi.iml generated
View File

@ -4,7 +4,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" /> <excludeFolder url="file://$MODULE_DIR$/venv" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="jdk" jdkName="Python 3.11" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="PyDocumentationSettings"> <component name="PyDocumentationSettings">

View File

@ -1,6 +1,6 @@
import logging import logging
import gym import gymnasium
from gym import spaces from gymnasium import spaces
import numpy as np import numpy as np
import pwnagotchi.ai.featurizer as featurizer import pwnagotchi.ai.featurizer as featurizer
@ -8,7 +8,7 @@ import pwnagotchi.ai.reward as reward
from pwnagotchi.ai.parameter import Parameter from pwnagotchi.ai.parameter import Parameter
class Environment(gym.Env): class Environment(gymnasium.Env):
metadata = {'render.modes': ['human']} metadata = {'render.modes': ['human']}
params = [ params = [
Parameter('min_rssi', min_value=-200, max_value=-50), Parameter('min_rssi', min_value=-200, max_value=-50),

View File

@ -1,4 +1,4 @@
from gym import spaces from gymnasium import spaces
class Parameter(object): class Parameter(object):

View File

@ -181,7 +181,7 @@ class AsyncTrainer(object):
if os.path.isfile(self._nn_path): if os.path.isfile(self._nn_path):
back = "%s.bak" % self._nn_path back = "%s.bak" % self._nn_path
os.replace(self._nn_path, back) os.replace(self._nn_path, back)
self._view.set("mode", "AI") self._view.set("mode", " AI")
self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step) self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step)
except Exception as e: except Exception as e:
logging.exception("[ai] error while training (%s)", e) logging.exception("[ai] error while training (%s)", e)

View File

@ -28,8 +28,8 @@ main.plugins.grid.exclude = [
"YourHomeNetworkHere" "YourHomeNetworkHere"
] ]
main.plugins.auto-update.enabled = false main.plugins.auto-update.enabled = true
main.plugins.auto-update.install = false main.plugins.auto-update.install = true
main.plugins.auto-update.interval = 24 main.plugins.auto-update.interval = 24
main.plugins.net-pos.enabled = false main.plugins.net-pos.enabled = false
@ -39,13 +39,6 @@ main.plugins.gps.enabled = false
main.plugins.gps.speed = 19200 main.plugins.gps.speed = 19200
main.plugins.gps.device = "/dev/ttyUSB0" main.plugins.gps.device = "/dev/ttyUSB0"
main.plugins.exp.enabled = true
main.plugins.exp.lvl_x_coord = 0
main.plugins.exp.lvl_y_coord = 81
main.plugins.exp.exp_x_coord = 38
main.plugins.exp.exp_y_coord = 81
main.plugins.exp.bar_symbols_count = 12
main.plugins.bluetoothsniffer.enabled = false main.plugins.bluetoothsniffer.enabled = false
main.plugins.bluetoothsniffer.timer = 45 # On how may seconds to scan for bluetooth devices main.plugins.bluetoothsniffer.timer = 45 # On how may seconds to scan for bluetooth devices
main.plugins.bluetoothsniffer.devices_file = "/root/handshakes/bluetooth_devices.json" # Path to the JSON file with bluetooth devices main.plugins.bluetoothsniffer.devices_file = "/root/handshakes/bluetooth_devices.json" # Path to the JSON file with bluetooth devices
@ -146,12 +139,6 @@ main.log.path = "/var/log/pwnagotchi.log"
main.log.rotation.enabled = true main.log.rotation.enabled = true
main.log.rotation.size = "10M" main.log.rotation.size = "10M"
main.plugins.age.enabled = false
main.plugins.age.age_x_coord = 0
main.plugins.age.age_y_coord = 32
main.plugins.age.str_x_coord = 67
main.plugins.age.str_y_coord = 32
ai.enabled = true ai.enabled = true
ai.path = "/root/brain.nn" ai.path = "/root/brain.nn"
ai.laziness = 0.1 ai.laziness = 0.1

View File

@ -1,18 +1,15 @@
import os import os
import glob import glob
import _thread
import threading import threading
import importlib, importlib.util import importlib, importlib.util
import logging import logging
from concurrent.futures import ThreadPoolExecutor
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default") default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
loaded = {} loaded = {}
database = {} database = {}
locks = {} locks = {}
THREAD_POOL_SIZE = 10
executor = ThreadPoolExecutor(max_workers=THREAD_POOL_SIZE)
class Plugin: class Plugin:
@classmethod @classmethod
@ -45,7 +42,7 @@ def toggle_plugin(name, enable=True):
global loaded, database global loaded, database
if pwnagotchi.config: if pwnagotchi.config:
if name not in pwnagotchi.config['main']['plugins']: if not name in pwnagotchi.config['main']['plugins']:
pwnagotchi.config['main']['plugins'][name] = dict() pwnagotchi.config['main']['plugins'][name] = dict()
pwnagotchi.config['main']['plugins'][name]['enabled'] = enable pwnagotchi.config['main']['plugins'][name]['enabled'] = enable
save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml') save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml')
@ -97,7 +94,7 @@ def one(plugin_name, event_name, *args, **kwargs):
try: try:
lock_name = "%s::%s" % (plugin_name, cb_name) lock_name = "%s::%s" % (plugin_name, cb_name)
locked_cb_args = (lock_name, callback, *args, *kwargs) locked_cb_args = (lock_name, callback, *args, *kwargs)
executor.submit(locked_cb, *locked_cb_args) _thread.start_new_thread(locked_cb, locked_cb_args)
except Exception as e: except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True) logging.error(e, exc_info=True)

View File

@ -1,88 +0,0 @@
import os
import json
import logging
import pwnagotchi
import pwnagotchi.plugins as plugins
import pwnagotchi.ui.faces as faces
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
class Age(plugins.Plugin):
__author__ = 'HannaDiamond'
__version__ = '1.0.1'
__license__ = 'MIT'
__description__ = 'A plugin that will add age and strength stats based on epochs and trained epochs'
def __init__(self):
self.epochs = 0
self.train_epochs = 0
def on_loaded(self):
data_path = '/root/brain.json'
self.load_data(data_path)
def on_ui_setup(self, ui):
ui.add_element('Age', LabeledValue(color=BLACK, label='♥ Age', value=0,
position=(int(self.options["age_x_coord"]),
int(self.options["age_y_coord"])),
label_font=fonts.Bold, text_font=fonts.Medium))
ui.add_element('Strength', LabeledValue(color=BLACK, label='Str', value=0,
position=(int(self.options["str_x_coord"]),
int(self.options["str_y_coord"])),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_unload(self, ui):
with ui._lock:
ui.remove_element('Age')
ui.remove_element('Strength')
def on_ui_update(self, ui):
ui.set('Age', str(self.abrev_number(self.epochs)))
ui.set('Strength', str(self.abrev_number(self.train_epochs)))
def on_ai_training_step(self, agent, _locals, _globals):
self.train_epochs += 1
if self.train_epochs % 100 == 0:
self.strength_checkpoint(agent)
def on_epoch(self, agent, epoch, epoch_data):
self.epochs += 1
if self.epochs % 100 == 0:
self.age_checkpoint(agent)
def abrev_number(self, num):
if num < 100000:
return str(num)
else:
magnitude = 0
while abs(num) >= 1000:
magnitude += 1
num /= 1000.0
abbr = ['', 'K', 'M', 'B', 'T', 'P'][magnitude]
return '{}{}'.format('{:.2f}'.format(num).rstrip('0').rstrip('.'), abbr)
def age_checkpoint(self, agent):
view = agent.view()
view.set('face', faces.HAPPY)
view.set('status', "Wow, I've lived for " + str(self.abrev_number(self.epochs)) + " epochs!")
view.update(force=True)
def strength_checkpoint(self, agent):
view = agent.view()
view.set('face', faces.MOTIVATED)
view.set('status', "Look at my strength go up! \n"
"I've trained for " + str(self.abrev_number(self.train_epochs)) + " epochs")
view.update(force=True)
def load_data(self, data_path):
if os.path.exists(data_path):
with open(data_path) as f:
data = json.load(f)
self.epochs = data['epochs_lived']
self.train_epochs = data['epochs_trained']

View File

@ -1,336 +0,0 @@
import logging
import os
import random
import json
import pwnagotchi
import pwnagotchi.agent
import pwnagotchi.plugins as plugins
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
# Static Variables
MULTIPLIER_ASSOCIATION = 1
MULTIPLIER_DEAUTH = 2
MULTIPLIER_HANDSHAKE = 3
MULTIPLIER_AI_BEST_REWARD = 5
TAG = "[EXP Plugin]"
FACE_LEVELUP = '(≧◡◡≦)'
BAR_ERROR = "| error |"
FILE_SAVE = "exp_stats"
FILE_SAVE_LEGACY = "exp"
JSON_KEY_LEVEL = "level"
JSON_KEY_EXP = "exp"
JSON_KEY_EXP_TOT = "exp_tot"
class EXP(plugins.Plugin):
__author__ = 'GaelicThunder'
__version__ = '1.0.5'
__license__ = 'GPL3'
__description__ = 'Get exp every time a handshake get captured.'
# Attention number masking
def LogInfo(self, text):
logging.info(TAG + " " + text)
# Attention number masking
def LogDebug(self, text):
logging.debug(TAG + " " + text)
def __init__(self):
self.percent = 0
self.calculateInitialXP = False
self.exp = 0
self.lv = 1
self.exp_tot = 0
# Sets the file type I recommend json
self.save_file_mode = self.save_file_modes("json")
self.save_file = self.getSaveFileName(self.save_file_mode)
# Migrate from old save system
self.migrateLegacySave()
# Create save file
if not os.path.exists(self.save_file):
self.Save(self.save_file, self.save_file_mode)
else:
try:
# Try loading
self.Load(self.save_file, self.save_file_mode)
except:
# Likely throws an exception if json file is corrupted, so we need to calculate from scratch
self.calculateInitialXP = True
# No previous data, try get it
if self.lv == 1 and self.exp == 0:
self.calculateInitialXP = True
if self.exp_tot == 0:
self.LogInfo("Need to calculate Total Exp")
self.exp_tot = self.calcActualSum(self.lv, self.exp)
self.Save(self.save_file, self.save_file_mode)
self.expneeded = self.calcExpNeeded(self.lv)
def on_loaded(self):
# logging.info("Exp plugin loaded for %s" % self.options['device'])
self.LogInfo("Plugin Loaded")
def save_file_modes(self, argument):
switcher = {
"txt": 0,
"json": 1,
}
return switcher.get(argument, 0)
def Save(self, file, save_file_mode):
self.LogDebug('Saving Exp')
if save_file_mode == 0:
self.saveToTxtFile(file)
if save_file_mode == 1:
self.saveToJsonFile(file)
def saveToTxtFile(self, file):
outfile = open(file, 'w')
print(self.exp, file=outfile)
print(self.lv, file=outfile)
print(self.exp_tot, file=outfile)
outfile.close()
def loadFromTxtFile(self, file):
if os.path.exists(file):
outfile = open(file, 'r+')
lines = outfile.readlines()
linecounter = 1
for line in lines:
if linecounter == 1:
self.exp = int(line)
elif linecounter == 2:
self.lv == int(line)
elif linecounter == 3:
self.exp_tot == int(line)
linecounter += 1
outfile.close()
def saveToJsonFile(self, file):
data = {
JSON_KEY_LEVEL: self.lv,
JSON_KEY_EXP: self.exp,
JSON_KEY_EXP_TOT: self.exp_tot
}
with open(file, 'w') as f:
f.write(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')))
def loadFromJsonFile(self, file):
# Tot exp is introduced with json, no check needed
data = {}
with open(file, 'r') as f:
data = json.loads(f.read())
if bool(data):
self.lv = data[JSON_KEY_LEVEL]
self.exp = data[JSON_KEY_EXP]
self.exp_tot = data[JSON_KEY_EXP_TOT]
else:
self.LogInfo("Empty json")
# TODO: one day change save file mode to file date
def Load(self, file, save_file_mode):
self.LogDebug('Loading Exp')
if save_file_mode == 0:
self.loadFromTxtFile(file)
if save_file_mode == 1:
self.loadFromJsonFile(file)
def getSaveFileName(self, save_file_mode):
file = os.path.dirname(os.path.realpath(__file__))
file = file + "/" + FILE_SAVE
if save_file_mode == 0:
file = file + ".txt"
elif save_file_mode == 1:
file = file + ".json"
else:
# See switcher
file = file + ".txt"
return file
def migrateLegacySave(self):
legacyFile = os.path.dirname(os.path.realpath(__file__))
legacyFile = legacyFile + "/" + FILE_SAVE_LEGACY + ".txt"
if os.path.exists(legacyFile):
self.loadFromTxtFile(legacyFile)
self.LogInfo("Migrating Legacy Save...")
self.Save(self.save_file, self.save_file_mode)
os.remove(legacyFile)
def barString(self, symbols_count, p):
if p > 100:
return BAR_ERROR
length = symbols_count - 2
bar_char = ''
blank_char = ' '
bar_length = int(round((length / 100) * p))
blank_length = length - bar_length
res = '|' + bar_char * bar_length + blank_char * blank_length + '|'
return res
def on_ui_setup(self, ui):
ui.add_element('Lv', LabeledValue(color=BLACK, label='Lv', value=0,
position=(int(self.options["lvl_x_coord"]),
int(self.options["lvl_y_coord"])),
label_font=fonts.Bold, text_font=fonts.Medium))
ui.add_element('Exp', LabeledValue(color=BLACK, label='Exp', value=0,
position=(int(self.options["exp_x_coord"]),
int(self.options["exp_y_coord"])),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_update(self, ui):
self.expneeded = self.calcExpNeeded(self.lv)
self.percent = int((self.exp / self.expneeded) * 100)
symbols_count = int(self.options["bar_symbols_count"])
bar = self.barString(symbols_count, self.percent)
ui.set('Lv', "%d" % self.lv)
ui.set('Exp', "%s" % bar)
def calcExpNeeded(self, level):
# If the pwnagotchi is lvl <1 it causes the keys to be deleted
if level == 1:
return 5
return int((level ** 3) / 2)
def exp_check(self, agent):
self.LogDebug("EXP CHECK")
if self.exp >= self.expneeded:
self.exp = 1
self.lv = self.lv + 1
self.expneeded = self.calcExpNeeded(self.lv)
self.displayLevelUp(agent)
def parseSessionStats(self):
sum = 0
dir = pwnagotchi.config['main']['plugins']['session-stats']['save_directory']
# TODO: remove
self.LogInfo("Session-Stats dir: " + dir)
for filename in os.listdir(dir):
self.LogInfo("Parsing " + filename + "...")
if filename.endswith(".json") & filename.startswith("stats"):
try:
sum += self.parseSessionStatsFile(os.path.join(dir, filename))
except:
self.LogInfo("ERROR parsing File: " + filename)
return sum
def parseSessionStatsFile(self, path):
sum = 0
deauths = 0
handshakes = 0
associations = 0
with open(path) as json_file:
data = json.load(json_file)
for entry in data["data"]:
deauths += data["data"][entry]["num_deauths"]
handshakes += data["data"][entry]["num_handshakes"]
associations += data["data"][entry]["num_associations"]
sum += deauths * MULTIPLIER_DEAUTH
sum += handshakes * MULTIPLIER_HANDSHAKE
sum += associations * MULTIPLIER_ASSOCIATION
return sum
# If initial sum is 0, we try to parse it
def calculateInitialSum(self, agent):
sessionStatsActive = False
sum = 0
# Check if session stats is loaded
for plugin in pwnagotchi.plugins.loaded:
if plugin == "session-stats":
sessionStatsActive = True
break
if sessionStatsActive:
try:
self.LogInfo("parsing session-stats")
sum = self.parseSessionStats()
except:
self.LogInfo("Error parsing session-stats")
else:
self.LogInfo("parsing last session")
sum = self.lastSessionPoints(agent)
self.LogInfo(str(sum) + " Points calculated")
return sum
# Get Last Sessions Points
def lastSessionPoints(self, agent):
summary = 0
summary += agent.LastSession.handshakes * MULTIPLIER_HANDSHAKE
summary += agent.LastSession.associated * MULTIPLIER_ASSOCIATION
summary += agent.LastSession.deauthed * MULTIPLIER_DEAUTH
return summary
# Helper function to calculate multiple Levels from a sum of EXPs
def calcLevelFromSum(self, sum, agent):
sum1 = sum
level = 1
while sum1 > self.calcExpNeeded(level):
sum1 -= self.calcExpNeeded(level)
level += 1
self.lv = level
self.exp = sum1
self.expneeded = self.calcExpNeeded(level) - sum1
if level > 1:
# get Excited ;-)
self.displayLevelUp(agent)
def calcActualSum(self, level, exp):
lvlCounter = 1
sum = exp
# I know it wouldn't work if you change the lvl algorithm
while lvlCounter < level:
sum += self.calcExpNeeded(lvlCounter)
lvlCounter += 1
return sum
def displayLevelUp(self, agent):
view = agent.view()
view.set('face', FACE_LEVELUP)
view.set('status', "Level Up!")
view.update(force=True)
# Event Handling
def on_association(self, agent, access_point):
self.exp += MULTIPLIER_ASSOCIATION
self.exp_tot += MULTIPLIER_ASSOCIATION
self.exp_check(agent)
self.Save(self.save_file, self.save_file_mode)
def on_deauthentication(self, agent, access_point, client_station):
self.exp += MULTIPLIER_DEAUTH
self.exp_tot += MULTIPLIER_DEAUTH
self.exp_check(agent)
self.Save(self.save_file, self.save_file_mode)
def on_handshake(self, agent, filename, access_point, client_station):
self.exp += MULTIPLIER_HANDSHAKE
self.exp_tot += MULTIPLIER_HANDSHAKE
self.exp_check(agent)
self.Save(self.save_file, self.save_file_mode)
def on_ai_best_reward(self, agent, reward):
self.exp += MULTIPLIER_AI_BEST_REWARD
self.exp_tot += MULTIPLIER_AI_BEST_REWARD
self.exp_check(agent)
self.Save(self.save_file, self.save_file_mode)
def on_ready(self, agent):
if self.calculateInitialXP:
self.LogInfo("Initial point calculation")
sum = self.calculateInitialSum(agent)
self.exp_tot = sum
self.calcLevelFromSum(sum, agent)
self.Save(self.save_file, self.save_file_mode)

View File

@ -226,7 +226,7 @@ class Fix_BRCMF(plugins.Plugin):
logging.info("[FixBRCMF] recon paused. Now trying wlan0mon reload") logging.info("[FixBRCMF] recon paused. Now trying wlan0mon reload")
try: try:
cmd_output = subprocess.check_output("sudo ifconfig wlan0mon down && sudo iw dev wlan0mon del", shell=True) cmd_output = subprocess.check_output("sudo airmon-ng stop wlan0mon", shell=True)
self._status = "dn" self._status = "dn"
self.logPrintView("info", "[FixBRCMF] wlan0mon down and deleted: %s" % cmd_output, self.logPrintView("info", "[FixBRCMF] wlan0mon down and deleted: %s" % cmd_output,
display, {"status": "wlan0mon d-d-d-down!", "face": faces.BORED}) display, {"status": "wlan0mon d-d-d-down!", "face": faces.BORED})

View File

@ -1,24 +1,24 @@
gym Gymnasium
shimmy shimmy
pycryptodome>=3.9.4 pycryptodome
requests>=2.21.0 requests
PyYAML>=5.3.1 PyYAML
scapy>=2.4.3 scapy
tweepy>=3.7.0 tweepy
file-read-backwards>=2.0.0 file-read-backwards
inky>=1.2.0 inky
smbus2>=0.3.0 smbus2
Pillow>=5.4.1 Pillow
spidev>=3.4 spidev
gast>=0.2.2 gast
flask>=1.0.2 flask
flask-cors>=3.0.7 flask-cors
flask-wtf>=0.14.3 flask-wtf
dbus-python>=1.2.12 dbus-python
toml>=0.10.0 toml
python-dateutil>=2.8.1 python-dateutil
websockets>=8.1 websockets
torch>=2.0.1 torch
torchvision>=0.15.2 torchvision
stable-baselines3>=1.4.0 stable_baselines3
RPi.GPIO RPi.GPIO