From fa2b8b97c4d383e055314b854292d0c95a535262 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Tue, 26 Nov 2024 07:34:37 +0100 Subject: [PATCH 01/25] Update package requirements Signed-off-by: jayofelony --- pwnagotchi/plugins/default/bt-tether.py | 2 +- pwnagotchi/ui/view.py | 145 ++++++------------------ 2 files changed, 36 insertions(+), 111 deletions(-) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 675f148d..cae4e765 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -174,7 +174,7 @@ class BTNap: """ Wait for device - returns device if found None if not + returns device if found, None if not """ logging.debug("BT-TETHER: Waiting for device") diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index abb5a744..d8e4ca03 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -1,4 +1,4 @@ -#import _thread +# import _thread import threading import logging import random @@ -6,7 +6,6 @@ import time from threading import Lock from PIL import ImageDraw -from PIL import ImageColor as colors import pwnagotchi import pwnagotchi.plugins as plugins @@ -19,105 +18,28 @@ from pwnagotchi.ui.components import * from pwnagotchi.ui.state import State from pwnagotchi.voice import Voice -WHITE = 0x00 # white is actually black on jays image -BLACK = 0xFF # black is actually white on jays image - -BACKGROUND_1 = 0 -FOREGROUND_1 = 1 - -BACKGROUND_L = 0 -FOREGROUND_L = 255 - -BACKGROUND_BGR_16 = (0,0,0) -FOREGROUND_BGR_16 = (31,63,31) - -BACKGROUND_RGB = (0,0,0) -FOREGROUND_RGB = (255,255,255) - - -ROOT = None - - - - -#1 (1-bit pixels, black and white, stored with one pixel per byte) - -#L (8-bit pixels, grayscale) - -#P (8-bit pixels, mapped to any other mode using a color palette) - -#BGR;16 (5,6,5 bits, for 65k color) - -#RGB (3x8-bit pixels, true color) - -#RGBA (4x8-bit pixels, true color with transparency mask) - -#CMYK (4x8-bit pixels, color separation) - -#YCbCr (3x8-bit pixels, color video format) - -#self.FOREGROUND is the main color -#self.BACKGROUNDGROUND is the 2ndary color, used for background - +WHITE = 0x00 # white is actually black on jays image +BLACK = 0xFF # black is actually white on jays image class View(object): def __init__(self, config, impl, state=None): global ROOT, BLACK, WHITE - - #values/code for display color mode - - self.mode = '1' # 1 = (1-bit pixels, black and white, stored with one pixel per byte) - if hasattr(impl, 'mode'): - self.mode = impl.mode - - - - match self.mode: - case '1': - self.BACKGROUND = BACKGROUND_1 - self.FOREGROUND = FOREGROUND_1 - # do stuff is color mode is 1 when View object is created. - case 'L': - self.BACKGROUND = BACKGROUND_L # black 0 to 255 - self.FOREGROUND = FOREGROUND_L - # do stuff is color mode is L when View object is created. - case 'P': - pass - # do stuff is color mode is P when View object is created. - case 'BGR;16': - self.BACKGROUND = BACKGROUND_BGR_16 #black tuple - self.FOREGROUND = FOREGROUND_BGR_16 #white tuple - case 'RGB': - self.BACKGROUND = BACKGROUND_RGB #black tuple - self.FOREGROUND = FOREGROUND_RGB #white tuple - # do stuff is color mode is RGB when View object is created. - case 'RGBA': - # do stuff is color mode is RGBA when View object is created. - pass - case 'CMYK': - # do stuff is color mode is CMYK when View object is created. - pass - case 'YCbCr': - # do stuff is color mode is YCbCr when View object is created. - pass - case _: - # do stuff when color mode doesnt exist for display - self.BACKGROUND = BACKGROUND_1 - self.FOREGROUND = FOREGROUND_1 - - + self.invert = 0 + self._black = 0xFF + self._white = 0x00 if 'invert' in config['ui'] and config['ui']['invert'] == True: - logging.debug("INVERT:" + str(config['ui']['invert'])) + logging.debug("INVERT BLACK/WHITES:" + str(config['ui']['invert'])) self.invert = 1 - tmp = self.FOREGROUND - self.FOREGROUND = self.FOREGROUND - self.FOREGROUND = tmp + BLACK = 0x00 + WHITE = 0xFF + self._black = 0x00 + self._white = 0xFF # setup faces from the configuration in case the user customized them faces.load_from_config(config['ui']['faces']) - + self._agent = None self._render_cbs = [] self._config = config @@ -130,40 +52,42 @@ class View(object): self._width = self._layout['width'] self._height = self._layout['height'] self._state = State(state={ - 'channel': LabeledValue(color=self.FOREGROUND, label='CH', value='00', position=self._layout['channel'], + 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'], label_font=fonts.Bold, text_font=fonts.Medium), - 'aps': LabeledValue(color=self.FOREGROUND, label='APS', value='0 (00)', position=self._layout['aps'], + 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'], label_font=fonts.Bold, text_font=fonts.Medium), - 'uptime': LabeledValue(color=self.FOREGROUND, label='UP', value='00:00:00', position=self._layout['uptime'], + 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'], label_font=fonts.Bold, text_font=fonts.Medium), - 'line1': Line(self._layout['line1'], color=self.FOREGROUND), - 'line2': Line(self._layout['line2'], color=self.FOREGROUND), + 'line1': Line(self._layout['line1'], color=BLACK), + 'line2': Line(self._layout['line2'], color=BLACK), - 'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=self.FOREGROUND, font=fonts.Huge, png=config['ui']['faces']['png']), + 'face': Text(value=faces.SLEEP, + position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), + color=BLACK, font=fonts.Huge, png=config['ui']['faces']['png']), - # 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=self.FOREGROUND), - 'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=self.FOREGROUND), + # 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK), + 'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=BLACK), - 'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=self.FOREGROUND, font=fonts.Bold), + 'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold), 'status': Text(value=self._voice.default(), position=self._layout['status']['pos'], - color=self.FOREGROUND, + color=BLACK, font=self._layout['status']['font'], wrap=True, # the current maximum number of characters per line, assuming each character is 6 pixels wide max_length=self._layout['status']['max']), - 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=self.FOREGROUND, + 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, position=self._layout['shakes'], label_font=fonts.Bold, text_font=fonts.Medium), 'mode': Text(value='AUTO', position=self._layout['mode'], - font=fonts.Bold, color=self.FOREGROUND), + font=fonts.Bold, color=BLACK), }) if state: @@ -173,8 +97,8 @@ class View(object): plugins.on('ui_setup', self) if config['ui']['fps'] > 0.0: - threading.Thread(target=self._refresh_handler, args=(), name="UI Handler", daemon = True).start() - + threading.Thread(target=self._refresh_handler, args=(), name="UI Handler", daemon=True).start() + self._ignore_changes = () else: logging.warning("ui.fps is 0, the display will only update for major changes") @@ -189,7 +113,7 @@ class View(object): self._state.has_element(key) def add_element(self, key, elem): - if self.invert is 1 and hasattr(elem, 'color'): + if self.invert is 1 and elem.color: if elem.color == 0xff: elem.color = 0x00 elif elem.color == 0x00: @@ -250,7 +174,8 @@ class View(object): self.set('uptime', last_session.duration) self.set('channel', '-') 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('shakes', '%d (%s)' % ( + last_session.handshakes, utils.total_unique_handshakes(self._config['bettercap']['handshakes']))) self.set_closest_peer(last_session.last_peer, last_session.peers) self.update() @@ -340,7 +265,7 @@ class View(object): def wait(self, secs, sleeping=True): was_normal = self.is_normal() - part = secs/10.0 + part = secs / 10.0 for step in range(0, 10): # if we weren't in a normal state before going @@ -468,13 +393,13 @@ class View(object): state = self._state changes = state.changes(ignore=self._ignore_changes) if force or len(changes): - self._canvas = Image.new(self.mode, (self._width, self._height), self.BACKGROUND) - drawer = ImageDraw.Draw(self._canvas, self.mode) + self._canvas = Image.new('1', (self._width, self._height), self._white) + drawer = ImageDraw.Draw(self._canvas) plugins.on('ui_update', self) for key, lv in state.items(): - #lv is a ui element + # lv is a ui element lv.draw(self._canvas, drawer) web.update_frame(self._canvas) From 12b386f74e50f40dbc10acdb88b05d512f98fa9f Mon Sep 17 00:00:00 2001 From: Kris Henriksen Date: Wed, 27 Nov 2024 09:01:44 +0700 Subject: [PATCH 02/25] Create wittypi.py Signed-off-by: Kris Henriksen --- pwnagotchi/plugins/default/wittypi.py | 75 +++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 pwnagotchi/plugins/default/wittypi.py diff --git a/pwnagotchi/plugins/default/wittypi.py b/pwnagotchi/plugins/default/wittypi.py new file mode 100644 index 00000000..fce3e5b6 --- /dev/null +++ b/pwnagotchi/plugins/default/wittypi.py @@ -0,0 +1,75 @@ +# Witty Pi 4 L3V7 +# +import logging +import pwnagotchi.plugins as plugins +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.components import LabeledValue +from pwnagotchi.ui.view import BLACK + +class UPS: + I2C_MC_ADDRESS = 0x08 + I2C_VOLTAGE_IN_I = 1 + I2C_VOLTAGE_IN_D = 2 + I2C_CURRENT_OUT_I = 5 + I2C_CURRENT_OUT_D = 6 + I2C_POWER_MODE = 7 + + def __init__(self): + # only import when the module is loaded and enabled + import smbus + # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1) + self._bus = smbus.SMBus(1) + + def voltage(self): + try: + i = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_VOLTAGE_IN_I) + d = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_VOLTAGE_IN_D) + return (i + d / 100) + except Exception as e: + logging.info(f"register={i} failed (exception={e})") + return 0.0 + + def current(self): + try: + i = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_CURRENT_OUT_I) + d = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_CURRENT_OUT_D) + return (i + d / 100) + except Exception as e: + logging.info(f"register={i} failed (exception={e})") + return 0.0 + + def capacity(self): + voltage = max(3.1, min(self.voltage(), 4.2)) # Clamp voltage + return round((voltage - 3.1) / (4.2 - 3.1) * 100) + + def charging(self): + try: + dc = self._bus.read_byte_data(self.I2C_MC_ADDRESS, self.I2C_POWER_MODE) + return '+' if dc == 0 else '-' + except: + return '-' + +class WittyPi(plugins.Plugin): + __author__ = 'https://github.com/krishenriksen' + __version__ = '1.0.0' + __license__ = 'GPL3' + __description__ = 'A plugin that will display battery info from Witty Pi 4 L3V7' + + def __init__(self): + self.ups = None + + def on_loaded(self): + self.ups = UPS() + logging.info("wittypi plugin loaded.") + + def on_ui_setup(self, ui): + ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%', position=(ui.width() / 2 + 15, 0), label_font=fonts.Bold, text_font=fonts.Medium)) + + def on_unload(self, ui): + with ui._lock: + ui.remove_element('ups') + + def on_ui_update(self, ui): + capacity = self.ups.capacity() + charging = self.ups.charging() + ui.set('ups', "%2i%s" % (capacity, charging)) From cf1e8aa4faf7089c56bb79547320edc7c2bed74f Mon Sep 17 00:00:00 2001 From: jayofelony Date: Wed, 27 Nov 2024 16:38:17 +0100 Subject: [PATCH 03/25] Remove workflow, as it is deprecated now. Signed-off-by: jayofelony --- .github/workflows/publish.yml | 55 ----------------------------------- 1 file changed, 55 deletions(-) delete mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index ed066f4e..00000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Publish - -on: - workflow_dispatch: - -jobs: - build: - name: ${{ matrix.name }} - runs-on: ubuntu-latest - strategy: - matrix: - include: - - name: "Raspberry Pi 32-bit" - id: "32bit" - - name: "Raspberry Pi 64-bit" - id: "64bit" - - steps: - - uses: actions/checkout@v4 - with: - path: publish/build - - - name: Extract version from file - id: get_version - run: | - VERSION=$(cut -d "'" -f2 < publish/build/pwnagotchi/_version.py) - echo "VERSION=$VERSION" >> $GITHUB_ENV - - - name: Install qemu dependencies - run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y - - - name: Build ${{ matrix.name }} img file - run: cd publish/build; ls -la .; pwd; make packer; make ${{ matrix.id }} - - - name: Change name of .img.xz to add version - run: | - sudo chown runner:docker "pwnagotchi-${{ matrix.id }}.img" - mv "pwnagotchi-${{ matrix.id }}.img" "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img" - - - name: PiShrink - run: | - wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh - chmod +x pishrink.sh - sudo mv pishrink.sh /usr/local/bin - sudo pishrink.sh -aZ "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img" - - - name: Release - uses: softprops/action-gh-release@v2 - with: - prerelease: false - make_latest: true - tag_name: v${{ env.VERSION }} - name: Pwnagotchi v${{ env.VERSION }} - files: pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img.xz - generate_release_notes: true From 4ade52925d378d8dfbfac64f749787afb6ac8907 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Fri, 29 Nov 2024 09:23:20 +0100 Subject: [PATCH 04/25] Update build --- pwnagotchi/locale/ja/LC_MESSAGES/voice.mo | Bin 5857 -> 5857 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/pwnagotchi/locale/ja/LC_MESSAGES/voice.mo b/pwnagotchi/locale/ja/LC_MESSAGES/voice.mo index 57bfecff8fca64f02633275e78d42f8e6085f612..f235ecfe6f5e9cfdbc0b6df97eb6f9f4d4a732ff 100644 GIT binary patch delta 15 XcmaE;`%rho6CNfLgU!!)escl Date: Sat, 30 Nov 2024 08:25:39 +0700 Subject: [PATCH 05/25] Create gps_listener.py Receive GPS coordinates via termux-location and save whenever an handshake is captured. Signed-off-by: Kris Henriksen --- pwnagotchi/plugins/default/gps_listener.py | 233 +++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 pwnagotchi/plugins/default/gps_listener.py diff --git a/pwnagotchi/plugins/default/gps_listener.py b/pwnagotchi/plugins/default/gps_listener.py new file mode 100644 index 00000000..88d27723 --- /dev/null +++ b/pwnagotchi/plugins/default/gps_listener.py @@ -0,0 +1,233 @@ +import json +import logging +import os +import subprocess +import threading + +import pwnagotchi.plugins as plugins +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.components import LabeledValue +from pwnagotchi.ui.view import BLACK + +""" +# Android +# Termux:API : https://f-droid.org/en/packages/com.termux.api/ +# Termux : https://f-droid.org/en/packages/com.termux/ +pkg install termux-api socat bc + +----- +#!/data/data/com.termux/files/usr/bin/bash + +# Server details +SERVER_IP="192.168.44.45" # IP of the socat receiver +SERVER_PORT="5000" # UDP port to send data to + +# Function to calculate checksum +calculate_checksum() { + local sentence="$1" + local checksum=0 + # Loop through each character in the sentence + for ((i = 0; i < ${#sentence}; i++)); do + checksum=$((checksum ^ $(printf '%d' "'${sentence:i:1}"))) + done + # Return checksum in hexadecimal + printf "%02X" $checksum +} + +# Infinite loop to send GPS data +while true; do + # Get location data + LOCATION=$(termux-location -p gps) + + # Extract latitude, longitude, altitude, speed, and bearing + LATITUDE=$(echo "$LOCATION" | jq '.latitude') + LONGITUDE=$(echo "$LOCATION" | jq '.longitude') + ALTITUDE=$(echo "$LOCATION" | jq '.altitude') + SPEED=$(echo "$LOCATION" | jq '.speed') # Speed in meters per second + BEARING=$(echo "$LOCATION" | jq '.bearing') + + # Convert speed from meters per second to knots and km/h + SPEED_KNOTS=$(echo "$SPEED" | awk '{printf "%.1f", $1 * 1.943844}') + SPEED_KMH=$(echo "$SPEED" | awk '{printf "%.1f", $1 * 3.6}') + + # Format latitude and longitude for NMEA + LAT_DEGREES=$(printf "%.0f" "${LATITUDE%.*}") + LAT_MINUTES=$(echo "(${LATITUDE#${LAT_DEGREES}} * 60)" | bc -l) + LAT_DIRECTION=$(if (( $(echo "$LATITUDE >= 0" | bc -l) )); then echo "N"; else echo "S"; fi) + LON_DEGREES=$(printf "%.0f" "${LONGITUDE%.*}") + LON_MINUTES=$(echo "(${LONGITUDE#${LON_DEGREES}} * 60)" | bc -l) + LON_DIRECTION=$(if (( $(echo "$LONGITUDE >= 0" | bc -l) )); then echo "E"; else echo "W"; fi) + + # Format the NMEA GGA sentence + RAW_NMEA_GGA="GPGGA,123519,$(printf "%02d%07.4f" ${LAT_DEGREES#-} $LAT_MINUTES),$LAT_DIRECTION,$(printf "%03d%07.4f" ${LON_DEGREES#-} $LON_MINUTES),$LON_DIRECTION,1,08,0.9,$(printf "%.1f" $ALTITUDE),M,46.9,M,," + CHECKSUM=$(calculate_checksum "$RAW_NMEA_GGA") + NMEA_GGA="\$${RAW_NMEA_GGA}*${CHECKSUM}" + + # Format the VTG sentence + RAW_NMEA_VTG="GPVTG,$(printf "%.1f" $BEARING),T,,M,$(printf "%.1f" $SPEED_KNOTS),N,$(printf "%.1f" $SPEED_KMH),K" + CHECKSUM_VTG=$(calculate_checksum "$RAW_NMEA_VTG") + NMEA_VTG="\$${RAW_NMEA_VTG}*${CHECKSUM_VTG}" + + # Send data via UDP + echo "$NMEA_GGA" + echo "$NMEA_GGA" | socat - UDP:$SERVER_IP:$SERVER_PORT + #echo "$NMEA_VTG" + #echo "$NMEA_VTG" | socat - UDP:$SERVER_IP:$SERVER_PORT + + sleep 1 +done +----- + +# Pwnagotchi +main.plugins.gps_listener.enabled = true + +# packages +sudo apt-get install socat +""" + +class GPS(plugins.Plugin): + __author__ = 'https://github.com/krishenriksen' + __version__ = "1.0.0" + __license__ = "GPL3" + __description__ = "Receive GPS coordinates via termux-location and save whenever an handshake is captured." + + def __init__(self): + self.listen_ip = self.get_ip_address('bnep0') + self.listen_port = "5000" + self.write_virtual_serial = "/dev/ttyUSB1" + self.read_virtual_serial = "/dev/ttyUSB0" + self.baud_rate = "19200" + self.socat_process = None + self.stop_event = threading.Event() + self.status_lock = threading.Lock() + self.status = '-' + self.socat_thread = threading.Thread(target=self.run_socat) + + def get_ip_address(self, interface): + try: + result = subprocess.run( + ["ip", "addr", "show", interface], + capture_output=True, + text=True, + check=True + ) + for line in result.stdout.split('\n'): + if 'inet ' in line: + ip_address = line.strip().split()[1].split('/')[0] + return ip_address + except subprocess.CalledProcessError: + logging.warning(f"Could not get IP address for interface {interface}") + return None + + def set_status(self, status): + with self.status_lock: + self.status = status + + def get_status(self): + with self.status_lock: + return self.status + + def on_loaded(self): + logging.info("GPS Listener plugin loaded") + self.cleanup_virtual_serial_ports() + self.create_virtual_serial_ports() + self.socat_thread.start() + + def cleanup_virtual_serial_ports(self): + if os.path.exists(self.write_virtual_serial): + self.log.info(f"Removing old {self.write_virtual_serial}") + os.remove(self.write_virtual_serial) + + if os.path.exists(self.read_virtual_serial): + self.log.info(f"Removing old {self.read_virtual_serial}") + os.remove(self.read_virtual_serial) + + def create_virtual_serial_ports(self): + self.socat_process = subprocess.Popen( + ["socat", "-d", "-d", f"pty,link={self.write_virtual_serial},mode=777", + f"pty,link={self.read_virtual_serial},mode=777"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + def run_socat(self): + while not self.stop_event.is_set(): + self.socat_process = subprocess.Popen( + ["socat", f"UDP-RECVFROM:{self.listen_port},reuseaddr,bind={self.listen_ip}", "-"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + self.set_status('C') + + with open(self.write_virtual_serial, 'w') as serial_port: + for line in self.socat_process.stdout: + if self.stop_event.is_set(): + break + serial_port.write(line) + serial_port.flush() # Ensure the data is written immediately + self.status = 'C' + + self.socat_process.wait() + if self.stop_event.is_set(): + break + + self.set_status('-') + + def cleanup(self): + if self.socat_process: + self.socat_process.terminate() + self.socat_process.wait() # Ensure the process is reaped + self.stop_event.set() + self.socat_thread.join() + self.cleanup_virtual_serial_ports() + + def on_ready(self, agent): + if os.path.exists(self.read_virtual_serial): + logging.info( + f"enabling bettercap's gps module for {self.read_virtual_serial}" + ) + try: + agent.run("gps off") + except Exception: + logging.info(f"bettercap gps module was already off") + pass + + agent.run(f"set gps.device {self.read_virtual_serial}") + agent.run(f"set gps.baudrate {self.baud_rate}") + agent.run("gps on") + + logging.info(f"bettercap gps module enabled on {self.read_virtual_serial}") + else: + self.set_status('NF') + logging.warning("no GPS detected") + + def on_handshake(self, agent, filename, access_point, client_station): + info = agent.session() + coordinates = info["gps"] + gps_filename = filename.replace(".pcap", ".gps.json") + + if coordinates and all([ + # avoid 0.000... measurements + coordinates["Latitude"], coordinates["Longitude"] + ]): + self.set_status('S') + logging.info(f"saving GPS to {gps_filename} ({coordinates})") + with open(gps_filename, "w+t") as fp: + json.dump(coordinates, fp) + else: + logging.warning("not saving GPS. Couldn't find location.") + + def on_ui_setup(self, ui): + with ui._lock: + ui.add_element('gps', LabeledValue(color=BLACK, label='GPS', value='-', position=(ui.width() / 2 - 40, 0), label_font=fonts.Bold, text_font=fonts.Medium)) + + def on_unload(self, ui): + self.cleanup() + + with ui._lock: + ui.remove_element('gps') + + def on_ui_update(self, ui): + ui.set('gps', self.get_status()) From f6f30632f8ab2e87ba1ac1045a914bf72acd0842 Mon Sep 17 00:00:00 2001 From: RasTacsko Date: Sun, 1 Dec 2024 12:47:34 +0100 Subject: [PATCH 06/25] Display update for 2.9.2 no AI display check with 2.9.2 -gfxhat sn3218 library missing from the venv, added to /hw/libs/pimoroni/gfxhat/ cleanup of the code (EPD changed to LCD) logging info added for config options (contrast, color) touch control work in progress (6 touch buttons with short and long press and LED feedback) -i2coled cleanup of the code (EPD changed to OLED) Logging info added for config options (width, height, i2c address) -oledlcd and oledlcdvert working out of the box logging info added for available gpio breakouts (buttons) -displayhatmini working out of the box logging info added for available gpio breakouts (buttons, rgb LED, i2c bus) -pirateaudio working out of the box logging info added for available gpio breakouts (buttons, i2s bus) -pitft working out of the box logging info added for available gpio breakouts (buttons) -tftbonnet working out of the box logging info added for available gpio breakouts (buttons, i2c bus) -minipitft working out of the box logging info added for available gpio breakouts (buttons, i2c bus) -minipitft2 cant test on HW but should work! logging info added for available gpio breakouts (buttons, i2c bus) -argonpod cant test on HW but should work! logging info added for available gpio breakouts (buttons, IR) --- pwnagotchi/ui/hw/argonpod.py | 15 +- pwnagotchi/ui/hw/displayhatmini.py | 9 +- pwnagotchi/ui/hw/gfxhat.py | 16 +- pwnagotchi/ui/hw/i2coled.py | 8 +- .../ui/hw/libs/i2coled/{epd.py => oled.py} | 2 +- .../ui/hw/libs/pimoroni/gfxhat/backlight.py | 2 +- pwnagotchi/ui/hw/libs/pimoroni/gfxhat/epd.py | 55 ----- pwnagotchi/ui/hw/libs/pimoroni/gfxhat/lcd.py | 98 ++++---- .../ui/hw/libs/pimoroni/gfxhat/sn3218.py | 214 ++++++++++++++++++ pwnagotchi/ui/hw/minipitft.py | 9 +- pwnagotchi/ui/hw/minipitft2.py | 7 +- pwnagotchi/ui/hw/pirateaudio.py | 4 + pwnagotchi/ui/hw/pitft.py | 3 + pwnagotchi/ui/hw/tftbonnet.py | 3 + pwnagotchi/ui/hw/waveshareoledlcd.py | 1 + pwnagotchi/ui/hw/waveshareoledlcdvert.py | 1 + 16 files changed, 316 insertions(+), 131 deletions(-) rename pwnagotchi/ui/hw/libs/i2coled/{epd.py => oled.py} (95%) delete mode 100644 pwnagotchi/ui/hw/libs/pimoroni/gfxhat/epd.py create mode 100644 pwnagotchi/ui/hw/libs/pimoroni/gfxhat/sn3218.py diff --git a/pwnagotchi/ui/hw/argonpod.py b/pwnagotchi/ui/hw/argonpod.py index 1749df34..cb416b57 100644 --- a/pwnagotchi/ui/hw/argonpod.py +++ b/pwnagotchi/ui/hw/argonpod.py @@ -1,12 +1,12 @@ # board GPIO: -# Key1: -# Key2: -# Key3: -# Key4: -# +# Key1: GPIO 16 +# Key2: GPIO 20 +# Key3: GPIO 21 +# Key4: GPIO 26 +# IR: GPIO 23 # Touch chipset: # HW info: https://argon40.com/products/pod-display-2-8inch -# HW datasheet: +# HW MANUAL: https://cdn.shopify.com/s/files/1/0556/1660/2177/files/ARGON_POD_MANUAL.pdf?v=1668390711%0A import logging @@ -44,6 +44,9 @@ class ArgonPod(DisplayImpl): def initialize(self): logging.info("Initializing Argon Pod display") + logging.info("Available pins for GPIO Buttons: 16, 20, 21, 26") + logging.info("IR available on GPIO 23") + logging.info("Backlight pin available on GPIO 18") from pwnagotchi.ui.hw.libs.argon.argonpod.ILI9341 import ILI9341 self._display = ILI9341(0, 0, 22, 18) diff --git a/pwnagotchi/ui/hw/displayhatmini.py b/pwnagotchi/ui/hw/displayhatmini.py index 55cb86ef..ba9cbb8a 100644 --- a/pwnagotchi/ui/hw/displayhatmini.py +++ b/pwnagotchi/ui/hw/displayhatmini.py @@ -1,9 +1,7 @@ import logging - import pwnagotchi.ui.fonts as fonts from pwnagotchi.ui.hw.base import DisplayImpl - class DisplayHatMini(DisplayImpl): def __init__(self, config): super(DisplayHatMini, self).__init__(config, 'displayhatmini') @@ -29,11 +27,14 @@ class DisplayHatMini(DisplayImpl): 'font': fonts.status_font(fonts.Medium), 'max': 20 } - return self._layout def initialize(self): - logging.info("initializing Display Hat Mini") + logging.info("Initializing Display Hat Mini") + logging.info("Available pins for GPIO Buttons A/B/X/Y: 5, 6, 16, 24") + logging.info("Available pins for RGB Led: 17, 27, 22") + logging.info("Backlight pin available on GPIO 13") + logging.info("I2C bus available on stemma QT and Breakout Garden headers") from pwnagotchi.ui.hw.libs.pimoroni.displayhatmini.ST7789 import ST7789 self._display = ST7789(0,1,9,13) diff --git a/pwnagotchi/ui/hw/gfxhat.py b/pwnagotchi/ui/hw/gfxhat.py index ced3d5ea..51160402 100644 --- a/pwnagotchi/ui/hw/gfxhat.py +++ b/pwnagotchi/ui/hw/gfxhat.py @@ -8,7 +8,7 @@ # ui.display.blcolor = "olive" # # Contrast should be between 30-50, default is 40 -# Backlight are predefined in the epd.py +# Backlight are predefined in the lcd.py # Available backlight colors: # white, grey, maroon, red, purple, fuchsia, green, # lime, olive, yellow, navy, blue, teal, aqua @@ -48,10 +48,16 @@ class GfxHat(DisplayImpl): def initialize(self): contrast = self._config['contrast'] if 'contrast' in self._config else 40 blcolor = self._config['blcolor'] if 'blcolor' in self._config else 'OLIVE' - logging.info("initializing Pimoroni GfxHat") - logging.info("initializing Pimoroni GfxHat - Contrast: %d Backlight color: %s" % (contrast, blcolor)) - from pwnagotchi.ui.hw.libs.pimoroni.gfxhat.epd import EPD - self._display = EPD(contrast=contrast) + logging.info("Initializing Pimoroni GfxHat - Contrast: %d Backlight color: %s" % (contrast, blcolor)) + logging.info("Available config options: ui.display.contrast and ui.display.color") + logging.info("Contrast should be between 30-50, default is 40") + logging.info("Backlight are predefined in the lcd.py") + logging.info("Available backlight colors:") + logging.info("white, grey, maroon, red, purple, fuchsia, green,") + logging.info("lime, olive, yellow, navy, blue, teal, aqua") + logging.info("Touch control work in progress (6 touch buttons with short and long press and LED feedback)") + from pwnagotchi.ui.hw.libs.pimoroni.gfxhat.lcd import LCD + self._display = LCD(contrast=contrast) self._display.Init(color_name=blcolor) self._display.Clear() diff --git a/pwnagotchi/ui/hw/i2coled.py b/pwnagotchi/ui/hw/i2coled.py index 0f58e4bb..402c3657 100644 --- a/pwnagotchi/ui/hw/i2coled.py +++ b/pwnagotchi/ui/hw/i2coled.py @@ -52,11 +52,11 @@ class I2COled(DisplayImpl): i2caddr = self._config['i2c_addr'] if 'i2c_addr' in self._config else 0x3C width = self._config['width'] if 'width' in self._config else 128 height = self._config['height'] if 'height' in self._config else 64 + logging.info("Initializing SSD1306 based %dx%d I2C Oled Display on address 0x%X" % (width, height, i2caddr)) + logging.info("Available config options: ui.display.width, ui.display.height and ui.display.i2caddr") - logging.info("initializing %dx%d I2C Oled Display on address 0x%X" % (width, height, i2caddr)) - - from pwnagotchi.ui.hw.libs.i2coled.epd import EPD - self._display = EPD(address=i2caddr, width=width, height=height) + from pwnagotchi.ui.hw.libs.i2coled.oled import OLED + self._display = OLED(address=i2caddr, width=width, height=height) self._display.Init() self._display.Clear() diff --git a/pwnagotchi/ui/hw/libs/i2coled/epd.py b/pwnagotchi/ui/hw/libs/i2coled/oled.py similarity index 95% rename from pwnagotchi/ui/hw/libs/i2coled/epd.py rename to pwnagotchi/ui/hw/libs/i2coled/oled.py index 12023664..347afb4d 100644 --- a/pwnagotchi/ui/hw/libs/i2coled/epd.py +++ b/pwnagotchi/ui/hw/libs/i2coled/oled.py @@ -9,7 +9,7 @@ EPD_HEIGHT = 64 # disp = SSD1306.SSD1306_96_16(96, 16, address=0x3C) # If you change for different resolution, you have to modify the layout in pwnagotchi/ui/hw/i2coled.py -class EPD(object): +class OLED(object): def __init__(self, address=0x3C, width=EPD_WIDTH, height=EPD_HEIGHT): self.width = width diff --git a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/backlight.py b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/backlight.py index 98ccc885..59a15f1f 100644 --- a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/backlight.py +++ b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/backlight.py @@ -9,7 +9,7 @@ LED_MAP = [2, 1, 0, 5, 4, 3] def setup(): """Set up the backlight on GFX HAT.""" global _sn3218 - import sn3218 as _sn3218 + from . import sn3218 as _sn3218 _sn3218.enable() _sn3218.enable_leds(0b111111111111111111) diff --git a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/epd.py b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/epd.py deleted file mode 100644 index 48768e0f..00000000 --- a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/epd.py +++ /dev/null @@ -1,55 +0,0 @@ -from . import st7567 -from . import backlight -CONTRAST = 40 - -# Define RGB colors -WHITE = (255, 255, 255) -GREY = (255, 255, 255) -MAROON = (128, 0, 0) -RED = (255, 0, 0) -PURPLE = (128, 0, 128) -FUCHSIA = (255, 0, 255) -GREEN = (0, 128, 0) -LIME = (0, 255, 0) -OLIVE = (128, 128, 0) -YELLOW = (255, 255, 0) -NAVY = (0, 0, 128) -BLUE = (0, 0, 255) -TEAL = (0, 128, 128) -AQUA = (0, 255, 255) - -# Map color names to RGB values -color_map = { - 'WHITE': WHITE, - 'GREY' : GREY, - 'MAROON': MAROON, - 'RED': RED, - 'PURPLE': PURPLE, - 'FUCHSIA': FUCHSIA, - 'GREEN' : GREEN, - 'LIME' : LIME, - 'OLIVE' : OLIVE, - 'YELLOW' : YELLOW, - 'NAVY' : NAVY, - 'BLUE' : BLUE, - 'TEAL' : TEAL, - 'AQUA' : AQUA -} - -class EPD(object): - - def __init__(self, contrast=CONTRAST, blcolor=('OLIVE')): - self.disp = st7567.ST7567() - self.disp.contrast(contrast) - - def Init(self, color_name): - self.disp.setup() - blcolor = color_map.get(color_name.upper(), OLIVE) # Default to olive if color not found - backlight.set_all(*blcolor) - backlight.show() - - def Clear(self): - self.disp.clear() - - def Display(self, image): - self.disp.show(image) \ No newline at end of file diff --git a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/lcd.py b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/lcd.py index 9ba0e4aa..b6644d25 100644 --- a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/lcd.py +++ b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/lcd.py @@ -1,57 +1,55 @@ -"""Library for the GFX HAT ST7567 SPI LCD.""" -from .st7567 import ST7567 +from . import st7567 +from . import backlight +CONTRAST = 40 -st7567 = ST7567() +# Define RGB colors +WHITE = (255, 255, 255) +GREY = (255, 255, 255) +MAROON = (128, 0, 0) +RED = (255, 0, 0) +PURPLE = (128, 0, 128) +FUCHSIA = (255, 0, 255) +GREEN = (0, 128, 0) +LIME = (0, 255, 0) +OLIVE = (128, 128, 0) +YELLOW = (255, 255, 0) +NAVY = (0, 0, 128) +BLUE = (0, 0, 255) +TEAL = (0, 128, 128) +AQUA = (0, 255, 255) -dimensions = st7567.dimensions +# Map color names to RGB values +color_map = { + 'WHITE': WHITE, + 'GREY' : GREY, + 'MAROON': MAROON, + 'RED': RED, + 'PURPLE': PURPLE, + 'FUCHSIA': FUCHSIA, + 'GREEN' : GREEN, + 'LIME' : LIME, + 'OLIVE' : OLIVE, + 'YELLOW' : YELLOW, + 'NAVY' : NAVY, + 'BLUE' : BLUE, + 'TEAL' : TEAL, + 'AQUA' : AQUA +} +class LCD(object): -def clear(): - """Clear GFX HAT's display buffer.""" - st7567.clear() + def __init__(self, contrast=CONTRAST, blcolor=('OLIVE')): + self.disp = st7567.ST7567() + self.disp.contrast(contrast) + def Init(self, color_name): + self.disp.setup() + blcolor = color_map.get(color_name.upper(), OLIVE) # Default to olive if color not found + backlight.set_all(*blcolor) + backlight.show() -def set_pixel(x, y, value): - """Set a single pixel in GTX HAT's display buffer. + def Clear(self): + self.disp.clear() - :param x: X position (from 0 to 127) - :param y: Y position (from 0 to 63) - :param value: pixel state 1 = On, 0 = Off - - """ - st7567.set_pixel(x, y, value) - - -def show(): - """Update GFX HAT with the current buffer contents.""" - st7567.show() - - -def contrast(value): - """Change GFX HAT LCD contrast.""" - st7567.contrast(value) - - -def rotation(r=0): - """Set the display rotation. - - :param r: Specify the rotation in degrees: 0, or 180 - - """ - if r == 0: - st7567.rotated = False - - elif r == 180: - st7567.rotated = True - - else: - raise ValueError('Rotation must be 0 or 180 degrees') - - -def get_rotation(): - """Get the display rotation value. - - Returns an integer, either 0, or 180 - - """ - return 180 if st7567.rotated else 0 + def Display(self, image): + self.disp.show(image) \ No newline at end of file diff --git a/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/sn3218.py b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/sn3218.py new file mode 100644 index 00000000..5ea24d29 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/pimoroni/gfxhat/sn3218.py @@ -0,0 +1,214 @@ +import sys +import warnings + +__version__ = '1.2.7' + +I2C_ADDRESS = 0x54 +CMD_ENABLE_OUTPUT = 0x00 +CMD_SET_PWM_VALUES = 0x01 +CMD_ENABLE_LEDS = 0x13 +CMD_UPDATE = 0x16 +CMD_RESET = 0x17 + +if sys.version_info < (3, ): + SMBUS = "python-smbus" +else: + SMBUS = "python3-smbus" + +# Helper function to avoid exception chaining output in python3. +# use exec to shield newer syntax from older python +if sys.version_info < (3, 3): + def _raise_from_none(exc): + raise exc +else: + exec("def _raise_from_none(exc): raise exc from None") + +try: + from smbus import SMBus +except ImportError: + err_string = "This library requires {smbus}\nInstall with: sudo apt install {smbus}".format(smbus=SMBUS) + import_error = ImportError(err_string) + _raise_from_none(import_error) + +def i2c_bus_id(): + """ + Returns the i2c bus ID. + """ + with open('/proc/cpuinfo') as cpuinfo: + revision = [l[12:-1] for l in cpuinfo if l[:8] == "Revision"][0] + # https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md + return 1 if int(revision, 16) >= 4 else 0 + + +def enable(): + """ + Enables output. + """ + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_ENABLE_OUTPUT, [0x01]) + + +def disable(): + """ + Disables output. + """ + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_ENABLE_OUTPUT, [0x00]) + + +def reset(): + """ + Resets all internal registers. + """ + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_RESET, [0xFF]) + + +def enable_leds(enable_mask): + """ + Enables or disables each LED channel. The first 18 bit values are + used to determine the state of each channel (1=on, 0=off) if fewer + than 18 bits are provided the remaining channels are turned off. + + Args: + enable_mask (int): up to 18 bits of data + Raises: + TypeError: if enable_mask is not an integer. + """ + if not isinstance(enable_mask, int): + raise TypeError("enable_mask must be an integer") + + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_ENABLE_LEDS, + [enable_mask & 0x3F, (enable_mask >> 6) & 0x3F, (enable_mask >> 12) & 0X3F]) + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_UPDATE, [0xFF]) + + +def channel_gamma(channel, gamma_table): + """ + Overrides the gamma table for a single channel. + + Args: + channel (int): channel number + gamma_table (list): list of 256 gamma correction values + Raises: + TypeError: if channel is not an integer. + ValueError: if channel is not in the range 0..17. + TypeError: if gamma_table is not a list. + """ + global channel_gamma_table + + if not isinstance(channel, int): + raise TypeError("channel must be an integer") + + if channel not in range(18): + raise ValueError("channel be an integer in the range 0..17") + + if not isinstance(gamma_table, list) or len(gamma_table) != 256: + raise TypeError("gamma_table must be a list of 256 integers") + + channel_gamma_table[channel] = gamma_table + + +def output(values): + """ + Outputs a new set of values to the driver. + + Args: + values (list): channel number + Raises: + TypeError: if values is not a list of 18 integers. + """ + if not isinstance(values, list) or len(values) != 18: + raise TypeError("values must be a list of 18 integers") + + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_SET_PWM_VALUES, [channel_gamma_table[i][values[i]] for i in range(18)]) + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_UPDATE, [0xFF]) + + +def output_raw(values): + """ + Outputs a new set of values to the driver. + Similar to output(), but does not use channel_gamma_table. + + Args: + values (list): channel number + Raises: + TypeError: if values is not a list of 18 integers. + """ + # SMBus.write_i2c_block_data does the type check, so we don't have to + if len(values) != 18: + raise TypeError("values must be a list of 18 integers") + + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_SET_PWM_VALUES, values) + i2c.write_i2c_block_data(I2C_ADDRESS, CMD_UPDATE, [0xFF]) + + +try: + i2c = SMBus(i2c_bus_id()) +except IOError as e: + warntxt=""" +###### ###### ###### ###### ###### ###### ###### ###### +i2c initialization failed - is i2c enabled on this system? +See https://github.com/pimoroni/sn3218/wiki/missing-i2c +###### ###### ###### ###### ###### ###### ###### ###### +""" + warnings.warn(warntxt) + raise(e) + +# generate a good default gamma table +default_gamma_table = [int(pow(255, float(i - 1) / 255)) for i in range(256)] +channel_gamma_table = [default_gamma_table] * 18 + +enable_leds(0b111111111111111111) + +def test_cycles(): + print("sn3218 test cycles") + + import time + import math + + # enable output + enable() + enable_leds(0b111111111111111111) + + print(">> test enable mask (on/off)") + enable_mask = 0b000000000000000000 + output([0x10] * 18) + for i in range(10): + enable_mask = ~enable_mask + enable_leds(enable_mask) + time.sleep(0.15) + + print(">> test enable mask (odd/even)") + enable_mask = 0b101010101010101010 + output([0x10] * 18) + for i in range(10): + enable_mask = ~enable_mask + enable_leds(enable_mask) + time.sleep(0.15) + + print(">> test enable mask (rotate)") + enable_mask = 0b100000100000100000 + output([0x10] * 18) + for i in range(10): + enable_mask = ((enable_mask & 0x01) << 18) | enable_mask >> 1 + enable_leds(enable_mask) + time.sleep(0.15) + + print(">> test gamma gradient") + enable_mask = 0b111111111111111111 + enable_leds(enable_mask) + for i in range(256): + output([((j * (256//18)) + (i * (256//18))) % 256 for j in range(18)]) + time.sleep(0.01) + + print(">> test gamma fade") + enable_mask = 0b111111111111111111 + enable_leds(enable_mask) + for i in range(512): + output([int((math.sin(float(i)/64.0) + 1.0) * 128.0)]*18) + time.sleep(0.01) + + # turn everything off and disable output + output([0 for i in range(18)]) + disable() + +if __name__ == "__main__": + test_cycles() \ No newline at end of file diff --git a/pwnagotchi/ui/hw/minipitft.py b/pwnagotchi/ui/hw/minipitft.py index 02efff78..9f859f1d 100644 --- a/pwnagotchi/ui/hw/minipitft.py +++ b/pwnagotchi/ui/hw/minipitft.py @@ -1,6 +1,6 @@ # board GPIO: -# A: GPIO22 -# B: GPIO23 +# A: GPIO23 +# B: GPIO24 # # HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview @@ -38,7 +38,10 @@ class MiniPitft(DisplayImpl): return self._layout def initialize(self): - logging.info("initializing Adafruit Mini Pi Tft 240x240") + logging.info("Initializing Adafruit Mini Pi Tft 240x240") + logging.info("Available pins for GPIO Buttons: 23, 24") + logging.info("Backlight pin available on GPIO 22") + logging.info("I2C bus available on stemma QT header") from pwnagotchi.ui.hw.libs.adafruit.minipitft.ST7789 import ST7789 self._display = ST7789(0,0,25,22) diff --git a/pwnagotchi/ui/hw/minipitft2.py b/pwnagotchi/ui/hw/minipitft2.py index d8905d05..bf9a5c71 100644 --- a/pwnagotchi/ui/hw/minipitft2.py +++ b/pwnagotchi/ui/hw/minipitft2.py @@ -1,6 +1,6 @@ # board GPIO: -# A: GPIO22 -# B: GPIO23 +# A: GPIO23 +# B: GPIO24 # # HW datasheet: https://learn.adafruit.com/adafruit-1-3-color-tft-bonnet-for-raspberry-pi/overview @@ -39,6 +39,9 @@ class MiniPitft2(DisplayImpl): def initialize(self): logging.info("initializing Adafruit Mini Pi Tft 135x240") + logging.info("Available pins for GPIO Buttons: 23, 24") + logging.info("Backlight pin available on GPIO 22") + logging.info("I2C bus available on stemma QT header") from pwnagotchi.ui.hw.libs.adafruit.minipitft2.ST7789 import ST7789 self._display = ST7789(0,0,25,22) diff --git a/pwnagotchi/ui/hw/pirateaudio.py b/pwnagotchi/ui/hw/pirateaudio.py index e55a6226..5a570b7e 100644 --- a/pwnagotchi/ui/hw/pirateaudio.py +++ b/pwnagotchi/ui/hw/pirateaudio.py @@ -45,6 +45,10 @@ class PirateAudio(DisplayImpl): def initialize(self): logging.info("Initializing PirateAudio - display only") + logging.info("Available pins for GPIO Buttons A/B/X/Y: 5, 6, 16, 20 or 24") + logging.info("refer to the pimoroni site or pinout.xyz") + logging.info("Backlight pin available on GPIO 13") + logging.info("I2S for the DAC available on pins: 18, 19 and 21") from pwnagotchi.ui.hw.libs.pimoroni.pirateaudio.ST7789 import ST7789 self._display = ST7789(0,1,9,13) diff --git a/pwnagotchi/ui/hw/pitft.py b/pwnagotchi/ui/hw/pitft.py index dbf6c358..4334af5c 100644 --- a/pwnagotchi/ui/hw/pitft.py +++ b/pwnagotchi/ui/hw/pitft.py @@ -50,6 +50,9 @@ class Pitft(DisplayImpl): def initialize(self): logging.info("Initializing adafruit pitft 320x240 screen") + logging.info("Available pins for GPIO Buttons on the 3,2inch: 17, 22, 23, 27") + logging.info("Available pins for GPIO Buttons on the 2,8inch: 26, 13, 12, 6, 5") + logging.info("Backlight pin available on GPIO 18") from pwnagotchi.ui.hw.libs.adafruit.pitft.ILI9341 import ILI9341 self._display = ILI9341(0, 0, 25, 18) diff --git a/pwnagotchi/ui/hw/tftbonnet.py b/pwnagotchi/ui/hw/tftbonnet.py index 5b014b92..fc742910 100644 --- a/pwnagotchi/ui/hw/tftbonnet.py +++ b/pwnagotchi/ui/hw/tftbonnet.py @@ -44,6 +44,9 @@ class TftBonnet(DisplayImpl): def initialize(self): logging.info("initializing Adafruit Tft Bonnet") + logging.info("Available pins for GPIO Buttons Up/Down/Left/Right/Center/A/B: 17, 22, 27, 23, 4, 5, 6") + logging.info("Backlight pin available on GPIO 26") + logging.info("I2C bus available on stemma QT header") from pwnagotchi.ui.hw.libs.adafruit.tftbonnet.ST7789 import ST7789 self._display = ST7789(0,0,25,26) diff --git a/pwnagotchi/ui/hw/waveshareoledlcd.py b/pwnagotchi/ui/hw/waveshareoledlcd.py index 19e58b31..60e82e69 100644 --- a/pwnagotchi/ui/hw/waveshareoledlcd.py +++ b/pwnagotchi/ui/hw/waveshareoledlcd.py @@ -49,6 +49,7 @@ class Waveshareoledlcd(DisplayImpl): def initialize(self): logging.info("initializing Waveshare OLED/LCD hat") + logging.info("Available pins for GPIO Buttons K1/K2/K3/K4: 4, 17, 23, 24") from pwnagotchi.ui.hw.libs.waveshare.oled.oledlcd.ST7789 import ST7789 self._display = ST7789(0,0,22,18) diff --git a/pwnagotchi/ui/hw/waveshareoledlcdvert.py b/pwnagotchi/ui/hw/waveshareoledlcdvert.py index ee817a26..2e86983b 100644 --- a/pwnagotchi/ui/hw/waveshareoledlcdvert.py +++ b/pwnagotchi/ui/hw/waveshareoledlcdvert.py @@ -49,6 +49,7 @@ class Waveshareoledlcdvert(DisplayImpl): def initialize(self): logging.info("initializing Waveshare OLED/LCD hat vertical mode") + logging.info("Available pins for GPIO Buttons K1/K2/K3/K4: 4, 17, 23, 24") from pwnagotchi.ui.hw.libs.waveshare.oled.oledlcd.ST7789vert import ST7789 self._display = ST7789(0,0,22,18) From 8edf72c8ef5d9dbc9e2c7c15ca80010cf8efa821 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 12:54:06 +0100 Subject: [PATCH 07/25] Remove builder/data folder, we don't need that anymore. Signed-off-by: jayofelony --- .../pwnagotchi_completion.sh | 36 ---- builder/data/etc/dphys-swapfile | 26 --- builder/data/etc/modules-load.d/modules.conf | 6 - .../data/etc/network/interfaces.d/eth0-cfg | 2 - builder/data/etc/network/interfaces.d/lo-cfg | 2 - .../data/etc/network/interfaces.d/usb0-cfg | 8 - .../data/etc/network/interfaces.d/wlan0-cfg | 2 - .../data/etc/systemd/system/bettercap.service | 13 -- .../data/etc/systemd/system/bluetooth.service | 20 -- .../etc/systemd/system/pwnagotchi.service | 19 -- .../etc/systemd/system/pwngrid-peer.service | 16 -- builder/data/etc/update-motd.d/01-motd | 34 ---- builder/data/root/client_secrets.json | 0 builder/data/root/settings.yaml | 15 -- builder/data/usr/bin/bettercap-launcher | 19 -- builder/data/usr/bin/decryption-webserver | 148 --------------- builder/data/usr/bin/hdmioff | 2 - builder/data/usr/bin/hdmion | 2 - builder/data/usr/bin/monstart | 3 - builder/data/usr/bin/monstop | 3 - builder/data/usr/bin/pwnagotchi-launcher | 17 -- builder/data/usr/bin/pwnlib | 176 ------------------ pwnagotchi/automata.py | 8 +- pyproject.toml | 2 +- requirements.txt | 25 --- setup.py | 110 ----------- 26 files changed, 8 insertions(+), 706 deletions(-) delete mode 100644 builder/data/etc/bash_completion.d/pwnagotchi_completion.sh delete mode 100644 builder/data/etc/dphys-swapfile delete mode 100644 builder/data/etc/modules-load.d/modules.conf delete mode 100644 builder/data/etc/network/interfaces.d/eth0-cfg delete mode 100644 builder/data/etc/network/interfaces.d/lo-cfg delete mode 100644 builder/data/etc/network/interfaces.d/usb0-cfg delete mode 100644 builder/data/etc/network/interfaces.d/wlan0-cfg delete mode 100644 builder/data/etc/systemd/system/bettercap.service delete mode 100644 builder/data/etc/systemd/system/bluetooth.service delete mode 100644 builder/data/etc/systemd/system/pwnagotchi.service delete mode 100644 builder/data/etc/systemd/system/pwngrid-peer.service delete mode 100755 builder/data/etc/update-motd.d/01-motd delete mode 100644 builder/data/root/client_secrets.json delete mode 100644 builder/data/root/settings.yaml delete mode 100755 builder/data/usr/bin/bettercap-launcher delete mode 100755 builder/data/usr/bin/decryption-webserver delete mode 100755 builder/data/usr/bin/hdmioff delete mode 100755 builder/data/usr/bin/hdmion delete mode 100755 builder/data/usr/bin/monstart delete mode 100755 builder/data/usr/bin/monstop delete mode 100755 builder/data/usr/bin/pwnagotchi-launcher delete mode 100755 builder/data/usr/bin/pwnlib delete mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/builder/data/etc/bash_completion.d/pwnagotchi_completion.sh b/builder/data/etc/bash_completion.d/pwnagotchi_completion.sh deleted file mode 100644 index 68edc4e9..00000000 --- a/builder/data/etc/bash_completion.d/pwnagotchi_completion.sh +++ /dev/null @@ -1,36 +0,0 @@ -_show_complete() -{ - local cur opts node_names all_options opt_line - all_options=" -pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --wizard --check-update --donate {plugins,google} -pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade} -pwnagotchi plugins list -i --installed -h --help -pwnagotchi plugins install -h --help -pwnagotchi plugins uninstall -h --help -pwnagotchi plugins enable -h --help -pwnagotchi plugins disable -h --help -pwnagotchi plugins update -h --help -pwnagotchi plugins upgrade -h --help -pwnagotchi google -h --help {login,refresh} -pwnagotchi google login -h --help -pwnagotchi google refresh -h --help -" - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - # shellcheck disable=SC2124 - cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}" - opt_line="$(grep -m1 "$cmd" <<<"$all_options")" - if [[ ${cur} == -* ]] ; then - opts="$(echo "$opt_line" | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')" - # shellcheck disable=SC2207 - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - fi - - # shellcheck disable=SC2086 - opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')" - # shellcheck disable=SC2207 - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) -} - -complete -F _show_complete pwnagotchi diff --git a/builder/data/etc/dphys-swapfile b/builder/data/etc/dphys-swapfile deleted file mode 100644 index 1c908a10..00000000 --- a/builder/data/etc/dphys-swapfile +++ /dev/null @@ -1,26 +0,0 @@ -# /etc/dphys-swapfile - user settings for dphys-swapfile package -# author Neil Franklin, last modification 2010.05.05 -# copyright ETH Zuerich Physics Departement -# use under either modified/non-advertising BSD or GPL license - -# this file is sourced with . so full normal sh syntax applies - -# the default settings are added as commented out CONF_*=* lines - - -# where we want the swapfile to be, this is the default -#CONF_SWAPFILE=/var/swap - -# set size to absolute value, leaving empty (default) then uses computed value -# you most likely don't want this, unless you have an special disk situation -CONF_SWAPSIZE=2048 - -# set size to computed value, this times RAM size, dynamically adapts, -# guarantees that there is enough swap without wasting disk space on excess -#CONF_SWAPFACTOR=2 - -# restrict size (computed and absolute!) to maximally this limit -# can be set to empty for no limit, but beware of filled partitions! -# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it -# but is also sensible on 64bit to prevent filling /var or even / partition -#CONF_MAXSWAP=2048 \ No newline at end of file diff --git a/builder/data/etc/modules-load.d/modules.conf b/builder/data/etc/modules-load.d/modules.conf deleted file mode 100644 index bb4fa86c..00000000 --- a/builder/data/etc/modules-load.d/modules.conf +++ /dev/null @@ -1,6 +0,0 @@ -# /etc/modules: kernel modules to load at boot time. -# -# This file contains the names of kernel modules that should be loaded -# at boot time, one per line. Lines beginning with "#" are ignored. -# Parameters can be specified after the module name. -i2c-dev \ No newline at end of file diff --git a/builder/data/etc/network/interfaces.d/eth0-cfg b/builder/data/etc/network/interfaces.d/eth0-cfg deleted file mode 100644 index 2166051a..00000000 --- a/builder/data/etc/network/interfaces.d/eth0-cfg +++ /dev/null @@ -1,2 +0,0 @@ -allow-hotplug eth0 -iface eth0 inet dhcp \ No newline at end of file diff --git a/builder/data/etc/network/interfaces.d/lo-cfg b/builder/data/etc/network/interfaces.d/lo-cfg deleted file mode 100644 index a283b0ff..00000000 --- a/builder/data/etc/network/interfaces.d/lo-cfg +++ /dev/null @@ -1,2 +0,0 @@ -auto lo usb0 -iface lo inet loopback \ No newline at end of file diff --git a/builder/data/etc/network/interfaces.d/usb0-cfg b/builder/data/etc/network/interfaces.d/usb0-cfg deleted file mode 100644 index 3521780a..00000000 --- a/builder/data/etc/network/interfaces.d/usb0-cfg +++ /dev/null @@ -1,8 +0,0 @@ -allow-hotplug usb0 -iface usb0 inet static - address 10.0.0.2 - netmask 255.255.255.0 - network 10.0.0.0 - broadcast 10.0.0.255 - gateway 10.0.0.1 - metric 101 diff --git a/builder/data/etc/network/interfaces.d/wlan0-cfg b/builder/data/etc/network/interfaces.d/wlan0-cfg deleted file mode 100644 index f5425694..00000000 --- a/builder/data/etc/network/interfaces.d/wlan0-cfg +++ /dev/null @@ -1,2 +0,0 @@ -allow-hotplug wlan0 -iface wlan0 inet static \ No newline at end of file diff --git a/builder/data/etc/systemd/system/bettercap.service b/builder/data/etc/systemd/system/bettercap.service deleted file mode 100644 index ce8e8290..00000000 --- a/builder/data/etc/systemd/system/bettercap.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=bettercap api.rest service. -Documentation=https://bettercap.org -Wants=network.target - -[Service] -Type=simple -ExecStart=/usr/bin/bettercap-launcher -Restart=always -RestartSec=30 - -[Install] -WantedBy=multi-user.target diff --git a/builder/data/etc/systemd/system/bluetooth.service b/builder/data/etc/systemd/system/bluetooth.service deleted file mode 100644 index 6780e4b8..00000000 --- a/builder/data/etc/systemd/system/bluetooth.service +++ /dev/null @@ -1,20 +0,0 @@ -[Unit] -Description=Bluetooth service -Documentation=man:bluetoothd(8) -ConditionPathIsDirectory=/sys/class/bluetooth - -[Service] -Type=dbus -BusName=org.bluez -ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp -NotifyAccess=main -#WatchdogSec=10 -#Restart=on-failure -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE -LimitNPROC=1 -ProtectHome=true -ProtectSystem=full - -[Install] -WantedBy=bluetooth.target -Alias=dbus-org.bluez.service diff --git a/builder/data/etc/systemd/system/pwnagotchi.service b/builder/data/etc/systemd/system/pwnagotchi.service deleted file mode 100644 index daa53acc..00000000 --- a/builder/data/etc/systemd/system/pwnagotchi.service +++ /dev/null @@ -1,19 +0,0 @@ -[Unit] -Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning. -Documentation=https://pwnagotchi.org -Wants=network.target -After=pwngrid-peer.service - -[Service] -Type=simple -WorkingDirectory=~ -ExecStart=/usr/bin/pwnagotchi-launcher -Restart=always -RestartSec=30 -TasksMax=infinity -LimitNPROC=infinity -StandardOutput=null -StandardError=null - -[Install] -WantedBy=multi-user.target diff --git a/builder/data/etc/systemd/system/pwngrid-peer.service b/builder/data/etc/systemd/system/pwngrid-peer.service deleted file mode 100644 index d6ff2223..00000000 --- a/builder/data/etc/systemd/system/pwngrid-peer.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=pwngrid peer service. -Documentation=https://pwnagotchi.org -Wants=network.target -After=bettercap.service - -[Service] -Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1 -Environment=LD_LIBRARY_PATH=/usr/local/lib -Type=simple -ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /etc/pwnagotchi/log/pwngrid-peer.log -iface wlan0mon -Restart=always -RestartSec=30 - -[Install] -WantedBy=multi-user.target diff --git a/builder/data/etc/update-motd.d/01-motd b/builder/data/etc/update-motd.d/01-motd deleted file mode 100755 index aa4258c9..00000000 --- a/builder/data/etc/update-motd.d/01-motd +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh -_hostname=$(hostname) -_version=$(cut -d"'" -f2 < /usr/local/lib/python3.11/dist-packages/pwnagotchi/_version.py) -echo -echo "(◕‿‿◕) $_hostname" -echo -echo " Hi! I'm a pwnagotchi $_version, please take good care of me!" -echo " Here are some basic things you need to know to raise me properly!" -echo -echo " If you want to change my configuration, use /etc/pwnagotchi/config.toml" -echo " All plugin config files are located in /etc/pwnagotchi/conf.d/" -echo " Read the readme if you want to use gdrivesync plugin!!" -echo -echo " All the configuration options can be found on /etc/pwnagotchi/default.toml," -echo " but don't change this file because I will recreate it every time I'm restarted!" -echo -echo " I use oPwnGrid as my main API, you can check stats at https://opwngrid.xyz" -echo -echo " I'm managed by systemd. Here are some basic commands." -echo -echo " If you want to know what I'm doing, you can check my logs with the command" -echo " - pwnlog" -echo " - sudo pwnagotchi --wizard, to help set up a config.toml" -echo " - sudo pwnagotchi --version, to check the current version" -echo " - sudo pwnagotchi --donate, to see how you can donate to this project" -echo " - sudo pwnagotchi --check-update, to see if there is a new version available" -echo -echo " If you want to know if I'm running, you can use" -echo " sudo systemctl status pwnagotchi" -echo -echo " You can restart me using" -echo " pwnkill" -echo -echo " You can learn more about me at https://pwnagotchi.org/" diff --git a/builder/data/root/client_secrets.json b/builder/data/root/client_secrets.json deleted file mode 100644 index e69de29b..00000000 diff --git a/builder/data/root/settings.yaml b/builder/data/root/settings.yaml deleted file mode 100644 index 5f198bde..00000000 --- a/builder/data/root/settings.yaml +++ /dev/null @@ -1,15 +0,0 @@ -client_config_backend: file -client_config_file: /root/client_secrets.json -client_config: - client_id: - client_secret: - -save_credentials: True -save_credentials_backend: file -save_credentials_file: /root/credentials.json - -get_refresh_token: True - -oauth_scope: - - https://www.googleapis.com/auth/drive - - https://www.googleapis.com/auth/drive.install \ No newline at end of file diff --git a/builder/data/usr/bin/bettercap-launcher b/builder/data/usr/bin/bettercap-launcher deleted file mode 100755 index b44c253b..00000000 --- a/builder/data/usr/bin/bettercap-launcher +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -source /usr/bin/pwnlib - -# we need to decrypt something -if is_crypted_mode; then - while ! is_decrypted; do - echo "Waiting for decryption..." - sleep 1 - done -fi - -reload_brcm -start_monitor_interface - -if is_auto_mode_no_delete; then - /usr/local/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface wlan0mon -else - /usr/local/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface wlan0mon -fi diff --git a/builder/data/usr/bin/decryption-webserver b/builder/data/usr/bin/decryption-webserver deleted file mode 100755 index 7eb9c52f..00000000 --- a/builder/data/usr/bin/decryption-webserver +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python3 - -from http.server import HTTPServer, BaseHTTPRequestHandler -from urllib.parse import parse_qsl - - -_HTML_FORM_TEMPLATE = """ - - - - Decryption - - - -
-

Decryption

-

Some of your files are encrypted.

-

Please provide the decryption password.

-
-
- {password_fields} - -
-
-
- - -""" - -POST_RESPONSE = """ - - - - - - - - - -
- - - -""" - -HTML_FORM = None - -class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): - - def do_GET(self): - self.send_response(200) - self.end_headers() - self.wfile.write(HTML_FORM.encode()) - - def do_POST(self): - content_length = int(self.headers['Content-Length']) - body = self.rfile.read(content_length) - for mapping, password in parse_qsl(body.decode('UTF-8')): - with open('/tmp/.pwnagotchi-secret-{}'.format(mapping), 'wt') as pwfile: - pwfile.write(password) - self.send_response(200) - self.end_headers() - self.wfile.write(POST_RESPONSE.encode()) - - -with open('/root/.pwnagotchi-crypted') as crypted_file: - mappings = [line.split()[0] for line in crypted_file.readlines()] - fields = ''.join(['\n
'.format(m=m) - for m in mappings]) - HTML_FORM = _HTML_FORM_TEMPLATE.format(password_fields=fields) - -httpd = HTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler) -httpd.serve_forever() diff --git a/builder/data/usr/bin/hdmioff b/builder/data/usr/bin/hdmioff deleted file mode 100755 index 5c32d62c..00000000 --- a/builder/data/usr/bin/hdmioff +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -sudo /usr/bin/tvservice -o \ No newline at end of file diff --git a/builder/data/usr/bin/hdmion b/builder/data/usr/bin/hdmion deleted file mode 100755 index eec440fc..00000000 --- a/builder/data/usr/bin/hdmion +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -sudo /usr/bin/tvservice -p \ No newline at end of file diff --git a/builder/data/usr/bin/monstart b/builder/data/usr/bin/monstart deleted file mode 100755 index 4c00a5c5..00000000 --- a/builder/data/usr/bin/monstart +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -source /usr/bin/pwnlib -start_monitor_interface diff --git a/builder/data/usr/bin/monstop b/builder/data/usr/bin/monstop deleted file mode 100755 index c90a3aef..00000000 --- a/builder/data/usr/bin/monstop +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -source /usr/bin/pwnlib -stop_monitor_interface \ No newline at end of file diff --git a/builder/data/usr/bin/pwnagotchi-launcher b/builder/data/usr/bin/pwnagotchi-launcher deleted file mode 100755 index be0ceb03..00000000 --- a/builder/data/usr/bin/pwnagotchi-launcher +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -source /usr/bin/pwnlib - -# we need to decrypt something -if is_crypted_mode; then - while ! is_decrypted; do - echo "Waiting for decryption..." - sleep 1 - done -fi - -if is_auto_mode; then - /opt/.pwn/bin/pwnagotchi - systemctl restart bettercap -else - /opt/.pwn/bin/pwnagotchi --manual -fi diff --git a/builder/data/usr/bin/pwnlib b/builder/data/usr/bin/pwnlib deleted file mode 100755 index 7cb7b335..00000000 --- a/builder/data/usr/bin/pwnlib +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env bash - -# reload mod -reload_brcm() { - if ! modprobe -r brcmfmac; then - return 1 - fi - sleep 1 - if ! modprobe brcmfmac; then - return 1 - fi - sleep 2 - iw dev wlan0 set power_save off - return 0 -} - -# starts mon0 -start_monitor_interface() { - ifconfig wlan0 up - sleep 3 - iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor - sleep 2 - rfkill unblock all - ifconfig wlan0 down - ifconfig wlan0mon up - iw dev wlan0mon set power_save off -} - -# stops mon0 -stop_monitor_interface() { - ifconfig wlan0mon down && iw dev wlan0mon del - reload_brcm - ifconfig wlan0 up -} - -# returns 0 if the specified network interface is up -is_interface_up() { - if grep -qi 'up' /sys/class/net/"$1"/operstate; then - return 0 - fi - return 1 -} - -# returns 0 if conditions for AUTO mode are met -is_auto_mode() { - # check override file first - if [ -f /root/.pwnagotchi-manual ]; then - # remove the override file if found - rm -rf /root/.pwnagotchi-manual - return 1 - fi - - # check override file first - if [ -f /root/.pwnagotchi-auto ]; then - # remove the override file if found - rm -rf /root/.pwnagotchi-auto - return 0 - fi - - # if usb0 is up, we're in MANU - if is_interface_up usb0; then - return 1 - fi - - # if eth0 is up (for other boards), we're in MANU - if is_interface_up eth0; then - return 0 - fi - - # no override, but none of the interfaces is up -> AUTO - return 0 -} - -# returns 0 if conditions for AUTO mode are met -is_auto_mode_no_delete() { - # check override file first - if [ -f /root/.pwnagotchi-manual ]; then - return 1 - fi - - # check override file first - if [ -f /root/.pwnagotchi-auto ]; then - return 0 - fi - - # if usb0 is up, we're in MANU - if is_interface_up usb0; then - return 1 - fi - - # if eth0 is up (for other boards), we're in MANU - if is_interface_up eth0; then - return 0 - fi - - # no override, but none of the interfaces is up -> AUTO - return 0 -} - -# check if we need to decrypt something -is_crypted_mode() { - if [ -f /root/.pwnagotchi-crypted ]; then - return 0 - fi - return 1 -} - -# decryption loop -is_decrypted() { - while read -r mapping container mount; do - # mapping = name the device or file will be mapped to - # container = the luks encrypted device or file - # mount = the mountpoint - - # fail if not mounted - if ! mountpoint -q "$mount" >/dev/null 2>&1; then - if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then - /dev/null 2>&1; then - echo "Container decrypted!" - fi - fi - - if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then - echo "Mounted /dev/mapper/$mapping to $mount" - continue - fi - fi - - if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then - >/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0 - fi - - if ! pgrep -f decryption-webserver >/dev/null 2>&1; then - >/dev/null 2>&1 decryption-webserver & - fi - - if ! pgrep wpa_supplicant >/dev/null 2>&1; then - >/tmp/wpa_supplicant.conf cat </dev/null 2>&1 wpa_supplicant -u -s -O -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf & - fi - - if ! pgrep dnsmasq >/dev/null 2>&1; then - >/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h & - fi - - return 1 - fi - done /dev/null - # delete - rm /tmp/.pwnagotchi-secret-* - sync # flush - - pkill wpa_supplicant - pkill dnsmasq - pid="$(pgrep -f "decryption-webserver")" - [[ -n "$pid" ]] && kill "$pid" - - return 0 -} diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py index 9b7f9c8e..6e6b2e20 100644 --- a/pwnagotchi/automata.py +++ b/pwnagotchi/automata.py @@ -2,6 +2,7 @@ import logging import pwnagotchi.plugins as plugins from pwnagotchi.ai.epoch import Epoch +import os # basic mood system @@ -136,7 +137,12 @@ class Automata(object): self.set_grateful() plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data()) - + if self._epoch.blind_for % 10 == 2: + logging.info("two blind epochs -> restarting wifi.recon...", self._epoch.blind_for) + self.run('wifi.recon on') + if self._epoch.blind_for and self._epoch.blind_for % 5 == 0: + logging.info("%d epochs without visible access points -> restarting bettercap...", self._epoch.blind_for) + os.system("systemctl restart bettercap") if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']: logging.critical("%d epochs without visible access points -> restarting ...", self._epoch.blind_for) self._restart() diff --git a/pyproject.toml b/pyproject.toml index eac141ae..8ed3ee3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ "tweepy", "websockets", ] -requires-python = ">=3.9" +requires-python = ">=3.11" authors = [ {name = "Evilsocket", email = "evilsocket@gmail.com"}, {name = "Jayofelony", email = "oudshoorn.jeroen@gmail.com"} diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c6921379..00000000 --- a/requirements.txt +++ /dev/null @@ -1,25 +0,0 @@ -PyYAML -dbus-python -file-read-backwards -flask -flask-cors -flask-wtf -gast -gpiozero -inky -numpy -pycryptodome -pydrive2 -python-dateutil -requests -rpi-lgpio -rpi_hardware_pwm -scapy -setuptools -shimmy -smbus -smbus2 -spidev -toml -tweepy -websockets \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 9b132347..00000000 --- a/setup.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from setuptools import setup, find_packages -from setuptools.command.install import install -import glob -import logging -import os -import re -import shutil -import warnings -import platform - -log = logging.getLogger(__name__) - - -def install_file(source_filename, dest_filename): - # do not overwrite network configuration if it exists already - # https://github.com/evilsocket/pwnagotchi/issues/483 - if dest_filename.startswith('/etc/network/interfaces.d/') and os.path.exists(dest_filename): - log.info(f"{dest_filename} exists, skipping ...") - return - elif dest_filename.startswith('/root/') and os.path.exists(dest_filename): - log.info(f"{dest_filename} exists, skipping ...") - return - - log.info(f"installing {source_filename} to {dest_filename} ...") - dest_folder = os.path.dirname(dest_filename) - if not os.path.isdir(dest_folder): - os.makedirs(dest_folder) - - shutil.copy2(source_filename, dest_filename) - if dest_filename.startswith("/usr/bin/"): - os.chmod(dest_filename, 0o755) - - -def install_system_files(): - data_path = None - if os.stat("apt_packages.txt").st_size != 0: - f = open("apt_packages.txt", "r") - for x in f: - os.system(f"apt-get install -y {x}") - f.close() - setup_path = os.path.dirname(__file__) - data_path = os.path.join(setup_path, "builder/data") - - for source_filename in glob.glob("%s/**" % data_path, recursive=True): - if os.path.isfile(source_filename): - dest_filename = source_filename.replace(data_path, '') - install_file(source_filename, dest_filename) - - -def restart_services(): - # reload systemd units - os.system("systemctl daemon-reload") - - # for people updating https://github.com/evilsocket/pwnagotchi/pull/551/files - os.system("systemctl enable fstrim.timer") - - -class CustomInstall(install): - def run(self): - super().run() - if os.geteuid() != 0: - warnings.warn("Not running as root, can't install pwnagotchi system files!") - return - install_system_files() - restart_services() - - -def version(version_file): - with open(version_file, 'rt') as vf: - version_file_content = vf.read() - - version_match = re.search(r"__version__\s*=\s*[\"\']([^\"\']+)", version_file_content) - if version_match: - return version_match.groups()[0] - - return None - - -with open('requirements.txt') as fp: - required = [ - line.strip() - for line in fp - if line.strip() and not line.startswith("--") - ] - -VERSION_FILE = 'pwnagotchi/_version.py' -pwnagotchi_version = version(VERSION_FILE) - -setup(name='pwnagotchi', - version=pwnagotchi_version, - description='(⌐■_■) - Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.', - author='evilsocket && the dev team', - author_email='evilsocket@gmail.com', - url='https://pwnagotchi.ai/', - license='GPL', - cmdclass={ - "install": CustomInstall, - }, - scripts=['bin/pwnagotchi'], - package_data={'pwnagotchi': ['defaults.toml', 'pwnagotchi/defaults.toml', 'locale/*/LC_MESSAGES/*.mo']}, - include_package_data=True, - packages=find_packages(), - classifiers=[ - 'Programming Language :: Python :: 3', - 'Development Status :: 5 - Production/Stable', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Environment :: Console', - ]) From a78042b0fda03b93e54da86d8f8067715b5ea9b5 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 12:57:33 +0100 Subject: [PATCH 08/25] Remove builder/data folder, we don't need that anymore. Signed-off-by: jayofelony --- .gitignore | 10 +- apt_packages.txt | 0 release.stork | 7 -- scripts/backup.sh | 47 ++++----- scripts/preview.py | 217 ----------------------------------------- scripts/pypi_upload.sh | 6 -- 6 files changed, 34 insertions(+), 253 deletions(-) delete mode 100644 apt_packages.txt delete mode 100755 release.stork delete mode 100755 scripts/preview.py delete mode 100755 scripts/pypi_upload.sh diff --git a/.gitignore b/.gitignore index fd20fddf..45df4bf2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,10 @@ - +scripts/ *.pyc +CODE_OF_CONDUCT.md +CONTRIBUTING.md +LICENSE.md +Makefile +MANIFEST.in +README.md +README-google.md +RE \ No newline at end of file diff --git a/apt_packages.txt b/apt_packages.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/release.stork b/release.stork deleted file mode 100755 index 023c4ecf..00000000 --- a/release.stork +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env stork -f - -version:parser "__version__\\s*=\\s*['\"]([\\d\\.ab]+)[\"']" -version:file "pwnagotchi/_version.py" -version:from_user - -git:create_tag $VERSION \ No newline at end of file diff --git a/scripts/backup.sh b/scripts/backup.sh index dd3f2349..70164c5d 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -32,28 +32,31 @@ OUTPUT=${OUTPUT:-${UNIT_HOSTNAME}-backup-$(date +%s).tgz} # username to use for ssh UNIT_USERNAME=${UNIT_USERNAME:-pi} # what to backup -FILES_TO_BACKUP="/root/brain.nn \ - /root/brain.json \ - /root/.api-report.json \ - /root/.ssh \ - /root/.bashrc \ - /root/.profile \ - /root/handshakes \ - /root/peers \ - /etc/pwnagotchi/ \ - /etc/ssh/ \ - /var/log/pwnagotchi.log \ - /var/log/pwnagotchi*.gz \ - /home/pi/.ssh \ - /home/pi/.bashrc \ - /home/pi/.profile \ - /root/.api-report.json \ - /root/.auto-update \ - /root/.bt-tether* \ - /root/.net_pos_saved \ - /root/.ohc_uploads \ - /root/.wigle_uploads \ - /root/.wpa_sec_uploads" +FILES_TO_BACKUP=" + /boot/firmware/cmdline.txt \ + /boot/firmware/config.txt \ + /root/settings.yaml \ + /root/client_sercrets.json \ + /root/.api-report.json \ + /root/.ssh \ + /root/.bashrc \ + /root/.profile \ + /root/handshakes \ + /root/peers \ + /etc/modprobe.d/g_ether.conf \ + /etc/pwnagotchi/ \ + /etc/ssh/ \ + /etc/pwnagotchi/log/pwnagotchi.log \ + /etc/pwnagotchi/log/pwnagotchi*.gz \ + /home/pi/.ssh \ + /home/pi/.bashrc \ + /home/pi/.profile \ + /root/.api-report.json \ + /root/.auto-update \ + /root/.bt-tether* \ + /root/.ohc_uploads \ + /root/.wigle_uploads \ + /root/.wpa_sec_uploads" ping -c 1 "${UNIT_HOSTNAME}" > /dev/null 2>&1 || { echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface." diff --git a/scripts/preview.py b/scripts/preview.py deleted file mode 100755 index de043ce0..00000000 --- a/scripts/preview.py +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/env python3 -import sys -import os -import argparse -import yaml -import toml - -sys.path.insert(0, - os.path.join(os.path.dirname(os.path.realpath(__file__)), - '../')) - -import pwnagotchi.ui.faces as faces -from pwnagotchi.ui.display import Display -from PIL import Image - - -class CustomDisplay(Display): - - def __init__(self, config, state): - self.last_image = None - super(CustomDisplay, self).__init__(config, state) - - def _http_serve(self): - # do nothing - pass - - def _on_view_rendered(self, img): - self.last_image = img - - def get_image(self): - """ - Return the saved image - """ - return self.last_image - - -class DummyPeer: - - def __init__(self): - self.rssi = -50 - - @staticmethod - def name(): - return "beta" - - @staticmethod - def pwnd_run(): - return 50 - - @staticmethod - def pwnd_total(): - return 100 - - @staticmethod - def first_encounter(): - return 1 - - @staticmethod - def face(): - return faces.FRIEND - - -def append_images(images, horizontal=True, xmargin=0, ymargin=0): - w, h = zip(*(i.size for i in images)) - - if horizontal: - t_w = sum(w) - t_h = max(h) - else: - t_w = max(w) - t_h = sum(h) - - result = Image.new('RGB', (t_w, t_h)) - - x_offset = 0 - y_offset = 0 - - for im in images: - result.paste(im, (x_offset, y_offset)) - if horizontal: - x_offset += im.size[0] + xmargin - else: - y_offset += im.size[1] + ymargin - - return result - - -def main(): - parser = argparse.ArgumentParser(description="This program emulates\ - the pwnagotchi display") - parser.add_argument('--displays', help="Which displays to use.", nargs="+", default=["waveshare_2"]) - parser.add_argument('--lang', help="Language to use", default="en") - parser.add_argument('--output', help="Path to output image (PNG)", default="preview.png") - parser.add_argument('--show-peer', dest="showpeer", help="This options will show a dummy peer", action="store_true") - parser.add_argument('--xmargin', help="Add X-Margin", type=int, default=5) - parser.add_argument('--ymargin', help="Add Y-Margin", type=int, default=5) - args = parser.parse_args() - - config_template = ''' - main: - lang: {lang} - ui: - font: - name: 'DejaVuSansMono' - size_offset: 0 - size: 0 - fps: 0.3 - display: - enabled: false - rotation: 180 - color: black - refresh: 30 - type: {display} - web: - enabled: true - address: '::' - port: 8080 - - faces: - look_r: '( ⚆_⚆)' - look_l: '(☉_☉ )' - look_r_happy: '( ◕‿◕)' - look_l_happy: '(◕‿◕ )' - sleep: '(⇀‿‿↼)' - sleep2: '(≖‿‿≖)' - awake: '(◕‿‿◕)' - bored: '(-__-)' - intense: '(°▃▃°)' - cool: '(⌐■_■)' - happy: '(•‿‿•)' - excited: '(ᵔ◡◡ᵔ)' - grateful: '(^‿‿^)' - motivated: '(☼‿‿☼)' - demotivated: '(≖__≖)' - smart: '(✜‿‿✜)' - lonely: '(ب__ب)' - sad: '(╥☁╥ )' - friend: '(♥‿‿♥)' - broken: '(☓‿‿☓)' - debug: '(#__#)' - ''' - - list_of_displays = list() - for display_type in args.displays: - config = yaml.safe_load(config_template.format(display=display_type, - lang=args.lang)) - display = CustomDisplay(config=config, state={'name': f"{display_type}>"}) - list_of_displays.append(display) - - columns = list() - - for display in list_of_displays: - emotions = list() - if args.showpeer: - display.set_closest_peer(DummyPeer(), 10) - display.on_starting() - display.update() - emotions.append(display.get_image()) - display.on_ai_ready() - display.update() - emotions.append(display.get_image()) - display.on_normal() - display.update() - emotions.append(display.get_image()) - display.on_new_peer(DummyPeer()) - display.update() - emotions.append(display.get_image()) - display.on_lost_peer(DummyPeer()) - display.update() - emotions.append(display.get_image()) - display.on_free_channel('6') - display.update() - emotions.append(display.get_image()) - display.wait(2) - display.update() - emotions.append(display.get_image()) - display.on_bored() - display.update() - emotions.append(display.get_image()) - display.on_sad() - display.update() - emotions.append(display.get_image()) - display.on_motivated(1) - display.update() - emotions.append(display.get_image()) - display.on_demotivated(-1) - display.update() - emotions.append(display.get_image()) - display.on_excited() - display.update() - emotions.append(display.get_image()) - display.on_deauth({'mac': 'DE:AD:BE:EF:CA:FE'}) - display.update() - emotions.append(display.get_image()) - display.on_miss('test') - display.update() - emotions.append(display.get_image()) - display.on_lonely() - display.update() - emotions.append(display.get_image()) - display.on_handshakes(1) - display.update() - emotions.append(display.get_image()) - display.on_rebooting() - display.update() - emotions.append(display.get_image()) - - # append them all together (vertical) - columns.append(append_images(emotions, horizontal=False, xmargin=args.xmargin, ymargin=args.ymargin)) - - # append columns side by side - final_image = append_images(columns, horizontal=True, xmargin=args.xmargin, ymargin=args.ymargin) - final_image.save(args.output, 'PNG') - - -if __name__ == '__main__': - SystemExit(main()) diff --git a/scripts/pypi_upload.sh b/scripts/pypi_upload.sh deleted file mode 100755 index 265a56ae..00000000 --- a/scripts/pypi_upload.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -rm -rf build dist pwnagotchi.egg-info && - python3 setup.py sdist bdist_wheel && - clear && - twine upload dist/* From 644d342daa62a98a4ee088bd1673ca880a12a1c4 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 12:58:37 +0100 Subject: [PATCH 09/25] Remove builder/data folder, we don't need that anymore. Signed-off-by: jayofelony --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 45df4bf2..fdb77e0d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,4 @@ LICENSE.md Makefile MANIFEST.in README.md -README-google.md -RE \ No newline at end of file +README-google.md \ No newline at end of file From b8ecc7a75f2bda9b8ddaf5b08e5c2973bd6c880a Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 12:59:56 +0100 Subject: [PATCH 10/25] Update build --- pyproject.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8ed3ee3c..d339ae22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools", "wheel"] +requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] @@ -32,6 +32,10 @@ dependencies = [ "tweepy", "websockets", ] + +[tool.setuptools.dynamic] +version = {attr = "pwnagotchi.__version__"} + requires-python = ">=3.11" authors = [ {name = "Evilsocket", email = "evilsocket@gmail.com"}, @@ -59,4 +63,4 @@ Issues = "https://github.com/jayofelony/pwnagotchi/issues" Download = "https://github.com/jayofelony/pwnagotchi/releases/latest" [project.scripts] -pwnagotchi_cli = "bin.pwnagotchi:pwnagotchi_cli" +pwnagotchi = "bin.pwnagotchi:pwnagotchi_cli" From 5956eb22b56dad96061b979ea4279646509d741f Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 13:09:19 +0100 Subject: [PATCH 11/25] Small edit Signed-off-by: jayofelony --- .gitignore | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index fdb77e0d..7e99e367 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1 @@ -scripts/ -*.pyc -CODE_OF_CONDUCT.md -CONTRIBUTING.md -LICENSE.md -Makefile -MANIFEST.in -README.md -README-google.md \ No newline at end of file +*.pyc \ No newline at end of file From 6edd91f9b9843e792add152cd3a5d05040355952 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 15:34:59 +0100 Subject: [PATCH 12/25] Update build --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d339ae22..5d9d65c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,9 +33,6 @@ dependencies = [ "websockets", ] -[tool.setuptools.dynamic] -version = {attr = "pwnagotchi.__version__"} - requires-python = ">=3.11" authors = [ {name = "Evilsocket", email = "evilsocket@gmail.com"}, From 1cf8fadddabd2684946bdc5199ed7138ce27b69b Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 15:54:16 +0100 Subject: [PATCH 13/25] Update build --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 5d9d65c8..932ff5d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,9 @@ classifiers = [ 'Environment :: Console', ] +[tool.setuptools.dynamic] +version = {attr = "pwnagotchi.__version__"} + [project.urls] Homepage = "https://pwnagotchi.org/" Documentation = "https://github.com/jayofelony/pwnagotchi/wiki" From cafc8de5640dc2ff66b8059b0b22ca38705d25c9 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 16:27:11 +0100 Subject: [PATCH 14/25] Update build --- pyproject.toml | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 932ff5d9..6fc49cb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,31 +6,10 @@ build-backend = "setuptools.build_meta" name = "pwnagotchi" dynamic = ["version"] dependencies = [ - "PyYAML", - "dbus-python", - "file-read-backwards", - "flask", - "flask-cors", - "flask-wtf", - "gast", - "gpiozero", - "inky", - "numpy", - "pycryptodome", - "pydrive2", - "python-dateutil", - "requests", - "rpi-lgpio", - "rpi_hardware_pwm", - "scapy", - "setuptools", - "shimmy", - "smbus", - "smbus2", - "spidev", - "toml", - "tweepy", - "websockets", + "PyYAML", "dbus-python", "file-read-backwards", "flask", "flask-cors", + "flask-wtf", "gast", "gpiozero", "inky", "numpy", "pycryptodome", "pydrive2", "python-dateutil", + "requests", "rpi-lgpio", "rpi_hardware_pwm", "scapy", "setuptools", "shimmy", "smbus", "smbus2", + "spidev", "toml", "tweepy", "websockets", ] requires-python = ">=3.11" @@ -45,7 +24,7 @@ description = "(⌐■_■) - Deep Reinforcement Learning instrumenting betterca readme = "README.md" license = {file = "LICENSE.md"} classifiers = [ - 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.11', 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Environment :: Console', From 9217338a743b2d72050d3ce257563bf0495ef786 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 16:28:57 +0100 Subject: [PATCH 15/25] Update build --- scripts/backup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/backup.sh b/scripts/backup.sh index 70164c5d..a4504362 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -36,7 +36,7 @@ FILES_TO_BACKUP=" /boot/firmware/cmdline.txt \ /boot/firmware/config.txt \ /root/settings.yaml \ - /root/client_sercrets.json \ + /root/client_secrets.json \ /root/.api-report.json \ /root/.ssh \ /root/.bashrc \ From 6f16c651321882d6038cb639b50762c908c6b175 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 18:53:14 +0100 Subject: [PATCH 16/25] Small edit Signed-off-by: jayofelony --- bin/pwnagotchi | 337 ----------------------------------------- pwnagotchi/__init__.py | 333 ++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 334 insertions(+), 338 deletions(-) delete mode 100755 bin/pwnagotchi diff --git a/bin/pwnagotchi b/bin/pwnagotchi deleted file mode 100755 index 4f31cd3c..00000000 --- a/bin/pwnagotchi +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/python3 -import logging -import argparse -import time -import signal -import sys -import toml -import requests -import os -import re - -import pwnagotchi -from pwnagotchi import utils -from pwnagotchi.google import cmd as google_cmd -from pwnagotchi.plugins import cmd as plugins_cmd -from pwnagotchi import log -from pwnagotchi import restart -from pwnagotchi import fs -from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple - - -def pwnagotchi_cli(): - def do_clear(display): - logging.info("clearing the display ...") - display.clear() - sys.exit(0) - - def do_manual_mode(agent): - logging.info("entering manual mode ...") - - agent.mode = 'manual' - agent.last_session.parse(agent.view(), args.skip_session) - if not args.skip_session: - logging.info( - "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( - 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(agent.last_session) - time.sleep(5) - if grid.is_connected(): - plugins.on('internet_available', agent) - - def do_auto_mode(agent): - logging.info("entering auto mode ...") - - agent.mode = 'auto' - agent.start() - - while True: - try: - # recon on all channels - agent.recon() - # get nearby access points grouped by channel - channels = agent.get_access_points_by_channel() - # for each channel - for ch, aps in channels: - time.sleep(1) - agent.set_channel(ch) - - if not agent.is_stale() and agent.any_activity(): - logging.info("%d access points on channel %d" % (len(aps), ch)) - - # for each ap on this channel - for ap in aps: - # send an association frame in order to get for a PMKID - agent.associate(ap) - # deauth all client stations in order to get a full handshake - for sta in ap['clients']: - agent.deauth(ap, sta) - time.sleep(1) # delay to not trigger nexmon firmware bugs - - # An interesting effect of this: - # - # From Pwnagotchi's perspective, the more new access points - # and / or client stations nearby, the longer one epoch of - # its relative time will take ... basically, in Pwnagotchi's universe, - # Wi-Fi electromagnetic fields affect time like gravitational fields - # affect ours ... neat ^_^ - agent.next_epoch() - - if grid.is_connected(): - plugins.on('internet_available', agent) - - except Exception as e: - if str(e).find("wifi.interface not set") > 0: - logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e) - logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger") - time.sleep(60) - agent.next_epoch() - else: - logging.exception("main loop exception (%s)", e) - - def add_parsers(parser): - """ - Adds the plugins and google subcommands - """ - subparsers = parser.add_subparsers() - - # Add parsers from plugins_cmd - plugins_cmd.add_parsers(subparsers) - - # Add parsers from google_cmd - google_cmd.add_parsers(subparsers) - - parser = argparse.ArgumentParser(prog="pwnagotchi") - # pwnagotchi --help - parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml', - help='Main configuration file.') - parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml', - help='If this file exists, configuration will be merged and this will override default values.') - - parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.") - parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False, - help="Skip last session parsing in manual mode.") - - parser.add_argument('--clear', dest="do_clear", action="store_true", default=False, - help="Clear the ePaper display and exit.") - - parser.add_argument('--debug', dest="debug", action="store_true", default=False, - help="Enable debug logs.") - - parser.add_argument('--version', dest="version", action="store_true", default=False, - help="Print the version.") - - parser.add_argument('--print-config', dest="print_config", action="store_true", default=False, - help="Print the configuration.") - - # Jayofelony added these - parser.add_argument('--wizard', dest="wizard", action="store_true", default=False, - help="Interactive installation of your personal configuration.") - parser.add_argument('--check-update', dest="check_update", action="store_true", default=False, - help="Check for updates on Pwnagotchi. And tells current version.") - parser.add_argument('--donate', dest="donate", action="store_true", default=False, - help="How to donate to this project.") - - # pwnagotchi plugins --help - add_parsers(parser) - args = parser.parse_args() - - if plugins_cmd.used_plugin_cmd(args): - config = utils.load_config(args) - log.setup_logging(args, config) - rc = plugins_cmd.handle_cmd(args, config) - sys.exit(rc) - if google_cmd.used_google_cmd(args): - config = utils.load_config(args) - log.setup_logging(args, config) - rc = google_cmd.handle_cmd(args) - sys.exit(rc) - - if args.version: - print(pwnagotchi.__version__) - sys.exit(0) - - if args.wizard: - def is_valid_hostname(hostname): - if len(hostname) > 255: - return False - if hostname[-1] == ".": - hostname = hostname[:-1] # strip exactly one dot from the right, if present - allowed = re.compile("(?!-)[A-Z\d-]{1,63}(? 0: - f.write("main.whitelist = [\n") - for x in range(int(pwn_whitelist)): - ssid = input("SSID (Name): ") - bssid = input("BSSID (MAC): ") - f.write(f"\t\"{ssid}\",\n") - f.write(f"\t\"{bssid}\",\n") - f.write("]\n") - # set bluetooth tether - pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n" - "[Y/N] ") - if pwn_bluetooth.lower() in ('y', 'yes'): - f.write("main.plugins.bt-tether.enabled = true\n\n") - pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n" - "Device: ") - if pwn_bluetooth_device.lower() == "android": - f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n") - pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" - "MAC: ") - if pwn_bluetooth_mac != "": - f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n") - elif pwn_bluetooth_device.lower() == "ios": - f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n") - pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" - "MAC: ") - if pwn_bluetooth_mac != "": - f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n") - # set up display settings - pwn_display_enabled = input("Do you want to enable a display?\n\n" - "[Y/N]: ") - if pwn_display_enabled.lower() in ('y', 'yes'): - f.write("ui.display.enabled = true\n") - pwn_display_type = input("What display do you use?\n\n" - "Be sure to check for the correct display type @ \n" - "https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L501\n\n" - "Display type: ") - if pwn_display_type != "": - f.write(f"ui.display.type = \"{pwn_display_type}\"\n") - pwn_display_invert = input("Do you want to invert the display colors?\n" - "N = Black background\n" - "Y = White background\n\n" - "[Y/N]: ") - if pwn_display_invert.lower() in ('y', 'yes'): - f.write("ui.invert = true\n") - f.close() - if pwn_bluetooth.lower() in ('y', 'yes'): - if pwn_bluetooth_device.lower == "android": - print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n" - "Your configuration is done, and I will restart in 5 seconds.") - elif pwn_bluetooth_device.lower == "ios": - print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n" - "Your configuration is done, and I will restart in 5 seconds.") - else: - print("Your configuration is done, and I will restart in 5 seconds.") - time.sleep(5) - os.system("service pwnagotchi restart") - else: - print("Ok, doing nothing.") - sys.exit(0) - - if args.donate: - print("Donations can be made @ \n " - "https://github.com/sponsors/jayofelony \n\n" - "But only if you really want to!") - sys.exit(0) - - if args.check_update: - resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest") - latest = resp.json() - latest_ver = latest['tag_name'].replace('v', '') - - local = version_to_tuple(pwnagotchi.__version__) - remote = version_to_tuple(latest_ver) - if remote > local: - user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (pwnagotchi.__version__, latest_ver)) - # input validation - if user_input.lower() in ('y', 'yes'): - if os.path.exists('/root/.auto-update'): - os.system("rm /root/.auto-update && systemctl restart pwnagotchi") - else: - logging.error("You should make sure auto-update is enabled!") - print("Okay, give me a couple minutes. Just watch pwnlog while you wait.") - elif user_input.lower() in ('n', 'no'): # using this elif for readability - print("Okay, guess not!") - else: - print("You are currently on the latest release, v%s." % pwnagotchi.__version__) - sys.exit(0) - - config = utils.load_config(args) - - if args.print_config: - print(toml.dumps(config, encoder=DottedTomlEncoder())) - sys.exit(0) - - from pwnagotchi.identity import KeyPair - from pwnagotchi.agent import Agent - from pwnagotchi.ui import fonts - from pwnagotchi.ui.display import Display - from pwnagotchi import grid - from pwnagotchi import plugins - - pwnagotchi.config = config - fs.setup_mounts(config) - log.setup_logging(args, config) - fonts.init(config) - - pwnagotchi.set_name(config['main']['name']) - - plugins.load(config) - - display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) - - if args.do_clear: - do_clear(display) - sys.exit(0) - - agent = Agent(view=display, config=config, keypair=KeyPair(view=display)) - - def usr1_handler(*unused): - logging.info('Received USR1 signal. Restart process ...') - agent._restart("MANU" if args.do_manual else "AUTO") - - signal.signal(signal.SIGUSR1, usr1_handler) - - if args.do_manual: - do_manual_mode(agent) - else: - do_auto_mode(agent) - - -if __name__ == '__main__': - pwnagotchi_cli() diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 5c4d9637..2173e2de 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -166,3 +166,336 @@ def reboot(mode=None): os.system("sync") os.system("shutdown -r now") + +#!/usr/bin/python3 +import logging +import argparse +import time +import signal +import sys +import toml +import requests +import os +import re + +import pwnagotchi +from pwnagotchi import utils +from pwnagotchi.google import cmd as google_cmd +from pwnagotchi.plugins import cmd as plugins_cmd +from pwnagotchi import log +from pwnagotchi import fs +from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple + + +def pwnagotchi_cli(): + def do_clear(display): + logging.info("clearing the display ...") + display.clear() + sys.exit(0) + + def do_manual_mode(agent): + logging.info("entering manual mode ...") + + agent.mode = 'manual' + agent.last_session.parse(agent.view(), args.skip_session) + if not args.skip_session: + logging.info( + "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( + 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(agent.last_session) + time.sleep(5) + if grid.is_connected(): + plugins.on('internet_available', agent) + + def do_auto_mode(agent): + logging.info("entering auto mode ...") + + agent.mode = 'auto' + agent.start() + + while True: + try: + # recon on all channels + agent.recon() + # get nearby access points grouped by channel + channels = agent.get_access_points_by_channel() + # for each channel + for ch, aps in channels: + time.sleep(1) + agent.set_channel(ch) + + if not agent.is_stale() and agent.any_activity(): + logging.info("%d access points on channel %d" % (len(aps), ch)) + + # for each ap on this channel + for ap in aps: + # send an association frame in order to get for a PMKID + agent.associate(ap) + # deauth all client stations in order to get a full handshake + for sta in ap['clients']: + agent.deauth(ap, sta) + time.sleep(1) # delay to not trigger nexmon firmware bugs + + # An interesting effect of this: + # + # From Pwnagotchi's perspective, the more new access points + # and / or client stations nearby, the longer one epoch of + # its relative time will take ... basically, in Pwnagotchi's universe, + # Wi-Fi electromagnetic fields affect time like gravitational fields + # affect ours ... neat ^_^ + agent.next_epoch() + + if grid.is_connected(): + plugins.on('internet_available', agent) + + except Exception as e: + if str(e).find("wifi.interface not set") > 0: + logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e) + logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger") + time.sleep(60) + agent.next_epoch() + else: + logging.exception("main loop exception (%s)", e) + + def add_parsers(parser): + """ + Adds the plugins and google subcommands + """ + subparsers = parser.add_subparsers() + + # Add parsers from plugins_cmd + plugins_cmd.add_parsers(subparsers) + + # Add parsers from google_cmd + google_cmd.add_parsers(subparsers) + + parser = argparse.ArgumentParser(prog="pwnagotchi") + # pwnagotchi --help + parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml', + help='Main configuration file.') + parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml', + help='If this file exists, configuration will be merged and this will override default values.') + + parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.") + parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False, + help="Skip last session parsing in manual mode.") + + parser.add_argument('--clear', dest="do_clear", action="store_true", default=False, + help="Clear the ePaper display and exit.") + + parser.add_argument('--debug', dest="debug", action="store_true", default=False, + help="Enable debug logs.") + + parser.add_argument('--version', dest="version", action="store_true", default=False, + help="Print the version.") + + parser.add_argument('--print-config', dest="print_config", action="store_true", default=False, + help="Print the configuration.") + + # Jayofelony added these + parser.add_argument('--wizard', dest="wizard", action="store_true", default=False, + help="Interactive installation of your personal configuration.") + parser.add_argument('--check-update', dest="check_update", action="store_true", default=False, + help="Check for updates on Pwnagotchi. And tells current version.") + parser.add_argument('--donate', dest="donate", action="store_true", default=False, + help="How to donate to this project.") + + # pwnagotchi plugins --help + add_parsers(parser) + args = parser.parse_args() + + if plugins_cmd.used_plugin_cmd(args): + config = utils.load_config(args) + log.setup_logging(args, config) + rc = plugins_cmd.handle_cmd(args, config) + sys.exit(rc) + if google_cmd.used_google_cmd(args): + config = utils.load_config(args) + log.setup_logging(args, config) + rc = google_cmd.handle_cmd(args) + sys.exit(rc) + + if args.version: + print(pwnagotchi.__version__) + sys.exit(0) + + if args.wizard: + def is_valid_hostname(hostname): + if len(hostname) > 255: + return False + if hostname[-1] == ".": + hostname = hostname[:-1] # strip exactly one dot from the right, if present + allowed = re.compile("(?!-)[A-Z\d-]{1,63}(? 0: + f.write("main.whitelist = [\n") + for x in range(int(pwn_whitelist)): + ssid = input("SSID (Name): ") + bssid = input("BSSID (MAC): ") + f.write(f"\t\"{ssid}\",\n") + f.write(f"\t\"{bssid}\",\n") + f.write("]\n") + # set bluetooth tether + pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n" + "[Y/N] ") + if pwn_bluetooth.lower() in ('y', 'yes'): + f.write("main.plugins.bt-tether.enabled = true\n\n") + pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n" + "Device: ") + if pwn_bluetooth_device.lower() == "android": + f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n") + pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" + "MAC: ") + if pwn_bluetooth_mac != "": + f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n") + elif pwn_bluetooth_device.lower() == "ios": + f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n") + pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" + "MAC: ") + if pwn_bluetooth_mac != "": + f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n") + # set up display settings + pwn_display_enabled = input("Do you want to enable a display?\n\n" + "[Y/N]: ") + if pwn_display_enabled.lower() in ('y', 'yes'): + f.write("ui.display.enabled = true\n") + pwn_display_type = input("What display do you use?\n\n" + "Be sure to check for the correct display type @ \n" + "https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L501\n\n" + "Display type: ") + if pwn_display_type != "": + f.write(f"ui.display.type = \"{pwn_display_type}\"\n") + pwn_display_invert = input("Do you want to invert the display colors?\n" + "N = Black background\n" + "Y = White background\n\n" + "[Y/N]: ") + if pwn_display_invert.lower() in ('y', 'yes'): + f.write("ui.invert = true\n") + f.close() + if pwn_bluetooth.lower() in ('y', 'yes'): + if pwn_bluetooth_device.lower == "android": + print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n" + "Your configuration is done, and I will restart in 5 seconds.") + elif pwn_bluetooth_device.lower == "ios": + print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n" + "Your configuration is done, and I will restart in 5 seconds.") + else: + print("Your configuration is done, and I will restart in 5 seconds.") + time.sleep(5) + os.system("service pwnagotchi restart") + else: + print("Ok, doing nothing.") + sys.exit(0) + + if args.donate: + print("Donations can be made @ \n " + "https://github.com/sponsors/jayofelony \n\n" + "But only if you really want to!") + sys.exit(0) + + if args.check_update: + resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest") + latest = resp.json() + latest_ver = latest['tag_name'].replace('v', '') + + local = version_to_tuple(pwnagotchi.__version__) + remote = version_to_tuple(latest_ver) + if remote > local: + user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (pwnagotchi.__version__, latest_ver)) + # input validation + if user_input.lower() in ('y', 'yes'): + if os.path.exists('/root/.auto-update'): + os.system("rm /root/.auto-update && systemctl restart pwnagotchi") + else: + logging.error("You should make sure auto-update is enabled!") + print("Okay, give me a couple minutes. Just watch pwnlog while you wait.") + elif user_input.lower() in ('n', 'no'): # using this elif for readability + print("Okay, guess not!") + else: + print("You are currently on the latest release, v%s." % pwnagotchi.__version__) + sys.exit(0) + + config = utils.load_config(args) + + if args.print_config: + print(toml.dumps(config, encoder=DottedTomlEncoder())) + sys.exit(0) + + from pwnagotchi.identity import KeyPair + from pwnagotchi.agent import Agent + from pwnagotchi.ui import fonts + from pwnagotchi.ui.display import Display + from pwnagotchi import grid + from pwnagotchi import plugins + + pwnagotchi.config = config + fs.setup_mounts(config) + log.setup_logging(args, config) + fonts.init(config) + + pwnagotchi.set_name(config['main']['name']) + + plugins.load(config) + + display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) + + if args.do_clear: + do_clear(display) + sys.exit(0) + + agent = Agent(view=display, config=config, keypair=KeyPair(view=display)) + + def usr1_handler(*unused): + logging.info('Received USR1 signal. Restart process ...') + agent._restart("MANU" if args.do_manual else "AUTO") + + signal.signal(signal.SIGUSR1, usr1_handler) + + if args.do_manual: + do_manual_mode(agent) + else: + do_auto_mode(agent) diff --git a/pyproject.toml b/pyproject.toml index d339ae22..ba6da294 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,4 +63,4 @@ Issues = "https://github.com/jayofelony/pwnagotchi/issues" Download = "https://github.com/jayofelony/pwnagotchi/releases/latest" [project.scripts] -pwnagotchi = "bin.pwnagotchi:pwnagotchi_cli" +pwnagotchi = "pwnagotchi:pwnagotchi_cli" From bdc6afd0bea8f265fa702ab9ef3bfa32265b0cff Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 19:10:48 +0100 Subject: [PATCH 17/25] Small edit Signed-off-by: jayofelony --- pwnagotchi/__init__.py | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 2173e2de..e2ef8d20 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -1,10 +1,23 @@ -import os import logging import time import re +import argparse +import time +import signal +import sys +import toml +import requests +import os from pwnagotchi._version import __version__ +from pwnagotchi import utils +from pwnagotchi.google import cmd as google_cmd +from pwnagotchi.plugins import cmd as plugins_cmd +from pwnagotchi import log +from pwnagotchi import fs +from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple + _name = None config = None _cpu_stats = {} @@ -167,26 +180,6 @@ def reboot(mode=None): os.system("sync") os.system("shutdown -r now") -#!/usr/bin/python3 -import logging -import argparse -import time -import signal -import sys -import toml -import requests -import os -import re - -import pwnagotchi -from pwnagotchi import utils -from pwnagotchi.google import cmd as google_cmd -from pwnagotchi.plugins import cmd as plugins_cmd -from pwnagotchi import log -from pwnagotchi import fs -from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple - - def pwnagotchi_cli(): def do_clear(display): logging.info("clearing the display ...") From 85856b26982d30f6ec7205ce1513be9130537686 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 19:14:27 +0100 Subject: [PATCH 18/25] Revert "Small edit" This reverts commit bdc6afd0bea8f265fa702ab9ef3bfa32265b0cff. --- pwnagotchi/__init__.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index e2ef8d20..2173e2de 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -1,23 +1,10 @@ +import os import logging import time import re -import argparse -import time -import signal -import sys -import toml -import requests -import os from pwnagotchi._version import __version__ -from pwnagotchi import utils -from pwnagotchi.google import cmd as google_cmd -from pwnagotchi.plugins import cmd as plugins_cmd -from pwnagotchi import log -from pwnagotchi import fs -from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple - _name = None config = None _cpu_stats = {} @@ -180,6 +167,26 @@ def reboot(mode=None): os.system("sync") os.system("shutdown -r now") +#!/usr/bin/python3 +import logging +import argparse +import time +import signal +import sys +import toml +import requests +import os +import re + +import pwnagotchi +from pwnagotchi import utils +from pwnagotchi.google import cmd as google_cmd +from pwnagotchi.plugins import cmd as plugins_cmd +from pwnagotchi import log +from pwnagotchi import fs +from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple + + def pwnagotchi_cli(): def do_clear(display): logging.info("clearing the display ...") From b6b5e92f6b644c635c1eb3b3c4d5c97aa44a75fe Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 19:14:33 +0100 Subject: [PATCH 19/25] Revert "Small edit" This reverts commit 6f16c651321882d6038cb639b50762c908c6b175. --- bin/pwnagotchi | 337 +++++++++++++++++++++++++++++++++++++++++ pwnagotchi/__init__.py | 333 ---------------------------------------- pyproject.toml | 2 +- 3 files changed, 338 insertions(+), 334 deletions(-) create mode 100755 bin/pwnagotchi diff --git a/bin/pwnagotchi b/bin/pwnagotchi new file mode 100755 index 00000000..4f31cd3c --- /dev/null +++ b/bin/pwnagotchi @@ -0,0 +1,337 @@ +#!/usr/bin/python3 +import logging +import argparse +import time +import signal +import sys +import toml +import requests +import os +import re + +import pwnagotchi +from pwnagotchi import utils +from pwnagotchi.google import cmd as google_cmd +from pwnagotchi.plugins import cmd as plugins_cmd +from pwnagotchi import log +from pwnagotchi import restart +from pwnagotchi import fs +from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple + + +def pwnagotchi_cli(): + def do_clear(display): + logging.info("clearing the display ...") + display.clear() + sys.exit(0) + + def do_manual_mode(agent): + logging.info("entering manual mode ...") + + agent.mode = 'manual' + agent.last_session.parse(agent.view(), args.skip_session) + if not args.skip_session: + logging.info( + "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( + 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(agent.last_session) + time.sleep(5) + if grid.is_connected(): + plugins.on('internet_available', agent) + + def do_auto_mode(agent): + logging.info("entering auto mode ...") + + agent.mode = 'auto' + agent.start() + + while True: + try: + # recon on all channels + agent.recon() + # get nearby access points grouped by channel + channels = agent.get_access_points_by_channel() + # for each channel + for ch, aps in channels: + time.sleep(1) + agent.set_channel(ch) + + if not agent.is_stale() and agent.any_activity(): + logging.info("%d access points on channel %d" % (len(aps), ch)) + + # for each ap on this channel + for ap in aps: + # send an association frame in order to get for a PMKID + agent.associate(ap) + # deauth all client stations in order to get a full handshake + for sta in ap['clients']: + agent.deauth(ap, sta) + time.sleep(1) # delay to not trigger nexmon firmware bugs + + # An interesting effect of this: + # + # From Pwnagotchi's perspective, the more new access points + # and / or client stations nearby, the longer one epoch of + # its relative time will take ... basically, in Pwnagotchi's universe, + # Wi-Fi electromagnetic fields affect time like gravitational fields + # affect ours ... neat ^_^ + agent.next_epoch() + + if grid.is_connected(): + plugins.on('internet_available', agent) + + except Exception as e: + if str(e).find("wifi.interface not set") > 0: + logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e) + logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger") + time.sleep(60) + agent.next_epoch() + else: + logging.exception("main loop exception (%s)", e) + + def add_parsers(parser): + """ + Adds the plugins and google subcommands + """ + subparsers = parser.add_subparsers() + + # Add parsers from plugins_cmd + plugins_cmd.add_parsers(subparsers) + + # Add parsers from google_cmd + google_cmd.add_parsers(subparsers) + + parser = argparse.ArgumentParser(prog="pwnagotchi") + # pwnagotchi --help + parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml', + help='Main configuration file.') + parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml', + help='If this file exists, configuration will be merged and this will override default values.') + + parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.") + parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False, + help="Skip last session parsing in manual mode.") + + parser.add_argument('--clear', dest="do_clear", action="store_true", default=False, + help="Clear the ePaper display and exit.") + + parser.add_argument('--debug', dest="debug", action="store_true", default=False, + help="Enable debug logs.") + + parser.add_argument('--version', dest="version", action="store_true", default=False, + help="Print the version.") + + parser.add_argument('--print-config', dest="print_config", action="store_true", default=False, + help="Print the configuration.") + + # Jayofelony added these + parser.add_argument('--wizard', dest="wizard", action="store_true", default=False, + help="Interactive installation of your personal configuration.") + parser.add_argument('--check-update', dest="check_update", action="store_true", default=False, + help="Check for updates on Pwnagotchi. And tells current version.") + parser.add_argument('--donate', dest="donate", action="store_true", default=False, + help="How to donate to this project.") + + # pwnagotchi plugins --help + add_parsers(parser) + args = parser.parse_args() + + if plugins_cmd.used_plugin_cmd(args): + config = utils.load_config(args) + log.setup_logging(args, config) + rc = plugins_cmd.handle_cmd(args, config) + sys.exit(rc) + if google_cmd.used_google_cmd(args): + config = utils.load_config(args) + log.setup_logging(args, config) + rc = google_cmd.handle_cmd(args) + sys.exit(rc) + + if args.version: + print(pwnagotchi.__version__) + sys.exit(0) + + if args.wizard: + def is_valid_hostname(hostname): + if len(hostname) > 255: + return False + if hostname[-1] == ".": + hostname = hostname[:-1] # strip exactly one dot from the right, if present + allowed = re.compile("(?!-)[A-Z\d-]{1,63}(? 0: + f.write("main.whitelist = [\n") + for x in range(int(pwn_whitelist)): + ssid = input("SSID (Name): ") + bssid = input("BSSID (MAC): ") + f.write(f"\t\"{ssid}\",\n") + f.write(f"\t\"{bssid}\",\n") + f.write("]\n") + # set bluetooth tether + pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n" + "[Y/N] ") + if pwn_bluetooth.lower() in ('y', 'yes'): + f.write("main.plugins.bt-tether.enabled = true\n\n") + pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n" + "Device: ") + if pwn_bluetooth_device.lower() == "android": + f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n") + pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" + "MAC: ") + if pwn_bluetooth_mac != "": + f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n") + elif pwn_bluetooth_device.lower() == "ios": + f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n") + pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" + "MAC: ") + if pwn_bluetooth_mac != "": + f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n") + # set up display settings + pwn_display_enabled = input("Do you want to enable a display?\n\n" + "[Y/N]: ") + if pwn_display_enabled.lower() in ('y', 'yes'): + f.write("ui.display.enabled = true\n") + pwn_display_type = input("What display do you use?\n\n" + "Be sure to check for the correct display type @ \n" + "https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L501\n\n" + "Display type: ") + if pwn_display_type != "": + f.write(f"ui.display.type = \"{pwn_display_type}\"\n") + pwn_display_invert = input("Do you want to invert the display colors?\n" + "N = Black background\n" + "Y = White background\n\n" + "[Y/N]: ") + if pwn_display_invert.lower() in ('y', 'yes'): + f.write("ui.invert = true\n") + f.close() + if pwn_bluetooth.lower() in ('y', 'yes'): + if pwn_bluetooth_device.lower == "android": + print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n" + "Your configuration is done, and I will restart in 5 seconds.") + elif pwn_bluetooth_device.lower == "ios": + print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n" + "Your configuration is done, and I will restart in 5 seconds.") + else: + print("Your configuration is done, and I will restart in 5 seconds.") + time.sleep(5) + os.system("service pwnagotchi restart") + else: + print("Ok, doing nothing.") + sys.exit(0) + + if args.donate: + print("Donations can be made @ \n " + "https://github.com/sponsors/jayofelony \n\n" + "But only if you really want to!") + sys.exit(0) + + if args.check_update: + resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest") + latest = resp.json() + latest_ver = latest['tag_name'].replace('v', '') + + local = version_to_tuple(pwnagotchi.__version__) + remote = version_to_tuple(latest_ver) + if remote > local: + user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (pwnagotchi.__version__, latest_ver)) + # input validation + if user_input.lower() in ('y', 'yes'): + if os.path.exists('/root/.auto-update'): + os.system("rm /root/.auto-update && systemctl restart pwnagotchi") + else: + logging.error("You should make sure auto-update is enabled!") + print("Okay, give me a couple minutes. Just watch pwnlog while you wait.") + elif user_input.lower() in ('n', 'no'): # using this elif for readability + print("Okay, guess not!") + else: + print("You are currently on the latest release, v%s." % pwnagotchi.__version__) + sys.exit(0) + + config = utils.load_config(args) + + if args.print_config: + print(toml.dumps(config, encoder=DottedTomlEncoder())) + sys.exit(0) + + from pwnagotchi.identity import KeyPair + from pwnagotchi.agent import Agent + from pwnagotchi.ui import fonts + from pwnagotchi.ui.display import Display + from pwnagotchi import grid + from pwnagotchi import plugins + + pwnagotchi.config = config + fs.setup_mounts(config) + log.setup_logging(args, config) + fonts.init(config) + + pwnagotchi.set_name(config['main']['name']) + + plugins.load(config) + + display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) + + if args.do_clear: + do_clear(display) + sys.exit(0) + + agent = Agent(view=display, config=config, keypair=KeyPair(view=display)) + + def usr1_handler(*unused): + logging.info('Received USR1 signal. Restart process ...') + agent._restart("MANU" if args.do_manual else "AUTO") + + signal.signal(signal.SIGUSR1, usr1_handler) + + if args.do_manual: + do_manual_mode(agent) + else: + do_auto_mode(agent) + + +if __name__ == '__main__': + pwnagotchi_cli() diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 2173e2de..5c4d9637 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -166,336 +166,3 @@ def reboot(mode=None): os.system("sync") os.system("shutdown -r now") - -#!/usr/bin/python3 -import logging -import argparse -import time -import signal -import sys -import toml -import requests -import os -import re - -import pwnagotchi -from pwnagotchi import utils -from pwnagotchi.google import cmd as google_cmd -from pwnagotchi.plugins import cmd as plugins_cmd -from pwnagotchi import log -from pwnagotchi import fs -from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple - - -def pwnagotchi_cli(): - def do_clear(display): - logging.info("clearing the display ...") - display.clear() - sys.exit(0) - - def do_manual_mode(agent): - logging.info("entering manual mode ...") - - agent.mode = 'manual' - agent.last_session.parse(agent.view(), args.skip_session) - if not args.skip_session: - logging.info( - "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( - 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(agent.last_session) - time.sleep(5) - if grid.is_connected(): - plugins.on('internet_available', agent) - - def do_auto_mode(agent): - logging.info("entering auto mode ...") - - agent.mode = 'auto' - agent.start() - - while True: - try: - # recon on all channels - agent.recon() - # get nearby access points grouped by channel - channels = agent.get_access_points_by_channel() - # for each channel - for ch, aps in channels: - time.sleep(1) - agent.set_channel(ch) - - if not agent.is_stale() and agent.any_activity(): - logging.info("%d access points on channel %d" % (len(aps), ch)) - - # for each ap on this channel - for ap in aps: - # send an association frame in order to get for a PMKID - agent.associate(ap) - # deauth all client stations in order to get a full handshake - for sta in ap['clients']: - agent.deauth(ap, sta) - time.sleep(1) # delay to not trigger nexmon firmware bugs - - # An interesting effect of this: - # - # From Pwnagotchi's perspective, the more new access points - # and / or client stations nearby, the longer one epoch of - # its relative time will take ... basically, in Pwnagotchi's universe, - # Wi-Fi electromagnetic fields affect time like gravitational fields - # affect ours ... neat ^_^ - agent.next_epoch() - - if grid.is_connected(): - plugins.on('internet_available', agent) - - except Exception as e: - if str(e).find("wifi.interface not set") > 0: - logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e) - logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger") - time.sleep(60) - agent.next_epoch() - else: - logging.exception("main loop exception (%s)", e) - - def add_parsers(parser): - """ - Adds the plugins and google subcommands - """ - subparsers = parser.add_subparsers() - - # Add parsers from plugins_cmd - plugins_cmd.add_parsers(subparsers) - - # Add parsers from google_cmd - google_cmd.add_parsers(subparsers) - - parser = argparse.ArgumentParser(prog="pwnagotchi") - # pwnagotchi --help - parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml', - help='Main configuration file.') - parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml', - help='If this file exists, configuration will be merged and this will override default values.') - - parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.") - parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False, - help="Skip last session parsing in manual mode.") - - parser.add_argument('--clear', dest="do_clear", action="store_true", default=False, - help="Clear the ePaper display and exit.") - - parser.add_argument('--debug', dest="debug", action="store_true", default=False, - help="Enable debug logs.") - - parser.add_argument('--version', dest="version", action="store_true", default=False, - help="Print the version.") - - parser.add_argument('--print-config', dest="print_config", action="store_true", default=False, - help="Print the configuration.") - - # Jayofelony added these - parser.add_argument('--wizard', dest="wizard", action="store_true", default=False, - help="Interactive installation of your personal configuration.") - parser.add_argument('--check-update', dest="check_update", action="store_true", default=False, - help="Check for updates on Pwnagotchi. And tells current version.") - parser.add_argument('--donate', dest="donate", action="store_true", default=False, - help="How to donate to this project.") - - # pwnagotchi plugins --help - add_parsers(parser) - args = parser.parse_args() - - if plugins_cmd.used_plugin_cmd(args): - config = utils.load_config(args) - log.setup_logging(args, config) - rc = plugins_cmd.handle_cmd(args, config) - sys.exit(rc) - if google_cmd.used_google_cmd(args): - config = utils.load_config(args) - log.setup_logging(args, config) - rc = google_cmd.handle_cmd(args) - sys.exit(rc) - - if args.version: - print(pwnagotchi.__version__) - sys.exit(0) - - if args.wizard: - def is_valid_hostname(hostname): - if len(hostname) > 255: - return False - if hostname[-1] == ".": - hostname = hostname[:-1] # strip exactly one dot from the right, if present - allowed = re.compile("(?!-)[A-Z\d-]{1,63}(? 0: - f.write("main.whitelist = [\n") - for x in range(int(pwn_whitelist)): - ssid = input("SSID (Name): ") - bssid = input("BSSID (MAC): ") - f.write(f"\t\"{ssid}\",\n") - f.write(f"\t\"{bssid}\",\n") - f.write("]\n") - # set bluetooth tether - pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n" - "[Y/N] ") - if pwn_bluetooth.lower() in ('y', 'yes'): - f.write("main.plugins.bt-tether.enabled = true\n\n") - pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n" - "Device: ") - if pwn_bluetooth_device.lower() == "android": - f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n") - pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" - "MAC: ") - if pwn_bluetooth_mac != "": - f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n") - elif pwn_bluetooth_device.lower() == "ios": - f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n") - pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" - "MAC: ") - if pwn_bluetooth_mac != "": - f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n") - # set up display settings - pwn_display_enabled = input("Do you want to enable a display?\n\n" - "[Y/N]: ") - if pwn_display_enabled.lower() in ('y', 'yes'): - f.write("ui.display.enabled = true\n") - pwn_display_type = input("What display do you use?\n\n" - "Be sure to check for the correct display type @ \n" - "https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L501\n\n" - "Display type: ") - if pwn_display_type != "": - f.write(f"ui.display.type = \"{pwn_display_type}\"\n") - pwn_display_invert = input("Do you want to invert the display colors?\n" - "N = Black background\n" - "Y = White background\n\n" - "[Y/N]: ") - if pwn_display_invert.lower() in ('y', 'yes'): - f.write("ui.invert = true\n") - f.close() - if pwn_bluetooth.lower() in ('y', 'yes'): - if pwn_bluetooth_device.lower == "android": - print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n" - "Your configuration is done, and I will restart in 5 seconds.") - elif pwn_bluetooth_device.lower == "ios": - print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n" - "Your configuration is done, and I will restart in 5 seconds.") - else: - print("Your configuration is done, and I will restart in 5 seconds.") - time.sleep(5) - os.system("service pwnagotchi restart") - else: - print("Ok, doing nothing.") - sys.exit(0) - - if args.donate: - print("Donations can be made @ \n " - "https://github.com/sponsors/jayofelony \n\n" - "But only if you really want to!") - sys.exit(0) - - if args.check_update: - resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest") - latest = resp.json() - latest_ver = latest['tag_name'].replace('v', '') - - local = version_to_tuple(pwnagotchi.__version__) - remote = version_to_tuple(latest_ver) - if remote > local: - user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (pwnagotchi.__version__, latest_ver)) - # input validation - if user_input.lower() in ('y', 'yes'): - if os.path.exists('/root/.auto-update'): - os.system("rm /root/.auto-update && systemctl restart pwnagotchi") - else: - logging.error("You should make sure auto-update is enabled!") - print("Okay, give me a couple minutes. Just watch pwnlog while you wait.") - elif user_input.lower() in ('n', 'no'): # using this elif for readability - print("Okay, guess not!") - else: - print("You are currently on the latest release, v%s." % pwnagotchi.__version__) - sys.exit(0) - - config = utils.load_config(args) - - if args.print_config: - print(toml.dumps(config, encoder=DottedTomlEncoder())) - sys.exit(0) - - from pwnagotchi.identity import KeyPair - from pwnagotchi.agent import Agent - from pwnagotchi.ui import fonts - from pwnagotchi.ui.display import Display - from pwnagotchi import grid - from pwnagotchi import plugins - - pwnagotchi.config = config - fs.setup_mounts(config) - log.setup_logging(args, config) - fonts.init(config) - - pwnagotchi.set_name(config['main']['name']) - - plugins.load(config) - - display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) - - if args.do_clear: - do_clear(display) - sys.exit(0) - - agent = Agent(view=display, config=config, keypair=KeyPair(view=display)) - - def usr1_handler(*unused): - logging.info('Received USR1 signal. Restart process ...') - agent._restart("MANU" if args.do_manual else "AUTO") - - signal.signal(signal.SIGUSR1, usr1_handler) - - if args.do_manual: - do_manual_mode(agent) - else: - do_auto_mode(agent) diff --git a/pyproject.toml b/pyproject.toml index 5d2fa13f..6fc49cb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,4 +42,4 @@ Issues = "https://github.com/jayofelony/pwnagotchi/issues" Download = "https://github.com/jayofelony/pwnagotchi/releases/latest" [project.scripts] -pwnagotchi = "pwnagotchi:pwnagotchi_cli" +pwnagotchi = "bin.pwnagotchi:pwnagotchi_cli" From 5713613451a211c6c4e4ba0f8cff6f8af748fbf4 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 19:14:53 +0100 Subject: [PATCH 20/25] Small edit Signed-off-by: jayofelony --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6fc49cb6..b5502039 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,4 +42,4 @@ Issues = "https://github.com/jayofelony/pwnagotchi/issues" Download = "https://github.com/jayofelony/pwnagotchi/releases/latest" [project.scripts] -pwnagotchi = "bin.pwnagotchi:pwnagotchi_cli" +pwnagotchi = "bin/pwnagotchi:pwnagotchi_cli" From 706ae3b15275782728f00f7d29550c714bcd6db8 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 19:29:32 +0100 Subject: [PATCH 21/25] Small edit Signed-off-by: jayofelony --- bin/pwnagotchi | 337 ----------------------------------------- pwnagotchi/__init__.py | 329 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 328 insertions(+), 338 deletions(-) delete mode 100755 bin/pwnagotchi diff --git a/bin/pwnagotchi b/bin/pwnagotchi deleted file mode 100755 index 4f31cd3c..00000000 --- a/bin/pwnagotchi +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/python3 -import logging -import argparse -import time -import signal -import sys -import toml -import requests -import os -import re - -import pwnagotchi -from pwnagotchi import utils -from pwnagotchi.google import cmd as google_cmd -from pwnagotchi.plugins import cmd as plugins_cmd -from pwnagotchi import log -from pwnagotchi import restart -from pwnagotchi import fs -from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple - - -def pwnagotchi_cli(): - def do_clear(display): - logging.info("clearing the display ...") - display.clear() - sys.exit(0) - - def do_manual_mode(agent): - logging.info("entering manual mode ...") - - agent.mode = 'manual' - agent.last_session.parse(agent.view(), args.skip_session) - if not args.skip_session: - logging.info( - "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( - 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(agent.last_session) - time.sleep(5) - if grid.is_connected(): - plugins.on('internet_available', agent) - - def do_auto_mode(agent): - logging.info("entering auto mode ...") - - agent.mode = 'auto' - agent.start() - - while True: - try: - # recon on all channels - agent.recon() - # get nearby access points grouped by channel - channels = agent.get_access_points_by_channel() - # for each channel - for ch, aps in channels: - time.sleep(1) - agent.set_channel(ch) - - if not agent.is_stale() and agent.any_activity(): - logging.info("%d access points on channel %d" % (len(aps), ch)) - - # for each ap on this channel - for ap in aps: - # send an association frame in order to get for a PMKID - agent.associate(ap) - # deauth all client stations in order to get a full handshake - for sta in ap['clients']: - agent.deauth(ap, sta) - time.sleep(1) # delay to not trigger nexmon firmware bugs - - # An interesting effect of this: - # - # From Pwnagotchi's perspective, the more new access points - # and / or client stations nearby, the longer one epoch of - # its relative time will take ... basically, in Pwnagotchi's universe, - # Wi-Fi electromagnetic fields affect time like gravitational fields - # affect ours ... neat ^_^ - agent.next_epoch() - - if grid.is_connected(): - plugins.on('internet_available', agent) - - except Exception as e: - if str(e).find("wifi.interface not set") > 0: - logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e) - logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger") - time.sleep(60) - agent.next_epoch() - else: - logging.exception("main loop exception (%s)", e) - - def add_parsers(parser): - """ - Adds the plugins and google subcommands - """ - subparsers = parser.add_subparsers() - - # Add parsers from plugins_cmd - plugins_cmd.add_parsers(subparsers) - - # Add parsers from google_cmd - google_cmd.add_parsers(subparsers) - - parser = argparse.ArgumentParser(prog="pwnagotchi") - # pwnagotchi --help - parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml', - help='Main configuration file.') - parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml', - help='If this file exists, configuration will be merged and this will override default values.') - - parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.") - parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False, - help="Skip last session parsing in manual mode.") - - parser.add_argument('--clear', dest="do_clear", action="store_true", default=False, - help="Clear the ePaper display and exit.") - - parser.add_argument('--debug', dest="debug", action="store_true", default=False, - help="Enable debug logs.") - - parser.add_argument('--version', dest="version", action="store_true", default=False, - help="Print the version.") - - parser.add_argument('--print-config', dest="print_config", action="store_true", default=False, - help="Print the configuration.") - - # Jayofelony added these - parser.add_argument('--wizard', dest="wizard", action="store_true", default=False, - help="Interactive installation of your personal configuration.") - parser.add_argument('--check-update', dest="check_update", action="store_true", default=False, - help="Check for updates on Pwnagotchi. And tells current version.") - parser.add_argument('--donate', dest="donate", action="store_true", default=False, - help="How to donate to this project.") - - # pwnagotchi plugins --help - add_parsers(parser) - args = parser.parse_args() - - if plugins_cmd.used_plugin_cmd(args): - config = utils.load_config(args) - log.setup_logging(args, config) - rc = plugins_cmd.handle_cmd(args, config) - sys.exit(rc) - if google_cmd.used_google_cmd(args): - config = utils.load_config(args) - log.setup_logging(args, config) - rc = google_cmd.handle_cmd(args) - sys.exit(rc) - - if args.version: - print(pwnagotchi.__version__) - sys.exit(0) - - if args.wizard: - def is_valid_hostname(hostname): - if len(hostname) > 255: - return False - if hostname[-1] == ".": - hostname = hostname[:-1] # strip exactly one dot from the right, if present - allowed = re.compile("(?!-)[A-Z\d-]{1,63}(? 0: - f.write("main.whitelist = [\n") - for x in range(int(pwn_whitelist)): - ssid = input("SSID (Name): ") - bssid = input("BSSID (MAC): ") - f.write(f"\t\"{ssid}\",\n") - f.write(f"\t\"{bssid}\",\n") - f.write("]\n") - # set bluetooth tether - pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n" - "[Y/N] ") - if pwn_bluetooth.lower() in ('y', 'yes'): - f.write("main.plugins.bt-tether.enabled = true\n\n") - pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n" - "Device: ") - if pwn_bluetooth_device.lower() == "android": - f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n") - pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" - "MAC: ") - if pwn_bluetooth_mac != "": - f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n") - elif pwn_bluetooth_device.lower() == "ios": - f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n") - pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" - "MAC: ") - if pwn_bluetooth_mac != "": - f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n") - # set up display settings - pwn_display_enabled = input("Do you want to enable a display?\n\n" - "[Y/N]: ") - if pwn_display_enabled.lower() in ('y', 'yes'): - f.write("ui.display.enabled = true\n") - pwn_display_type = input("What display do you use?\n\n" - "Be sure to check for the correct display type @ \n" - "https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L501\n\n" - "Display type: ") - if pwn_display_type != "": - f.write(f"ui.display.type = \"{pwn_display_type}\"\n") - pwn_display_invert = input("Do you want to invert the display colors?\n" - "N = Black background\n" - "Y = White background\n\n" - "[Y/N]: ") - if pwn_display_invert.lower() in ('y', 'yes'): - f.write("ui.invert = true\n") - f.close() - if pwn_bluetooth.lower() in ('y', 'yes'): - if pwn_bluetooth_device.lower == "android": - print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n" - "Your configuration is done, and I will restart in 5 seconds.") - elif pwn_bluetooth_device.lower == "ios": - print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n" - "Your configuration is done, and I will restart in 5 seconds.") - else: - print("Your configuration is done, and I will restart in 5 seconds.") - time.sleep(5) - os.system("service pwnagotchi restart") - else: - print("Ok, doing nothing.") - sys.exit(0) - - if args.donate: - print("Donations can be made @ \n " - "https://github.com/sponsors/jayofelony \n\n" - "But only if you really want to!") - sys.exit(0) - - if args.check_update: - resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest") - latest = resp.json() - latest_ver = latest['tag_name'].replace('v', '') - - local = version_to_tuple(pwnagotchi.__version__) - remote = version_to_tuple(latest_ver) - if remote > local: - user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (pwnagotchi.__version__, latest_ver)) - # input validation - if user_input.lower() in ('y', 'yes'): - if os.path.exists('/root/.auto-update'): - os.system("rm /root/.auto-update && systemctl restart pwnagotchi") - else: - logging.error("You should make sure auto-update is enabled!") - print("Okay, give me a couple minutes. Just watch pwnlog while you wait.") - elif user_input.lower() in ('n', 'no'): # using this elif for readability - print("Okay, guess not!") - else: - print("You are currently on the latest release, v%s." % pwnagotchi.__version__) - sys.exit(0) - - config = utils.load_config(args) - - if args.print_config: - print(toml.dumps(config, encoder=DottedTomlEncoder())) - sys.exit(0) - - from pwnagotchi.identity import KeyPair - from pwnagotchi.agent import Agent - from pwnagotchi.ui import fonts - from pwnagotchi.ui.display import Display - from pwnagotchi import grid - from pwnagotchi import plugins - - pwnagotchi.config = config - fs.setup_mounts(config) - log.setup_logging(args, config) - fonts.init(config) - - pwnagotchi.set_name(config['main']['name']) - - plugins.load(config) - - display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) - - if args.do_clear: - do_clear(display) - sys.exit(0) - - agent = Agent(view=display, config=config, keypair=KeyPair(view=display)) - - def usr1_handler(*unused): - logging.info('Received USR1 signal. Restart process ...') - agent._restart("MANU" if args.do_manual else "AUTO") - - signal.signal(signal.SIGUSR1, usr1_handler) - - if args.do_manual: - do_manual_mode(agent) - else: - do_auto_mode(agent) - - -if __name__ == '__main__': - pwnagotchi_cli() diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 5c4d9637..a3f21506 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -2,13 +2,340 @@ import os import logging import time import re - +import argparse +import signal +import sys +import toml +import requests +import utils +from pwnagotchi.google import cmd as google_cmd +from pwnagotchi.plugins import cmd as plugins_cmd +from pwnagotchi import log +from pwnagotchi import fs +from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple from pwnagotchi._version import __version__ _name = None config = None _cpu_stats = {} +def pwnagotchi_cli(): + def do_clear(display): + logging.info("clearing the display ...") + display.clear() + sys.exit(0) + + def do_manual_mode(agent): + logging.info("entering manual mode ...") + + agent.mode = 'manual' + agent.last_session.parse(agent.view(), args.skip_session) + if not args.skip_session: + logging.info( + "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( + 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(agent.last_session) + time.sleep(5) + if grid.is_connected(): + plugins.on('internet_available', agent) + + def do_auto_mode(agent): + logging.info("entering auto mode ...") + + agent.mode = 'auto' + agent.start() + + while True: + try: + # recon on all channels + agent.recon() + # get nearby access points grouped by channel + channels = agent.get_access_points_by_channel() + # for each channel + for ch, aps in channels: + time.sleep(1) + agent.set_channel(ch) + + if not agent.is_stale() and agent.any_activity(): + logging.info("%d access points on channel %d" % (len(aps), ch)) + + # for each ap on this channel + for ap in aps: + # send an association frame in order to get for a PMKID + agent.associate(ap) + # deauth all client stations in order to get a full handshake + for sta in ap['clients']: + agent.deauth(ap, sta) + time.sleep(1) # delay to not trigger nexmon firmware bugs + + # An interesting effect of this: + # + # From Pwnagotchi's perspective, the more new access points + # and / or client stations nearby, the longer one epoch of + # its relative time will take ... basically, in Pwnagotchi's universe, + # Wi-Fi electromagnetic fields affect time like gravitational fields + # affect ours ... neat ^_^ + agent.next_epoch() + + if grid.is_connected(): + plugins.on('internet_available', agent) + + except Exception as e: + if str(e).find("wifi.interface not set") > 0: + logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e) + logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger") + time.sleep(60) + agent.next_epoch() + else: + logging.exception("main loop exception (%s)", e) + + def add_parsers(parser): + """ + Adds the plugins and google subcommands + """ + subparsers = parser.add_subparsers() + + # Add parsers from plugins_cmd + plugins_cmd.add_parsers(subparsers) + + # Add parsers from google_cmd + google_cmd.add_parsers(subparsers) + + parser = argparse.ArgumentParser(prog="pwnagotchi") + # pwnagotchi --help + parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml', + help='Main configuration file.') + parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml', + help='If this file exists, configuration will be merged and this will override default values.') + + parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.") + parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False, + help="Skip last session parsing in manual mode.") + + parser.add_argument('--clear', dest="do_clear", action="store_true", default=False, + help="Clear the ePaper display and exit.") + + parser.add_argument('--debug', dest="debug", action="store_true", default=False, + help="Enable debug logs.") + + parser.add_argument('--version', dest="version", action="store_true", default=False, + help="Print the version.") + + parser.add_argument('--print-config', dest="print_config", action="store_true", default=False, + help="Print the configuration.") + + # Jayofelony added these + parser.add_argument('--wizard', dest="wizard", action="store_true", default=False, + help="Interactive installation of your personal configuration.") + parser.add_argument('--check-update', dest="check_update", action="store_true", default=False, + help="Check for updates on Pwnagotchi. And tells current version.") + parser.add_argument('--donate', dest="donate", action="store_true", default=False, + help="How to donate to this project.") + + # pwnagotchi plugins --help + add_parsers(parser) + args = parser.parse_args() + + if plugins_cmd.used_plugin_cmd(args): + config = utils.load_config(args) + log.setup_logging(args, config) + rc = plugins_cmd.handle_cmd(args, config) + sys.exit(rc) + if google_cmd.used_google_cmd(args): + config = utils.load_config(args) + log.setup_logging(args, config) + rc = google_cmd.handle_cmd(args) + sys.exit(rc) + + if args.version: + print(__version__) + sys.exit(0) + + if args.wizard: + def is_valid_hostname(hostname): + if len(hostname) > 255: + return False + if hostname[-1] == ".": + hostname = hostname[:-1] # strip exactly one dot from the right, if present + allowed = re.compile("(?!-)[A-Z\d-]{1,63}(? 0: + f.write("main.whitelist = [\n") + for x in range(int(pwn_whitelist)): + ssid = input("SSID (Name): ") + bssid = input("BSSID (MAC): ") + f.write(f"\t\"{ssid}\",\n") + f.write(f"\t\"{bssid}\",\n") + f.write("]\n") + # set bluetooth tether + pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n" + "[Y/N] ") + if pwn_bluetooth.lower() in ('y', 'yes'): + f.write("main.plugins.bt-tether.enabled = true\n\n") + pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n" + "Device: ") + if pwn_bluetooth_device.lower() == "android": + f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n") + pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" + "MAC: ") + if pwn_bluetooth_mac != "": + f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n") + elif pwn_bluetooth_device.lower() == "ios": + f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n") + pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" + "MAC: ") + if pwn_bluetooth_mac != "": + f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n") + # set up display settings + pwn_display_enabled = input("Do you want to enable a display?\n\n" + "[Y/N]: ") + if pwn_display_enabled.lower() in ('y', 'yes'): + f.write("ui.display.enabled = true\n") + pwn_display_type = input("What display do you use?\n\n" + "Be sure to check for the correct display type @ \n" + "https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L501\n\n" + "Display type: ") + if pwn_display_type != "": + f.write(f"ui.display.type = \"{pwn_display_type}\"\n") + pwn_display_invert = input("Do you want to invert the display colors?\n" + "N = Black background\n" + "Y = White background\n\n" + "[Y/N]: ") + if pwn_display_invert.lower() in ('y', 'yes'): + f.write("ui.invert = true\n") + f.close() + if pwn_bluetooth.lower() in ('y', 'yes'): + if pwn_bluetooth_device.lower == "android": + print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n" + "Your configuration is done, and I will restart in 5 seconds.") + elif pwn_bluetooth_device.lower == "ios": + print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n" + "Your configuration is done, and I will restart in 5 seconds.") + else: + print("Your configuration is done, and I will restart in 5 seconds.") + time.sleep(5) + os.system("service pwnagotchi restart") + else: + print("Ok, doing nothing.") + sys.exit(0) + + if args.donate: + print("Donations can be made @ \n " + "https://github.com/sponsors/jayofelony \n\n" + "But only if you really want to!") + sys.exit(0) + + if args.check_update: + resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest") + latest = resp.json() + latest_ver = latest['tag_name'].replace('v', '') + + local = version_to_tuple(__version__) + remote = version_to_tuple(latest_ver) + if remote > local: + user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (__version__, latest_ver)) + # input validation + if user_input.lower() in ('y', 'yes'): + if os.path.exists('/root/.auto-update'): + os.system("rm /root/.auto-update && systemctl restart pwnagotchi") + else: + logging.error("You should make sure auto-update is enabled!") + print("Okay, give me a couple minutes. Just watch pwnlog while you wait.") + elif user_input.lower() in ('n', 'no'): # using this elif for readability + print("Okay, guess not!") + else: + print("You are currently on the latest release, v%s." % __version__) + sys.exit(0) + + config = utils.load_config(args) + + if args.print_config: + print(toml.dumps(config, encoder=DottedTomlEncoder())) + sys.exit(0) + + from identity import KeyPair + from agent import Agent + from ui import fonts + from ui.display import Display + import grid + import plugins + + _config = config + fs.setup_mounts(_config) + log.setup_logging(args, _config) + fonts.init(_config) + + set_name(_config['main']['name']) + + plugins.load(_config) + + display = Display(config=_config, state={'name': '%s>' % name()}) + + if args.do_clear: + do_clear(display) + sys.exit(0) + + agent = Agent(view=display, config=_config, keypair=KeyPair(view=display)) + + def usr1_handler(*unused): + logging.info('Received USR1 signal. Restart process ...') + agent._restart("MANU" if args.do_manual else "AUTO") + + signal.signal(signal.SIGUSR1, usr1_handler) + + if args.do_manual: + do_manual_mode(agent) + else: + do_auto_mode(agent) + + +if __name__ == '__main__': + pwnagotchi_cli() + def set_name(new_name): if new_name is None: From 07e7ec661c3208b740c103aef352d63d0d7f4b84 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 19:29:41 +0100 Subject: [PATCH 22/25] Small edit Signed-off-by: jayofelony --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b5502039..5d2fa13f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,4 +42,4 @@ Issues = "https://github.com/jayofelony/pwnagotchi/issues" Download = "https://github.com/jayofelony/pwnagotchi/releases/latest" [project.scripts] -pwnagotchi = "bin/pwnagotchi:pwnagotchi_cli" +pwnagotchi = "pwnagotchi:pwnagotchi_cli" From 5bbbcea2af4dbc36efb6bb1f7449c741e77910fa Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 19:47:01 +0100 Subject: [PATCH 23/25] Small edit Signed-off-by: jayofelony --- pwnagotchi/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index a3f21506..8668a0e1 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -333,10 +333,6 @@ def pwnagotchi_cli(): do_auto_mode(agent) -if __name__ == '__main__': - pwnagotchi_cli() - - def set_name(new_name): if new_name is None: return From 6a217f368422796a2459d4da9205c8d360df0b38 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 19:52:31 +0100 Subject: [PATCH 24/25] Small edit Signed-off-by: jayofelony --- pwnagotchi/__init__.py | 314 -------------------------------------- pwnagotchi/cli.py | 335 +++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 336 insertions(+), 315 deletions(-) create mode 100644 pwnagotchi/cli.py diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 8668a0e1..24844447 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -19,320 +19,6 @@ _name = None config = None _cpu_stats = {} -def pwnagotchi_cli(): - def do_clear(display): - logging.info("clearing the display ...") - display.clear() - sys.exit(0) - - def do_manual_mode(agent): - logging.info("entering manual mode ...") - - agent.mode = 'manual' - agent.last_session.parse(agent.view(), args.skip_session) - if not args.skip_session: - logging.info( - "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( - 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(agent.last_session) - time.sleep(5) - if grid.is_connected(): - plugins.on('internet_available', agent) - - def do_auto_mode(agent): - logging.info("entering auto mode ...") - - agent.mode = 'auto' - agent.start() - - while True: - try: - # recon on all channels - agent.recon() - # get nearby access points grouped by channel - channels = agent.get_access_points_by_channel() - # for each channel - for ch, aps in channels: - time.sleep(1) - agent.set_channel(ch) - - if not agent.is_stale() and agent.any_activity(): - logging.info("%d access points on channel %d" % (len(aps), ch)) - - # for each ap on this channel - for ap in aps: - # send an association frame in order to get for a PMKID - agent.associate(ap) - # deauth all client stations in order to get a full handshake - for sta in ap['clients']: - agent.deauth(ap, sta) - time.sleep(1) # delay to not trigger nexmon firmware bugs - - # An interesting effect of this: - # - # From Pwnagotchi's perspective, the more new access points - # and / or client stations nearby, the longer one epoch of - # its relative time will take ... basically, in Pwnagotchi's universe, - # Wi-Fi electromagnetic fields affect time like gravitational fields - # affect ours ... neat ^_^ - agent.next_epoch() - - if grid.is_connected(): - plugins.on('internet_available', agent) - - except Exception as e: - if str(e).find("wifi.interface not set") > 0: - logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e) - logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger") - time.sleep(60) - agent.next_epoch() - else: - logging.exception("main loop exception (%s)", e) - - def add_parsers(parser): - """ - Adds the plugins and google subcommands - """ - subparsers = parser.add_subparsers() - - # Add parsers from plugins_cmd - plugins_cmd.add_parsers(subparsers) - - # Add parsers from google_cmd - google_cmd.add_parsers(subparsers) - - parser = argparse.ArgumentParser(prog="pwnagotchi") - # pwnagotchi --help - parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml', - help='Main configuration file.') - parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml', - help='If this file exists, configuration will be merged and this will override default values.') - - parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.") - parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False, - help="Skip last session parsing in manual mode.") - - parser.add_argument('--clear', dest="do_clear", action="store_true", default=False, - help="Clear the ePaper display and exit.") - - parser.add_argument('--debug', dest="debug", action="store_true", default=False, - help="Enable debug logs.") - - parser.add_argument('--version', dest="version", action="store_true", default=False, - help="Print the version.") - - parser.add_argument('--print-config', dest="print_config", action="store_true", default=False, - help="Print the configuration.") - - # Jayofelony added these - parser.add_argument('--wizard', dest="wizard", action="store_true", default=False, - help="Interactive installation of your personal configuration.") - parser.add_argument('--check-update', dest="check_update", action="store_true", default=False, - help="Check for updates on Pwnagotchi. And tells current version.") - parser.add_argument('--donate', dest="donate", action="store_true", default=False, - help="How to donate to this project.") - - # pwnagotchi plugins --help - add_parsers(parser) - args = parser.parse_args() - - if plugins_cmd.used_plugin_cmd(args): - config = utils.load_config(args) - log.setup_logging(args, config) - rc = plugins_cmd.handle_cmd(args, config) - sys.exit(rc) - if google_cmd.used_google_cmd(args): - config = utils.load_config(args) - log.setup_logging(args, config) - rc = google_cmd.handle_cmd(args) - sys.exit(rc) - - if args.version: - print(__version__) - sys.exit(0) - - if args.wizard: - def is_valid_hostname(hostname): - if len(hostname) > 255: - return False - if hostname[-1] == ".": - hostname = hostname[:-1] # strip exactly one dot from the right, if present - allowed = re.compile("(?!-)[A-Z\d-]{1,63}(? 0: - f.write("main.whitelist = [\n") - for x in range(int(pwn_whitelist)): - ssid = input("SSID (Name): ") - bssid = input("BSSID (MAC): ") - f.write(f"\t\"{ssid}\",\n") - f.write(f"\t\"{bssid}\",\n") - f.write("]\n") - # set bluetooth tether - pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n" - "[Y/N] ") - if pwn_bluetooth.lower() in ('y', 'yes'): - f.write("main.plugins.bt-tether.enabled = true\n\n") - pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n" - "Device: ") - if pwn_bluetooth_device.lower() == "android": - f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n") - pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" - "MAC: ") - if pwn_bluetooth_mac != "": - f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n") - elif pwn_bluetooth_device.lower() == "ios": - f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n") - pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" - "MAC: ") - if pwn_bluetooth_mac != "": - f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n") - # set up display settings - pwn_display_enabled = input("Do you want to enable a display?\n\n" - "[Y/N]: ") - if pwn_display_enabled.lower() in ('y', 'yes'): - f.write("ui.display.enabled = true\n") - pwn_display_type = input("What display do you use?\n\n" - "Be sure to check for the correct display type @ \n" - "https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L501\n\n" - "Display type: ") - if pwn_display_type != "": - f.write(f"ui.display.type = \"{pwn_display_type}\"\n") - pwn_display_invert = input("Do you want to invert the display colors?\n" - "N = Black background\n" - "Y = White background\n\n" - "[Y/N]: ") - if pwn_display_invert.lower() in ('y', 'yes'): - f.write("ui.invert = true\n") - f.close() - if pwn_bluetooth.lower() in ('y', 'yes'): - if pwn_bluetooth_device.lower == "android": - print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n" - "Your configuration is done, and I will restart in 5 seconds.") - elif pwn_bluetooth_device.lower == "ios": - print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n" - "Your configuration is done, and I will restart in 5 seconds.") - else: - print("Your configuration is done, and I will restart in 5 seconds.") - time.sleep(5) - os.system("service pwnagotchi restart") - else: - print("Ok, doing nothing.") - sys.exit(0) - - if args.donate: - print("Donations can be made @ \n " - "https://github.com/sponsors/jayofelony \n\n" - "But only if you really want to!") - sys.exit(0) - - if args.check_update: - resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest") - latest = resp.json() - latest_ver = latest['tag_name'].replace('v', '') - - local = version_to_tuple(__version__) - remote = version_to_tuple(latest_ver) - if remote > local: - user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (__version__, latest_ver)) - # input validation - if user_input.lower() in ('y', 'yes'): - if os.path.exists('/root/.auto-update'): - os.system("rm /root/.auto-update && systemctl restart pwnagotchi") - else: - logging.error("You should make sure auto-update is enabled!") - print("Okay, give me a couple minutes. Just watch pwnlog while you wait.") - elif user_input.lower() in ('n', 'no'): # using this elif for readability - print("Okay, guess not!") - else: - print("You are currently on the latest release, v%s." % __version__) - sys.exit(0) - - config = utils.load_config(args) - - if args.print_config: - print(toml.dumps(config, encoder=DottedTomlEncoder())) - sys.exit(0) - - from identity import KeyPair - from agent import Agent - from ui import fonts - from ui.display import Display - import grid - import plugins - - _config = config - fs.setup_mounts(_config) - log.setup_logging(args, _config) - fonts.init(_config) - - set_name(_config['main']['name']) - - plugins.load(_config) - - display = Display(config=_config, state={'name': '%s>' % name()}) - - if args.do_clear: - do_clear(display) - sys.exit(0) - - agent = Agent(view=display, config=_config, keypair=KeyPair(view=display)) - - def usr1_handler(*unused): - logging.info('Received USR1 signal. Restart process ...') - agent._restart("MANU" if args.do_manual else "AUTO") - - signal.signal(signal.SIGUSR1, usr1_handler) - - if args.do_manual: - do_manual_mode(agent) - else: - do_auto_mode(agent) - - def set_name(new_name): if new_name is None: return diff --git a/pwnagotchi/cli.py b/pwnagotchi/cli.py new file mode 100644 index 00000000..f9d6f5e1 --- /dev/null +++ b/pwnagotchi/cli.py @@ -0,0 +1,335 @@ +import logging +import argparse +import time +import signal +import sys +import toml +import requests +import os +import re + +import pwnagotchi +from pwnagotchi import utils +from pwnagotchi.google import cmd as google_cmd +from pwnagotchi.plugins import cmd as plugins_cmd +from pwnagotchi import log +from pwnagotchi import fs +from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple + + +def pwnagotchi_cli(): + def do_clear(display): + logging.info("clearing the display ...") + display.clear() + sys.exit(0) + + def do_manual_mode(agent): + logging.info("entering manual mode ...") + + agent.mode = 'manual' + agent.last_session.parse(agent.view(), args.skip_session) + if not args.skip_session: + logging.info( + "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( + 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(agent.last_session) + time.sleep(5) + if grid.is_connected(): + plugins.on('internet_available', agent) + + def do_auto_mode(agent): + logging.info("entering auto mode ...") + + agent.mode = 'auto' + agent.start() + + while True: + try: + # recon on all channels + agent.recon() + # get nearby access points grouped by channel + channels = agent.get_access_points_by_channel() + # for each channel + for ch, aps in channels: + time.sleep(1) + agent.set_channel(ch) + + if not agent.is_stale() and agent.any_activity(): + logging.info("%d access points on channel %d" % (len(aps), ch)) + + # for each ap on this channel + for ap in aps: + # send an association frame in order to get for a PMKID + agent.associate(ap) + # deauth all client stations in order to get a full handshake + for sta in ap['clients']: + agent.deauth(ap, sta) + time.sleep(1) # delay to not trigger nexmon firmware bugs + + # An interesting effect of this: + # + # From Pwnagotchi's perspective, the more new access points + # and / or client stations nearby, the longer one epoch of + # its relative time will take ... basically, in Pwnagotchi's universe, + # Wi-Fi electromagnetic fields affect time like gravitational fields + # affect ours ... neat ^_^ + agent.next_epoch() + + if grid.is_connected(): + plugins.on('internet_available', agent) + + except Exception as e: + if str(e).find("wifi.interface not set") > 0: + logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e) + logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger") + time.sleep(60) + agent.next_epoch() + else: + logging.exception("main loop exception (%s)", e) + + def add_parsers(parser): + """ + Adds the plugins and google subcommands + """ + subparsers = parser.add_subparsers() + + # Add parsers from plugins_cmd + plugins_cmd.add_parsers(subparsers) + + # Add parsers from google_cmd + google_cmd.add_parsers(subparsers) + + parser = argparse.ArgumentParser(prog="pwnagotchi") + # pwnagotchi --help + parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml', + help='Main configuration file.') + parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml', + help='If this file exists, configuration will be merged and this will override default values.') + + parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.") + parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False, + help="Skip last session parsing in manual mode.") + + parser.add_argument('--clear', dest="do_clear", action="store_true", default=False, + help="Clear the ePaper display and exit.") + + parser.add_argument('--debug', dest="debug", action="store_true", default=False, + help="Enable debug logs.") + + parser.add_argument('--version', dest="version", action="store_true", default=False, + help="Print the version.") + + parser.add_argument('--print-config', dest="print_config", action="store_true", default=False, + help="Print the configuration.") + + # Jayofelony added these + parser.add_argument('--wizard', dest="wizard", action="store_true", default=False, + help="Interactive installation of your personal configuration.") + parser.add_argument('--check-update', dest="check_update", action="store_true", default=False, + help="Check for updates on Pwnagotchi. And tells current version.") + parser.add_argument('--donate', dest="donate", action="store_true", default=False, + help="How to donate to this project.") + + # pwnagotchi plugins --help + add_parsers(parser) + args = parser.parse_args() + + if plugins_cmd.used_plugin_cmd(args): + config = utils.load_config(args) + log.setup_logging(args, config) + rc = plugins_cmd.handle_cmd(args, config) + sys.exit(rc) + if google_cmd.used_google_cmd(args): + config = utils.load_config(args) + log.setup_logging(args, config) + rc = google_cmd.handle_cmd(args) + sys.exit(rc) + + if args.version: + print(pwnagotchi.__version__) + sys.exit(0) + + if args.wizard: + def is_valid_hostname(hostname): + if len(hostname) > 255: + return False + if hostname[-1] == ".": + hostname = hostname[:-1] # strip exactly one dot from the right, if present + allowed = re.compile("(?!-)[A-Z\d-]{1,63}(? 0: + f.write("main.whitelist = [\n") + for x in range(int(pwn_whitelist)): + ssid = input("SSID (Name): ") + bssid = input("BSSID (MAC): ") + f.write(f"\t\"{ssid}\",\n") + f.write(f"\t\"{bssid}\",\n") + f.write("]\n") + # set bluetooth tether + pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n" + "[Y/N] ") + if pwn_bluetooth.lower() in ('y', 'yes'): + f.write("main.plugins.bt-tether.enabled = true\n\n") + pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n" + "Device: ") + if pwn_bluetooth_device.lower() == "android": + f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n") + pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" + "MAC: ") + if pwn_bluetooth_mac != "": + f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n") + elif pwn_bluetooth_device.lower() == "ios": + f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n") + pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n" + "MAC: ") + if pwn_bluetooth_mac != "": + f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n") + # set up display settings + pwn_display_enabled = input("Do you want to enable a display?\n\n" + "[Y/N]: ") + if pwn_display_enabled.lower() in ('y', 'yes'): + f.write("ui.display.enabled = true\n") + pwn_display_type = input("What display do you use?\n\n" + "Be sure to check for the correct display type @ \n" + "https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L501\n\n" + "Display type: ") + if pwn_display_type != "": + f.write(f"ui.display.type = \"{pwn_display_type}\"\n") + pwn_display_invert = input("Do you want to invert the display colors?\n" + "N = Black background\n" + "Y = White background\n\n" + "[Y/N]: ") + if pwn_display_invert.lower() in ('y', 'yes'): + f.write("ui.invert = true\n") + f.close() + if pwn_bluetooth.lower() in ('y', 'yes'): + if pwn_bluetooth_device.lower == "android": + print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n" + "Your configuration is done, and I will restart in 5 seconds.") + elif pwn_bluetooth_device.lower == "ios": + print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n" + "Your configuration is done, and I will restart in 5 seconds.") + else: + print("Your configuration is done, and I will restart in 5 seconds.") + time.sleep(5) + os.system("service pwnagotchi restart") + else: + print("Ok, doing nothing.") + sys.exit(0) + + if args.donate: + print("Donations can be made @ \n " + "https://github.com/sponsors/jayofelony \n\n" + "But only if you really want to!") + sys.exit(0) + + if args.check_update: + resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest") + latest = resp.json() + latest_ver = latest['tag_name'].replace('v', '') + + local = version_to_tuple(pwnagotchi.__version__) + remote = version_to_tuple(latest_ver) + if remote > local: + user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (pwnagotchi.__version__, latest_ver)) + # input validation + if user_input.lower() in ('y', 'yes'): + if os.path.exists('/root/.auto-update'): + os.system("rm /root/.auto-update && systemctl restart pwnagotchi") + else: + logging.error("You should make sure auto-update is enabled!") + print("Okay, give me a couple minutes. Just watch pwnlog while you wait.") + elif user_input.lower() in ('n', 'no'): # using this elif for readability + print("Okay, guess not!") + else: + print("You are currently on the latest release, v%s." % pwnagotchi.__version__) + sys.exit(0) + + config = utils.load_config(args) + + if args.print_config: + print(toml.dumps(config, encoder=DottedTomlEncoder())) + sys.exit(0) + + from pwnagotchi.identity import KeyPair + from pwnagotchi.agent import Agent + from pwnagotchi.ui import fonts + from pwnagotchi.ui.display import Display + from pwnagotchi import grid + from pwnagotchi import plugins + + pwnagotchi.config = config + fs.setup_mounts(config) + log.setup_logging(args, config) + fonts.init(config) + + pwnagotchi.set_name(config['main']['name']) + + plugins.load(config) + + display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) + + if args.do_clear: + do_clear(display) + sys.exit(0) + + agent = Agent(view=display, config=config, keypair=KeyPair(view=display)) + + def usr1_handler(*unused): + logging.info('Received USR1 signal. Restart process ...') + agent._restart("MANU" if args.do_manual else "AUTO") + + signal.signal(signal.SIGUSR1, usr1_handler) + + if args.do_manual: + do_manual_mode(agent) + else: + do_auto_mode(agent) + + +if __name__ == '__main__': + pwnagotchi_cli() diff --git a/pyproject.toml b/pyproject.toml index 5d2fa13f..11865033 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,4 +42,4 @@ Issues = "https://github.com/jayofelony/pwnagotchi/issues" Download = "https://github.com/jayofelony/pwnagotchi/releases/latest" [project.scripts] -pwnagotchi = "pwnagotchi:pwnagotchi_cli" +pwnagotchi = "pwnagotchi.cli:pwnagotchi_cli" From 5b20c0fb16a692928061ec1525f54f046c455859 Mon Sep 17 00:00:00 2001 From: jayofelony Date: Sun, 1 Dec 2024 19:53:51 +0100 Subject: [PATCH 25/25] Small edit Signed-off-by: jayofelony --- pwnagotchi/__init__.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 24844447..6655dac4 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -2,17 +2,7 @@ import os import logging import time import re -import argparse -import signal -import sys -import toml -import requests -import utils -from pwnagotchi.google import cmd as google_cmd -from pwnagotchi.plugins import cmd as plugins_cmd -from pwnagotchi import log -from pwnagotchi import fs -from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple + from pwnagotchi._version import __version__ _name = None