Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
26b5f8e
make log files optional. log to syslog if no file
Sniffleupagus Mar 1, 2025
80c62fd
fix font sizing
Sniffleupagus Mar 1, 2025
b9c1840
serve pwnagotchi.png from memory instead of writing to SD all the tim…
Sniffleupagus Mar 1, 2025
6a50c80
Add config option to read session stats at start on AUTO, default to …
Sniffleupagus Mar 4, 2025
96d5ff6
have faces match canvas colormode, and fix paste to work with 2 or 4 …
Sniffleupagus Mar 5, 2025
a6af92c
some themes depend on that alpha mod. oh well
Sniffleupagus Mar 5, 2025
d479902
catch missing image error in draw()
Sniffleupagus Mar 5, 2025
dada524
handle missing image errors better. keep old image on error.
Sniffleupagus Mar 5, 2025
39b2c19
make log format configurable
Sniffleupagus Mar 5, 2025
b2b8e46
cfg not config
Sniffleupagus Mar 5, 2025
e007e9d
Added image scaling to Text component (faces mod)
Sniffleupagus Mar 5, 2025
1956361
fit into bounding box if all 4 coordinates are defined, otherwise sca…
Sniffleupagus Mar 5, 2025
ef463a7
change logging to debug
Sniffleupagus Mar 5, 2025
ff08574
pass colors to face mod, and colorize to ui colors
Sniffleupagus Mar 5, 2025
f556bd8
colorize images with changeable colors
Sniffleupagus Mar 5, 2025
787a149
Update dummydisplay.py
Sniffleupagus Mar 7, 2025
77af937
Merge branch 'noai' into no-png-file
Sniffleupagus Mar 8, 2025
3010bd9
Merge branch 'noai' into no-png-file
Sniffleupagus Mar 9, 2025
93217c2
configurable update period for pwnagotchi.png. Default of 0 keeps cur…
Sniffleupagus Mar 9, 2025
3618e1a
Merge branch 'jayofelony:noai' into no-png-file
Sniffleupagus Mar 9, 2025
1913b93
handle syslog logging
Sniffleupagus Mar 17, 2025
76c0f95
don't crash on bettercap fails
Sniffleupagus Mar 17, 2025
8e10d9e
allow 90 and 270 rotation
Sniffleupagus Mar 17, 2025
8c127b5
Merge branch 'jayofelony:noai' into no-png-file
Sniffleupagus Mar 17, 2025
d13b358
fix logic on syslog logging
Sniffleupagus Mar 18, 2025
4a1eaaf
Merge branch 'no-png-file' of github.com:Sniffleupagus/pwnagotchi-boo…
Sniffleupagus Mar 18, 2025
1f4d895
generic ST7789 driver for any display that uses it
Sniffleupagus Mar 19, 2025
aa56323
use new st7789_display class instead of separate copies
Sniffleupagus Mar 19, 2025
7bfd711
remove old functions
Sniffleupagus Mar 19, 2025
69c68d7
use generic ST7789 driver
Sniffleupagus Mar 19, 2025
1bcde9f
cleaner implementation of syslog, console log
Sniffleupagus Apr 14, 2025
08c6958
don't join dead worker thread
Sniffleupagus Apr 14, 2025
4c43ea2
bug fix
Sniffleupagus Apr 14, 2025
b46cd1a
bug fixes
Sniffleupagus Apr 14, 2025
4268432
bug fix- merge
Sniffleupagus Apr 14, 2025
e6d81fd
clickable ui, copied from pwnagotchi-snflpgs
Sniffleupagus Apr 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pwnagotchi/bettercap.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def decode(r, verbose_errors=True):
err = "error %d: %s" % (r.status_code, r.text.strip())
if verbose_errors:
logging.info(err)
raise Exception(err)
#raise Exception(err)
return r.text


Expand Down
3 changes: 2 additions & 1 deletion pwnagotchi/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def do_auto_mode(agent):
logging.info("entering auto mode ...")

agent.mode = 'auto'
agent.last_session.parse(agent.view(), args.skip_session) # show stats in AUTO
if agent.config().get('STATS_ON_AUTO', False):
agent.last_session.parse(agent.view(), args.skip_session) # show stats in AUTO
agent.start()

while True:
Expand Down
94 changes: 61 additions & 33 deletions pwnagotchi/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
import time
import re
import os
import sys
import logging
import logging.handlers
import syslog
import shutil
import gzip
import warnings
from datetime import datetime
from dateutil import parser as date_parser

from pwnagotchi.voice import Voice
from pwnagotchi.mesh.peer import Peer
Expand All @@ -30,6 +34,7 @@ def __init__(self, config):
self.config = config
self.voice = Voice(lang=config['main']['lang'])
self.path = config['main']['log']['path']
self.log_type = config['main']['log']['type'].lower()
self.last_session = []
self.last_session_id = ''
self.last_saved_session_id = ''
Expand Down Expand Up @@ -64,10 +69,19 @@ def save_session_id(self):
self.last_saved_session_id = self.last_session_id

def _parse_datetime(self, dt):
dt = dt.split('.')[0]
dt = dt.split(',')[0]
dt = datetime.strptime(dt.split('.')[0], '%Y-%m-%d %H:%M:%S')
return time.mktime(dt.timetuple())
try:
return time.mktime(date_parser.parse(dt))
except ValueError as ve:
logging.debug("%s: %s %s" % (dt,ve, sys.exc_info()))
dt = dt.split('.')[0]
dt = dt.split(',')[0]
try:
dt = datetime.strptime(dt.split('.')[0], '%Y-%m-%d %H:%M:%S')
return time.mktime(dt.timetuple())
except Exception as e:
# not a date
logging.debug("%s: %s" % (sys.exc_info(), e))
return time.time()

def _parse_stats(self):
self.duration = ''
Expand All @@ -91,7 +105,6 @@ def _parse_stats(self):
parts = line.split(']')
if len(parts) < 2:
continue

try:
line_timestamp = parts[0].strip('[')
line = ']'.join(parts[1:])
Expand Down Expand Up @@ -173,6 +186,8 @@ def _parse_stats(self):
def parse(self, ui, skip=False):
if skip:
logging.debug("skipping parsing of the last session logs ...")
elif self.log_type in ['syslog', 'console']:
logging.info("skipping last session stats with %s logging" % self.log_type)
else:
logging.debug("reading last session logs ...")

Expand Down Expand Up @@ -220,41 +235,54 @@ def setup_logging(args, config):
filenameDebug = cfg['path-debug']

#global formatter
formatter = logging.Formatter(cfg.get("format", "[%(asctime)s] [%(levelname)s] [%(threadName)s] : %(message)s"))
print("Format: %s" % cfg.get("format","<none>"))

formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] [%(threadName)s] : %(message)s")

logger = logging.getLogger()

dbg = args.debug or cfg.get('debug', False)

for handler in logger.handlers:
handler.setLevel(logging.DEBUG if args.debug else logging.INFO)
handler.setLevel(logging.DEBUG if dbg else logging.INFO)
handler.setFormatter(formatter)


logger.setLevel(logging.DEBUG if args.debug else logging.INFO)

if filename:
# since python default log rotation might break session data in different files,
# we need to do log rotation ourselves
log_rotation(filename, cfg)
log_rotation(filenameDebug, cfg)



# File handler for logging all normal messages
file_handler = logging.FileHandler(filename) #creates new
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

# File handler for logging all debug messages
file_handler = logging.FileHandler(filenameDebug) #creates new
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

# Console handler for logging debug messages if args.debug is true else just log normal
#console_handler = logging.StreamHandler() #creates new
#console_handler.setLevel(logging.DEBUG if args.debug else logging.INFO)
#console_handler.setFormatter(formatter)
#logger.addHandler(console_handler)
logger.setLevel(logging.DEBUG if dbg else logging.INFO)
log_type = cfg.get("type", "files").strip().lower() # files, syslog or console

if log_type == "syslog":
syslog_handler = logging.handlers.SysLogHandler(address='/dev/log', facility=syslog.LOG_USER)
syslog_handler.setLevel(logging.DEBUG if dbg else logging.INFO)
syslog_handler.setFormatter(formatter)
logger.addHandler(syslog_handler)

elif log_type == "console":
# Console handler for logging debug messages if args.debug is true else just log normal
console_handler = logging.StreamHandler() #creates new
console_handler.setLevel(logging.DEBUG if dbg else logging.INFO)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

else: # default is using file and debug file
if filename:
# since python default log rotation might break session data in different files,
# we need to do log rotation ourselves
log_rotation(filename, cfg)
# File handler for logging all normal messages
file_handler = logging.FileHandler(filename) #creates new
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

if filenameDebug:
log_rotation(filenameDebug, cfg)
# File handler for logging all debug messages
file_handler = logging.FileHandler(filenameDebug) #creates new
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

if not args.debug:
# disable scapy and tensorflow logging
Expand Down
3 changes: 2 additions & 1 deletion pwnagotchi/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ def __init__(self, plugin_name):

def __del__(self):
self.keep_going = False
self._worker_thread.join()
if self._worker_thread:
self._worker_thread.join()
if self.load_handler:
self.load_handler.join()

Expand Down
30 changes: 21 additions & 9 deletions pwnagotchi/plugins/default/pwncrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,50 @@ class UploadConvertPlugin(Plugin):
def __init__(self):
self.server_url = 'http://pwncrack.org/upload_handshake' # Leave this as is
self.potfile_url = 'http://pwncrack.org/download_potfile_script' # Leave this as is
self.timewait = 600
self.timewait = 30
self.last_run_time = 0
self.options = dict()

def on_loaded(self):
logging.info('[pwncrack] loading')
logging.info('[pwncrack] loading@@')

def on_config_changed(self, config):
self.handshake_dir = config["bettercap"].get("handshakes")
self.key = self.options.get('key', "") # Change this to your key
self.whitelist = config["main"].get("whitelist", [])
self.combined_file = os.path.join(self.handshake_dir, 'combined.hc22000')
self.potfile_path = os.path.join(self.handshake_dir, 'cracked.pwncrack.potfile')
self.last_upload_path = os.path.join(self.handshake_dir, '.pwncrack_last_up')

def on_internet_available(self, agent):
current_time = time.time()
remaining_wait_time = self.timewait - (current_time - self.last_run_time)
if remaining_wait_time > 0:
logging.debug(f"[pwncrack] Waiting {remaining_wait_time:.1f} more seconds before next run.")
logging.info(f"[pwncrack] Waiting {remaining_wait_time:.1f} more seconds before next run.")
return
self.last_run_time = current_time

if self.key == "":
logging.warn("PWNCrack enabled, but no api key specified. Add a key to config.toml")
return
logging.info(f"[pwncrack] Running upload process. Key: {self.key}, waiting: {self.timewait} seconds.")
try:
self._convert_and_upload()
self._download_potfile()
except Exception as e:
logging.error(f"[pwncrack] Error occurred during upload process: {e}", exc_info=True)

def on_ready(self, agent):
self.on_internet_available(agent)

def _convert_and_upload(self):
# Convert all .pcap files to .hc22000, excluding files matching whitelist items
last_up_time = os.path.getmtime(self.last_upload_path) if os.path.isfile(self.last_upload_path) else 0

pcap_files = [f for f in os.listdir(self.handshake_dir)
if f.endswith('.pcap') and not any(item in f for item in self.whitelist)]
if f.endswith('.pcap') and os.path.getmtime(os.path.join(self.handshake_dir,f)) > last_up_time and not any(item in f for item in self.whitelist)]
logging.info("Doing %s -> %s" % (self.whitelist, pcap_files))

if pcap_files:
for pcap_file in pcap_files:
subprocess.run(['hcxpcapngtool', '-o', self.combined_file, os.path.join(self.handshake_dir, pcap_file)])
Expand All @@ -60,9 +72,10 @@ def _convert_and_upload(self):
files = {'handshake': file}
data = {'key': self.key}
response = requests.post(self.server_url, files=files, data=data)

# Log the response
logging.info(f"[pwncrack] Upload response: {response.json()}")
# Log the response
logging.info(f"[pwncrack] Upload response({response.status_code}): {response.json()}")
with open(self.last_upload_path, 'w') as fout:
fout.write("\n".join(pcap_files))
os.remove(self.combined_file) # Remove the combined.hc22000 file
else:
logging.info("[pwncrack] No .pcap files found to convert (or all files are whitelisted).")
Expand All @@ -74,8 +87,7 @@ def _download_potfile(self):
file.write(response.text)
logging.info(f"[pwncrack] Potfile downloaded to {self.potfile_path}")
else:
logging.error(f"[pwncrack] Failed to download potfile: {response.status_code}")
logging.error(f"[pwncrack] {response.json()}") # Log the error message from the server
logging.error(f"[pwncrack] Failed to download potfile ({response.status_code}): {response.json()}")

def on_unload(self, ui):
logging.info('[pwncrack] unloading')
Loading