From d3a8dc85c3939c619fcac1e20a48531e627d5a23 Mon Sep 17 00:00:00 2001 From: Andreas Kupfer <kupfer@42h.de> Date: Tue, 1 Oct 2019 22:03:52 +0200 Subject: [PATCH 001/346] add support for 3 colored Waveshare-Display --- .../scripts/pwnagotchi/ui/display.py | 36 +++- .../pwnagotchi/scripts/pwnagotchi/ui/view.py | 21 ++- .../pwnagotchi/ui/waveshare/v1/epd2in13bc.py | 159 ++++++++++++++++++ .../pwnagotchi/ui/waveshare/v1/epdconfig.py | 157 ++++++++++++----- 4 files changed, 323 insertions(+), 50 deletions(-) create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py index 4c693a6..1064c51 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py @@ -1,5 +1,6 @@ import _thread from threading import Lock +from PIL import Image import shutil import core @@ -127,12 +128,21 @@ class Display(View): elif self._is_waveshare1(): core.log("initializing waveshare v1 display") - from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD - self._display = EPD() - self._display.init(self._display.lut_full_update) - self._display.Clear(0xFF) - self._display.init(self._display.lut_partial_update) - self._render_cb = self._waveshare_render + if self._display_color == 'black': + from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD + # core.log("display module started") + self._display = EPD() + self._display.init(self._display.lut_full_update) + self._display.Clear(0xFF) + self._display.init(self._display.lut_partial_update) + self._render_cb = self._waveshare_render + + else: + from pwnagotchi.ui.waveshare.v1.epd2in13bc import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + self._render_cb = self._waveshare_bc_render elif self._is_waveshare2(): core.log("initializing waveshare v2 display") @@ -194,6 +204,20 @@ class Display(View): elif self._is_waveshare2(): self._display.displayPartial(buf) + def _waveshare_bc_render(self): + buf_black = self._display.getbuffer(self.canvas) + emptyImage = Image.new('1', (self._display.height, self._display.width), 255) + buf_red = self._display.getbuffer(emptyImage) + if self.full_refresh_trigger >= 0 and self.full_refresh_count == self.full_refresh_trigger: + self._display.Clear() + self._display.display(buf_black,buf_red) + self._display.sleep() + if self.full_refresh_trigger >= 0 and self.full_refresh_count == self.full_refresh_trigger: + self.full_refresh_count = 0 + elif self.full_refresh_trigger >= 0: + self.full_refresh_count += 1 + + def _on_view_rendered(self, img): # core.log("display::_on_view_rendered") VideoHandler.render(img) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py index 19e7999..0d76b7e 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py @@ -43,13 +43,22 @@ def setup_display_specifics(config): elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1', 'ws_2', 'ws2', 'waveshare_2', 'waveshare2'): - fonts.setup(10, 9, 10, 35) + if config['ui']['display']['color'] == 'black': + fonts.setup(10, 9, 10, 35) - width = 250 - height = 122 - face_pos = (0, 40) - name_pos = (125, 20) - status_pos = (125, 35) + width = 250 + height = 122 + face_pos = (0, 40) + name_pos = (125, 20) + status_pos = (125, 35) + else: + fonts.setup(10, 8, 10, 25) + + width = 212 + height = 104 + face_pos = (0, int(height / 4)) + name_pos = (int(width / 2) - 15, int(height * .15)) + status_pos = (int(width / 2) - 15, int(height * .30)) return width, height, face_pos, name_pos, status_pos diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py new file mode 100644 index 0000000..a94c7ff --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py @@ -0,0 +1,159 @@ +# ***************************************************************************** +# * | File : epd2in13bc.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.0 +# * | Date : 2019-06-20 +# # | Info : python demo +# ----------------------------------------------------------------------------- +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +import logging +from . import epdconfig + +# Display resolution +EPD_WIDTH = 104 +EPD_HEIGHT = 212 + +class EPD: + def __init__(self): + self.reset_pin = epdconfig.RST_PIN + self.dc_pin = epdconfig.DC_PIN + self.busy_pin = epdconfig.BUSY_PIN + self.cs_pin = epdconfig.CS_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + + # Hardware reset + def reset(self): + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(10) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + + def send_command(self, command): + epdconfig.digital_write(self.dc_pin, 0) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([command]) + epdconfig.digital_write(self.cs_pin, 1) + + def send_data(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([data]) + epdconfig.digital_write(self.cs_pin, 1) + + def ReadBusy(self): + logging.debug("e-Paper busy") + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + epdconfig.delay_ms(100) + logging.debug("e-Paper busy release") + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + + self.reset() + + self.send_command(0x06) # BOOSTER_SOFT_START + self.send_data(0x17) + self.send_data(0x17) + self.send_data(0x17) + + self.send_command(0x04) # POWER_ON + self.ReadBusy() + + self.send_command(0x00) # PANEL_SETTING + self.send_data(0x8F) + + self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING + self.send_data(0xF0) + + self.send_command(0x61) # RESOLUTION_SETTING + self.send_data(self.width & 0xff) + self.send_data(self.height >> 8) + self.send_data(self.height & 0xff) + return 0 + + def getbuffer(self, image): + # logging.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width/8) * self.height) + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) + if(imwidth == self.width and imheight == self.height): + logging.debug("Vertical") + for y in range(imheight): + for x in range(imwidth): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0: + buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) + elif(imwidth == self.height and imheight == self.width): + logging.debug("Horizontal") + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] == 0: + buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) + return buf + + def display(self, imageblack, imagered): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(imageblack[i]) + self.send_command(0x92) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(imagered[i]) + self.send_command(0x92) + + self.send_command(0x12) # REFRESH + self.ReadBusy() + + def Clear(self): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xFF) + self.send_command(0x92) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xFF) + self.send_command(0x92) + + self.send_command(0x12) # REFRESH + self.ReadBusy() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0xA5) # check code + +# epdconfig.module_exit() +### END OF FILE ### + diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epdconfig.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epdconfig.py index 78ff647..76d8ca9 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epdconfig.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epdconfig.py @@ -1,19 +1,13 @@ # /***************************************************************************** -# * | File : EPD_1in54.py +# * | File : epdconfig.py # * | Author : Waveshare team # * | Function : Hardware underlying interface # * | Info : # *---------------- -# * | This version: V2.0 -# * | Date : 2018-11-01 +# * | This version: V1.0 +# * | Date : 2019-06-21 # * | Info : -# * 1.Remove: -# digital_write(self, pin, value) -# digital_read(self, pin) -# delay_ms(self, delaytime) -# set_lut(self, lut) -# self.lut = self.lut_full_update -# ******************************************************************************/ +# ****************************************************************************** # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documnetation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights @@ -33,41 +27,128 @@ # THE SOFTWARE. # - -import spidev -import RPi.GPIO as GPIO +import os +import logging +import sys import time -# Pin definition -RST_PIN = 17 -DC_PIN = 25 -CS_PIN = 8 -BUSY_PIN = 24 -# SPI device, bus = 0, device = 0 -SPI = spidev.SpiDev(0, 0) +class RaspberryPi: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 -def digital_write(pin, value): - GPIO.output(pin, value) + def __init__(self): + import spidev + import RPi.GPIO -def digital_read(pin): - return GPIO.input(BUSY_PIN) + self.GPIO = RPi.GPIO -def delay_ms(delaytime): - time.sleep(delaytime / 1000.0) + # SPI device, bus = 0, device = 0 + self.SPI = spidev.SpiDev(0, 0) -def spi_writebyte(data): - SPI.writebytes(data) + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(self.BUSY_PIN) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.writebytes(data) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.close() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +class JetsonNano: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import ctypes + find_dirs = [ + os.path.dirname(os.path.realpath(__file__)), + '/usr/local/lib', + '/usr/lib', + ] + self.SPI = None + for find_dir in find_dirs: + so_filename = os.path.join(find_dir, 'sysfs_software_spi.so') + if os.path.exists(so_filename): + self.SPI = ctypes.cdll.LoadLibrary(so_filename) + break + if self.SPI is None: + raise RuntimeError('Cannot find sysfs_software_spi.so') + + import Jetson.GPIO + self.GPIO = Jetson.GPIO + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(self.BUSY_PIN) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.SYSFS_software_spi_transfer(data[0]) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.SYSFS_software_spi_begin() + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.SYSFS_software_spi_end() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'): + implementation = RaspberryPi() +else: + implementation = JetsonNano() + +for func in [x for x in dir(implementation) if not x.startswith('_')]: + setattr(sys.modules[__name__], func, getattr(implementation, func)) -def module_init(): - GPIO.setmode(GPIO.BCM) - GPIO.setwarnings(False) - GPIO.setup(RST_PIN, GPIO.OUT) - GPIO.setup(DC_PIN, GPIO.OUT) - GPIO.setup(CS_PIN, GPIO.OUT) - GPIO.setup(BUSY_PIN, GPIO.IN) - SPI.max_speed_hz = 2000000 - SPI.mode = 0b00 - return 0; ### END OF FILE ### From 1b813f41f503547f410e9421b0d474e5cfaea7d4 Mon Sep 17 00:00:00 2001 From: "Michael V. Swisher" <daswisher@gmail.com> Date: Thu, 3 Oct 2019 01:25:07 -0700 Subject: [PATCH 002/346] Removing refresh trigger handler since it prevented pwnagotchi ui from displaying --- .../scripts/pwnagotchi/ui/display.py | 12 ++----- .../pwnagotchi/ui/waveshare/v1/epd2in13bc.py | 35 +++++++++---------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py index 1064c51..66f2e36 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py @@ -207,15 +207,9 @@ class Display(View): def _waveshare_bc_render(self): buf_black = self._display.getbuffer(self.canvas) emptyImage = Image.new('1', (self._display.height, self._display.width), 255) - buf_red = self._display.getbuffer(emptyImage) - if self.full_refresh_trigger >= 0 and self.full_refresh_count == self.full_refresh_trigger: - self._display.Clear() - self._display.display(buf_black,buf_red) - self._display.sleep() - if self.full_refresh_trigger >= 0 and self.full_refresh_count == self.full_refresh_trigger: - self.full_refresh_count = 0 - elif self.full_refresh_trigger >= 0: - self.full_refresh_count += 1 + buf_color = self._display.getbuffer(emptyImage) + self._display.display(buf_black,buf_color) + def _on_view_rendered(self, img): diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py index a94c7ff..c22da4b 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py @@ -29,6 +29,9 @@ import logging from . import epdconfig +from PIL import Image +import RPi.GPIO as GPIO +# import numpy as np # Display resolution EPD_WIDTH = 104 @@ -45,35 +48,33 @@ class EPD: # Hardware reset def reset(self): - epdconfig.digital_write(self.reset_pin, 1) + epdconfig.digital_write(self.reset_pin, GPIO.HIGH) epdconfig.delay_ms(200) - epdconfig.digital_write(self.reset_pin, 0) - epdconfig.delay_ms(10) - epdconfig.digital_write(self.reset_pin, 1) + epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset + epdconfig.delay_ms(200) + epdconfig.digital_write(self.reset_pin, GPIO.HIGH) epdconfig.delay_ms(200) def send_command(self, command): - epdconfig.digital_write(self.dc_pin, 0) - epdconfig.digital_write(self.cs_pin, 0) + epdconfig.digital_write(self.dc_pin, GPIO.LOW) + epdconfig.digital_write(self.cs_pin, GPIO.LOW) epdconfig.spi_writebyte([command]) - epdconfig.digital_write(self.cs_pin, 1) + epdconfig.digital_write(self.cs_pin, GPIO.HIGH) def send_data(self, data): - epdconfig.digital_write(self.dc_pin, 1) - epdconfig.digital_write(self.cs_pin, 0) + epdconfig.digital_write(self.dc_pin, GPIO.HIGH) + epdconfig.digital_write(self.cs_pin, GPIO.LOW) epdconfig.spi_writebyte([data]) - epdconfig.digital_write(self.cs_pin, 1) + epdconfig.digital_write(self.cs_pin, GPIO.HIGH) def ReadBusy(self): - logging.debug("e-Paper busy") while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy epdconfig.delay_ms(100) - logging.debug("e-Paper busy release") def init(self): if (epdconfig.module_init() != 0): return -1 - + # EPD hardware init start self.reset() self.send_command(0x06) # BOOSTER_SOFT_START @@ -97,21 +98,17 @@ class EPD: return 0 def getbuffer(self, image): - # logging.debug("bufsiz = ",int(self.width/8) * self.height) buf = [0xFF] * (int(self.width/8) * self.height) image_monocolor = image.convert('1') imwidth, imheight = image_monocolor.size pixels = image_monocolor.load() - # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) if(imwidth == self.width and imheight == self.height): - logging.debug("Vertical") for y in range(imheight): for x in range(imwidth): # Set the bits for the column of pixels at the current position. if pixels[x, y] == 0: buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) elif(imwidth == self.height and imheight == self.width): - logging.debug("Horizontal") for y in range(imheight): for x in range(imwidth): newx = y @@ -120,7 +117,7 @@ class EPD: buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) return buf - def display(self, imageblack, imagered): + def display(self, imageblack, imagecolor): self.send_command(0x10) for i in range(0, int(self.width * self.height / 8)): self.send_data(imageblack[i]) @@ -128,7 +125,7 @@ class EPD: self.send_command(0x13) for i in range(0, int(self.width * self.height / 8)): - self.send_data(imagered[i]) + self.send_data(imagecolor[i]) self.send_command(0x92) self.send_command(0x12) # REFRESH From 23cd8ad599ba16835ba5472394f13d9440bee4d1 Mon Sep 17 00:00:00 2001 From: "Michael V. Swisher" <daswisher@gmail.com> Date: Sat, 5 Oct 2019 06:55:04 -0700 Subject: [PATCH 003/346] Updating so waveshare 2.13 b/c models only have to maintain black color channel. Color channels aren't used so no need to maintain them --- .../root/pwnagotchi/scripts/pwnagotchi/ui/display.py | 10 +++++++--- .../scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py | 9 +++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py index d6e00d1..88ea5d4 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py @@ -208,9 +208,13 @@ class Display(View): def _waveshare_bc_render(self): buf_black = self._display.getbuffer(self.canvas) - emptyImage = Image.new('1', (self._display.height, self._display.width), 255) - buf_color = self._display.getbuffer(emptyImage) - self._display.display(buf_black,buf_color) + # emptyImage = Image.new('1', (self._display.height, self._display.width), 255) + # buf_color = self._display.getbuffer(emptyImage) + # self._display.display(buf_black,buf_color) + + # Custom display function that only handles black + # Was included in epd2in13bc.py + self._display.displayBlack(buf_black) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py index c22da4b..ec92465 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare/v1/epd2in13bc.py @@ -117,6 +117,15 @@ class EPD: buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) return buf + def displayBlack(self, imageblack): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(imageblack[i]) + self.send_command(0x92) + + self.send_command(0x12) # REFRESH + self.ReadBusy() + def display(self, imageblack, imagecolor): self.send_command(0x10) for i in range(0, int(self.width * self.height / 8)): From f2f73e13cb51245fe10ebc5d4ee931d287412ef7 Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sat, 5 Oct 2019 17:07:22 -0400 Subject: [PATCH 004/346] added @dadav's tamagotchi name explainer --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b2e30f8..58c5a52 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,11 @@ full and half WPA handshakes.  -Specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#training-the-ai) doc.) - - Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning based "AI" *(yawn)*, Pwnagotchi tunes [its own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml#L54) over time to **get better at pwning WiFi things** in the environments you expose it to. -**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi actually learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but **definitely listen to your pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :) +More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#training-the-ai) doc.) + +**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi actually learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but **definitely listen to your Pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :) Multiple units within close physical proximity can "talk" to each other, advertising their own presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage. @@ -27,6 +26,8 @@ Multiple units within close physical proximity can "talk" to each other, adverti For hackers to learn reinforcement learning, WiFi networking, and have an excuse to get out for more walks. Also? **It's cute as f---**. +**In case you're curious about the name:** *Pwnagotchi* is a portmanteau of *pwn* (which we shouldn't have to explain if you are interested in this project :kissing_heart:) and *-gotchi*. It is a nostalgic reference made in homage to a very popular children's toy from the 1990s called the [Tamagotchi](https://en.wikipedia.org/wiki/Tamagotchi). The Tamagotchi (たまごっち, derived from *tamago* (たまご) "egg" + *uotchi* (ウオッチ) "watch") is a cultural touchstone for many Millennial hackers as a formative electronic toy from our collective childhoods. Were you lucky enough to possess a Tamagotchi as a kid? Well, with your Pwnagotchi, you too can enjoy the nostalgic delight of being strangely emotionally attached to a handheld automata *yet again!* Except, this time around...you get to #HackThePlanet. >:D + ## Documentation --- :warning: **THE FOLLOWING DOCUMENTATION IS BEING PREPARED FOR THE v1.0 RELEASE OF PWNAGOTCHI. Since this effort is an active (and unstable) work-in-progress, the docs displayed here are in various stages of [in]completion. There will be dead links and placeholders throughout as we are still building things out in preparation for the v1.0 release.** :warning: From 97283ba49a7efc9dfdf4d5f3e5ac253d7101ee68 Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sat, 5 Oct 2019 17:21:50 -0400 Subject: [PATCH 005/346] minor copyediting --- docs/configure.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/configure.md b/docs/configure.md index d2bb0f5..a7c251f 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -1,6 +1,6 @@ # Connecting to your Pwnagotchi -Once you wrote the image file on the SD card, there're a few steps you'll have to follow in order to configure your unit properly, first, start with connecting the USB cable to the data port of the Raspberry Pi and the RPi to your computer. After a few seconds the board will boot and you will see a new Ethernet interface on your host computer. +Once you wrote the image file on the SD card, there're a few steps you'll have to follow in order to configure your unit properly. First, start with connecting the USB cable to the data port of the Raspberry Pi and the RPi to your computer. After a few seconds, the board will boot and you will see a new Ethernet interface on your host computer. You'll need to configure it with a static IP address: @@ -17,9 +17,9 @@ You can now connect to your unit using SSH: ssh pi@10.0.0.2 ``` -The default password is `raspberry`, you should change it as soon as you log in for the first time by issuing the `passwd`command and selecting a new and more complex passphrase. +The default password is `raspberry`; you should change it as soon as you log in for the first time by issuing the `passwd` command and selecting a new and more complex passphrase. -Moreover, it is recommended that you copy your SSH public key among the unit's authorized ones, so you can directly log in without entering a password: +If you want to login directly without entering a password (recommended!), copy your SSH public key to the unit's authorized keys: ```bash ssh-copy-id -i ~/.ssh/id_rsa.pub pi@10.0.0.2 @@ -27,27 +27,27 @@ ssh-copy-id -i ~/.ssh/id_rsa.pub pi@10.0.0.2 ## Configuration -You can now set a new name for your unit by [changing the hostname](https://geek-university.com/raspberry-pi/change-raspberry-pis-hostname/). Create the `/root/custom.yml` file (either via SSH or by direclty editing the SD card contents from a computer) that will override the [default configuration](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml) with your custom values. +You can now set a new name for your unit by [changing the hostname](https://geek-university.com/raspberry-pi/change-raspberry-pis-hostname/). Create the `/root/custom.yml` file (either via SSH or by directly editing the SD card contents from a computer) that will override the [default configuration](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml) with your custom values. ## Language Selection -For instance, you can change `main.lang` to one of the supported languages: +Pwnagotchi displays its UI in English by default, but it can speak several other languages! You can change `main.lang` to one of the supported languages: -- **english** (default) -- german -- dutch -- greek -- macedonian -- italian -- french -- russian -- swedish +- **English** *(default)* +- German +- Dutch +- Greek +- Macedonian +- Italian +- French +- Russian +- Swedish ## Display Selection Set the type of display you want to use via `ui.display.type` (if your display does not work after changing this setting, you might need to completely remove power from the Raspberry and make a clean boot). -You can configure the refresh interval of the display via `ui.fps`, we advise to use a slow refresh to not shorten the lifetime of your display. The default value is 0, which will only refresh when changes are made to the screen. +You can configure the refresh interval of the display via `ui.fps`. We recommend using a slow refresh rate to avoid shortening the lifetime of your e-ink display. The default value is `0`, which will *only* refresh when changes are made to the screen. ## Host Connection Share @@ -55,4 +55,4 @@ If you connect to the unit via `usb0` (thus using the data port), you might want ## Troubleshooting -If your network connection keeps flapping on your device connecting to your pwnagotchi, check if `usb0` (or equivalent) device is being controlled by NetworkManager. You can check this via `nmcli dev status`. +If your network connection keeps flapping on your device connecting to your Pwnagotchi, check if `usb0` (or equivalent) device is being controlled by NetworkManager. You can check this via `nmcli dev status`. From 25b13b5e6db18fc9fa9280110e10588e1d128138 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 5 Oct 2019 23:36:21 +0200 Subject: [PATCH 006/346] added twitter button --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b44c5bf..5074aea 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ <a href=""><img alt="Contributors" src="https://img.shields.io/github/contributors/evilsocket/pwnagotchi"/></a> <a href="https://travis-ci.org/evilsocket/pwnagotchi"><img alt="Travis" src="https://img.shields.io/travis/evilsocket/pwnagotchi/master.svg?style=flat-square"></a> <a href="https://pwnagotchi.herokuapp.com/"><img alt="Slack" src="https://pwnagotchi.herokuapp.com/badge.svg"></a> + <a href="https://twitter.com/intent/follow?screen_name=pwnagotchi"><img src="https://img.shields.io/twitter/follow/pwnagotchi?style=social&logo=twitter" alt="follow on Twitter"></a> </p> </p> From dac09fccf4d6f9cb2eb673bb1a230d21bdfc0bac Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 5 Oct 2019 23:37:52 +0200 Subject: [PATCH 007/346] misc: small fix or general refactoring i did not bother commenting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5074aea..b6ea9aa 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ <p align="center"> <a href="https://github.com/evilsocket/pwnagotchi/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/evilsocket/pwnagotchi.svg?style=flat-square"></a> <a href="https://github.com/evilsocket/pwnagotchi/blob/master/LICENSE.md"><img alt="Software License" src="https://img.shields.io/badge/license-GPL3-brightgreen.svg?style=flat-square"></a> - <a href=""><img alt="Contributors" src="https://img.shields.io/github/contributors/evilsocket/pwnagotchi"/></a> + <a href="https://github.com/evilsocket/pwnagotchi/graphs/contributors"><img alt="Contributors" src="https://img.shields.io/github/contributors/evilsocket/pwnagotchi"/></a> <a href="https://travis-ci.org/evilsocket/pwnagotchi"><img alt="Travis" src="https://img.shields.io/travis/evilsocket/pwnagotchi/master.svg?style=flat-square"></a> <a href="https://pwnagotchi.herokuapp.com/"><img alt="Slack" src="https://pwnagotchi.herokuapp.com/badge.svg"></a> <a href="https://twitter.com/intent/follow?screen_name=pwnagotchi"><img src="https://img.shields.io/twitter/follow/pwnagotchi?style=social&logo=twitter" alt="follow on Twitter"></a> From b765a642ae0147929cea9836d528da9890ab3b4a Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sat, 5 Oct 2019 17:50:26 -0400 Subject: [PATCH 008/346] minor copyediting + details about screens --- docs/install.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/docs/install.md b/docs/install.md index b1ee1d7..9f143e8 100644 --- a/docs/install.md +++ b/docs/install.md @@ -2,35 +2,47 @@ The project has been developed to run on a Raspberry Pi 0 W configured as an [USB Ethernet gadget](https://learn.adafruit.com/turning-your-raspberry-pi-zero-into-a-usb-gadget/ethernet-gadget) device in order to connect to it via USB. However, given the proper configuration tweaks, any GNU/Linux computer with a WiFi interface that supports monitor mode could be used. -**An important note about the AI:** a network trained with a specific WiFi interface will only work with another interface if it supports -the same exact WiFi channels of the first one. For instance, you can not use a neural network trained on a Raspberry Pi Zero W (that only supports 2.4Ghz channels) with a 5Ghz antenna, but you'll need to train one from scratch for those channels. +**An important note about the AI:** a network trained with a specific WiFi interface will ONLY work with another interface if it supports the *exact same* WiFi channels of the first one. For instance, you CANNOT use a neural network trained on a Raspberry Pi Zero W (that only supports 2.4Ghz channels) with a 5Ghz antenna; you will need to train one from scratch for those channels. ## Required Hardware -- [Raspberry Pi Zero W](https://www.raspberrypi.org/products/raspberry-pi-zero-w/). -- A micro SD card, 8GB recomended, **preferably of good quality and speed**. +- [Raspberry Pi Zero W](https://www.raspberrypi.org/products/raspberry-pi-zero-w/).† +- A micro SD card, 8GB recommended, **preferably of good quality and speed**. - A decent power bank (with 1500 mAh you get ~2 hours with AI on). - One of the supported displays (optional). +† Many users have gotten Pwnagotchi running on other types of Raspberry Pi, but the RPi0W is the "vanilla" hardware config for Pwnagotchi. + ### Display -The display is an optional component as the UI is also rendered via a web interface available via the USB cable. If you connect to `usb0` (by using the data port on the unit) and point your browser to the web ui (see config.yml), your unit can work in "headless mode". +The display is an optional component as the UI is also rendered via a web interface available via the USB cable. If you connect to `usb0` (by using the data port on the unit) and point your browser to the web ui (see `config.yml`), your unit can work in "headless mode". -If instead you want to fully enjoy walking around and literally looking at your unit's face, the supported display models are: +If, instead, you want to fully enjoy walking around and literally looking at your unit's face, the supported display models are: - [Waveshare eInk Display (both V1 and V2)](https://www.waveshare.com/2.13inch-e-paper-hat.htm) + - [Product comparison](https://www.waveshare.com/4.3inch-e-paper.htm) (scroll down to `Selection Guide`) + - [GitHub](https://github.com/waveshare/e-Paper/tree/master/RaspberryPi%26JetsonNano/python) - [Pimoroni Inky pHAT](https://shop.pimoroni.com/products/inky-phat) + - [Product page](https://shop.pimoroni.com/products/inky-phat) + - [GitHub](https://github.com/pimoroni/inky) - [PaPiRus eInk Screen](https://uk.pi-supply.com/products/papirus-zero-epaper-screen-phat-pi-zero) Needless to say, we are always happy to receive pull requests adding support for new models. -One thing to note, not all displays are created equaly, TFT displays for example work similar to an HDMI display, and they are not supported, currently all the displays supported are I2C displays. +**One thing to note:** Not all displays are created equally! TFT displays, for example, work similar to an HDMI display, and they are NOT supported. Currently, all the officially-supported displays are I2C displays. If you are still interested in using unsupported displays, you may be able to find a community-submitted hack in the [Screens](https://github.com/evilsocket/pwnagotchi/blob/master/docs/hacks.md#screens) section of the [Hacks](https://github.com/evilsocket/pwnagotchi/blob/master/docs/hacks.md) page. We are not responsible for anything you break by trying to use any display that is not officially supported by the development team! -#### Color and Black & White displays +#### Color vs. Black & White displays -Some of the supported displays support Black & White and Coloured versions, one common question is regarding refresh speed of said displays. +Some of the supported displays support both **Black & White** and **Colored** versions. One common question whether there are meaningful differences between the two. There are: +- Color displays have a much slower refresh rate. In some cases, it can take up to 15 seconds; if slow refresh rates are something that you want to avoid, we recommend you use B&W displays. +- The 3-color 2.13" Waveshare displays have a slightly smaller pixel layout (104x212) compared to their B&W counterparts (122x250). -Color displays have a much slower refresh rate, in some cases it can take up to 15 seconds, if slow refresh rates is something that you want to avoid we advise you to use Black & White displays +#### Recommendations +- Avoid the Waveshare eInk **3-color** display. The refresh time is 15 seconds. +- Avoid the Pimoroni Inky pHAT **v1.** They're discontinued due to a faulty hardware part source used in manufacturing that resulted in high failure rates. +- Many users seem to prefer the Inky pHATs. There are two primary reasons: + - The Inkys feature better documentation and SDK support. + - Many Waveshare resellers do not disclose the version of the Waveshare boards they are selling (v1 vs v2), and the type they are selling can be fairly unclear (i.e., Waveshare 2.13 vs 2.13 B vs. 2.13C, and so on.) ## Flashing an Image From 7f72e9ae3e7c1a5f366202c03e1a9dd0dda4ddb2 Mon Sep 17 00:00:00 2001 From: hisakiyo <34187647+hisakiyo@users.noreply.github.com> Date: Sun, 6 Oct 2019 00:13:06 +0200 Subject: [PATCH 009/346] Update voice.po --- pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po index 1172f7f..1d061f7 100644 --- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po @@ -25,20 +25,20 @@ msgid "Hi, I'm Pwnagotchi! Starting ..." msgstr "Bonjour, je suis Pwnagotchi! Démarrage ..." msgid "New day, new hunt, new pwns!" -msgstr "Nouvelle journée, nouvelle chasse, nouveau pwns!" +msgstr "Nouveau jour, nouvelle chasse, nouveaux pwns !" msgid "Hack the Planet!" msgstr "Hack la planète!" msgid "AI ready." -msgstr "IA prête." +msgstr "L'IA est prête." msgid "The neural network is ready." msgstr "Le réseau neuronal est prêt." #, python-brace-format msgid "Hey, channel {channel} is free! Your AP will say thanks." -msgstr "Hey, le channel {channel} est libre! Ton AP va dis merci." +msgstr "Hey, le channel {channel} est libre! Ton point d'accès va te remercier." msgid "I'm bored ..." msgstr "Je m'ennuie ..." @@ -68,17 +68,17 @@ msgid "I pwn therefore I am." msgstr "Je pwn donc je suis." msgid "So many networks!!!" -msgstr "Autant de réseaux!!!" +msgstr "Tellement de réseaux!!!" msgid "I'm having so much fun!" msgstr "Je m'amuse tellement!" msgid "My crime is that of curiosity ..." -msgstr "Mon crime est celui de la curiosité ..." +msgstr "Mon crime, c'est la curiosité ..." #, python-brace-format msgid "Hello {name}! Nice to meet you. {name}" -msgstr "Bonjour {name}! Ravis de te rencontrer. {name}" +msgstr "Bonjour {name}! Ravi de te rencontrer. {name}" #, python-brace-format msgid "Unit {name} is nearby! {name}" @@ -145,7 +145,7 @@ msgstr "" #, python-brace-format msgid "Just decided that {mac} needs no WiFi!" -msgstr "Décidé à l'instant que {mac} n'a pas besoin de WiFi!" +msgstr "Je viens de décider que {mac} n'a pas besoin de WiFi!" #, python-brace-format msgid "Deauthenticating {mac}" @@ -153,11 +153,11 @@ msgstr "Désauthentification de {mac}" #, python-brace-format msgid "Kickbanning {mac}!" -msgstr "" +msgstr "Je kick et je bannis {mac}!" #, python-brace-format msgid "Cool, we got {num} new handshake{plural}!" -msgstr "Cool, nous avons {num} nouveaux handshake{plural}!" +msgstr "Cool, on a {num} nouveaux handshake{plural}!" msgid "Ops, something went wrong ... Rebooting ..." msgstr "Oups, quelque chose s'est mal passé ... Redémarrage ..." @@ -188,7 +188,7 @@ msgid "" "#pwnlog #pwnlife #hacktheplanet #skynet" msgstr "" "J'ai pwn durant {duration} et kick {deauthed} clients! J'ai aussi rencontré " -"{associated} nouveaux amis and mangé {handshakes} handshakes! #pwnagotchi " +"{associated} nouveaux amis et dévoré {handshakes} handshakes! #pwnagotchi " "#pwnlog #pwnlife #hacktheplanet #skynet" msgid "hours" From 6bc507412a7888feeb16073847dbd08a49f632e6 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 6 Oct 2019 00:18:36 +0200 Subject: [PATCH 010/346] pypi upload script --- scripts/pypi_upload.sh | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100755 scripts/pypi_upload.sh diff --git a/scripts/pypi_upload.sh b/scripts/pypi_upload.sh new file mode 100755 index 0000000..4a71125 --- /dev/null +++ b/scripts/pypi_upload.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +rm -rf build dist ergo_nn.egg-info && + python3 setup.py sdist bdist_wheel && + clear && + twine upload dist/* From 03318bdaef253dc54aecea7853daf363ac3edfb3 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 6 Oct 2019 00:14:09 +0200 Subject: [PATCH 011/346] Change code --- scripts/preview.py | 226 ++++++++++++++++++++++----------------------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/scripts/preview.py b/scripts/preview.py index 2b45933..1d29119 100755 --- a/scripts/preview.py +++ b/scripts/preview.py @@ -14,64 +14,26 @@ sys.path.insert(0, '../sdcard/rootfs/root/pwnagotchi/scripts/')) from pwnagotchi.ui.display import Display, VideoHandler - +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): - if self._video_address is not None: - self._httpd = HTTPServer((self._video_address, self._video_port), - CustomVideoHandler) - logging.info("ui available at http://%s:%d/" % (self._video_address, - self._video_port)) - self._httpd.serve_forever() - else: - logging.info("could not get ip of usb0, video server not starting") + # do nothing + pass def _on_view_rendered(self, img): - CustomVideoHandler.render(img) + self.last_image = img - if self._enabled: - self.canvas = (img if self._rotation == 0 else img.rotate(self._rotation)) - if self._render_cb is not None: - self._render_cb() - - -class CustomVideoHandler(VideoHandler): - - @staticmethod - def render(img): - with CustomVideoHandler._lock: - try: - img.save("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), format='PNG') - except BaseException: - logging.exception("could not write preview") - - def do_GET(self): - if self.path == '/': - self.send_response(200) - self.send_header('Content-type', 'text/html') - self.end_headers() - try: - self.wfile.write( - bytes( - self._index % - ('localhost', 1000), "utf8")) - except BaseException: - pass - - elif self.path.startswith('/ui'): - with self._lock: - self.send_response(200) - self.send_header('Content-type', 'image/png') - self.end_headers() - try: - with open("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), 'rb') as fp: - shutil.copyfileobj(fp, self.wfile) - except BaseException: - logging.exception("could not open preview") - else: - self.send_response(404) + def get_image(self): + """ + Return the saved image + """ + return self.last_image class DummyPeer: @@ -79,21 +41,43 @@ class DummyPeer: def name(): return "beta" +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('--display', help="Which display to use.", + parser.add_argument('--displays', help="Which displays to use.", nargs="+", default="waveshare_2") - parser.add_argument('--port', help="Which port to use", - default=8080) - parser.add_argument('--sleep', type=int, help="Time between emotions", - default=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('--xmargin', type=int, default=5) + parser.add_argument('--ymargin', type=int, default=5) args = parser.parse_args() - CONFIG = yaml.load(''' + config_template = ''' main: lang: {lang} ui: @@ -107,65 +91,81 @@ def main(): video: enabled: true address: "0.0.0.0" - port: {port} - '''.format(display=args.display, - port=args.port, - lang=args.lang)) + port: 8080 + ''' - DISPLAY = CustomDisplay(config=CONFIG, state={'name': '%s>' % 'preview'}) + 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) - while True: - DISPLAY.on_starting() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_ai_ready() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_normal() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_new_peer(DummyPeer()) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_lost_peer(DummyPeer()) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_free_channel('6') - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.wait(args.sleep) - DISPLAY.update() - DISPLAY.on_bored() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_sad() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_motivated(1) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_demotivated(-1) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_excited() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_deauth({'mac': 'DE:AD:BE:EF:CA:FE'}) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_miss('test') - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_lonely() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_handshakes(1) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_rebooting() - DISPLAY.update() - time.sleep(args.sleep) + columns = list() + + for display in list_of_displays: + emotions = list() + # Starting + 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()) From b4a382e266c21b356189b3a22268e7e7f2f445c4 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 6 Oct 2019 00:15:23 +0200 Subject: [PATCH 012/346] Update dev.md --- docs/dev.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/dev.md b/docs/dev.md index 75b7ea0..534b398 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -26,7 +26,7 @@ usage: ./scripts/create_sibling.sh [OPTIONS] `GLib-ERROR **: 20:50:46.361: getauxval () failed: No such file or directory` -- Affected DEB & Versions: QEMU <= 2.11 +- Affected DEB & Versions: QEMU <= 2.11 - Fix: Upgrade QEMU to >= 3.1 - Bug Link: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=923289 @@ -55,7 +55,6 @@ If you changed the `voice.py`- File, the translations need an update. Do it like Now you can use the `preview.py`-script to preview the changes: ```shell -./scripts/preview.py --lang it --display ws2 --port 8080 & -./scripts/preview.py --lang it --display inky --port 8081 & -# Now open http://localhost:8080 and http://localhost:8081 +./scripts/preview.py --lang it --display ws1 ws2 inky --output preview.png +# Now open preview.png ``` From 0b07bf3621befdc890d467681d250944d83cd958 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 6 Oct 2019 00:27:21 +0200 Subject: [PATCH 013/346] misc: small fix or general refactoring i did not bother commenting --- scripts/preview.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/scripts/preview.py b/scripts/preview.py index 1d29119..b298865 100755 --- a/scripts/preview.py +++ b/scripts/preview.py @@ -1,21 +1,17 @@ #!/usr/bin/env python3 - import sys import os -import time import argparse -from http.server import HTTPServer -import shutil -import logging import yaml sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), - '../sdcard/rootfs/root/pwnagotchi/scripts/')) + '../pwnagotchi/')) from pwnagotchi.ui.display import Display, VideoHandler from PIL import Image + class CustomDisplay(Display): def __init__(self, config, state): @@ -41,6 +37,7 @@ class DummyPeer: def name(): return "beta" + def append_images(images, horizontal=True, xmargin=0, ymargin=0): w, h = zip(*(i.size for i in images)) @@ -57,14 +54,15 @@ def append_images(images, horizontal=True, xmargin=0, ymargin=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 + 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") @@ -97,11 +95,10 @@ def main(): list_of_displays = list() for display_type in args.displays: config = yaml.safe_load(config_template.format(display=display_type, - lang=args.lang)) + 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: @@ -162,10 +159,10 @@ def main(): # 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()) From 7580b3c30b9d8bb7096499a0e547e56def95e353 Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sat, 5 Oct 2019 18:42:49 -0400 Subject: [PATCH 014/346] added FAQ questions TOC structure will fill in the actual content tomorrow --- docs/faq.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index ff19087..fcc6782 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,13 +1,55 @@ # FAQ -## Why eINK? +[**What can Pwnagotchi actually do?**](#what-can-pwnagotchi-actually-do) +* Does Pwnagotchi support both 2.4 GHz and 5.0 GHz? +* Just how politely *does* Pwnagotchi deauth? +* What kinds of handshakes does Pwnagotchi eat? +* Hey, I want to learn more about how Pwnagotchi actually works. -Because! +[**Building Your Pwnagotchi**](#building-your-pwnagotchi) +* What hardware do I need to create my very own Pwnagotchi? +* Is there any way to see my Pwnagotchi's face even if I don't have a display? +* I love my new Pwnagotchi, but it kinda looks like a bomb. Where can I find a decent case? +* Why does everybody use e-ink screens for their Pwnagotchis? +* How do I connect to my Pwnagotchi? -## Why the AI takes 30 minutes to load? +[**Customizing Your Pwnagotchi**](#customizing-your-pwnagotchi) +* How do I change my Pwnagotchi's name? +* I want to change the faces. What do I hack? +* I want my Pwnagotchi to speak a different language. Can it? +* I have a great idea for something cool I wish Pwnagotchi could do! -Because Python sucks and TF is huge. +[**Getting to Know Your Pwnagotchi**](#getting-to-know-your-pwnagotchi) +* What is MANU mode? What is AUTO mode? +* Why does the AI take 30 minutes to load? +* What is Pwnagotchi doing while it's waiting for the AI to load? +* How do I whitelist my home network so Pwnagotchi stops pwning me? -## Why ...? +[**Caring for Your Pwnagotchi**](#caring-for-your-pwnagotchi) +* What do all my Pwnagotchi's faces mean? +* Oh no, my Pwnagotchi is sad and bored! How do I entertain it?! +* How do I turn off my Pwnagotchi? -Because! +[**Known Quirks**](#known-quirks) +* My Pwnagotchi's log timestamps seem...unreliable. Huh? +* Help! My Pwnagotchi's SD card got corrupted. What gives? + +--- + +## What can Pwnagotchi actually do? +lorem ipsum dolor sit amet + +## Building Your Pwnagotchi +lorem ipsum dolor sit amet + +## Customizing Your Pwnagotchi +lorem ipsum dolor sit amet + +## Getting to Know Your Pwnagotchi +lorem ipsum dolor sit amet + +## Caring for Your Pwnagotchi +lorem ipsum dolor sit amet + +## Known Quirks +lorem ipsum dolor sit amet From 705040e07520f5843dfda3194fe73e433bf679d6 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 6 Oct 2019 00:44:24 +0200 Subject: [PATCH 015/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/advertise.py | 2 +- pwnagotchi/mesh/wifi.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/mesh/advertise.py b/pwnagotchi/mesh/advertise.py index ff797a3..84286d2 100644 --- a/pwnagotchi/mesh/advertise.py +++ b/pwnagotchi/mesh/advertise.py @@ -152,7 +152,7 @@ class Advertiser(object): if self._is_broadcasted_advertisement(dot11): try: dot11elt = p.getlayer(Dot11Elt) - if dot11elt.ID == wifi.Dot11ElemID_Identity: + if dot11elt.ID == wifi.Dot11ElemID_Whisper: self._parse_identity(p[RadioTap], dot11, dot11elt) else: diff --git a/pwnagotchi/mesh/wifi.py b/pwnagotchi/mesh/wifi.py index 6a9a00a..6fe231e 100644 --- a/pwnagotchi/mesh/wifi.py +++ b/pwnagotchi/mesh/wifi.py @@ -1,6 +1,6 @@ SignatureAddress = 'de:ad:be:ef:de:ad' BroadcastAddress = 'ff:ff:ff:ff:ff:ff' -Dot11ElemID_Identity = 222 +Dot11ElemID_Whisper = 222 NumChannels = 140 def freq_to_channel(freq): @@ -30,7 +30,7 @@ def encapsulate(payload, addr_from, addr_to=BroadcastAddress): while data_left > 0: sz = min(chunk_size, data_left) chunk = payload[data_off: data_off + sz] - frame /= Dot11Elt(ID=Dot11ElemID_Identity, info=chunk, len=sz) + frame /= Dot11Elt(ID=Dot11ElemID_Whisper, info=chunk, len=sz) data_off += sz data_left -= sz From e369d596d689aff499944c6689feb19091ced163 Mon Sep 17 00:00:00 2001 From: SecurityWaffle <SecurityWaffle@users.noreply.github.com> Date: Sat, 5 Oct 2019 21:22:03 -0500 Subject: [PATCH 016/346] Fixes "No module named 'pwnagotchi'" error /usr/local/bin/pwnagotchi Traceback (most recent call last): File "/usr/local/bin/pwnagotchi", line 8, in <module> import pwnagotchi ModuleNotFoundError: No module named 'pwnagotchi' --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a1e6e84..41d7475 100644 --- a/setup.py +++ b/setup.py @@ -19,9 +19,10 @@ setup(name='pwnagotchi', install_requires=required, scripts=['bin/pwnagotchi'], package_data={'pwnagotchi': ('pwnagotchi/defaults.yml',)}, + packages=find_packages(), classifiers=[ 'Programming Language :: Python :: 3', 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Environment :: Console', - ]) \ No newline at end of file + ]) From 3bb42549f67762868be2dbd14cf340bdd02e9cae Mon Sep 17 00:00:00 2001 From: gh0stshell <gh0stshell@users.noreply.github.com> Date: Sat, 5 Oct 2019 22:25:10 -0700 Subject: [PATCH 017/346] BMAPTOOL Check #2 update switched second check with faster builtin tool, tested fix --- scripts/create_sibling.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/create_sibling.sh b/scripts/create_sibling.sh index 02537cd..1e07a12 100755 --- a/scripts/create_sibling.sh +++ b/scripts/create_sibling.sh @@ -94,7 +94,7 @@ function provide_raspbian() { function setup_raspbian(){ # Detect the ability to create sparse files if [ "${OPT_SPARSE}" -eq 0 ]; then - if [ which bmaptool -eq 0 ]; then + if ! type "bmaptool" > /dev/null; then echo "[!] bmaptool not available, not creating a sparse image" else From 2682a5487a52e6f4625d5eb82893115ab00d8c91 Mon Sep 17 00:00:00 2001 From: gh0stshell <gh0stshell@users.noreply.github.com> Date: Sat, 5 Oct 2019 22:26:03 -0700 Subject: [PATCH 018/346] Removed extra return --- scripts/create_sibling.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/create_sibling.sh b/scripts/create_sibling.sh index 1e07a12..650d40c 100755 --- a/scripts/create_sibling.sh +++ b/scripts/create_sibling.sh @@ -96,7 +96,6 @@ function setup_raspbian(){ if [ "${OPT_SPARSE}" -eq 0 ]; then if ! type "bmaptool" > /dev/null; then echo "[!] bmaptool not available, not creating a sparse image" - else echo "[+] Defaulting to sparse image generation as bmaptool is available" OPT_SPARSE=1 From eb3836ba1e23819e8c1d1b5206cd9742b1c8780f Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 6 Oct 2019 08:55:51 +0200 Subject: [PATCH 019/346] Fix path to pwnagotchi --- .gitignore | 1 + scripts/preview.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fc54ebf..0cac4ae 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.img.bmap *.pcap *.po~ +preview.png __pycache__ _backups _emulation diff --git a/scripts/preview.py b/scripts/preview.py index b298865..4f69a3d 100755 --- a/scripts/preview.py +++ b/scripts/preview.py @@ -6,7 +6,7 @@ import yaml sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), - '../pwnagotchi/')) + '../')) from pwnagotchi.ui.display import Display, VideoHandler from PIL import Image From c7b31ae45698477d8cf313f699a6d88bf1c9745f Mon Sep 17 00:00:00 2001 From: Panos Vasilopoulos <hello@alwayslivid.com> Date: Sun, 6 Oct 2019 07:59:33 +0000 Subject: [PATCH 020/346] fixed spacing error in greek translation --- pwnagotchi/locale/el/LC_MESSAGES/voice.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/locale/el/LC_MESSAGES/voice.po b/pwnagotchi/locale/el/LC_MESSAGES/voice.po index f113ce1..b1cfa69 100644 --- a/pwnagotchi/locale/el/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/el/LC_MESSAGES/voice.po @@ -33,11 +33,11 @@ msgid "AI ready." msgstr "ΤΝ έτοιμη." msgid "The neural network is ready." -msgstr "Το νευρωνικό δίκτυοείναι έτοιμο." +msgstr "Το νευρωνικό δίκτυο είναι έτοιμο." #, python-brace-format msgid "Hey, channel {channel} is free! Your AP will say thanks." -msgstr "Ε, το κανάλι {channel} είναιελεύθερο! Το AP σου θαείναι ευγνώμων." +msgstr "Ε, το κανάλι {channel} είναιελεύθερο! Το AP σου θα είναι ευγνώμων." msgid "I'm bored ..." msgstr "Βαριέμαι ..." From 2e6967bd1f92be95698bb3dbdd0da3a4e0855817 Mon Sep 17 00:00:00 2001 From: swedishmike <mike@swedishmike.org> Date: Sun, 6 Oct 2019 09:48:18 +0100 Subject: [PATCH 021/346] Added hdmion/hdmioff scripts --- builder/pwnagotchi.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index fab9c2e..2fe3ff9 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -291,6 +291,22 @@ #!/usr/bin/env bash ifconfig mon0 down && iw dev mon0 del + - name: create hdmion script + copy: + dest: /usr/bin/hdmion + mode: 0755 + content: | + #!/usr/bin/env bash + sudo /opt/vc/bin/tvservice -p + + - name: create hdmioff script + copy: + dest: /usr/bin/hdmioff + mode: 0755 + content: | + #!/usr/bin/env bash + sudo /opt/vc/bin/tvservice -o + - name: configure rc.local blockinfile: path: /etc/rc.local From acb09effce8a3358b2fee604d986c9d46dfe6369 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 6 Oct 2019 12:49:25 +0200 Subject: [PATCH 022/346] fix: pinned requirements versions (fixes #168) --- requirements.txt | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8fa5e8a..57579fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,15 @@ -Crypto -requests -pyyaml -scapy -gym -stable-baselines -tensorflow -tweepy -file_read_backwards -numpy -inky -smbus -pillow +crypto==1.4.1 +requests==2.21.0 +PyYAML==3.13 +scapy==2.4.3 +gym==0.14.0 +stable-baselines==2.7.0 +tensorflow==1.13.1 +tensorflow-estimator==1.14.0 +tweepy==3.6.0 +file-read-backwards==2.0.0 +numpy==1.17.2 +inky==0.0.5 +smbus2==0.3.0 +Pillow==5.4.1 +spidev==3.4 \ No newline at end of file From a7b43b6d0dd38cf674eaa4614058953a7c883f4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2019 10:53:50 +0000 Subject: [PATCH 023/346] Bump pyyaml from 3.13 to 5.1 Bumps [pyyaml](https://github.com/yaml/pyyaml) from 3.13 to 5.1. - [Release notes](https://github.com/yaml/pyyaml/releases) - [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES) - [Commits](https://github.com/yaml/pyyaml/compare/3.13...5.1) Signed-off-by: dependabot[bot] <support@github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 57579fd..f364dbc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ crypto==1.4.1 requests==2.21.0 -PyYAML==3.13 +PyYAML==5.1 scapy==2.4.3 gym==0.14.0 stable-baselines==2.7.0 From ac1f1ce8f034f67a8fff3c17392aa9250a60d4ba Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sun, 6 Oct 2019 06:56:15 -0400 Subject: [PATCH 024/346] reordered docs list --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58c5a52..f492955 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,12 @@ For hackers to learn reinforcement learning, WiFi networking, and have an excuse --- - [About the Project](https://github.com/evilsocket/pwnagotchi/blob/master/docs/about.md) -- [FAQ](https://github.com/evilsocket/pwnagotchi/blob/master/docs/faq.md) - [How to Install](https://github.com/evilsocket/pwnagotchi/blob/master/docs/install.md) - [Configuration](https://github.com/evilsocket/pwnagotchi/blob/master/docs/configure.md) - [Usage](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md) - [Plugins](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md) - [Development](https://github.com/evilsocket/pwnagotchi/blob/master/docs/dev.md) +- [FAQ](https://github.com/evilsocket/pwnagotchi/blob/master/docs/faq.md) - [Community Hacks](https://github.com/evilsocket/pwnagotchi/blob/master/docs/hacks.md) ## Links From 5737460ebdc7725e488b37280e470945807e83fc Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 6 Oct 2019 13:19:38 +0200 Subject: [PATCH 025/346] docs: WPA/WPA2 handshakes 101 (fixes #179) --- docs/about.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/about.md b/docs/about.md index eacbd73..f08fa43 100644 --- a/docs/about.md +++ b/docs/about.md @@ -17,6 +17,25 @@ Multiple units within close physical proximity can "talk" to each other, adverti Of course, it is possible to run your Pwnagotchi with the AI disabled (configurable in `config.yml`). Why might you want to do this? Perhaps you simply want to use your own fixed parameters (instead of letting the AI decide for you), or maybe you want to save battery and CPU cycles, or maybe it's just you have strong concerns about aiding and abetting baby Skynet. Whatever your particular reasons may be: an AI-disabled Pwnagotchi is still a simple and very effective automated deauther, WPA handshake sniffer, and portable [bettercap](https://www.bettercap.org/) + [webui](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#bettercaps-web-ui) dedicated hardware. +## WPA/WPA2 Handshakes 101 + +Before a device that's connecting to a wireless access point (say, your phone connecting to your home WiFi) is able to securely transmit and receive data, a process called *4-Way Handshake* needs to happen in order for WPA encryption keys to be generated. +This process consists in the exchange of four packets (therefore the "4" in the name) between the station and the AP that are used to derive session keys from the main access point WiFi password, once the packets are successfully +exchanged and the keys generated, the client station is authenticated and can start sending data packets that are secured by encryption. + +<p> +<img src="https://i.imgur.com/nI8IE6a.png"/> +<br/> +<small>image taken from <a target="_blank" href="https://www.wifi-professionals.com/2019/01/4-way-handshake">wifi-professionals.com</a></small> +</p> + +The catch here is that these four packets can be "sniffed" by an attacker and, through the use of dictionary and/or bruteforce attacks, the original WiFi key can be recovered from them. Technically speaking, the recovery of +the WiFi key doesn't necessarily need all four packets: an half-handshake (containing ony two of the four packets) can be cracked too, and in some (most) cases even just [a single packet is enough](https://hashcat.net/forum/thread-7717-post-41447.html), even without clients. + +In order to get these packets, Pwnagotchi will deauthenticate client stations it detects (thus forcing them to reauthenticate to their access point, resending the handshake packets) and send association frames to the access points +to try to force them to [leak the PMKID](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/). + +All the handshakes captured this way are saved into `.pcap` files (organized as one file per access point containing all the captured handshakes for that access point) that can later be [cracked with proper hardware and software](https://hashcat.net/wiki/doku.php?id=cracking_wpawpa2). ## License From 0d292cdd10cf1f84061b11b82d1629c58619697d Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sun, 6 Oct 2019 07:25:50 -0400 Subject: [PATCH 026/346] added hyperlinks and headers to all FAQ questions --- docs/faq.md | 202 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 174 insertions(+), 28 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index fcc6782..47305b4 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,55 +1,201 @@ # FAQ +<!-- IF YOU CHANGE ANY CHARACTERS IN AN FAQ QUESTION, YOU MUST ALSO CHANGE THE TABLE OF CONTENTS ENTRY &&&AND&&& THE ANCHOR LINK TEXT, OR ELSE THE LINKS WILL BREAK. --> [**What can Pwnagotchi actually do?**](#what-can-pwnagotchi-actually-do) -* Does Pwnagotchi support both 2.4 GHz and 5.0 GHz? -* Just how politely *does* Pwnagotchi deauth? -* What kinds of handshakes does Pwnagotchi eat? -* Hey, I want to learn more about how Pwnagotchi actually works. + +* [Why does Pwnagotchi eat handshakes?](#why-does-pwnagotchi-eat-handshakes) +* [What kinds of handshakes does Pwnagotchi eat?](#what-kinds-of-handshakes-does-pwnagotchi-eat) +* [Does Pwnagotchi support both 2.4 GHz and 5.0 GHz?](#does-pwnagotchi-support-both-24-ghz-and-50-ghz) +* [Just how politely *does* Pwnagotchi deauth?](#just-how-politely-does-pwnagotchi-deauth) +* [Hey, I want to learn more about how Pwnagotchi actually works.](#hey-i-want-to-learn-more-about-how-pwnagotchi-actually-works) +* [How is Pwnagotchi using bettercap?](#how-is-pwnagotchi-using-bettercap) +* [What happens if I run a Pwnagotchi without the AI enabled?](#what-happens-if-i-run-a-pwnagotchi-without-the-ai-enabled) +* [How easy is it to hack Pwnagotchi to add additional functionality?](#how-easy-is-it-to-hack-pwnagotchi-to-add-additional-functionality) [**Building Your Pwnagotchi**](#building-your-pwnagotchi) -* What hardware do I need to create my very own Pwnagotchi? -* Is there any way to see my Pwnagotchi's face even if I don't have a display? -* I love my new Pwnagotchi, but it kinda looks like a bomb. Where can I find a decent case? -* Why does everybody use e-ink screens for their Pwnagotchis? -* How do I connect to my Pwnagotchi? + +* [What hardware do I need to create my very own Pwnagotchi?](#what-hardware-do-i-need-to-create-my-very-own-pwnagotchi) +* [Is there any way to see my Pwnagotchi's face even if I don't have a display?](#is-there-any-way-to-see-my-pwnagotchis-face-even-if-i-dont-have-a-display) +* [How do I attach the screen to the Raspberry Pi?](#how-do-i-attach-the-screen-to-the-raspberry-pi) +* [I love my new Pwnagotchi, but it kinda looks like a bomb. Where can I find a decent case?](#i-love-my-new-pwnagotchi-but-it-kinda-looks-like-a-bomb-where-can-i-find-a-decent-case) +* [Why does everybody use e-ink screens for their Pwnagotchis?](#why-does-everybody-use-e-ink-screens-for-their-pwnagotchis) +* [How do I connect to my Pwnagotchi?](#how-do-i-connect-to-my-pwnagotchi) [**Customizing Your Pwnagotchi**](#customizing-your-pwnagotchi) -* How do I change my Pwnagotchi's name? -* I want to change the faces. What do I hack? -* I want my Pwnagotchi to speak a different language. Can it? -* I have a great idea for something cool I wish Pwnagotchi could do! + +* [How do I change my Pwnagotchi's name?](#how-do-i-change-my-pwnagotchis-name) +* [I want to change the faces. What do I hack?](#i-want-to-change-the-faces-what-do-i-hack) +* [I want my Pwnagotchi to speak a different language. Can it?](#i-want-my-pwnagotchi-to-speak-a-different-language-can-it) +* [I have a great idea for something cool I wish Pwnagotchi could do!](#i-have-a-great-idea-for-something-cool-i-wish-pwnagotchi-could-do) +* [Are there any unofficial community "hacks" for further customizing my Pwnagotchi?](#are-there-any-unofficial-community-"hacks"-for-further-customizing-my-pwnagotchi) [**Getting to Know Your Pwnagotchi**](#getting-to-know-your-pwnagotchi) -* What is MANU mode? What is AUTO mode? -* Why does the AI take 30 minutes to load? -* What is Pwnagotchi doing while it's waiting for the AI to load? -* How do I whitelist my home network so Pwnagotchi stops pwning me? + +* [What does everything on the screen mean?](#what-does-everything-on-the-screen-mean) +* [How do I whitelist my home network so Pwnagotchi stops pwning me?](#how-do-i-whitelist-my-home-network-so-pwnagotchi-stops-pwning-me) +* [What is MANU mode? What is AUTO mode?](#what-is-manu-mode-what-is-auto-mode) +* [Why does the AI take 30 minutes to load?](#why-does-the-ai-take-30-minutes-to-load) +* [What is Pwnagotchi doing while it's waiting for the AI to load?](#what-is-pwnagotchi-doing-while-its-waiting-for-the-ai-to-load) +* [How do I know when the AI is running?](#how-do-i-know-when-the-ai-is-running) +* [Where does Pwnagotchi store all the handshakes it's eaten?](#where-does-pwnagotchi-store-all-the-handshakes-its-eaten) [**Caring for Your Pwnagotchi**](#caring-for-your-pwnagotchi) -* What do all my Pwnagotchi's faces mean? -* Oh no, my Pwnagotchi is sad and bored! How do I entertain it?! -* How do I turn off my Pwnagotchi? + +* [What do all my Pwnagotchi's faces mean?](#what-do-all-my-pwnagotchis-faces-mean) +* [How do I feed my Pwnagotchi?](#how-do-i-feed-my-pwnagotchi) +* [Oh no, my Pwnagotchi is sad and bored! How do I entertain it?!](#oh-no,-my-pwnagotchi-is-sad-and-bored-how-do-i-entertain-it) +* [How do I update my Pwnagotchi?](#how-do-i-update-my-pwnagotchi) +* [I'm extremely emotionally-attached to my Pwnagotchi. How can I back up its brain?](#im-extremely-emotionally-attached-to-my-pwnagotchi-how-can-i-back-up-its-brain) +* [How do I turn off my Pwnagotchi?](#how-do-i-turn-off-my-pwnagotchi) +* [Uh. So. What do I do with all these handshakes my Pwnagotchi has been eating?](#uh-so-what-do-i-do-with-all-these-handshakes-my-pwnagotchi-has-been-eating) [**Known Quirks**](#known-quirks) -* My Pwnagotchi's log timestamps seem...unreliable. Huh? -* Help! My Pwnagotchi's SD card got corrupted. What gives? + +* [My Pwnagotchi's log timestamps seem...unreliable. Huh?](#my-pwnagotchis-log-timestamps-seemunreliable-huh) +* [Help! My Pwnagotchi's SD card got corrupted. What gives?](#help-my-pwnagotchis-sd-card-got-corrupted-what-gives) --- -## What can Pwnagotchi actually do? +## **What can Pwnagotchi actually do?** +### Why does Pwnagotchi eat handshakes? lorem ipsum dolor sit amet -## Building Your Pwnagotchi +--- +### What kinds of handshakes does Pwnagotchi eat? lorem ipsum dolor sit amet -## Customizing Your Pwnagotchi +--- +### Does Pwnagotchi support both 2.4 GHz and 5.0 GHz? lorem ipsum dolor sit amet -## Getting to Know Your Pwnagotchi +--- +### Just how politely *does* Pwnagotchi deauth? lorem ipsum dolor sit amet -## Caring for Your Pwnagotchi +--- +### Hey, I want to learn more about how Pwnagotchi actually works. lorem ipsum dolor sit amet -## Known Quirks +--- +### How is Pwnagotchi using bettercap? +lorem ipsum dolor sit amet + +--- +### What happens if I run a Pwnagotchi without the AI enabled? +lorem ipsum dolor sit amet + +--- +### How easy is it to hack Pwnagotchi to add additional functionality? +lorem ipsum dolor sit amet + +--- + +## **Building Your Pwnagotchi** +### What hardware do I need to create my very own Pwnagotchi? +lorem ipsum dolor sit amet + +--- +### Is there any way to see my Pwnagotchi's face even if I don't have a display? +lorem ipsum dolor sit amet + +--- +### How do I attach the screen to the Raspberry Pi? +lorem ipsum dolor sit amet + +--- +### I love my new Pwnagotchi, but it kinda looks like a bomb. Where can I find a decent case? +lorem ipsum dolor sit amet + +--- +### Why does everybody use e-ink screens for their Pwnagotchis? +lorem ipsum dolor sit amet + +--- +### How do I connect to my Pwnagotchi? +lorem ipsum dolor sit amet + +--------------------------------------------------------------------------------------------------------------- +## **Customizing Your Pwnagotchi** +### How do I change my Pwnagotchi's name? +lorem ipsum dolor sit amet + +--- +### I want to change the faces. What do I hack? +lorem ipsum dolor sit amet + +--- +### I want my Pwnagotchi to speak a different language. Can it? +lorem ipsum dolor sit amet + +--- +### I have a great idea for something cool I wish Pwnagotchi could do! +lorem ipsum dolor sit amet + +--- +### Are there any unofficial community "hacks" for further customizing my Pwnagotchi? +lorem ipsum dolor sit amet + +--------------------------------------------------------------------------------------------------------------- +## **Getting to Know Your Pwnagotchi** +### What does everything on the screen mean? +lorem ipsum dolor sit amet + +--- +### How do I whitelist my home network so Pwnagotchi stops pwning me? +lorem ipsum dolor sit amet + +--- +### What is MANU mode? What is AUTO mode? +lorem ipsum dolor sit amet + +--- +### Why does the AI take 30 minutes to load? +lorem ipsum dolor sit amet + +--- +### What is Pwnagotchi doing while it's waiting for the AI to load? +lorem ipsum dolor sit amet + +--- +### How do I know when the AI is running? +lorem ipsum dolor sit amet + +--- +### Where does Pwnagotchi store all the handshakes it's eaten? + +--------------------------------------------------------------------------------------------------------------- +## **Caring for Your Pwnagotchi** +### What do all my Pwnagotchi's faces mean? +lorem ipsum dolor sit amet + +--- +### How do I feed my Pwnagotchi? +lorem ipsum dolor sit amet + +--- +### Oh no, my Pwnagotchi is sad and bored! How do I entertain it?! +lorem ipsum dolor sit amet + +--- +### How do I update my Pwnagotchi? +lorem ipsum dolor sit amet + +--- +### I'm extremely emotionally-attached to my Pwnagotchi. How can I back up its brain? +lorem ipsum dolor sit amet + +--- +### How do I turn off my Pwnagotchi? +lorem ipsum dolor sit amet + +--- +### Uh. So. What do I do with all these handshakes my Pwnagotchi has been eating? + +--------------------------------------------------------------------------------------------------------------- +## **Known Quirks** +### My Pwnagotchi's log timestamps seem...unreliable. Huh? +lorem ipsum dolor sit amet + +--- +### Help! My Pwnagotchi's SD card got corrupted. What gives? lorem ipsum dolor sit amet From 7a4254a7a411ffb6c53e5dac0257dbb876317c56 Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sun, 6 Oct 2019 07:36:47 -0400 Subject: [PATCH 027/346] fixed broken anchor lnks in TOC & missing lipsum txt --- docs/faq.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 47305b4..348e039 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -27,7 +27,7 @@ * [I want to change the faces. What do I hack?](#i-want-to-change-the-faces-what-do-i-hack) * [I want my Pwnagotchi to speak a different language. Can it?](#i-want-my-pwnagotchi-to-speak-a-different-language-can-it) * [I have a great idea for something cool I wish Pwnagotchi could do!](#i-have-a-great-idea-for-something-cool-i-wish-pwnagotchi-could-do) -* [Are there any unofficial community "hacks" for further customizing my Pwnagotchi?](#are-there-any-unofficial-community-"hacks"-for-further-customizing-my-pwnagotchi) +* [Are there any unofficial community "hacks" for further customizing my Pwnagotchi?](#are-there-any-unofficial-community-hacks-for-further-customizing-my-pwnagotchi) [**Getting to Know Your Pwnagotchi**](#getting-to-know-your-pwnagotchi) @@ -38,12 +38,13 @@ * [What is Pwnagotchi doing while it's waiting for the AI to load?](#what-is-pwnagotchi-doing-while-its-waiting-for-the-ai-to-load) * [How do I know when the AI is running?](#how-do-i-know-when-the-ai-is-running) * [Where does Pwnagotchi store all the handshakes it's eaten?](#where-does-pwnagotchi-store-all-the-handshakes-its-eaten) +* [What happens when my Pwnagotchi meets another Pwnagotchi?](#what-happens-when-my-pwnagotchi-meets-another-pwnagotchi) [**Caring for Your Pwnagotchi**](#caring-for-your-pwnagotchi) * [What do all my Pwnagotchi's faces mean?](#what-do-all-my-pwnagotchis-faces-mean) * [How do I feed my Pwnagotchi?](#how-do-i-feed-my-pwnagotchi) -* [Oh no, my Pwnagotchi is sad and bored! How do I entertain it?!](#oh-no,-my-pwnagotchi-is-sad-and-bored-how-do-i-entertain-it) +* [Oh no, my Pwnagotchi is sad and bored! How do I entertain it?!](#oh-no-my-pwnagotchi-is-sad-and-bored-how-do-i-entertain-it) * [How do I update my Pwnagotchi?](#how-do-i-update-my-pwnagotchi) * [I'm extremely emotionally-attached to my Pwnagotchi. How can I back up its brain?](#im-extremely-emotionally-attached-to-my-pwnagotchi-how-can-i-back-up-its-brain) * [How do I turn off my Pwnagotchi?](#how-do-i-turn-off-my-pwnagotchi) @@ -162,6 +163,11 @@ lorem ipsum dolor sit amet --- ### Where does Pwnagotchi store all the handshakes it's eaten? +lorem ipsum dolor sit amet + +--- +### What happens when my Pwnagotchi meets another Pwnagotchi? +lorem ipsum dolor sit amet --------------------------------------------------------------------------------------------------------------- ## **Caring for Your Pwnagotchi** @@ -190,6 +196,7 @@ lorem ipsum dolor sit amet --- ### Uh. So. What do I do with all these handshakes my Pwnagotchi has been eating? +lorem ipsum dolor sit amet --------------------------------------------------------------------------------------------------------------- ## **Known Quirks** From 7bde5a021057d9386d47b6e9fe2f4c11248aecf8 Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sun, 6 Oct 2019 08:47:20 -0400 Subject: [PATCH 028/346] rephrased for clarity --- docs/about.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/about.md b/docs/about.md index eacbd73..2883206 100644 --- a/docs/about.md +++ b/docs/about.md @@ -17,6 +17,27 @@ Multiple units within close physical proximity can "talk" to each other, adverti Of course, it is possible to run your Pwnagotchi with the AI disabled (configurable in `config.yml`). Why might you want to do this? Perhaps you simply want to use your own fixed parameters (instead of letting the AI decide for you), or maybe you want to save battery and CPU cycles, or maybe it's just you have strong concerns about aiding and abetting baby Skynet. Whatever your particular reasons may be: an AI-disabled Pwnagotchi is still a simple and very effective automated deauther, WPA handshake sniffer, and portable [bettercap](https://www.bettercap.org/) + [webui](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#bettercaps-web-ui) dedicated hardware. +## WiFi Handshakes 101 + +In order to understand why it's valuable to have an AI that wants to eat handshakes, it's helpful to understand a little bit about how handshakes are used in the WPA/WPA2 wireless protocol. + +Before a client device that's connecting to a wireless access point—say, for instance, your phone connecting to your home WiFi network—is able to securely transmit to and receive data from that access point, a process called the **4-Way Handshake** needs to happen in order for the WPA encryption keys to be generated. This process consists of the exchange of four packets (hence the "4" in "4-Way") between the client device and the AP; these are used to derive session keys from the access point's WiFi password. Once the packets are successfully exchanged and the keys have been generated, the client device is authenticated and can start sending and receiving data packets to and from the wireless AP that are secured by encryption. + +<p align="center"> +<img src="https://i.imgur.com/nI8IE6a.png"/> +<br/> +<small>image taken from <a target="_blank" href="https://www.wifi-professionals.com/2019/01/4-way-handshake">wifi-professionals.com</a></small> +</p> + +So...what's the catch? Well, these four packets can easily be "sniffed" by an attacker monitoring nearby (say, with a Pwnagotchi :innocent:). And once recorded, that attacker can use [dictionary and/or bruteforce attacks](https://hashcat.net/wiki/doku.php?id=cracking_wpawpa2) to crack the handshakes and recover the original WiFi key. In fact, **successful recovery of the WiFi key doesn't necessarily even need all four packets!** A half-handshake (containing only two of the four packets) can be cracked, too—and in some *(most)* cases, just [a single packet is enough](https://hashcat.net/forum/thread-7717-post-41447.html), *even without clients.* + +In order to ~~eat~~ collect as many of these crackable handshake packets as possible, Pwnagotchi uses two strategies: + +- **Deauthenticating the client stations it detects.** A deauthenticated device must reauthenticate to its access point by resending the 4-Way Handshake, thereby giving Pwnagotchi another chance to sniff the handshake packets and collect more crackable material. +- **Send association frames directly to the access points themselves** +to try to force them to [leak the PMKID](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/). + +All the handshakes captured this way are saved into `.pcap` files on Pwnagotchi's filesystem. Each PCAP file that Pwnagotchi generates is organized according to access point; one PCAP will contain all the handshakes that Pwnagotchi has ever captured for that particular AP. These handshakes can later be [cracked with proper hardware and software](https://hashcat.net/wiki/doku.php?id=cracking_wpawpa2). ## License From 76f75c07515e1294b62f590dd4ef4e7e8fb88cce Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sun, 6 Oct 2019 09:28:19 -0400 Subject: [PATCH 029/346] added bluetooth face hack by Systemic (in Slack) --- docs/hacks.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/docs/hacks.md b/docs/hacks.md index 184cff0..5e47d51 100644 --- a/docs/hacks.md +++ b/docs/hacks.md @@ -36,3 +36,67 @@ Some of this guide will work with other framebuffer-based displays. - Reboot. And you should be good! + +--- +### Pwnagotchi face via Bluetooth +Last tested on | Pwnagotchi version | Working? | Reference +---------------|--------------------|----------|-----------| +2019 October 6 | Unknown | :white_check_mark: | on Android +2019 October 6 | Unknown | :white_check_mark: | on iPad iOS 9.3.5 + +A way to view your Pwnagotchi's ~~face~~ UI wirelessly via Bluetooth on a separate device. Refresh rate is the same as the e-ink display (every few seconds). This is NOT Bluetooth tethering; this is only Bluetooth as a server on pi side; you connect the Bluetooth and get a DHCP IP address and that's it. This hack cannot leverage the data connection. + +Contributed by Systemic in the Slack. + +##### 1. First Step +- Comment out the Bluetooth disable line from `/boot/config.txt` : `#dtoverlay=pi3-disable-bt` +- Change `/root/pwnagotchi/config.yml` to have `0.0.0.0` instead of `10.0.0.2` to listen as well on Bluetooth. +- Then launch the following commands: + +##### 2. Install required packages. + +```sudo apt-get install bluez bluez-tools bridge-utils dnsmasq``` + +##### 3. Configure Bluetooth and start it. +```sudo modprobe bnep +sudo brctl addbr pan0 +sudo brctl setfd pan0 0 +sudo brctl stp pan0 off +sudo ifconfig pan0 172.26.0.1 netmask 255.255.255.0 +sudo ip link set pan0 up +``` + +```cat <<- EOF > /tmp/dnsmasq_bt.conf``` + +```bind-interfaces +port=0 +interface=pan0 +listen-address=172.26.0.1 +dhcp-range=172.26.0.2,172.26.0.100,255.255.255.0,5m +dhcp-leasefile=/tmp/dnsmasq_bt.leases +dhcp-authoritative +log-dhcp +``` + +```EOF``` + +```sudo dnsmasq -C /tmp/dnsmasq_bt.conf +sudo bt-agent -c NoInputNoOutput& +sudo bt-adapter -a hci0 --set Discoverable 1 +sudo bt-adapter -a hci0 --set DiscoverableTimeout 0 +sudo bt-adapter -a hci0 --set Pairable 1 +sudo bt-adapter -a hci0 --set PairableTimeout 0 +sudo bt-network -a hci0 -s nap pan0 & +``` + +##### 4. Finally: on your phone, you have to disable all existing interfaces: + +- Shutdown WiFi. +- Shutdown mobile data. +- Connect to the newly available Bluetooth device (which has the name of your Pwnagotchi). + - Once connected, you can test: `http://172.26.0.1:8080` +- You can also install bettercap's UI (`sudo buttercap` then `ui.update`) + - You'll need to change the http caplets to change `127.0.0.1` to `0.0.0.0`. +- You can connect to the shell with a terminal emulator ... + +Happy tweaking. From 662193673544bb2270fdb38049870d745bb41cb9 Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sun, 6 Oct 2019 09:38:50 -0400 Subject: [PATCH 030/346] typo --- docs/hacks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hacks.md b/docs/hacks.md index 5e47d51..6f58c3f 100644 --- a/docs/hacks.md +++ b/docs/hacks.md @@ -95,7 +95,7 @@ sudo bt-network -a hci0 -s nap pan0 & - Shutdown mobile data. - Connect to the newly available Bluetooth device (which has the name of your Pwnagotchi). - Once connected, you can test: `http://172.26.0.1:8080` -- You can also install bettercap's UI (`sudo buttercap` then `ui.update`) +- You can also install bettercap's UI (`sudo bettercap` then `ui.update`) - You'll need to change the http caplets to change `127.0.0.1` to `0.0.0.0`. - You can connect to the shell with a terminal emulator ... From 042e5adcbeb97f6560a478782ef0cade745811f3 Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sun, 6 Oct 2019 09:40:08 -0400 Subject: [PATCH 031/346] minor edit --- docs/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about.md b/docs/about.md index 2883206..4777c21 100644 --- a/docs/about.md +++ b/docs/about.md @@ -33,7 +33,7 @@ So...what's the catch? Well, these four packets can easily be "sniffed" by an at In order to ~~eat~~ collect as many of these crackable handshake packets as possible, Pwnagotchi uses two strategies: -- **Deauthenticating the client stations it detects.** A deauthenticated device must reauthenticate to its access point by resending the 4-Way Handshake, thereby giving Pwnagotchi another chance to sniff the handshake packets and collect more crackable material. +- **Deauthenticating the client stations it detects.** A deauthenticated device must reauthenticate to its access point by re-performing the 4-Way Handshake with the AP, thereby giving Pwnagotchi another chance to sniff the handshake packets and collect more crackable material. - **Send association frames directly to the access points themselves** to try to force them to [leak the PMKID](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/). From a35d55400792620582e71c6b81b69caa2eccec63 Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sun, 6 Oct 2019 10:26:11 -0400 Subject: [PATCH 032/346] minor copyediting --- docs/configure.md | 56 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/docs/configure.md b/docs/configure.md index a7c251f..f85fd35 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -1,22 +1,26 @@ -# Connecting to your Pwnagotchi +# Configuration -Once you wrote the image file on the SD card, there're a few steps you'll have to follow in order to configure your unit properly. First, start with connecting the USB cable to the data port of the Raspberry Pi and the RPi to your computer. After a few seconds, the board will boot and you will see a new Ethernet interface on your host computer. +Once you've [written the image file onto the SD card](https://github.com/evilsocket/pwnagotchi/blob/master/docs/install.md#flashing-an-image), there're a few steps you'll have to follow in order to configure your new Pwnagotchi properly. -You'll need to configure it with a static IP address: +## Connect to your Pwnagotchi -- IP: `10.0.0.1` -- Netmask: `255.255.255.0` -- Gateway: `10.0.0.1` -- DNS (if required): `8.8.8.8` (or whatever) +1. First, start with connecting the USB cable to the data port of the Raspberry Pi and the RPi to your computer. +2. After a few seconds, the board will boot and you will see a new Ethernet interface on your host computer. +3. You'll need to configure it with a static IP address: -If everything's been configured properly, you will now be able to `ping` both `10.0.0.2` or `pwnagotchi.local` (if you haven't customized the hostname yet). + - IP: `10.0.0.1` + - Netmask: `255.255.255.0` + - Gateway: `10.0.0.1` + - DNS (if required): `8.8.8.8` (or whatever) -You can now connect to your unit using SSH: +4. If everything's been configured properly, you will now be able to `ping` both `10.0.0.2` or `pwnagotchi.local` (if you haven't customized the hostname yet—if you have named your unit already, this address will be *your unit's name* + `.local`). + +5. **Congratulations!** You can now connect to your unit using SSH: ```bash ssh pi@10.0.0.2 ``` - +##### About your SSH connection The default password is `raspberry`; you should change it as soon as you log in for the first time by issuing the `passwd` command and selecting a new and more complex passphrase. If you want to login directly without entering a password (recommended!), copy your SSH public key to the unit's authorized keys: @@ -25,13 +29,17 @@ If you want to login directly without entering a password (recommended!), copy y ssh-copy-id -i ~/.ssh/id_rsa.pub pi@10.0.0.2 ``` -## Configuration +## Give your Pwnagotchi a name -You can now set a new name for your unit by [changing the hostname](https://geek-university.com/raspberry-pi/change-raspberry-pis-hostname/). Create the `/root/custom.yml` file (either via SSH or by directly editing the SD card contents from a computer) that will override the [default configuration](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml) with your custom values. +You can now set a new name for your unit by [changing the hostname](https://geek-university.com/raspberry-pi/change-raspberry-pis-hostname/)! -## Language Selection +Create the `/root/custom.yml` file (either via SSH or by directly editing the SD card contents from a computer) that will override the [default configuration](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml) with your custom values. -Pwnagotchi displays its UI in English by default, but it can speak several other languages! You can change `main.lang` to one of the supported languages: +## Choose your Pwnagotchi's language + +Pwnagotchi displays its UI in English by default, but it can speak several other languages! If you're fine with English, you don't need to do anything special. + +But if you want, you can change `main.lang` to one of the supported languages: - **English** *(default)* - German @@ -45,14 +53,26 @@ Pwnagotchi displays its UI in English by default, but it can speak several other ## Display Selection -Set the type of display you want to use via `ui.display.type` (if your display does not work after changing this setting, you might need to completely remove power from the Raspberry and make a clean boot). +**Set the type of display you want to use via `ui.display.type`.** +If your display does not work after changing this setting, you might need to completely remove power from the Raspberry Pi and make a clean boot. -You can configure the refresh interval of the display via `ui.fps`. We recommend using a slow refresh rate to avoid shortening the lifetime of your e-ink display. The default value is `0`, which will *only* refresh when changes are made to the screen. +**You can configure the refresh interval of the display via `ui.fps`.** We recommend using a slow refresh rate to avoid shortening the lifetime of your e-ink display. The default value is `0`, which will *only* refresh when changes are made to the screen. ## Host Connection Share -If you connect to the unit via `usb0` (thus using the data port), you might want to use the `scripts/linux_connection_share.sh`, `scripts/macos_connection_share.sh` or `scripts/win_connection_share.ps1` script to bring the interface up on your end and share internet connectivity from another interface, so you can update the unit and generally download things from the internet on it. +Want to be able to update your Pwnagotchi and access things from the internet on it? *Sure you do!* + +1. Connect to the Pwnagotchi unit via `usb0` (A.K.A., using the data port). +2. Run the appropriate connection sharing script to bring the interface up on your end and share internet connectivity from another interface: + +OS | Script Location +------|--------------------------- +Linux | `scripts/linux_connection_share.sh` +Mac OS X | `scripts/macos_connection_share.sh` +Windows | `scripts/win_connection_share.ps1` ## Troubleshooting -If your network connection keeps flapping on your device connecting to your Pwnagotchi, check if `usb0` (or equivalent) device is being controlled by NetworkManager. You can check this via `nmcli dev status`. +##### If your network connection keeps flapping on your device connecting to your Pwnagotchi. +* Check if `usb0` (or equivalent) device is being controlled by NetworkManager. +* You can check this via `nmcli dev status`. From f9435a5ad2a45f4bb889c0ebbf8d3d284ab0c3cb Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sun, 6 Oct 2019 10:31:34 -0400 Subject: [PATCH 033/346] minor copyediting --- docs/configure.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/configure.md b/docs/configure.md index f85fd35..b232e3e 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -7,13 +7,13 @@ Once you've [written the image file onto the SD card](https://github.com/evilsoc 1. First, start with connecting the USB cable to the data port of the Raspberry Pi and the RPi to your computer. 2. After a few seconds, the board will boot and you will see a new Ethernet interface on your host computer. 3. You'll need to configure it with a static IP address: + - IP: `10.0.0.1` + - Netmask: `255.255.255.0` + - Gateway: `10.0.0.1` + - DNS (if required): `8.8.8.8` (or whatever) - - IP: `10.0.0.1` - - Netmask: `255.255.255.0` - - Gateway: `10.0.0.1` - - DNS (if required): `8.8.8.8` (or whatever) - -4. If everything's been configured properly, you will now be able to `ping` both `10.0.0.2` or `pwnagotchi.local` (if you haven't customized the hostname yet—if you have named your unit already, this address will be *your unit's name* + `.local`). +4. If everything's been configured properly, you will now be able to `ping` both `10.0.0.2` or `pwnagotchi.local` + * If you have already customized the hostname of your Pwnagotchi, `pwnagotchi.local` won't work. Instead, try *your unit's hostname* + `.local`. 5. **Congratulations!** You can now connect to your unit using SSH: From fae6115e445c65f0d61db46e8526ff7dbb1e8813 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 6 Oct 2019 16:36:25 +0200 Subject: [PATCH 034/346] fix: using sha256 for the public key fingerprint --- pwnagotchi/mesh/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/mesh/__init__.py b/pwnagotchi/mesh/__init__.py index 14403d2..9e68dc8 100644 --- a/pwnagotchi/mesh/__init__.py +++ b/pwnagotchi/mesh/__init__.py @@ -11,4 +11,4 @@ def get_identity(config): pubkey = None with open(config['main']['pubkey']) as fp: pubkey = RSA.importKey(fp.read()) - return pubkey, hashlib.sha1(pubkey.exportKey('DER')).hexdigest() + return pubkey, hashlib.sha256(pubkey.exportKey('DER')).hexdigest() From 4dd7365cf58ba228d63757e77532411a8da29cd8 Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sun, 6 Oct 2019 12:51:29 -0400 Subject: [PATCH 035/346] added UI diagram (will copyedit text later) --- docs/usage.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 8883cb7..fff0fb1 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -4,13 +4,16 @@ The UI is available either via display if installed, or via http://pwnagotchi.local:8080/ if you connect to the unit via `usb0` and set a static address on the network interface (change `pwnagotchi` with the hostname of your unit). - + * **CH**: Current channel the unit is operating on or `*` when hopping on all channels. * **APS**: Number of access points on the current channel and total visible access points. * **UP**: Time since the unit has been activated. * **PWND**: Number of handshakes captured in this session and number of unique networks we own at least one handshake of, from the beginning. -* **AUTO**: This indicates that the algorithm is running with AI disabled (or still loading), it disappears once the AI dependencies have been bootrapped and the neural network loaded. +* **MODE**: + * **AUTO:** This indicates that the Pwnagotchi algorithm is running in AUTOMATIC mode, with AI disabled (or still loading); it disappears once the AI dependencies have been bootstrapped and the neural network has finished loading. + * **MANU:** This appears when the unit is running in MANUAL mode. +* **FRIEND:** If another unit is nearby, its presence will be indicated here. If more than one unit is nearby, only one—whichever has the stronger signal strength—will be displayed. ## Training the AI From b4d7ea7dbf2b8c6aed3b03854fcdd0d01f579da1 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 6 Oct 2019 19:59:03 +0200 Subject: [PATCH 036/346] Add peer --- scripts/preview.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/scripts/preview.py b/scripts/preview.py index 4f69a3d..06be3c6 100755 --- a/scripts/preview.py +++ b/scripts/preview.py @@ -8,6 +8,7 @@ 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, VideoHandler from PIL import Image @@ -33,10 +34,26 @@ class CustomDisplay(Display): 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 face(): + return faces.FRIEND + def append_images(images, horizontal=True, xmargin=0, ymargin=0): w, h = zip(*(i.size for i in images)) @@ -71,8 +88,9 @@ def main(): 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('--xmargin', type=int, default=5) - parser.add_argument('--ymargin', type=int, default=5) + 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 = ''' @@ -103,7 +121,8 @@ def main(): for display in list_of_displays: emotions = list() - # Starting + if args.showpeer: + display.set_closest_peer(DummyPeer()) display.on_starting() display.update() emotions.append(display.get_image()) From b7c3f41e656634913f7d01f8d7a6ad78b20ec614 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sat, 5 Oct 2019 15:39:14 +0200 Subject: [PATCH 037/346] Add wigle plugin --- pwnagotchi/plugins/default/wigle.py | 261 ++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 pwnagotchi/plugins/default/wigle.py diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py new file mode 100644 index 0000000..b87e71b --- /dev/null +++ b/pwnagotchi/plugins/default/wigle.py @@ -0,0 +1,261 @@ +__author__ = '33197631+dadav@users.noreply.github.com' +__version__ = '1.0.0' +__name__ = 'wigle' +__license__ = 'GPL3' +__description__ = 'This plugin automatically uploades collected wifis to wigle.net' + +import os +import logging +import json +from io import StringIO +import csv +from datetime import datetime +import requests +from pwnagotchi.mesh.wifi import freq_to_channel +from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA + +READY = False +ALREADY_UPLOADED = None +SKIP = None +OPTIONS = dict() + +AKMSUITE_TYPES = { + 0x00: "Reserved", + 0x01: "802.1X", + 0x02: "PSK", +} + +def _handle_packet(packet, result): + """ + Analyze each packet and extract the data from Dot11 layers + """ + + if hasattr(packet, 'cap') and 'privacy' in packet.cap: + # packet is encrypted + if 'encryption' not in result: + result['encryption'] = set() + + if packet.haslayer(Dot11Beacon): + if packet.haslayer(Dot11Beacon)\ + or packet.haslayer(Dot11ProbeResp)\ + or packet.haslayer(Dot11AssoReq)\ + or packet.haslayer(Dot11ReassoReq): + if 'bssid' not in result and hasattr(packet[Dot11], 'addr3'): + result['bssid'] = packet[Dot11].addr3 + if 'essid' not in result and hasattr(packet[Dot11Elt], 'info'): + result['essid'] = packet[Dot11Elt].info + if 'channel' not in result and hasattr(packet[Dot11Elt:3], 'info'): + result['channel'] = int(ord(packet[Dot11Elt:3].info)) + + if packet.haslayer(RadioTap): + if 'rssi' not in result and hasattr(packet[RadioTap], 'dBm_AntSignal'): + result['rssi'] = packet[RadioTap].dBm_AntSignal + if 'channel' not in result and hasattr(packet[RadioTap], 'ChannelFrequency'): + result['channel'] = freq_to_channel(packet[RadioTap].ChannelFrequency) + + # see: https://fossies.org/linux/scapy/scapy/layers/dot11.py + if packet.haslayer(Dot11EltRSN): + if hasattr(packet[Dot11EltRSN], 'akm_suites'): + auth = AKMSUITE_TYPES.get(packet[Dot11EltRSN].akm_suites[0].suite) + result['encryption'].add(f"WPA2/{auth}") + else: + result['encryption'].add("WPA2") + + if packet.haslayer(Dot11EltVendorSpecific)\ + and (packet.haslayer(Dot11EltMicrosoftWPA) + or packet.info.startswith(b'\x00P\xf2\x01\x01\x00')): + + if hasattr(packet, 'akm_suites'): + auth = AKMSUITE_TYPES.get(packet.akm_suites[0].suite) + result['encryption'].add(f"WPA2/{auth}") + else: + result['encryption'].add("WPA2") + # end see + + return result + + +def _analyze_pcap(pcap): + """ + Iterate over the packets and extract data + """ + result = dict() + + try: + packets = rdpcap(pcap) + for packet in packets: + result = _handle_packet(packet, result) + except Scapy_Exception as sc_e: + raise sc_e + + return result + + +def on_loaded(): + """ + Gets called when the plugin gets loaded + """ + global READY + global ALREADY_UPLOADED + global SKIP + + SKIP = list() + + if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): + logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net") + return + + try: + with open('/root/.wigle_uploads', 'r') as f: + ALREADY_UPLOADED = f.read().splitlines() + except OSError: + logging.warning('WIGLE: No upload-file found.') + ALREADY_UPLOADED = [] + + READY = True + + +def _extract_gps_data(path): + """ + Extract data from gps-file + + return json-obj + """ + + try: + with open(path, 'r') as json_file: + return json.load(json_file) + except OSError as os_err: + logging.error("WIGLE: %s", os_err) + except json.JSONDecodeError as json_err: + logging.error("WIGLE: %s", json_err) + + return None + +def _format_auth(data): + out = "" + for auth in data: + out = f"{out}[{auth}]" + return out + +def _transform_wigle_entry(gps_data, pcap_data): + """ + Transform to wigle entry in file + """ + dummy = StringIO() + # write kismet header + dummy.write("WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n") + dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type") + + writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE) + writer.writerow([ + pcap_data['bssid'], + pcap_data['essid'].decode('utf-8'), + _format_auth(pcap_data['encryption']), + datetime.strptime(gps_data['Updated'].rsplit('.')[0], + "%Y-%m-%dT%H:%M:%S").strftime('%Y-%m-%d %H:%M:%S'), + pcap_data['channel'], + pcap_data['rssi'], + gps_data['Latitude'], + gps_data['Longitude'], + gps_data['Altitude'], + 0, # accuracy? + 'WIFI']) + return dummy.getvalue() + +def _send_to_wigle(lines, api_key, timeout=30): + """ + Uploads the file to wigle-net + """ + + dummy = StringIO() + + for line in lines: + dummy.write(f"{line}") + + dummy.seek(0) + + headers = {'Authorization': f"Basic {api_key}", + 'Accept': 'application/json'} + data = {'donate': 'false'} + payload = {'file': dummy, 'type': 'text/csv'} + + try: + res = requests.post('https://api.wigle.net/api/v2/file/upload', + data=data, + headers=headers, + files=payload, + timeout=timeout) + json_res = res.json() + if not json_res['success']: + raise requests.exceptions.RequestException(json_res['message']) + except requests.exceptions.RequestException as re_e: + raise re_e + + +def on_internet_available(display, config, log): + """ + Called in manual mode when there's internet connectivity + """ + global ALREADY_UPLOADED + global SKIP + + if READY: + handshake_dir = config['bettercap']['handshakes'] + all_files = os.listdir(handshake_dir) + gps_files = [os.path.join(handshake_dir, filename) + for filename in all_files + if filename.endswith('.gps.json')] + gps_new = set(gps_files) - set(ALREADY_UPLOADED) - set(SKIP) + + if gps_new: + logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net") + + lines = list() + for gps_file in gps_new: + pcap_filename = gps_file.replace('.gps.json', '.pcap') + + if not os.path.exists(pcap_filename): + logging.error("WIGLE: Can't find pcap for %s", gps_file) + SKIP.append(gps_file) + continue + + gps_data = _extract_gps_data(gps_file) + try: + pcap_data = _analyze_pcap(pcap_filename) + except Scapy_Exception as sc_e: + logging.error("WIGLE: %s", sc_e) + SKIP.append(gps_file) + continue + + if 'encryption' in pcap_data: + if not pcap_data['encryption']: + pcap_data['encryption'].add('WEP') + else: + pcap_data['encryption'] = set() + pcap_data['encryption'].add('OPN') + + if len(pcap_data) < 5: + # not enough data + SKIP.append(gps_file) + continue + + new_entry = _transform_wigle_entry(gps_data, pcap_data) + lines.append(new_entry) + + if lines: + display.set('status', "Uploading gps-data to wigle.net ...") + display.update(force=True) + try: + _send_to_wigle(lines, OPTIONS['api_key']) + ALREADY_UPLOADED += gps_new + with open('/root/.wigle_uploads', 'a') as up_file: + for gps in gps_new: + up_file.write(gps + "\n") + logging.info("WIGLE: Successfuly uploaded %d files", len(gps_new)) + except requests.exceptions.RequestException as re_e: + SKIP += lines + logging.error("WIGLE: Got an exception while uploading %s", re_e) + except OSError as os_e: + SKIP += lines + logging.error("WIGLE: Got the following error: %s", os_e) From a9ef098d3291860f48ce27e075b6957c1121f039 Mon Sep 17 00:00:00 2001 From: SecurityWaffle <SecurityWaffle@users.noreply.github.com> Date: Sun, 6 Oct 2019 13:32:17 -0500 Subject: [PATCH 038/346] Fixed bug with defaults.yml path --- bin/pwnagotchi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 4733105..9b0b561 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -16,7 +16,7 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-C', '--config', action='store', dest='config', - default=os.path.join(os.path.abspath(os.path.dirname(pwnagotchi.__file__)), '/defaults.yml'), + default=os.path.join(os.path.abspath(os.path.dirname(pwnagotchi.__file__)), 'defaults.yml'), help='Main configuration file.') parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.yml', help='If this file exists, configuration will be merged and this will override default values.') From 90f0d6dc4ea7ac021813216edf6904eafe2b9504 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 6 Oct 2019 20:32:43 +0200 Subject: [PATCH 039/346] update defaults --- pwnagotchi/defaults.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index cceac71..813b97f 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -39,6 +39,9 @@ main: wpa-sec: enabled: false api_key: ~ + wigle: + enabled: false + api_key: ~ # monitor interface to use iface: mon0 From 27255bd8ec696cc463d79abd893f9e478b8d5b86 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 6 Oct 2019 20:37:28 +0200 Subject: [PATCH 040/346] Fix bug with gps.json files --- pwnagotchi/plugins/default/wpa-sec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py index 68e4171..cfe35da 100644 --- a/pwnagotchi/plugins/default/wpa-sec.py +++ b/pwnagotchi/plugins/default/wpa-sec.py @@ -61,7 +61,7 @@ def on_internet_available(display, config, log): if READY: handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) - handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames] + handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')] handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED) if handshake_new: From cb943ae4e168ae93ce3357bfe9a311e6622c54d1 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 6 Oct 2019 20:39:16 +0200 Subject: [PATCH 041/346] Fix bug with gps.json files --- pwnagotchi/plugins/default/onlinehashcrack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/onlinehashcrack.py b/pwnagotchi/plugins/default/onlinehashcrack.py index 249ea19..2319d96 100644 --- a/pwnagotchi/plugins/default/onlinehashcrack.py +++ b/pwnagotchi/plugins/default/onlinehashcrack.py @@ -62,7 +62,7 @@ def on_internet_available(display, config, log): if READY: handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) - handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames] + handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')] handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED) if handshake_new: From 1cf45138b8c8793cd7e1dbc8b8e0962a5ab5214a Mon Sep 17 00:00:00 2001 From: SecurityWaffle <SecurityWaffle@users.noreply.github.com> Date: Sun, 6 Oct 2019 13:40:16 -0500 Subject: [PATCH 042/346] Fixes path to defaults.yml --- bin/pwnagotchi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 9b0b561..56300af 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -16,7 +16,7 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-C', '--config', action='store', dest='config', - default=os.path.join(os.path.abspath(os.path.dirname(pwnagotchi.__file__)), 'defaults.yml'), + default=os.path.join(os.path.dirname(pwnagotchi.__file__), 'defaults.yml'), help='Main configuration file.') parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.yml', help='If this file exists, configuration will be merged and this will override default values.') From a3052c3b990558db9d3c60c5ed55fa1de72efc02 Mon Sep 17 00:00:00 2001 From: SecurityWaffle <SecurityWaffle@users.noreply.github.com> Date: Sun, 6 Oct 2019 13:41:55 -0500 Subject: [PATCH 043/346] fixes issue where defaults.yml is not included in the install --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 41d7475..98ee45c 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,8 @@ setup(name='pwnagotchi', license='GPL', install_requires=required, scripts=['bin/pwnagotchi'], - package_data={'pwnagotchi': ('pwnagotchi/defaults.yml',)}, + package_data={'pwnagotchi': ['defaults.yml', 'pwnagotchi/defaults.yml']}, + include_package_data=True, packages=find_packages(), classifiers=[ 'Programming Language :: Python :: 3', From 5b1bf6dd4afe62354e8499048b09e4d8a88288a4 Mon Sep 17 00:00:00 2001 From: waxwing <katherine.snyder@socraticarts.com> Date: Sun, 6 Oct 2019 15:23:37 -0400 Subject: [PATCH 044/346] added UI anatomy diagram --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4fb4153..02b9506 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [Pwnagotchi](https://twitter.com/pwnagotchi) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment in order to maximize the crackable WPA key material it captures (either passively, or by performing deauthentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), full and half WPA handshakes. - + Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning based "AI" *(yawn)*, Pwnagotchi tunes [its own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml#L54) over time to **get better at pwning WiFi things** in the environments you expose it to. From 9cf00805e007c31f37be3cac2a3e5f8ee3178901 Mon Sep 17 00:00:00 2001 From: Zenzen San <zenzenzen@riseup.net> Date: Sat, 5 Oct 2019 20:05:46 -0400 Subject: [PATCH 045/346] - Plugin geowifi saves wifi geolocation on hndshk Saves a json file with the access points with more signal whenever a handshake is captured. This data is usable to retrieve the geographic location using Google Geolocation API or Mozilla Location Service --- pwnagotchi/defaults.yml | 2 ++ pwnagotchi/plugins/default/geowifi.py | 30 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 pwnagotchi/plugins/default/geowifi.py diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index cceac71..c1fc2b6 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -25,6 +25,8 @@ main: commands: - 'tar czf /tmp/backup.tar.gz {files}' - 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz' + geowifi: + enabled: false gps: enabled: false twitter: diff --git a/pwnagotchi/plugins/default/geowifi.py b/pwnagotchi/plugins/default/geowifi.py new file mode 100644 index 0000000..b7f7982 --- /dev/null +++ b/pwnagotchi/plugins/default/geowifi.py @@ -0,0 +1,30 @@ +__author__ = 'zenzen san' +__version__ = '1.0.0' +__name__ = 'geowifi' +__license__ = 'GPL3' +__description__ = 'Saves a json file with the access points with more signal whenever a handshake is captured. This data is usable to retrieve the geographic location using Google Geolocation API or Mozilla Location Service' + +import logging +import json + +def on_loaded(): + logging.info("geowifi plugin loaded. :)") + +def on_handshake(agent, filename, access_point, client_station): + info = agent.session() + aps = agent.get_access_points() + geowifi = _geowifi_location(aps) + geowifi_filename = filename.replace('.pcap', '.geowifi.json') + + logging.info("saving GEOWIFI location to %s" % (geowifi_filename)) + with open(geowifi_filename, 'w+t') as fp: + json.dump(geowifi, fp) + +def _geowifi_location(aps): + geowifi = {} + geowifi['wifiAccessPoints'] = [] + # size seems a good number to save a wifi networks location + for ap in sorted(aps,key=lambda i:i['rssi'],reverse=True)[:6]: + geowifi['wifiAccessPoints'].append({'macAddress': ap['mac'], 'signalStrength': ap['rssi']}) + return geowifi + From 8d8e4b037651f708c24a314aacb49e4fef0d3fdd Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 6 Oct 2019 22:30:43 +0200 Subject: [PATCH 046/346] Rename some vars and fix some bugs --- pwnagotchi/plugins/default/wigle.py | 50 ++++++++++++++++++----------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index b87e71b..cba5745 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -126,11 +126,10 @@ def _extract_gps_data(path): with open(path, 'r') as json_file: return json.load(json_file) except OSError as os_err: - logging.error("WIGLE: %s", os_err) + raise os_err except json.JSONDecodeError as json_err: - logging.error("WIGLE: %s", json_err) + raise json_err - return None def _format_auth(data): out = "" @@ -203,16 +202,18 @@ def on_internet_available(display, config, log): if READY: handshake_dir = config['bettercap']['handshakes'] all_files = os.listdir(handshake_dir) - gps_files = [os.path.join(handshake_dir, filename) + all_gps_files = [os.path.join(handshake_dir, filename) for filename in all_files if filename.endswith('.gps.json')] - gps_new = set(gps_files) - set(ALREADY_UPLOADED) - set(SKIP) + new_gps_files = set(all_gps_files) - set(ALREADY_UPLOADED) - set(SKIP) - if gps_new: + if new_gps_files: logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net") - lines = list() - for gps_file in gps_new: + csv_entries = list() + no_err_entries = list() + + for gps_file in new_gps_files: pcap_filename = gps_file.replace('.gps.json', '.pcap') if not os.path.exists(pcap_filename): @@ -220,7 +221,17 @@ def on_internet_available(display, config, log): SKIP.append(gps_file) continue - gps_data = _extract_gps_data(gps_file) + try: + gps_data = _extract_gps_data(gps_file) + except OSError as os_err: + logging.error("WIGLE: %s", os_err) + SKIP.append(gps_file) + continue + except json.JSONDecodeError as json_err: + logging.error("WIGLE: %s", json_err) + SKIP.append(gps_file) + continue + try: pcap_data = _analyze_pcap(pcap_filename) except Scapy_Exception as sc_e: @@ -228,34 +239,37 @@ def on_internet_available(display, config, log): SKIP.append(gps_file) continue + # encrypption-key is only there if privacy-cap was set if 'encryption' in pcap_data: if not pcap_data['encryption']: pcap_data['encryption'].add('WEP') else: + # no encryption, nothing to eat :( pcap_data['encryption'] = set() pcap_data['encryption'].add('OPN') if len(pcap_data) < 5: - # not enough data + # not enough data; try next time SKIP.append(gps_file) continue new_entry = _transform_wigle_entry(gps_data, pcap_data) - lines.append(new_entry) + csv_entries.append(new_entry) + no_err_entries.append(gps_file) - if lines: + if csv_entries: display.set('status', "Uploading gps-data to wigle.net ...") display.update(force=True) try: - _send_to_wigle(lines, OPTIONS['api_key']) - ALREADY_UPLOADED += gps_new + _send_to_wigle(csv_entries, OPTIONS['api_key']) + ALREADY_UPLOADED += no_err_entries with open('/root/.wigle_uploads', 'a') as up_file: - for gps in gps_new: + for gps in no_err_entries: up_file.write(gps + "\n") - logging.info("WIGLE: Successfuly uploaded %d files", len(gps_new)) + logging.info("WIGLE: Successfuly uploaded %d files", len(no_err_entries)) except requests.exceptions.RequestException as re_e: - SKIP += lines + SKIP += no_err_entries logging.error("WIGLE: Got an exception while uploading %s", re_e) except OSError as os_e: - SKIP += lines + SKIP += no_err_entries logging.error("WIGLE: Got the following error: %s", os_e) From f6a80aae7823ba3ad70df15eb5fcf58b2d16ab1e Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 6 Oct 2019 23:00:31 +0200 Subject: [PATCH 047/346] Fix 404 url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 02b9506..670ed47 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ full and half WPA handshakes.  -Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning based "AI" *(yawn)*, Pwnagotchi tunes [its own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml#L54) over time to **get better at pwning WiFi things** in the environments you expose it to. +Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning based "AI" *(yawn)*, Pwnagotchi tunes [its own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things** in the environments you expose it to. More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#training-the-ai) doc.) From 1c251fc09381d0ac28940bd5fd3dd066fa1996c4 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 6 Oct 2019 23:25:02 +0200 Subject: [PATCH 048/346] new: fixed rsa identity generation and implemented api enrollment plugin --- bin/pwnagotchi | 8 +-- pwnagotchi/agent.py | 4 +- pwnagotchi/defaults.yml | 2 + pwnagotchi/identity.py | 47 +++++++++++++++++ pwnagotchi/mesh/__init__.py | 10 ---- pwnagotchi/mesh/utils.py | 7 ++- pwnagotchi/plugins/default/api.py | 51 +++++++++++++++++++ pwnagotchi/plugins/default/auto-backup.py | 2 +- pwnagotchi/plugins/default/auto-update.py | 2 +- pwnagotchi/plugins/default/example.py | 2 +- pwnagotchi/plugins/default/onlinehashcrack.py | 2 +- pwnagotchi/plugins/default/twitter.py | 2 +- pwnagotchi/plugins/default/wigle.py | 9 +++- pwnagotchi/plugins/default/wpa-sec.py | 2 +- pwnagotchi/utils.py | 3 ++ setup.py | 1 + 16 files changed, 127 insertions(+), 27 deletions(-) create mode 100644 pwnagotchi/identity.py create mode 100644 pwnagotchi/plugins/default/api.py diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 56300af..f72d84c 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -10,6 +10,7 @@ if __name__ == '__main__': import pwnagotchi.plugins as plugins from pwnagotchi.log import SessionParser + from pwnagotchi.identity import KeyPair from pwnagotchi.agent import Agent from pwnagotchi.ui.display import Display @@ -34,10 +35,11 @@ if __name__ == '__main__': plugins.load(config) + keypair = KeyPair() display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) - agent = Agent(view=display, config=config) + agent = Agent(view=display, config=config, keypair=keypair) - logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._identity, pwnagotchi.version)) + logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._keypair.fingerprint, pwnagotchi.version)) for _, plugin in plugins.loaded.items(): logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__)) @@ -64,7 +66,7 @@ if __name__ == '__main__': time.sleep(1) if Agent.is_connected(): - plugins.on('internet_available', display, config, log) + plugins.on('internet_available', display, keypair, config, log) else: logging.info("entering auto mode ...") diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 98f8aab..0e89763 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -17,13 +17,13 @@ RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery' class Agent(Client, AsyncAdvertiser, AsyncTrainer): - def __init__(self, view, config): + def __init__(self, view, config, keypair): Client.__init__(self, config['bettercap']['hostname'], config['bettercap']['scheme'], config['bettercap']['port'], config['bettercap']['username'], config['bettercap']['password']) - AsyncAdvertiser.__init__(self, config, view) + AsyncAdvertiser.__init__(self, config, view, keypair) AsyncTrainer.__init__(self, config) self._started_at = time.time() diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 813b97f..dff6b3b 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -6,6 +6,8 @@ main: custom_plugins: # which plugins to load and enable plugins: + api: + enabled: false auto-update: enabled: false interval: 1 # every day diff --git a/pwnagotchi/identity.py b/pwnagotchi/identity.py new file mode 100644 index 0000000..ae12490 --- /dev/null +++ b/pwnagotchi/identity.py @@ -0,0 +1,47 @@ +from Crypto.Signature import PKCS1_PSS +from Crypto.PublicKey import RSA +import Crypto.Hash.SHA256 as SHA256 +import base64 +import hashlib +import os +import logging + +DefaultPath = "/etc/pwnagotchi/" + + +class KeyPair(object): + def __init__(self, path=DefaultPath): + self.path = path + self.priv_path = os.path.join(path, "id_rsa") + self.priv_key = None + self.pub_path = "%s.pub" % self.priv_path + self.pub_key = None + + if not os.path.exists(self.path): + os.makedirs(self.path) + + if not os.path.exists(self.priv_path) or not os.path.exists(self.pub_path): + logging.info("generating %s ..." % self.priv_path) + os.system("/usr/bin/ssh-keygen -t rsa -m PEM -b 4096 -N '' -f '%s'" % self.priv_path) + + with open(self.priv_path) as fp: + self.priv_key = RSA.importKey(fp.read()) + + with open(self.pub_path) as fp: + self.pub_key = RSA.importKey(fp.read()) + self.pub_key_pem = self.pub_key.exportKey('PEM').decode("ascii") + # python is special + if 'RSA PUBLIC KEY' not in self.pub_key_pem: + self.pub_key_pem = self.pub_key_pem.replace('PUBLIC KEY', 'RSA PUBLIC KEY') + + pem = self.pub_key_pem.encode("ascii") + + self.pub_key_pem_b64 = base64.b64encode(pem).decode("ascii") + self.fingerprint = hashlib.sha256(pem).hexdigest() + + def sign(self, message): + hasher = SHA256.new(message.encode("ascii")) + signer = PKCS1_PSS.new(self.priv_key, saltLen=16) + signature = signer.sign(hasher) + signature_b64 = base64.b64encode(signature).decode("ascii") + return signature, signature_b64 \ No newline at end of file diff --git a/pwnagotchi/mesh/__init__.py b/pwnagotchi/mesh/__init__.py index 9e68dc8..e1c033f 100644 --- a/pwnagotchi/mesh/__init__.py +++ b/pwnagotchi/mesh/__init__.py @@ -1,14 +1,4 @@ import os -from Crypto.PublicKey import RSA -import hashlib - def new_session_id(): return ':'.join(['%02x' % b for b in os.urandom(6)]) - - -def get_identity(config): - pubkey = None - with open(config['main']['pubkey']) as fp: - pubkey = RSA.importKey(fp.read()) - return pubkey, hashlib.sha256(pubkey.exportKey('DER')).hexdigest() diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index 3b63d09..a775ad4 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -3,14 +3,13 @@ import logging import pwnagotchi import pwnagotchi.plugins as plugins -from pwnagotchi.mesh import get_identity class AsyncAdvertiser(object): - def __init__(self, config, view): + def __init__(self, config, view, keypair): self._config = config self._view = view - self._public_key, self._identity = get_identity(config) + self._keypair = keypair self._advertiser = None def start_advertising(self): @@ -24,7 +23,7 @@ class AsyncAdvertiser(object): self._config['main']['iface'], pwnagotchi.name(), pwnagotchi.version, - self._identity, + self._keypair.fingerprint, period=0.3, data=self._config['personality']) diff --git a/pwnagotchi/plugins/default/api.py b/pwnagotchi/plugins/default/api.py new file mode 100644 index 0000000..afd882c --- /dev/null +++ b/pwnagotchi/plugins/default/api.py @@ -0,0 +1,51 @@ +__author__ = 'evilsocket@gmail.com' +__version__ = '1.0.0' +__name__ = 'api' +__license__ = 'GPL3' +__description__ = 'This plugin signals the unit cryptographic identity to api.pwnagotchi.ai' + +import logging +import json +import requests +import pwnagotchi +from pwnagotchi.utils import StatusFile + +OPTIONS = dict() +READY = False +STATUS = StatusFile('/root/.api-enrollment.json') + + +def on_loaded(): + logging.info("api plugin loaded.") + + +def on_internet_available(ui, keypair, config, log): + global STATUS + + if STATUS.newer_then_minutes(10): + return + + try: + logging.info("api: signign enrollment request ...") + identity = "%s@%s" % (pwnagotchi.name(), keypair.fingerprint) + _, signature_b64 = keypair.sign(identity) + + api_address = 'https://api.pwnagotchi.ai/api/v1/unit/enroll' + enroll = { + 'identity': identity, + 'public_key': keypair.pub_key_pem_b64, + 'signature': signature_b64 + } + + logging.info("api: enrolling unit to %s ..." % api_address) + + r = requests.post(api_address, json=enroll) + if r.status_code == 200: + token = r.json() + logging.info("api: enrolled") + STATUS.update(data=json.dumps(token)) + else: + logging.error("error %d: %s" % (r.status_code, r.json())) + + except Exception as e: + logging.exception("error while enrolling the unit") diff --git a/pwnagotchi/plugins/default/auto-backup.py b/pwnagotchi/plugins/default/auto-backup.py index 731a40f..48d8597 100644 --- a/pwnagotchi/plugins/default/auto-backup.py +++ b/pwnagotchi/plugins/default/auto-backup.py @@ -33,7 +33,7 @@ def on_loaded(): logging.info("AUTO-BACKUP: Successfuly loaded.") -def on_internet_available(display, config, log): +def on_internet_available(display, keypair, config, log): global STATUS if READY: diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 4a1f135..dab44b3 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -23,7 +23,7 @@ def on_loaded(): READY = True -def on_internet_available(display, config, log): +def on_internet_available(display, keypair, config, log): global STATUS if READY: diff --git a/pwnagotchi/plugins/default/example.py b/pwnagotchi/plugins/default/example.py index c18177c..2a82a18 100644 --- a/pwnagotchi/plugins/default/example.py +++ b/pwnagotchi/plugins/default/example.py @@ -20,7 +20,7 @@ def on_loaded(): # called in manual mode when there's internet connectivity -def on_internet_available(ui, config, log): +def on_internet_available(ui, keypair, config, log): pass diff --git a/pwnagotchi/plugins/default/onlinehashcrack.py b/pwnagotchi/plugins/default/onlinehashcrack.py index 2319d96..9771b88 100644 --- a/pwnagotchi/plugins/default/onlinehashcrack.py +++ b/pwnagotchi/plugins/default/onlinehashcrack.py @@ -55,7 +55,7 @@ def _upload_to_ohc(path, timeout=30): raise e -def on_internet_available(display, config, log): +def on_internet_available(display, keypair, config, log): """ Called in manual mode when there's internet connectivity """ diff --git a/pwnagotchi/plugins/default/twitter.py b/pwnagotchi/plugins/default/twitter.py index 560903a..8f21f25 100644 --- a/pwnagotchi/plugins/default/twitter.py +++ b/pwnagotchi/plugins/default/twitter.py @@ -14,7 +14,7 @@ def on_loaded(): # called in manual mode when there's internet connectivity -def on_internet_available(ui, config, log): +def on_internet_available(ui, keypair, config, log): if log.is_new() and log.handshakes > 0: try: import tweepy diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index cba5745..da57f94 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -12,7 +12,6 @@ import csv from datetime import datetime import requests from pwnagotchi.mesh.wifi import freq_to_channel -from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA READY = False ALREADY_UPLOADED = None @@ -26,6 +25,8 @@ AKMSUITE_TYPES = { } def _handle_packet(packet, result): + from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \ + Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA """ Analyze each packet and extract the data from Dot11 layers """ @@ -76,6 +77,8 @@ def _handle_packet(packet, result): def _analyze_pcap(pcap): + from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \ + Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA """ Iterate over the packets and extract data """ @@ -192,7 +195,9 @@ def _send_to_wigle(lines, api_key, timeout=30): raise re_e -def on_internet_available(display, config, log): +def on_internet_available(display, keypair, config, log): + from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \ + Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA """ Called in manual mode when there's internet connectivity """ diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py index cfe35da..7d61b7b 100644 --- a/pwnagotchi/plugins/default/wpa-sec.py +++ b/pwnagotchi/plugins/default/wpa-sec.py @@ -54,7 +54,7 @@ def _upload_to_wpasec(path, timeout=30): raise e -def on_internet_available(display, config, log): +def on_internet_available(display, keypair, config, log): """ Called in manual mode when there's internet connectivity """ diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index d1b2ba0..2834664 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -89,6 +89,9 @@ class StatusFile(object): if os.path.exists(path): self._updated = datetime.fromtimestamp(os.path.getmtime(path)) + def newer_then_minutes(self, minutes): + return self._updated is not None and ((datetime.now() - self._updated).seconds / 60) < minutes + def newer_then_days(self, days): return self._updated is not None and (datetime.now() - self._updated).days < days diff --git a/setup.py b/setup.py index 98ee45c..f8e22b3 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# -*- coding: utf-8 -*- from setuptools import setup, find_packages import pwnagotchi From 967ba3a7a510e650e9b098a72d45629ca8e8407e Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 00:10:00 +0200 Subject: [PATCH 049/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/api.py b/pwnagotchi/plugins/default/api.py index afd882c..6747d95 100644 --- a/pwnagotchi/plugins/default/api.py +++ b/pwnagotchi/plugins/default/api.py @@ -22,7 +22,7 @@ def on_loaded(): def on_internet_available(ui, keypair, config, log): global STATUS - if STATUS.newer_then_minutes(10): + if STATUS.newer_then_minutes(25): return try: From 8ef1f0f377771e6300a38d1aca66cbd29c385bce Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 00:27:22 +0200 Subject: [PATCH 050/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/api.py | 1 - scripts/pypi_upload.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/api.py b/pwnagotchi/plugins/default/api.py index 6747d95..480f07c 100644 --- a/pwnagotchi/plugins/default/api.py +++ b/pwnagotchi/plugins/default/api.py @@ -38,7 +38,6 @@ def on_internet_available(ui, keypair, config, log): } logging.info("api: enrolling unit to %s ..." % api_address) - r = requests.post(api_address, json=enroll) if r.status_code == 200: token = r.json() diff --git a/scripts/pypi_upload.sh b/scripts/pypi_upload.sh index 4a71125..265a56a 100755 --- a/scripts/pypi_upload.sh +++ b/scripts/pypi_upload.sh @@ -1,6 +1,6 @@ #!/bin/bash -rm -rf build dist ergo_nn.egg-info && +rm -rf build dist pwnagotchi.egg-info && python3 setup.py sdist bdist_wheel && clear && twine upload dist/* From b26d238f4091cfd671586380fcffa64f30b89ace Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 00:30:05 +0200 Subject: [PATCH 051/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 9905b0f..ff43494 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -1,6 +1,6 @@ import subprocess -version = '1.0.0plz4' +version = '1.0.0a' _name = None From 61da16ed915877f8cdb60cc4b1d00de175305fbe Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 01:31:37 +0200 Subject: [PATCH 052/346] fix: when the AI is ready the mode label reports AI --- pwnagotchi/ai/__init__.py | 2 +- pwnagotchi/ui/view.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/ai/__init__.py b/pwnagotchi/ai/__init__.py index 359a38c..190025f 100644 --- a/pwnagotchi/ai/__init__.py +++ b/pwnagotchi/ai/__init__.py @@ -39,4 +39,4 @@ def load(config, agent, epoch, from_disk=True): for key, value in config['params'].items(): logging.info(" %s: %s" % (key, value)) - return a2c + return a2c \ No newline at end of file diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 78e75b6..5847662 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -153,7 +153,7 @@ class View(object): self.set('face', faces.AWAKE) def on_ai_ready(self): - self.set('mode', '') + self.set('mode', ' AI') self.set('face', faces.HAPPY) self.set('status', self._voice.on_ai_ready()) self.update() From 6e9bb866c79506b310b3d05374d0d2ec2f87e6d1 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 02:00:08 +0200 Subject: [PATCH 053/346] fix: fixed epochs log parser --- pwnagotchi/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index 416ae4f..dab58e4 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -13,7 +13,7 @@ LAST_SESSION_FILE = '/root/.pwnagotchi-last-session' class SessionParser(object): EPOCH_TOKEN = '[epoch ' - EPOCH_PARSER = re.compile(r'^\s*\[epoch (\d+)\] (.+)') + EPOCH_PARSER = re.compile(r'^.+\[epoch (\d+)\] (.+)') EPOCH_DATA_PARSER = re.compile(r'([a-z_]+)=([^\s]+)') TRAINING_TOKEN = ' training epoch ' START_TOKEN = 'connecting to http' From 00582be3746c4adc23aed00797040a2902ffc9aa Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 10:25:46 +0200 Subject: [PATCH 054/346] new: reporting session data to the api --- pwnagotchi/plugins/default/api.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/api.py b/pwnagotchi/plugins/default/api.py index 480f07c..5828085 100644 --- a/pwnagotchi/plugins/default/api.py +++ b/pwnagotchi/plugins/default/api.py @@ -7,6 +7,7 @@ __description__ = 'This plugin signals the unit cryptographic identity to api.pw import logging import json import requests +import subprocess import pwnagotchi from pwnagotchi.utils import StatusFile @@ -27,6 +28,7 @@ def on_internet_available(ui, keypair, config, log): try: logging.info("api: signign enrollment request ...") + identity = "%s@%s" % (pwnagotchi.name(), keypair.fingerprint) _, signature_b64 = keypair.sign(identity) @@ -34,7 +36,16 @@ def on_internet_available(ui, keypair, config, log): enroll = { 'identity': identity, 'public_key': keypair.pub_key_pem_b64, - 'signature': signature_b64 + 'signature': signature_b64, + 'data': { + 'duration': log.duration, + 'epochs': log.epochs, + 'train_epochs': log.train_epochs, + 'avg_reward': log.avg_reward, + 'min_reward': log.min_reward, + 'max_reward': log.max_reward, + 'uname': subprocess.getoutput("uname -a") + } } logging.info("api: enrolling unit to %s ..." % api_address) From da52bcd705d2dfdfbd72bd3743246d1646fd5018 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 11:13:28 +0200 Subject: [PATCH 055/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/api.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pwnagotchi/plugins/default/api.py b/pwnagotchi/plugins/default/api.py index 5828085..60792af 100644 --- a/pwnagotchi/plugins/default/api.py +++ b/pwnagotchi/plugins/default/api.py @@ -30,6 +30,7 @@ def on_internet_available(ui, keypair, config, log): logging.info("api: signign enrollment request ...") identity = "%s@%s" % (pwnagotchi.name(), keypair.fingerprint) + # sign the identity string to prove we own both keys _, signature_b64 = keypair.sign(identity) api_address = 'https://api.pwnagotchi.ai/api/v1/unit/enroll' @@ -44,6 +45,10 @@ def on_internet_available(ui, keypair, config, log): 'avg_reward': log.avg_reward, 'min_reward': log.min_reward, 'max_reward': log.max_reward, + 'deauthed': log.deauthed, + 'associated': log.associated, + 'handshakes': log.handshakes, + 'peers': log.peers, 'uname': subprocess.getoutput("uname -a") } } From d6efc0b70d215907f3a54ddc7a8a2c58b7efc9bf Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 13:06:29 +0200 Subject: [PATCH 056/346] new: api plugin will report pwned access points --- pwnagotchi/defaults.yml | 1 + pwnagotchi/plugins/default/api.py | 168 +++++++++++++++++++++++------- pwnagotchi/utils.py | 25 ++++- 3 files changed, 154 insertions(+), 40 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index dff6b3b..e9b45b6 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -8,6 +8,7 @@ main: plugins: api: enabled: false + report: true # report pwned networks auto-update: enabled: false interval: 1 # every day diff --git a/pwnagotchi/plugins/default/api.py b/pwnagotchi/plugins/default/api.py index 60792af..eae9343 100644 --- a/pwnagotchi/plugins/default/api.py +++ b/pwnagotchi/plugins/default/api.py @@ -4,63 +4,155 @@ __name__ = 'api' __license__ = 'GPL3' __description__ = 'This plugin signals the unit cryptographic identity to api.pwnagotchi.ai' +import os import logging -import json import requests +import glob import subprocess import pwnagotchi -from pwnagotchi.utils import StatusFile +import pwnagotchi.utils as utils OPTIONS = dict() -READY = False -STATUS = StatusFile('/root/.api-enrollment.json') +AUTH = utils.StatusFile('/root/.api-enrollment.json', data_format='json') +REPORT = utils.StatusFile('/root/.api-report.json', data_format='json') def on_loaded(): logging.info("api plugin loaded.") -def on_internet_available(ui, keypair, config, log): - global STATUS +def get_api_token(log, keys): + global AUTH - if STATUS.newer_then_minutes(25): - return + if AUTH.newer_then_minutes(25) and AUTH.data is not None and 'token' in AUTH.data: + return AUTH.data['token'] + + if AUTH.data is None: + logging.info("api: enrolling unit ...") + else: + logging.info("api: refreshing token ...") + + identity = "%s@%s" % (pwnagotchi.name(), keys.fingerprint) + # sign the identity string to prove we own both keys + _, signature_b64 = keys.sign(identity) + + api_address = 'https://api.pwnagotchi.ai/api/v1/unit/enroll' + enrollment = { + 'identity': identity, + 'public_key': keys.pub_key_pem_b64, + 'signature': signature_b64, + 'data': { + 'duration': log.duration, + 'epochs': log.epochs, + 'train_epochs': log.train_epochs, + 'avg_reward': log.avg_reward, + 'min_reward': log.min_reward, + 'max_reward': log.max_reward, + 'deauthed': log.deauthed, + 'associated': log.associated, + 'handshakes': log.handshakes, + 'peers': log.peers, + 'uname': subprocess.getoutput("uname -a") + } + } + + r = requests.post(api_address, json=enrollment) + if r.status_code != 200: + raise Exception("(status %d) %s" % (r.status_code, r.json())) + + AUTH.update(data=r.json()) + + logging.info("api: done") + + return AUTH.data["token"] + + +def parse_packet(packet, info): + from scapy.all import Dot11Elt, Dot11Beacon, Dot11, Dot11ProbeResp, Dot11AssoReq, Dot11ReassoReq + + if packet.haslayer(Dot11Beacon): + if packet.haslayer(Dot11Beacon) \ + or packet.haslayer(Dot11ProbeResp) \ + or packet.haslayer(Dot11AssoReq) \ + or packet.haslayer(Dot11ReassoReq): + if 'bssid' not in info and hasattr(packet[Dot11], 'addr3'): + info['bssid'] = packet[Dot11].addr3 + if 'essid' not in info and hasattr(packet[Dot11Elt], 'info'): + info['essid'] = packet[Dot11Elt].info.decode('utf-8') + + return info + + +def parse_pcap(filename): + logging.info("api: parsing %s ..." % filename) + + essid = bssid = None try: - logging.info("api: signign enrollment request ...") + from scapy.all import rdpcap - identity = "%s@%s" % (pwnagotchi.name(), keypair.fingerprint) - # sign the identity string to prove we own both keys - _, signature_b64 = keypair.sign(identity) + info = {} - api_address = 'https://api.pwnagotchi.ai/api/v1/unit/enroll' - enroll = { - 'identity': identity, - 'public_key': keypair.pub_key_pem_b64, - 'signature': signature_b64, - 'data': { - 'duration': log.duration, - 'epochs': log.epochs, - 'train_epochs': log.train_epochs, - 'avg_reward': log.avg_reward, - 'min_reward': log.min_reward, - 'max_reward': log.max_reward, - 'deauthed': log.deauthed, - 'associated': log.associated, - 'handshakes': log.handshakes, - 'peers': log.peers, - 'uname': subprocess.getoutput("uname -a") - } + for pkt in rdpcap(filename): + info = parse_packet(pkt, info) + + bssid = info['bssid'] if 'bssid' in info else None + essid = info['essid'] if 'essid' in info else None + + except Exception as e: + bssid = None + logging.error("api: %s" % e) + + return essid, bssid + + +def api_report_ap(token, essid, bssid): + logging.info("api: reporting %s (%s)" % (essid, bssid)) + + try: + api_address = 'https://api.pwnagotchi.ai/api/v1/unit/report/ap' + headers = {'Authorization': 'access_token %s' % token} + report = { + 'essid': essid, + 'bssid': bssid, } + r = requests.post(api_address, headers=headers, json=report) + if r.status_code != 200: + raise Exception("(status %d) %s" % (r.status_code, r.text)) + except Exception as e: + logging.error("api: %s" % e) + return False - logging.info("api: enrolling unit to %s ..." % api_address) - r = requests.post(api_address, json=enroll) - if r.status_code == 200: - token = r.json() - logging.info("api: enrolled") - STATUS.update(data=json.dumps(token)) - else: - logging.error("error %d: %s" % (r.status_code, r.json())) + return True + + +def on_internet_available(ui, keys, config, log): + global REPORT + + try: + + pcap_files = glob.glob(os.path.join(config['bettercap']['handshakes'], "*.pcap")) + num_networks = len(pcap_files) + reported = REPORT.data_field_or('reported', default=[]) + num_reported = len(reported) + num_new = num_networks - num_reported + + if num_new > 0: + logging.info("api: %d new networks to report" % num_new) + token = get_api_token(log, keys) + + if OPTIONS['report']: + for pcap_file in pcap_files: + net_id = os.path.basename(pcap_file).replace('.pcap', '') + if net_id not in reported: + essid, bssid = parse_pcap(pcap_file) + if bssid: + if api_report_ap(token, essid, bssid): + reported.append(net_id) + + REPORT.update(data={'reported': reported}) + else: + logging.info("api: reporting disabled") except Exception as e: logging.exception("error while enrolling the unit") diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 2834664..e6e15f5 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -5,6 +5,7 @@ import os import time import subprocess import yaml +import json # https://stackoverflow.com/questions/823196/yaml-merge-in-python @@ -82,12 +83,24 @@ def blink(times=1, delay=0.3): class StatusFile(object): - def __init__(self, path): + def __init__(self, path, data_format='raw'): self._path = path self._updated = None + self._format = data_format + self.data = None if os.path.exists(path): self._updated = datetime.fromtimestamp(os.path.getmtime(path)) + with open(path) as fp: + if data_format == 'json': + self.data = json.load(fp) + else: + self.data = fp.read() + + def data_field_or(self, name, default=""): + if self.data is not None and name in self.data: + return self.data[name] + return default def newer_then_minutes(self, minutes): return self._updated is not None and ((datetime.now() - self._updated).seconds / 60) < minutes @@ -97,5 +110,13 @@ class StatusFile(object): def update(self, data=None): self._updated = datetime.now() + self.data = data with open(self._path, 'w') as fp: - fp.write(str(self._updated) if data is None else data) + if data is None: + fp.write(str(self._updated)) + + elif self._format == 'json': + json.dump(self.data, fp) + + else: + fp.write(data) From 4827dc65edf5251275e1336ec58f7640f8e3898e Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 14:12:22 +0200 Subject: [PATCH 057/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pwnagotchi/plugins/default/api.py b/pwnagotchi/plugins/default/api.py index eae9343..b912bb8 100644 --- a/pwnagotchi/plugins/default/api.py +++ b/pwnagotchi/plugins/default/api.py @@ -95,6 +95,8 @@ def parse_pcap(filename): for pkt in rdpcap(filename): info = parse_packet(pkt, info) + if 'essid' in info and info['essid'] is not None and 'bssid' in info and info['bssid'] is not None: + break bssid = info['bssid'] if 'bssid' in info else None essid = info['essid'] if 'essid' in info else None From 60f0f6c29eda2b2a761618af711a529d7bb00ccd Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 14:32:42 +0200 Subject: [PATCH 058/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/api.py b/pwnagotchi/plugins/default/api.py index b912bb8..0b2f1be 100644 --- a/pwnagotchi/plugins/default/api.py +++ b/pwnagotchi/plugins/default/api.py @@ -151,8 +151,7 @@ def on_internet_available(ui, keys, config, log): if bssid: if api_report_ap(token, essid, bssid): reported.append(net_id) - - REPORT.update(data={'reported': reported}) + REPORT.update(data={'reported': reported}) else: logging.info("api: reporting disabled") From 90b0e10e81a5a0e03753715ec1034278bdd5f600 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 15:32:44 +0200 Subject: [PATCH 059/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/api.py | 81 +++++++++++++++++-------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/pwnagotchi/plugins/default/api.py b/pwnagotchi/plugins/default/api.py index 0b2f1be..71d78e3 100644 --- a/pwnagotchi/plugins/default/api.py +++ b/pwnagotchi/plugins/default/api.py @@ -69,63 +69,70 @@ def get_api_token(log, keys): def parse_packet(packet, info): from scapy.all import Dot11Elt, Dot11Beacon, Dot11, Dot11ProbeResp, Dot11AssoReq, Dot11ReassoReq - if packet.haslayer(Dot11Beacon): - if packet.haslayer(Dot11Beacon) \ - or packet.haslayer(Dot11ProbeResp) \ - or packet.haslayer(Dot11AssoReq) \ - or packet.haslayer(Dot11ReassoReq): - if 'bssid' not in info and hasattr(packet[Dot11], 'addr3'): + if packet.haslayer(Dot11ProbeResp) or packet.haslayer(Dot11AssoReq) or packet.haslayer(Dot11ReassoReq): + if hasattr(packet[Dot11], 'addr3'): info['bssid'] = packet[Dot11].addr3 - if 'essid' not in info and hasattr(packet[Dot11Elt], 'info'): + if hasattr(packet[Dot11Elt], 'info'): info['essid'] = packet[Dot11Elt].info.decode('utf-8') - return info def parse_pcap(filename): logging.info("api: parsing %s ..." % filename) - essid = bssid = None + net_id = os.path.basename(filename).replace('.pcap', '') + + if '_' in net_id: + # /root/handshakes/ESSID_BSSID.pcap + essid, bssid = net_id.split('_') + else: + # /root/handshakes/BSSID.pcap + essid, bssid = '', net_id + + it = iter(bssid) + bssid = ':'.join([a + b for a, b in zip(it, it)]) + + info = { + 'essid': essid, + 'bssid': bssid + } try: from scapy.all import rdpcap - info = {} - for pkt in rdpcap(filename): info = parse_packet(pkt, info) - if 'essid' in info and info['essid'] is not None and 'bssid' in info and info['bssid'] is not None: - break - - bssid = info['bssid'] if 'bssid' in info else None - essid = info['essid'] if 'essid' in info else None except Exception as e: - bssid = None logging.error("api: %s" % e) - return essid, bssid + return info['essid'], info['bssid'] -def api_report_ap(token, essid, bssid): - logging.info("api: reporting %s (%s)" % (essid, bssid)) - - try: - api_address = 'https://api.pwnagotchi.ai/api/v1/unit/report/ap' - headers = {'Authorization': 'access_token %s' % token} - report = { - 'essid': essid, - 'bssid': bssid, - } - r = requests.post(api_address, headers=headers, json=report) - if r.status_code != 200: - raise Exception("(status %d) %s" % (r.status_code, r.text)) - except Exception as e: - logging.error("api: %s" % e) - return False - - return True +def api_report_ap(log, keys, token, essid, bssid): + while True: + logging.info("api: reporting %s (%s)" % (essid, bssid)) + try: + api_address = 'https://api.pwnagotchi.ai/api/v1/unit/report/ap' + headers = {'Authorization': 'access_token %s' % token} + report = { + 'essid': essid, + 'bssid': bssid, + } + r = requests.post(api_address, headers=headers, json=report) + if r.status_code != 200: + if r.status_code == 401: + logging.warning("token expired") + token = get_api_token(log, keys) + continue + else: + raise Exception("(status %d) %s" % (r.status_code, r.text)) + else: + return True + except Exception as e: + logging.error("api: %s" % e) + return False def on_internet_available(ui, keys, config, log): @@ -149,7 +156,7 @@ def on_internet_available(ui, keys, config, log): if net_id not in reported: essid, bssid = parse_pcap(pcap_file) if bssid: - if api_report_ap(token, essid, bssid): + if api_report_ap(log, keys, token, essid, bssid): reported.append(net_id) REPORT.update(data={'reported': reported}) else: From db3b7f577ab490e051e653141773a4ab7561df81 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 15:45:11 +0200 Subject: [PATCH 060/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pwnagotchi/plugins/default/api.py b/pwnagotchi/plugins/default/api.py index 71d78e3..f906b06 100644 --- a/pwnagotchi/plugins/default/api.py +++ b/pwnagotchi/plugins/default/api.py @@ -112,6 +112,7 @@ def parse_pcap(filename): def api_report_ap(log, keys, token, essid, bssid): while True: + token = AUTH.data['token'] logging.info("api: reporting %s (%s)" % (essid, bssid)) try: api_address = 'https://api.pwnagotchi.ai/api/v1/unit/report/ap' From a787e78f937b68d7788bc1f8ca7f25a333ac6830 Mon Sep 17 00:00:00 2001 From: Andrea Draghetti <drego85@draghetti.it> Date: Mon, 7 Oct 2019 16:00:46 +0200 Subject: [PATCH 061/346] Proper management of the IF cycle --- scripts/create_sibling.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/create_sibling.sh b/scripts/create_sibling.sh index 650d40c..c0d7558 100755 --- a/scripts/create_sibling.sh +++ b/scripts/create_sibling.sh @@ -94,7 +94,7 @@ function provide_raspbian() { function setup_raspbian(){ # Detect the ability to create sparse files if [ "${OPT_SPARSE}" -eq 0 ]; then - if ! type "bmaptool" > /dev/null; then + if ! type "bmaptool" >/dev/null 2>&1; then echo "[!] bmaptool not available, not creating a sparse image" else echo "[+] Defaulting to sparse image generation as bmaptool is available" From 7a24a1a0bca6630942a656707734834d5935849b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 16:23:38 +0200 Subject: [PATCH 062/346] fix: api plugin is now called grid and opt-out --- pwnagotchi/defaults.yml | 4 ++-- pwnagotchi/plugins/default/{api.py => grid.py} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename pwnagotchi/plugins/default/{api.py => grid.py} (98%) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index e9b45b6..3cda0cb 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -6,8 +6,8 @@ main: custom_plugins: # which plugins to load and enable plugins: - api: - enabled: false + grid: + enabled: true report: true # report pwned networks auto-update: enabled: false diff --git a/pwnagotchi/plugins/default/api.py b/pwnagotchi/plugins/default/grid.py similarity index 98% rename from pwnagotchi/plugins/default/api.py rename to pwnagotchi/plugins/default/grid.py index f906b06..09ee08a 100644 --- a/pwnagotchi/plugins/default/api.py +++ b/pwnagotchi/plugins/default/grid.py @@ -1,8 +1,8 @@ __author__ = 'evilsocket@gmail.com' __version__ = '1.0.0' -__name__ = 'api' +__name__ = 'grid' __license__ = 'GPL3' -__description__ = 'This plugin signals the unit cryptographic identity to api.pwnagotchi.ai' +__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned networks to api.pwnagotchi.ai' import os import logging From 6bd1d5f764caea62ea1a37be5b4ef778814760a2 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 16:24:44 +0200 Subject: [PATCH 063/346] fix: updated auto-backup.files --- pwnagotchi/defaults.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 3cda0cb..3f2d871 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -18,9 +18,8 @@ main: files: - /root/brain.nn - /root/brain.json - - /root/custom.yml - - /root/handshakes - - /etc/ssh + - /root/handshakes/ + - /etc/pwnagotchi/ - /etc/hostname - /etc/hosts - /etc/motd From 8bc037abb720c1bee3a6871473d6c26cbde2748b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 16:27:01 +0200 Subject: [PATCH 064/346] fix: fixed paths in doc --- docs/configure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configure.md b/docs/configure.md index b232e3e..a3fc7c2 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -33,7 +33,7 @@ ssh-copy-id -i ~/.ssh/id_rsa.pub pi@10.0.0.2 You can now set a new name for your unit by [changing the hostname](https://geek-university.com/raspberry-pi/change-raspberry-pis-hostname/)! -Create the `/root/custom.yml` file (either via SSH or by directly editing the SD card contents from a computer) that will override the [default configuration](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml) with your custom values. +Open the `/etc/pwnagotchi/config.yml` file (either via SSH or by directly editing the SD card contents from a computer) that will override the [default configuration](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml) with your custom values. ## Choose your Pwnagotchi's language From 1ebb8599b35c0e01116c0f19809ec749b5a3a170 Mon Sep 17 00:00:00 2001 From: "Michael V. Swisher" <daswisher@gmail.com> Date: Mon, 7 Oct 2019 07:30:23 -0700 Subject: [PATCH 065/346] Fixing function name mismatch --- sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py index 7fb6a6f..4b82443 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py @@ -128,7 +128,7 @@ class Display(View): self._display.clear() self._render_cb = self._papirus_render - elif self._is_waveshare1(): + elif self._is_waveshare_v1(): logging.info("initializing waveshare v1 display") if self._display_color == 'black': from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD From 6cf74dd282b46d5bdcff4dfd920a671fdd6a5076 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 16:35:25 +0200 Subject: [PATCH 066/346] docs: pwngrid docs --- docs/configure.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/configure.md b/docs/configure.md index a3fc7c2..11d204d 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -51,6 +51,32 @@ But if you want, you can change `main.lang` to one of the supported languages: - Russian - Swedish +## PwnGRID + +By default the `grid` [plugin](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md) is enabled, this means that whenever the unit will detect internet connectivity in manual mode, it'll signal its +presence to the PwnGRID server and periodically send a list of the networks that it has pwned. None of the captured cryptographic material is sent to this server, +just the minimum information to enroll the unit in the database and know how many networks it "conquered" so far. + +If you want to partially opt-out from this feature and have your unit only signal its presence without sending the list of networks, you can put this in your `/etc/pwnagotchi/config.yml` file: + +```yaml +main: + plugins: + grid: + enabled: true + report: false # partial opt-out +``` + +If you prefer to completely opt-out and also disable signaling: + +```yaml +main: + plugins: + grid: + enabled: false # full opt-out + report: false +``` + ## Display Selection **Set the type of display you want to use via `ui.display.type`.** From 2b50609752a5c8f8df48ae9a3c2d9a7df9ebe588 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 16:42:56 +0200 Subject: [PATCH 067/346] fix: phrasing --- docs/configure.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/configure.md b/docs/configure.md index 11d204d..8c9c6a9 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -55,7 +55,13 @@ But if you want, you can change `main.lang` to one of the supported languages: By default the `grid` [plugin](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md) is enabled, this means that whenever the unit will detect internet connectivity in manual mode, it'll signal its presence to the PwnGRID server and periodically send a list of the networks that it has pwned. None of the captured cryptographic material is sent to this server, -just the minimum information to enroll the unit in the database and know how many networks it "conquered" so far. +just the minimum information to enroll the unit in the database and know how many networks it "conquered" so far, namely: + +- The cryptographic identity of the unit, generated at first boot and used for authentication. +- The output of the `uname -a` command on the unit used to determine the type of hardware. +- The list of networks that the unit collected handshakes of, made of their `BSSID` and `ESSID`. + +Other than for easy unit identification and debugging, this data is collected in order to build rankings, scoreboards and regional statistics. **Like Pokèmon Go, but for WiFi!** If you want to partially opt-out from this feature and have your unit only signal its presence without sending the list of networks, you can put this in your `/etc/pwnagotchi/config.yml` file: @@ -67,7 +73,7 @@ main: report: false # partial opt-out ``` -If you prefer to completely opt-out and also disable signaling: +If you prefer to completely opt-out by disabling signaling: ```yaml main: From 1a13e744b539a9ff49ad411b4c00f463799666e7 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 16:52:23 +0200 Subject: [PATCH 068/346] fix: grid plugin is half opt-in --- docs/configure.md | 14 ++++++++------ pwnagotchi/defaults.yml | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/configure.md b/docs/configure.md index 8c9c6a9..935197f 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -53,27 +53,29 @@ But if you want, you can change `main.lang` to one of the supported languages: ## PwnGRID -By default the `grid` [plugin](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md) is enabled, this means that whenever the unit will detect internet connectivity in manual mode, it'll signal its -presence to the PwnGRID server and periodically send a list of the networks that it has pwned. None of the captured cryptographic material is sent to this server, +By default the `grid` [plugin](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md) is **only partially** enabled, this means that whenever the unit will detect internet connectivity in manual mode, it'll signal its +presence to the PwnGRID server without sending any data. + +It is possible to fully opt-in and also enable the unit to send basic information about the pwned networks. None of the captured cryptographic material is sent to this server, just the minimum information to enroll the unit in the database and know how many networks it "conquered" so far, namely: - The cryptographic identity of the unit, generated at first boot and used for authentication. - The output of the `uname -a` command on the unit used to determine the type of hardware. - The list of networks that the unit collected handshakes of, made of their `BSSID` and `ESSID`. -Other than for easy unit identification and debugging, this data is collected in order to build rankings, scoreboards and regional statistics. **Like Pokèmon Go, but for WiFi!** +Other than for easy unit identification and debugging, this data is collected in order to build drankings, scoreboards and regional statistics. **Like Pokèmon Go, but for WiFi!** -If you want to partially opt-out from this feature and have your unit only signal its presence without sending the list of networks, you can put this in your `/etc/pwnagotchi/config.yml` file: +In order to fully opt-in, you can put this in your `/etc/pwnagotchi/config.yml` file: ```yaml main: plugins: grid: enabled: true - report: false # partial opt-out + report: true # full-opt in ``` -If you prefer to completely opt-out by disabling signaling: +If you prefer to completely opt-out by also disabling signaling: ```yaml main: diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 3f2d871..7d4d92b 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -8,7 +8,7 @@ main: plugins: grid: enabled: true - report: true # report pwned networks + report: false # don't report pwned networks by default! auto-update: enabled: false interval: 1 # every day From f7bf12a0a05dc331ad9cd137a1f062d0df730b59 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 16:55:55 +0200 Subject: [PATCH 069/346] misc: small fix or general refactoring i did not bother commenting --- docs/configure.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/configure.md b/docs/configure.md index 935197f..47fe042 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -54,13 +54,14 @@ But if you want, you can change `main.lang` to one of the supported languages: ## PwnGRID By default the `grid` [plugin](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md) is **only partially** enabled, this means that whenever the unit will detect internet connectivity in manual mode, it'll signal its -presence to the PwnGRID server without sending any data. +presence to the PwnGRID server without sending any data other than: + +- The cryptographic identity of the unit, generated at first boot and used for authentication. +- The output of the `uname -a` command on the unit used to determine the type of hardware. It is possible to fully opt-in and also enable the unit to send basic information about the pwned networks. None of the captured cryptographic material is sent to this server, just the minimum information to enroll the unit in the database and know how many networks it "conquered" so far, namely: -- The cryptographic identity of the unit, generated at first boot and used for authentication. -- The output of the `uname -a` command on the unit used to determine the type of hardware. - The list of networks that the unit collected handshakes of, made of their `BSSID` and `ESSID`. Other than for easy unit identification and debugging, this data is collected in order to build drankings, scoreboards and regional statistics. **Like Pokèmon Go, but for WiFi!** From 52af8391317bfb655b0c934988b6c2a2afbe548b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 17:04:06 +0200 Subject: [PATCH 070/346] new: exclude option for the grid plugin --- docs/configure.md | 15 ++++++++++++++- pwnagotchi/defaults.yml | 2 ++ pwnagotchi/plugins/default/grid.py | 10 +++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/configure.md b/docs/configure.md index 47fe042..dfc33ae 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -76,7 +76,20 @@ main: report: true # full-opt in ``` -If you prefer to completely opt-out by also disabling signaling: +Even if fully opted-in, you can still disable reporting for specific networks, for instance if you don't want your home network to be in the system: + +```yaml +main: + plugins: + grid: + enabled: true + report: true + exclude: + - MyHomeNetwork + - de:ad:be:ef:de:ad # both ESSIDs and BSSIDs are supported +``` + +If instead you prefer to completely opt-out by also disabling signaling: ```yaml main: diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 7d4d92b..330218f 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -9,6 +9,8 @@ main: grid: enabled: true report: false # don't report pwned networks by default! + exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs) + - YourHomeNetworkHere auto-update: enabled: false interval: 1 # every day diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 09ee08a..6c151b3 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -154,7 +154,15 @@ def on_internet_available(ui, keys, config, log): if OPTIONS['report']: for pcap_file in pcap_files: net_id = os.path.basename(pcap_file).replace('.pcap', '') - if net_id not in reported: + do_skip = False + for skip in OPTIONS['exclude']: + skip = skip.lower() + net = net_id.lower() + if skip in net or skip.replace(':', '') in net: + do_skip = True + break + + if net_id not in reported and not do_skip: essid, bssid = parse_pcap(pcap_file) if bssid: if api_report_ap(log, keys, token, essid, bssid): From 7f880698a40a01680928bdd5ded0d214a67672c3 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 18:25:59 +0200 Subject: [PATCH 071/346] docs: moved docs to www.pwnagotchi.ai repo --- README.md | 10 +-- docs/about.md | 44 ---------- docs/configure.md | 126 ---------------------------- docs/dev.md | 60 ------------- docs/faq.md | 208 ---------------------------------------------- docs/hacks.md | 102 ----------------------- docs/index.md | 23 ----- docs/install.md | 60 ------------- docs/plugins.md | 56 ------------- docs/usage.md | 149 --------------------------------- 10 files changed, 1 insertion(+), 837 deletions(-) delete mode 100644 docs/about.md delete mode 100644 docs/configure.md delete mode 100644 docs/dev.md delete mode 100644 docs/faq.md delete mode 100644 docs/hacks.md delete mode 100644 docs/index.md delete mode 100644 docs/install.md delete mode 100644 docs/plugins.md delete mode 100644 docs/usage.md diff --git a/README.md b/README.md index 670ed47..363d7ec 100644 --- a/README.md +++ b/README.md @@ -36,15 +36,7 @@ For hackers to learn reinforcement learning, WiFi networking, and have an excuse **IMPORTANT NOTE:** If you'd like to alphatest Pwnagotchi and are trying to get yours up and running while the project is still very unstable, please understand that the documentation here may not reflect what is currently implemented. If you have questions, ask the community of alphatesters in the [official Pwnagotchi Slack](https://pwnagotchi.herokuapp.com). The Pwnagotchi dev team is entirely focused on the v1.0 release and will NOT be providing support for alphatesters trying to get their Pwnagotchis working in the meantime. All technical support during this period of development is being provided by your fellow alphatesters in the Slack (thanks, everybody! :heart:). ---- -- [About the Project](https://github.com/evilsocket/pwnagotchi/blob/master/docs/about.md) -- [How to Install](https://github.com/evilsocket/pwnagotchi/blob/master/docs/install.md) -- [Configuration](https://github.com/evilsocket/pwnagotchi/blob/master/docs/configure.md) -- [Usage](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md) -- [Plugins](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md) -- [Development](https://github.com/evilsocket/pwnagotchi/blob/master/docs/dev.md) -- [FAQ](https://github.com/evilsocket/pwnagotchi/blob/master/docs/faq.md) -- [Community Hacks](https://github.com/evilsocket/pwnagotchi/blob/master/docs/hacks.md) +https://www.pwnagotchi.ai ## Links diff --git a/docs/about.md b/docs/about.md deleted file mode 100644 index 4777c21..0000000 --- a/docs/about.md +++ /dev/null @@ -1,44 +0,0 @@ -# About the Project - -[Pwnagotchi](https://twitter.com/pwnagotchi) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment in order to maximize the WPA key material it captures (either passively, or by performing deauthentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), -full and half WPA handshakes. - - - -Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning based "AI" *(yawn)*, Pwnagotchi tunes [its own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml#L54) over time to **get better at pwning WiFi things** in the environments you expose it to. - -**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi actually learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but **definitely listen to your pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :) - -Multiple units within close physical proximity can "talk" to each other, advertising their own presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage. - - - -[Depending on the status of the unit](), several states and states transitions are configurable and represented on the display as different moods, expressions and sentences. Pwnagotchi speaks [many languages](https://github.com/evilsocket/pwnagotchi/blob/master/docs/configure.md#configuration), too! - -Of course, it is possible to run your Pwnagotchi with the AI disabled (configurable in `config.yml`). Why might you want to do this? Perhaps you simply want to use your own fixed parameters (instead of letting the AI decide for you), or maybe you want to save battery and CPU cycles, or maybe it's just you have strong concerns about aiding and abetting baby Skynet. Whatever your particular reasons may be: an AI-disabled Pwnagotchi is still a simple and very effective automated deauther, WPA handshake sniffer, and portable [bettercap](https://www.bettercap.org/) + [webui](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#bettercaps-web-ui) dedicated hardware. - -## WiFi Handshakes 101 - -In order to understand why it's valuable to have an AI that wants to eat handshakes, it's helpful to understand a little bit about how handshakes are used in the WPA/WPA2 wireless protocol. - -Before a client device that's connecting to a wireless access point—say, for instance, your phone connecting to your home WiFi network—is able to securely transmit to and receive data from that access point, a process called the **4-Way Handshake** needs to happen in order for the WPA encryption keys to be generated. This process consists of the exchange of four packets (hence the "4" in "4-Way") between the client device and the AP; these are used to derive session keys from the access point's WiFi password. Once the packets are successfully exchanged and the keys have been generated, the client device is authenticated and can start sending and receiving data packets to and from the wireless AP that are secured by encryption. - -<p align="center"> -<img src="https://i.imgur.com/nI8IE6a.png"/> -<br/> -<small>image taken from <a target="_blank" href="https://www.wifi-professionals.com/2019/01/4-way-handshake">wifi-professionals.com</a></small> -</p> - -So...what's the catch? Well, these four packets can easily be "sniffed" by an attacker monitoring nearby (say, with a Pwnagotchi :innocent:). And once recorded, that attacker can use [dictionary and/or bruteforce attacks](https://hashcat.net/wiki/doku.php?id=cracking_wpawpa2) to crack the handshakes and recover the original WiFi key. In fact, **successful recovery of the WiFi key doesn't necessarily even need all four packets!** A half-handshake (containing only two of the four packets) can be cracked, too—and in some *(most)* cases, just [a single packet is enough](https://hashcat.net/forum/thread-7717-post-41447.html), *even without clients.* - -In order to ~~eat~~ collect as many of these crackable handshake packets as possible, Pwnagotchi uses two strategies: - -- **Deauthenticating the client stations it detects.** A deauthenticated device must reauthenticate to its access point by re-performing the 4-Way Handshake with the AP, thereby giving Pwnagotchi another chance to sniff the handshake packets and collect more crackable material. -- **Send association frames directly to the access points themselves** -to try to force them to [leak the PMKID](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/). - -All the handshakes captured this way are saved into `.pcap` files on Pwnagotchi's filesystem. Each PCAP file that Pwnagotchi generates is organized according to access point; one PCAP will contain all the handshakes that Pwnagotchi has ever captured for that particular AP. These handshakes can later be [cracked with proper hardware and software](https://hashcat.net/wiki/doku.php?id=cracking_wpawpa2). - -## License - -`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It's released under the GPL3 license. diff --git a/docs/configure.md b/docs/configure.md deleted file mode 100644 index dfc33ae..0000000 --- a/docs/configure.md +++ /dev/null @@ -1,126 +0,0 @@ -# Configuration - -Once you've [written the image file onto the SD card](https://github.com/evilsocket/pwnagotchi/blob/master/docs/install.md#flashing-an-image), there're a few steps you'll have to follow in order to configure your new Pwnagotchi properly. - -## Connect to your Pwnagotchi - -1. First, start with connecting the USB cable to the data port of the Raspberry Pi and the RPi to your computer. -2. After a few seconds, the board will boot and you will see a new Ethernet interface on your host computer. -3. You'll need to configure it with a static IP address: - - IP: `10.0.0.1` - - Netmask: `255.255.255.0` - - Gateway: `10.0.0.1` - - DNS (if required): `8.8.8.8` (or whatever) - -4. If everything's been configured properly, you will now be able to `ping` both `10.0.0.2` or `pwnagotchi.local` - * If you have already customized the hostname of your Pwnagotchi, `pwnagotchi.local` won't work. Instead, try *your unit's hostname* + `.local`. - -5. **Congratulations!** You can now connect to your unit using SSH: - -```bash -ssh pi@10.0.0.2 -``` -##### About your SSH connection -The default password is `raspberry`; you should change it as soon as you log in for the first time by issuing the `passwd` command and selecting a new and more complex passphrase. - -If you want to login directly without entering a password (recommended!), copy your SSH public key to the unit's authorized keys: - -```bash -ssh-copy-id -i ~/.ssh/id_rsa.pub pi@10.0.0.2 -``` - -## Give your Pwnagotchi a name - -You can now set a new name for your unit by [changing the hostname](https://geek-university.com/raspberry-pi/change-raspberry-pis-hostname/)! - -Open the `/etc/pwnagotchi/config.yml` file (either via SSH or by directly editing the SD card contents from a computer) that will override the [default configuration](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml) with your custom values. - -## Choose your Pwnagotchi's language - -Pwnagotchi displays its UI in English by default, but it can speak several other languages! If you're fine with English, you don't need to do anything special. - -But if you want, you can change `main.lang` to one of the supported languages: - -- **English** *(default)* -- German -- Dutch -- Greek -- Macedonian -- Italian -- French -- Russian -- Swedish - -## PwnGRID - -By default the `grid` [plugin](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md) is **only partially** enabled, this means that whenever the unit will detect internet connectivity in manual mode, it'll signal its -presence to the PwnGRID server without sending any data other than: - -- The cryptographic identity of the unit, generated at first boot and used for authentication. -- The output of the `uname -a` command on the unit used to determine the type of hardware. - -It is possible to fully opt-in and also enable the unit to send basic information about the pwned networks. None of the captured cryptographic material is sent to this server, -just the minimum information to enroll the unit in the database and know how many networks it "conquered" so far, namely: - -- The list of networks that the unit collected handshakes of, made of their `BSSID` and `ESSID`. - -Other than for easy unit identification and debugging, this data is collected in order to build drankings, scoreboards and regional statistics. **Like Pokèmon Go, but for WiFi!** - -In order to fully opt-in, you can put this in your `/etc/pwnagotchi/config.yml` file: - -```yaml -main: - plugins: - grid: - enabled: true - report: true # full-opt in -``` - -Even if fully opted-in, you can still disable reporting for specific networks, for instance if you don't want your home network to be in the system: - -```yaml -main: - plugins: - grid: - enabled: true - report: true - exclude: - - MyHomeNetwork - - de:ad:be:ef:de:ad # both ESSIDs and BSSIDs are supported -``` - -If instead you prefer to completely opt-out by also disabling signaling: - -```yaml -main: - plugins: - grid: - enabled: false # full opt-out - report: false -``` - -## Display Selection - -**Set the type of display you want to use via `ui.display.type`.** -If your display does not work after changing this setting, you might need to completely remove power from the Raspberry Pi and make a clean boot. - -**You can configure the refresh interval of the display via `ui.fps`.** We recommend using a slow refresh rate to avoid shortening the lifetime of your e-ink display. The default value is `0`, which will *only* refresh when changes are made to the screen. - -## Host Connection Share - -Want to be able to update your Pwnagotchi and access things from the internet on it? *Sure you do!* - -1. Connect to the Pwnagotchi unit via `usb0` (A.K.A., using the data port). -2. Run the appropriate connection sharing script to bring the interface up on your end and share internet connectivity from another interface: - -OS | Script Location -------|--------------------------- -Linux | `scripts/linux_connection_share.sh` -Mac OS X | `scripts/macos_connection_share.sh` -Windows | `scripts/win_connection_share.ps1` - -## Troubleshooting - -##### If your network connection keeps flapping on your device connecting to your Pwnagotchi. -* Check if `usb0` (or equivalent) device is being controlled by NetworkManager. -* You can check this via `nmcli dev status`. diff --git a/docs/dev.md b/docs/dev.md deleted file mode 100644 index 534b398..0000000 --- a/docs/dev.md +++ /dev/null @@ -1,60 +0,0 @@ -# Software - -- Raspbian + [nexmon patches](https://re4son-kernel.com/re4son-pi-kernel/) for monitor mode, or any Linux with a monitor mode enabled interface (if you tune config.yml). - -**Do not try with Kali on the Raspberry Pi 0 W, it is compiled without hardware floating point support and TensorFlow is simply not available for it, use Raspbian.** - -## Creating an Image - -You can use the `scripts/create_sibling.sh` script to create an - ready to flash - rasbian image with pwnagotchi. - -```shell -usage: ./scripts/create_sibling.sh [OPTIONS] - - Options: - -n <name> # Name of the pwnagotchi (default: pwnagotchi) - -i <file> # Provide the path of an already downloaded raspbian image - -o <file> # Name of the img-file (default: pwnagotchi.img) - -s <size> # Size which should be added to second partition (in Gigabyte) (default: 4) - -v <version> # Version of raspbian (Supported: latest; default: latest) - -p # Only run provisioning (assumes the image is already mounted) - -d # Only run dependencies checks - -h # Show this help -``` - -#### Known Issues - -`GLib-ERROR **: 20:50:46.361: getauxval () failed: No such file or directory` - -- Affected DEB & Versions: QEMU <= 2.11 -- Fix: Upgrade QEMU to >= 3.1 -- Bug Link: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=923289 - -## Adding a Language - -If you want to add a language use the `language.sh` script. If you want to add for example the language **italian** you would type: - -```shell -./scripts/language.sh add it -# Now make your changes to the file -# sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po -./scripts/language.sh compile it -# DONE -``` - -If you changed the `voice.py`- File, the translations need an update. Do it like this: - -```shell -./scripts/language.sh update it -# Now make your changes to the file (changed lines are marked with "fuzzy") -# sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po -./scripts/language.sh compile it -# DONE -``` - -Now you can use the `preview.py`-script to preview the changes: - -```shell -./scripts/preview.py --lang it --display ws1 ws2 inky --output preview.png -# Now open preview.png -``` diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index 348e039..0000000 --- a/docs/faq.md +++ /dev/null @@ -1,208 +0,0 @@ -# FAQ -<!-- IF YOU CHANGE ANY CHARACTERS IN AN FAQ QUESTION, YOU MUST ALSO CHANGE THE TABLE OF CONTENTS ENTRY &&&AND&&& THE ANCHOR LINK TEXT, OR ELSE THE LINKS WILL BREAK. --> - -[**What can Pwnagotchi actually do?**](#what-can-pwnagotchi-actually-do) - -* [Why does Pwnagotchi eat handshakes?](#why-does-pwnagotchi-eat-handshakes) -* [What kinds of handshakes does Pwnagotchi eat?](#what-kinds-of-handshakes-does-pwnagotchi-eat) -* [Does Pwnagotchi support both 2.4 GHz and 5.0 GHz?](#does-pwnagotchi-support-both-24-ghz-and-50-ghz) -* [Just how politely *does* Pwnagotchi deauth?](#just-how-politely-does-pwnagotchi-deauth) -* [Hey, I want to learn more about how Pwnagotchi actually works.](#hey-i-want-to-learn-more-about-how-pwnagotchi-actually-works) -* [How is Pwnagotchi using bettercap?](#how-is-pwnagotchi-using-bettercap) -* [What happens if I run a Pwnagotchi without the AI enabled?](#what-happens-if-i-run-a-pwnagotchi-without-the-ai-enabled) -* [How easy is it to hack Pwnagotchi to add additional functionality?](#how-easy-is-it-to-hack-pwnagotchi-to-add-additional-functionality) - -[**Building Your Pwnagotchi**](#building-your-pwnagotchi) - -* [What hardware do I need to create my very own Pwnagotchi?](#what-hardware-do-i-need-to-create-my-very-own-pwnagotchi) -* [Is there any way to see my Pwnagotchi's face even if I don't have a display?](#is-there-any-way-to-see-my-pwnagotchis-face-even-if-i-dont-have-a-display) -* [How do I attach the screen to the Raspberry Pi?](#how-do-i-attach-the-screen-to-the-raspberry-pi) -* [I love my new Pwnagotchi, but it kinda looks like a bomb. Where can I find a decent case?](#i-love-my-new-pwnagotchi-but-it-kinda-looks-like-a-bomb-where-can-i-find-a-decent-case) -* [Why does everybody use e-ink screens for their Pwnagotchis?](#why-does-everybody-use-e-ink-screens-for-their-pwnagotchis) -* [How do I connect to my Pwnagotchi?](#how-do-i-connect-to-my-pwnagotchi) - -[**Customizing Your Pwnagotchi**](#customizing-your-pwnagotchi) - -* [How do I change my Pwnagotchi's name?](#how-do-i-change-my-pwnagotchis-name) -* [I want to change the faces. What do I hack?](#i-want-to-change-the-faces-what-do-i-hack) -* [I want my Pwnagotchi to speak a different language. Can it?](#i-want-my-pwnagotchi-to-speak-a-different-language-can-it) -* [I have a great idea for something cool I wish Pwnagotchi could do!](#i-have-a-great-idea-for-something-cool-i-wish-pwnagotchi-could-do) -* [Are there any unofficial community "hacks" for further customizing my Pwnagotchi?](#are-there-any-unofficial-community-hacks-for-further-customizing-my-pwnagotchi) - -[**Getting to Know Your Pwnagotchi**](#getting-to-know-your-pwnagotchi) - -* [What does everything on the screen mean?](#what-does-everything-on-the-screen-mean) -* [How do I whitelist my home network so Pwnagotchi stops pwning me?](#how-do-i-whitelist-my-home-network-so-pwnagotchi-stops-pwning-me) -* [What is MANU mode? What is AUTO mode?](#what-is-manu-mode-what-is-auto-mode) -* [Why does the AI take 30 minutes to load?](#why-does-the-ai-take-30-minutes-to-load) -* [What is Pwnagotchi doing while it's waiting for the AI to load?](#what-is-pwnagotchi-doing-while-its-waiting-for-the-ai-to-load) -* [How do I know when the AI is running?](#how-do-i-know-when-the-ai-is-running) -* [Where does Pwnagotchi store all the handshakes it's eaten?](#where-does-pwnagotchi-store-all-the-handshakes-its-eaten) -* [What happens when my Pwnagotchi meets another Pwnagotchi?](#what-happens-when-my-pwnagotchi-meets-another-pwnagotchi) - -[**Caring for Your Pwnagotchi**](#caring-for-your-pwnagotchi) - -* [What do all my Pwnagotchi's faces mean?](#what-do-all-my-pwnagotchis-faces-mean) -* [How do I feed my Pwnagotchi?](#how-do-i-feed-my-pwnagotchi) -* [Oh no, my Pwnagotchi is sad and bored! How do I entertain it?!](#oh-no-my-pwnagotchi-is-sad-and-bored-how-do-i-entertain-it) -* [How do I update my Pwnagotchi?](#how-do-i-update-my-pwnagotchi) -* [I'm extremely emotionally-attached to my Pwnagotchi. How can I back up its brain?](#im-extremely-emotionally-attached-to-my-pwnagotchi-how-can-i-back-up-its-brain) -* [How do I turn off my Pwnagotchi?](#how-do-i-turn-off-my-pwnagotchi) -* [Uh. So. What do I do with all these handshakes my Pwnagotchi has been eating?](#uh-so-what-do-i-do-with-all-these-handshakes-my-pwnagotchi-has-been-eating) - -[**Known Quirks**](#known-quirks) - -* [My Pwnagotchi's log timestamps seem...unreliable. Huh?](#my-pwnagotchis-log-timestamps-seemunreliable-huh) -* [Help! My Pwnagotchi's SD card got corrupted. What gives?](#help-my-pwnagotchis-sd-card-got-corrupted-what-gives) - ---- - -## **What can Pwnagotchi actually do?** -### Why does Pwnagotchi eat handshakes? -lorem ipsum dolor sit amet - ---- -### What kinds of handshakes does Pwnagotchi eat? -lorem ipsum dolor sit amet - ---- -### Does Pwnagotchi support both 2.4 GHz and 5.0 GHz? -lorem ipsum dolor sit amet - ---- -### Just how politely *does* Pwnagotchi deauth? -lorem ipsum dolor sit amet - ---- -### Hey, I want to learn more about how Pwnagotchi actually works. -lorem ipsum dolor sit amet - ---- -### How is Pwnagotchi using bettercap? -lorem ipsum dolor sit amet - ---- -### What happens if I run a Pwnagotchi without the AI enabled? -lorem ipsum dolor sit amet - ---- -### How easy is it to hack Pwnagotchi to add additional functionality? -lorem ipsum dolor sit amet - ---- - -## **Building Your Pwnagotchi** -### What hardware do I need to create my very own Pwnagotchi? -lorem ipsum dolor sit amet - ---- -### Is there any way to see my Pwnagotchi's face even if I don't have a display? -lorem ipsum dolor sit amet - ---- -### How do I attach the screen to the Raspberry Pi? -lorem ipsum dolor sit amet - ---- -### I love my new Pwnagotchi, but it kinda looks like a bomb. Where can I find a decent case? -lorem ipsum dolor sit amet - ---- -### Why does everybody use e-ink screens for their Pwnagotchis? -lorem ipsum dolor sit amet - ---- -### How do I connect to my Pwnagotchi? -lorem ipsum dolor sit amet - ---------------------------------------------------------------------------------------------------------------- -## **Customizing Your Pwnagotchi** -### How do I change my Pwnagotchi's name? -lorem ipsum dolor sit amet - ---- -### I want to change the faces. What do I hack? -lorem ipsum dolor sit amet - ---- -### I want my Pwnagotchi to speak a different language. Can it? -lorem ipsum dolor sit amet - ---- -### I have a great idea for something cool I wish Pwnagotchi could do! -lorem ipsum dolor sit amet - ---- -### Are there any unofficial community "hacks" for further customizing my Pwnagotchi? -lorem ipsum dolor sit amet - ---------------------------------------------------------------------------------------------------------------- -## **Getting to Know Your Pwnagotchi** -### What does everything on the screen mean? -lorem ipsum dolor sit amet - ---- -### How do I whitelist my home network so Pwnagotchi stops pwning me? -lorem ipsum dolor sit amet - ---- -### What is MANU mode? What is AUTO mode? -lorem ipsum dolor sit amet - ---- -### Why does the AI take 30 minutes to load? -lorem ipsum dolor sit amet - ---- -### What is Pwnagotchi doing while it's waiting for the AI to load? -lorem ipsum dolor sit amet - ---- -### How do I know when the AI is running? -lorem ipsum dolor sit amet - ---- -### Where does Pwnagotchi store all the handshakes it's eaten? -lorem ipsum dolor sit amet - ---- -### What happens when my Pwnagotchi meets another Pwnagotchi? -lorem ipsum dolor sit amet - ---------------------------------------------------------------------------------------------------------------- -## **Caring for Your Pwnagotchi** -### What do all my Pwnagotchi's faces mean? -lorem ipsum dolor sit amet - ---- -### How do I feed my Pwnagotchi? -lorem ipsum dolor sit amet - ---- -### Oh no, my Pwnagotchi is sad and bored! How do I entertain it?! -lorem ipsum dolor sit amet - ---- -### How do I update my Pwnagotchi? -lorem ipsum dolor sit amet - ---- -### I'm extremely emotionally-attached to my Pwnagotchi. How can I back up its brain? -lorem ipsum dolor sit amet - ---- -### How do I turn off my Pwnagotchi? -lorem ipsum dolor sit amet - ---- -### Uh. So. What do I do with all these handshakes my Pwnagotchi has been eating? -lorem ipsum dolor sit amet - ---------------------------------------------------------------------------------------------------------------- -## **Known Quirks** -### My Pwnagotchi's log timestamps seem...unreliable. Huh? -lorem ipsum dolor sit amet - ---- -### Help! My Pwnagotchi's SD card got corrupted. What gives? -lorem ipsum dolor sit amet diff --git a/docs/hacks.md b/docs/hacks.md deleted file mode 100644 index 6f58c3f..0000000 --- a/docs/hacks.md +++ /dev/null @@ -1,102 +0,0 @@ -# Unofficial Hacks ---- -**IMPORTANT DISCLAIMER:** The information provided on this page is NOT officially supported by the Pwnagotchi development team. These are unofficial "hacks" that users have worked out while customizing their units and decided to document for anybody else who might want to do something similar. - -- **Please do NOT open issues if you cannot get something described in this document to work.** -- It (almost) goes without saying, but obviously: **we are NOT responsible if you break your hardware by following any instructions documented here. Use this information at your own risk.** - ---- -If you test one of these hacks yourself and it still works, it's extra nice if you update the **Last Tested On** table and note any minor adjustments you may have had to make to the instructions to make it work with your particular Pwnagotchi setup. :heart: - - -## Screens -### Waveshare 3.5" SPI TFT screen - -Last tested on | Pwnagotchi version | Working? | Reference ----------------|--------------------|----------|-----------| -2019 October 3 | Unknown | :white_check_mark: | ([link](https://github.com/evilsocket/pwnagotchi/issues/124#issue-502346040)) - -Some of this guide will work with other framebuffer-based displays. - -- First: SSH into your Pwnagotchi, and give it some internet! - - Don't forget to check your default gateway and `apt-get update`. -- Follow the guide here: [www.waveshare.com/wiki/3.5inch_RPi_LCD_(A)#Method_1._Driver_installation](https://www.waveshare.com/wiki/3.5inch_RPi_LCD_(A)#Method_1._Driver_installation) - - At the step with `./LCD35-show`, add `lite` to the command prompt (e.g., `./LCD35-show lite`). -- Reboot. -- As root, make three symlinks: - - `cd ~` - - `ln -s pwnagotchi.png pwnagotchi_1.png` - - `ln -s pwnagotchi.png pwnagotchi_2.png` - - `ln -s pwnagotchi.png pwnagotchi_3.png` -- `apt install fbi` -- Change display type to `inky` in `config.yml` -- Add `modules-load=dwc2,g_ether` to your kernel command line (`/boot/cmdline.txt`) or it will break! -- Also must add `dtoverlay=dwc2` to the bottom of (`/boot/config.txt`) -- Edit `/etc/rc.local` and add: `fbi -T 1 -a -noverbose -t 15 -cachemem 0 /root/pwnagotchi_1.png /root/pwnagotchi_2.png /root/pwnagotchi_3.png &` -- Reboot. - -And you should be good! - ---- -### Pwnagotchi face via Bluetooth -Last tested on | Pwnagotchi version | Working? | Reference ----------------|--------------------|----------|-----------| -2019 October 6 | Unknown | :white_check_mark: | on Android -2019 October 6 | Unknown | :white_check_mark: | on iPad iOS 9.3.5 - -A way to view your Pwnagotchi's ~~face~~ UI wirelessly via Bluetooth on a separate device. Refresh rate is the same as the e-ink display (every few seconds). This is NOT Bluetooth tethering; this is only Bluetooth as a server on pi side; you connect the Bluetooth and get a DHCP IP address and that's it. This hack cannot leverage the data connection. - -Contributed by Systemic in the Slack. - -##### 1. First Step -- Comment out the Bluetooth disable line from `/boot/config.txt` : `#dtoverlay=pi3-disable-bt` -- Change `/root/pwnagotchi/config.yml` to have `0.0.0.0` instead of `10.0.0.2` to listen as well on Bluetooth. -- Then launch the following commands: - -##### 2. Install required packages. - -```sudo apt-get install bluez bluez-tools bridge-utils dnsmasq``` - -##### 3. Configure Bluetooth and start it. -```sudo modprobe bnep -sudo brctl addbr pan0 -sudo brctl setfd pan0 0 -sudo brctl stp pan0 off -sudo ifconfig pan0 172.26.0.1 netmask 255.255.255.0 -sudo ip link set pan0 up -``` - -```cat <<- EOF > /tmp/dnsmasq_bt.conf``` - -```bind-interfaces -port=0 -interface=pan0 -listen-address=172.26.0.1 -dhcp-range=172.26.0.2,172.26.0.100,255.255.255.0,5m -dhcp-leasefile=/tmp/dnsmasq_bt.leases -dhcp-authoritative -log-dhcp -``` - -```EOF``` - -```sudo dnsmasq -C /tmp/dnsmasq_bt.conf -sudo bt-agent -c NoInputNoOutput& -sudo bt-adapter -a hci0 --set Discoverable 1 -sudo bt-adapter -a hci0 --set DiscoverableTimeout 0 -sudo bt-adapter -a hci0 --set Pairable 1 -sudo bt-adapter -a hci0 --set PairableTimeout 0 -sudo bt-network -a hci0 -s nap pan0 & -``` - -##### 4. Finally: on your phone, you have to disable all existing interfaces: - -- Shutdown WiFi. -- Shutdown mobile data. -- Connect to the newly available Bluetooth device (which has the name of your Pwnagotchi). - - Once connected, you can test: `http://172.26.0.1:8080` -- You can also install bettercap's UI (`sudo bettercap` then `ui.update`) - - You'll need to change the http caplets to change `127.0.0.1` to `0.0.0.0`. -- You can connect to the shell with a terminal emulator ... - -Happy tweaking. diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index b998af0..0000000 --- a/docs/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# Documentation - -- [About the Project](https://github.com/evilsocket/pwnagotchi/blob/master/docs/about.md) -- [How to Install](https://github.com/evilsocket/pwnagotchi/blob/master/docs/install.md) -- [Configuration](https://github.com/evilsocket/pwnagotchi/blob/master/docs/configure.md) -- [Usage](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md) -- [Plugins](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md) -- [Development](https://github.com/evilsocket/pwnagotchi/blob/master/docs/dev.md) -- [FAQ](https://github.com/evilsocket/pwnagotchi/blob/master/docs/faq.md) -- [Community Hacks](https://github.com/evilsocket/pwnagotchi/blob/master/docs/hacks.md) - -## Links - - | Official Links ----------|------- -Slack | [pwnagotchi.slack.com](https://pwnagotchi.herokuapp.com) -Twitter | [@pwnagotchi](https://twitter.com/pwnagotchi) -Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/) -Website | [pwnagotchi.ai](https://pwnagotchi.ai/) - -## License - -`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It's released under the GPL3 license. diff --git a/docs/install.md b/docs/install.md deleted file mode 100644 index 9f143e8..0000000 --- a/docs/install.md +++ /dev/null @@ -1,60 +0,0 @@ -# Installation - -The project has been developed to run on a Raspberry Pi 0 W configured as an [USB Ethernet gadget](https://learn.adafruit.com/turning-your-raspberry-pi-zero-into-a-usb-gadget/ethernet-gadget) device in order to connect to it via USB. However, given the proper configuration tweaks, any GNU/Linux computer with a WiFi interface that supports monitor mode could be used. - -**An important note about the AI:** a network trained with a specific WiFi interface will ONLY work with another interface if it supports the *exact same* WiFi channels of the first one. For instance, you CANNOT use a neural network trained on a Raspberry Pi Zero W (that only supports 2.4Ghz channels) with a 5Ghz antenna; you will need to train one from scratch for those channels. - -## Required Hardware - -- [Raspberry Pi Zero W](https://www.raspberrypi.org/products/raspberry-pi-zero-w/).† -- A micro SD card, 8GB recommended, **preferably of good quality and speed**. -- A decent power bank (with 1500 mAh you get ~2 hours with AI on). -- One of the supported displays (optional). - -† Many users have gotten Pwnagotchi running on other types of Raspberry Pi, but the RPi0W is the "vanilla" hardware config for Pwnagotchi. - -### Display - -The display is an optional component as the UI is also rendered via a web interface available via the USB cable. If you connect to `usb0` (by using the data port on the unit) and point your browser to the web ui (see `config.yml`), your unit can work in "headless mode". - -If, instead, you want to fully enjoy walking around and literally looking at your unit's face, the supported display models are: - -- [Waveshare eInk Display (both V1 and V2)](https://www.waveshare.com/2.13inch-e-paper-hat.htm) - - [Product comparison](https://www.waveshare.com/4.3inch-e-paper.htm) (scroll down to `Selection Guide`) - - [GitHub](https://github.com/waveshare/e-Paper/tree/master/RaspberryPi%26JetsonNano/python) -- [Pimoroni Inky pHAT](https://shop.pimoroni.com/products/inky-phat) - - [Product page](https://shop.pimoroni.com/products/inky-phat) - - [GitHub](https://github.com/pimoroni/inky) -- [PaPiRus eInk Screen](https://uk.pi-supply.com/products/papirus-zero-epaper-screen-phat-pi-zero) - -Needless to say, we are always happy to receive pull requests adding support for new models. - -**One thing to note:** Not all displays are created equally! TFT displays, for example, work similar to an HDMI display, and they are NOT supported. Currently, all the officially-supported displays are I2C displays. If you are still interested in using unsupported displays, you may be able to find a community-submitted hack in the [Screens](https://github.com/evilsocket/pwnagotchi/blob/master/docs/hacks.md#screens) section of the [Hacks](https://github.com/evilsocket/pwnagotchi/blob/master/docs/hacks.md) page. We are not responsible for anything you break by trying to use any display that is not officially supported by the development team! - -#### Color vs. Black & White displays - -Some of the supported displays support both **Black & White** and **Colored** versions. One common question whether there are meaningful differences between the two. There are: -- Color displays have a much slower refresh rate. In some cases, it can take up to 15 seconds; if slow refresh rates are something that you want to avoid, we recommend you use B&W displays. -- The 3-color 2.13" Waveshare displays have a slightly smaller pixel layout (104x212) compared to their B&W counterparts (122x250). - -#### Recommendations -- Avoid the Waveshare eInk **3-color** display. The refresh time is 15 seconds. -- Avoid the Pimoroni Inky pHAT **v1.** They're discontinued due to a faulty hardware part source used in manufacturing that resulted in high failure rates. -- Many users seem to prefer the Inky pHATs. There are two primary reasons: - - The Inkys feature better documentation and SDK support. - - Many Waveshare resellers do not disclose the version of the Waveshare boards they are selling (v1 vs v2), and the type they are selling can be fairly unclear (i.e., Waveshare 2.13 vs 2.13 B vs. 2.13C, and so on.) - -## Flashing an Image - -The easiest way to create a new Pwnagotchi is downloading the latest stable image from [our release page](https://github.com/evilsocket/pwnagotchi/releases) and write it to your SD card. You will need to use an image writing tool to install the image you have downloaded on your SD card. - -[balenaEtcher](https://www.balena.io/etcher/) is a graphical SD card writing tool that works on Mac OS, Linux and Windows, and is the easiest option for most users. balenaEtcher also supports writing images directly from the zip file, without any unzipping required. To write your image with balenaEtcher: - -- Download the latest [Pwnagotchi .img file](https://github.com/evilsocket/pwnagotchi/releases). -- Download [balenaEtcher](https://www.balena.io/etcher/) and install it. -- Connect an SD card reader with the SD card inside. -- Open balenaEtcher and select from your hard drive the Raspberry Pi .img or .zip file you wish to write to the SD card. -- Select the SD card you wish to write your image to. -- Review your selections and click 'Flash!' to begin writing data to the SD card. - -Your SD card is now ready for the first boot! diff --git a/docs/plugins.md b/docs/plugins.md deleted file mode 100644 index 0038df9..0000000 --- a/docs/plugins.md +++ /dev/null @@ -1,56 +0,0 @@ -# Plugins - -Pwnagotchi has a simple plugins system that you can use to customize your unit and its behaviour. You can place your plugins anywhere -as python files and then edit the `config.yml` file (`main.plugins` value) to point to their containing folder. Check the [plugins folder](https://github.com/evilsocket/pwnagotchi/tree/master/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/) for a list of default plugins and all the callbacks that you can define for your own customizations. - -Here's as an example the GPS plugin: - -```python -__author__ = 'evilsocket@gmail.com' -__version__ = '1.0.0' -__name__ = 'gps' -__license__ = 'GPL3' -__description__ = 'Save GPS coordinates whenever an handshake is captured.' -__enabled__ = True # set to false if you just don't use GPS - -import core -import json -import os - -device = '/dev/ttyUSB0' -speed = 19200 -running = False - - -def on_loaded(): - logging.info("GPS plugin loaded for %s" % device) - - -def on_ready(agent): - global running - - if os.path.exists(device): - logging.info("enabling GPS bettercap's module for %s" % device) - try: - agent.run('gps off') - except: - pass - - agent.run('set gps.device %s' % device) - agent.run('set gps.speed %d' % speed) - agent.run('gps on') - running = True - else: - logging.info("no GPS detected") - - -def on_handshake(agent, filename, access_point, client_station): - if running: - info = agent.session() - gps = info['gps'] - gps_filename = filename.replace('.pcap', '.gps.json') - - logging.info("saving GPS to %s (%s)" % (gps_filename, gps)) - with open(gps_filename, 'w+t') as fp: - json.dump(gps, fp) -``` diff --git a/docs/usage.md b/docs/usage.md deleted file mode 100644 index fff0fb1..0000000 --- a/docs/usage.md +++ /dev/null @@ -1,149 +0,0 @@ -# Usage - -## User Interface - -The UI is available either via display if installed, or via http://pwnagotchi.local:8080/ if you connect to the unit via `usb0` and set a static address on the network interface (change `pwnagotchi` with the hostname of your unit). - - - -* **CH**: Current channel the unit is operating on or `*` when hopping on all channels. -* **APS**: Number of access points on the current channel and total visible access points. -* **UP**: Time since the unit has been activated. -* **PWND**: Number of handshakes captured in this session and number of unique networks we own at least one handshake of, from the beginning. -* **MODE**: - * **AUTO:** This indicates that the Pwnagotchi algorithm is running in AUTOMATIC mode, with AI disabled (or still loading); it disappears once the AI dependencies have been bootstrapped and the neural network has finished loading. - * **MANU:** This appears when the unit is running in MANUAL mode. -* **FRIEND:** If another unit is nearby, its presence will be indicated here. If more than one unit is nearby, only one—whichever has the stronger signal strength—will be displayed. - -## Training the AI - -At its core Pwnagotchi is a very simple creature: we could summarize its main algorithm as: - -```python -# main loop -while True: - # ask bettercap for all visible access points and their clients - aps = get_all_visible_access_points() - # loop each AP - for ap in aps: - # send an association frame in order to grab the PMKID - send_assoc(ap) - # loop each client station of the AP - for client in ap.clients: - # deauthenticate the client to get its half or full handshake - deauthenticate(client) - - wait_for_loot() -``` - -Despite its simplicity, this logic is controlled by several parameters that regulate the wait times, the timeouts, on which channels to hop and so on. - -From `config.yml`: - -```yaml -personality: - # advertise our presence - advertise: true - # perform a deauthentication attack to client stations in order to get full or half handshakes - deauth: true - # send association frames to APs in order to get the PMKID - associate: true - # list of channels to recon on, or empty for all channels - channels: [] - # minimum WiFi signal strength in dBm - min_rssi: -200 - # number of seconds for wifi.ap.ttl - ap_ttl: 120 - # number of seconds for wifi.sta.ttl - sta_ttl: 300 - # time in seconds to wait during channel recon - recon_time: 30 - # number of inactive epochs after which recon_time gets multiplied by recon_inactive_multiplier - max_inactive_scale: 2 - # if more than max_inactive_scale epochs are inactive, recon_time *= recon_inactive_multiplier - recon_inactive_multiplier: 2 - # time in seconds to wait during channel hopping if activity has been performed - hop_recon_time: 10 - # time in seconds to wait during channel hopping if no activity has been performed - min_recon_time: 5 - # maximum amount of deauths/associations per BSSID per session - max_interactions: 3 - # maximum amount of misses before considering the data stale and triggering a new recon - max_misses_for_recon: 5 - # number of active epochs that triggers the excited state - excited_num_epochs: 10 - # number of inactive epochs that triggers the bored state - bored_num_epochs: 15 - # number of inactive epochs that triggers the sad state - sad_num_epochs: 25 -``` - -There is no optimal set of parameters for every situation: when the unit is moving (during a walk for instance) smaller timeouts and RSSI thresholds might be preferred in order to quickly remove routers that are not in range anymore, while when stationary in high density areas (like an office) other parameters might be better. The role of the AI is to observe what's going on at the WiFi level, and adjust those parameters in order to maximize the cumulative reward of that loop / epoch. - -## Reward Function - -After each iteration of the main loop (an `epoch`), the reward, a score that represents how well the parameters performed, is computed as (an excerpt from `pwnagotchi/ai/reward.py`): - -```python -# state contains the information of the last epoch -# epoch_n is the number of the last epoch -tot_epochs = epoch_n + 1e-20 # 1e-20 is added to avoid a division by 0 -tot_interactions = max(state['num_deauths'] + state['num_associations'], state['num_handshakes']) + 1e-20 -tot_channels = wifi.NumChannels - -# ideally, for each interaction we would have an handshake -h = state['num_handshakes'] / tot_interactions -# small positive rewards the more active epochs we have -a = .2 * (state['active_for_epochs'] / tot_epochs) -# make sure we keep hopping on the widest channel spectrum -c = .1 * (state['num_hops'] / tot_channels) -# small negative reward if we don't see aps for a while -b = -.3 * (state['blind_for_epochs'] / tot_epochs) -# small negative reward if we interact with things that are not in range anymore -m = -.3 * (state['missed_interactions'] / tot_interactions) -# small negative reward for inactive epochs -i = -.2 * (state['inactive_for_epochs'] / tot_epochs) - -reward = h + a + c + b + i + m -``` - -By maximizing this reward value, the AI learns over time to find the set of parameters that better perform with the current environmental conditions. - -## BetterCAP's Web UI - -Moreover, given that the unit is running bettercap with API and Web UI, you'll be able to use the unit as a WiFi penetration testing portable station by accessing `http://pwnagotchi.local/`. - - - -## Update your Pwnagotchi - -You can use the `scripts/update_pwnagotchi.sh` script to update to the most recent version of pwnagotchi. - -```shell -usage: ./update_pwnagitchi.sh [OPTIONS] - - Options: - -v # Version to update to, can be a branch or commit. (default: master) - -u # Url to clone from. (default: https://github.com/evilsocket/pwnagotchi) - -m # Mode to restart to. (Supported: auto manual; default: auto) - -b # Backup the current pwnagotchi config. - -r # Restore the current pwnagotchi config. -b will be enabled. - -h # Shows this help. Shows this help. - -``` - -## Backup your Pwnagotchi - -You can use the `scripts/backup.sh` script to backup the important files of your unit. - -```shell -usage: ./scripts/backup.sh HOSTNAME backup.zip -``` - -## Random Info - -* **On a rpi0w, it'll take approximately 30 minutes to load the AI**. -* `/var/log/pwnagotchi.log` is your friend. -* if connected to a laptop via usb data port, with internet connectivity shared, magic things will happen. -* checkout the `ui.video` section of the `config.yml` - if you don't want to use a display, you can connect to it with the browser and a cable. -* If you get `[FAILED] Failed to start Remount Root and Kernel File Systems.` while booting pwnagotchi, make sure the `PARTUUID`s for `rootfs` and `boot` partitions are the same in `/etc/fstab`. Use `sudo blkid` to find those values when you are using `create_sibling.sh`. From 0577972867a06c51a1aee032fcf42f8d8c581c72 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 18:42:57 +0200 Subject: [PATCH 072/346] new: if more than one peer are present, display their number (closes #201) --- pwnagotchi/agent.py | 3 ++- pwnagotchi/ui/view.py | 10 ++++++++-- scripts/preview.py | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 0e89763..71e87f5 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -296,7 +296,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): def _update_peers(self): peer = self._advertiser.closest_peer() - self._view.set_closest_peer(peer) + tot = self._advertiser.num_peers() + self._view.set_closest_peer(peer, tot) def _save_recovery_data(self): logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 5847662..3289eb1 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -168,7 +168,7 @@ class View(object): self.set('aps', "%d" % log.associated) self.set('shakes', '%d (%s)' % (log.handshakes, \ utils.total_unique_handshakes(self._config['bettercap']['handshakes']))) - self.set_closest_peer(log.last_peer) + self.set_closest_peer(log.last_peer, log.peers) def is_normal(self): return self._state.get('face') not in ( @@ -188,7 +188,7 @@ class View(object): self.set('status', self._voice.on_normal()) self.update() - def set_closest_peer(self, peer): + def set_closest_peer(self, peer, num_total): if peer is None: self.set('friend_face', None) self.set('friend_name', None) @@ -207,6 +207,12 @@ class View(object): name += '│' * (4 - num_bars) name += ' %s %d (%d)' % (peer.name(), peer.pwnd_run(), peer.pwnd_total()) + if num_total > 1: + if num_total > 9000: + name += ' of over 9000' + else: + name += ' of %d' % num_total + self.set('friend_face', peer.face()) self.set('friend_name', name) self.update() diff --git a/scripts/preview.py b/scripts/preview.py index 06be3c6..e0ef7c4 100755 --- a/scripts/preview.py +++ b/scripts/preview.py @@ -122,7 +122,7 @@ def main(): for display in list_of_displays: emotions = list() if args.showpeer: - display.set_closest_peer(DummyPeer()) + display.set_closest_peer(DummyPeer(), 10) display.on_starting() display.update() emotions.append(display.get_image()) From 4f694ddb83244f094a6a0c5a0aa40c3b329ea50d Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 19:42:29 +0200 Subject: [PATCH 073/346] new: added clean shutdown button to the web ui (closes #161) --- pwnagotchi/__init__.py | 14 ++++++++++++++ pwnagotchi/ui/display.py | 27 ++++++++++++++++++++++++--- pwnagotchi/ui/view.py | 16 +++++++++++++++- pwnagotchi/voice.py | 5 +++++ 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index ff43494..c343bb3 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -1,4 +1,8 @@ import subprocess +import os +import logging +import time +import pwnagotchi.ui.view as view version = '1.0.0a' @@ -46,3 +50,13 @@ def temperature(celsius=True): temp = int(fp.read().strip()) c = int(temp / 1000) return c if celsius else ((c * (9 / 5)) + 32) + + +def shutdown(): + logging.warning("shutting down ...") + if view.ROOT: + view.ROOT.on_shutdown() + # give it some time to refresh the ui + time.sleep(5) + os.system("sync") + os.system("halt") diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 76e9a24..ffefa18 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -15,11 +15,29 @@ class VideoHandler(BaseHTTPRequestHandler): _lock = Lock() _index = """<html> <head> - <title>%s</title> + <title>%s</title> + <style> + .block { + -webkit-appearance: button; + -moz-appearance: button; + appearance: button; + + display: block; + cursor: pointer; + text-align: center; + } + </style> </head> <body> - <img src="/ui" id="ui"/> - + <div style="position: absolute; top:0; left:0; width:100%%;"> + <img src="/ui" id="ui" style="width:100%%"/> + <br/> + <hr/> + <form action="/shutdown" onsubmit="return confirm('This will halt the unit, continue?');"> + <input type="submit" class="block" value="Shutdown"/> + </form> + </div> + <script type="text/javascript"> window.onload = function() { var image = document.getElementById("ui"); @@ -50,6 +68,9 @@ class VideoHandler(BaseHTTPRequestHandler): except: pass + elif self.path.startswith('/shutdown'): + pwnagotchi.shutdown() + elif self.path.startswith('/ui'): with self._lock: self.send_response(200) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 3289eb1..3d247f9 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -15,7 +15,7 @@ from pwnagotchi.ui.state import State WHITE = 0xff BLACK = 0x00 - +ROOT = None def setup_display_specifics(config): width = 0 @@ -57,9 +57,12 @@ def setup_display_specifics(config): class View(object): def __init__(self, config, state={}): + global ROOT + self._render_cbs = [] self._config = config self._canvas = None + self._frozen = False self._lock = Lock() self._voice = Voice(lang=config['main']['lang']) @@ -119,6 +122,8 @@ class View(object): logging.warning("ui.fps is 0, the display will only update for major changes") self._ignore_changes = ('uptime', 'name') + ROOT = self + def add_element(self, key, elem): self._state.add_element(key, elem) @@ -261,6 +266,12 @@ class View(object): self.on_normal() + def on_shutdown(self): + self.set('face', faces.SLEEP) + self.set('status', self._voice.on_shutdown()) + self.update(force=True) + self._frozen = True + def on_bored(self): self.set('face', faces.BORED) self.set('status', self._voice.on_bored()) @@ -323,6 +334,9 @@ class View(object): def update(self, force=False): with self._lock: + if self._frozen: + return + changes = self._state.changes(ignore=self._ignore_changes) if force or len(changes): self._canvas = Image.new('1', (self._width, self._height), WHITE) diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index 9268b11..aa12334 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -90,6 +90,11 @@ class Voice: self._('Zzzzz'), self._('ZzzZzzz ({secs}s)').format(secs=secs)]) + def on_shutdown(self): + return random.choice([ + self._('Good night.'), + self._('Zzz')]) + def on_awakening(self): return random.choice(['...', '!']) From 72e6668c172b6b8a83d4a9d9ffb052e28ea1f470 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 7 Oct 2019 20:50:46 +0200 Subject: [PATCH 074/346] fix: fixed config merge with user config exists but it's empty --- pwnagotchi/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index e6e15f5..8c30e94 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -20,13 +20,15 @@ def merge_config(user, default): def load_config(args): - with open(args.config, 'rt') as fp: + with open(args.config) as fp: config = yaml.safe_load(fp) if os.path.exists(args.user_config): - with open(args.user_config, 'rt') as fp: + with open(args.user_config) as fp: user_config = yaml.safe_load(fp) - config = merge_config(user_config, config) + # if the file is empty, safe_load will return None and merge_config will boom. + if user_config: + config = merge_config(user_config, config) return config From 1f99a249c63b09152b89bdd5eb354b1d2c795391 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Mon, 7 Oct 2019 19:59:28 +0200 Subject: [PATCH 075/346] Put pcap parsing in utils class --- pwnagotchi/plugins/default/grid.py | 24 ++----- pwnagotchi/plugins/default/wigle.py | 35 +++++------ pwnagotchi/utils.py | 97 +++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 39 deletions(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 6c151b3..dfc54ad 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -11,6 +11,7 @@ import glob import subprocess import pwnagotchi import pwnagotchi.utils as utils +from pwnagotchi.utils import WifiInfo, extract_from_pcap OPTIONS = dict() AUTH = utils.StatusFile('/root/.api-enrollment.json', data_format='json') @@ -67,17 +68,6 @@ def get_api_token(log, keys): return AUTH.data["token"] -def parse_packet(packet, info): - from scapy.all import Dot11Elt, Dot11Beacon, Dot11, Dot11ProbeResp, Dot11AssoReq, Dot11ReassoReq - if packet.haslayer(Dot11Beacon): - if packet.haslayer(Dot11ProbeResp) or packet.haslayer(Dot11AssoReq) or packet.haslayer(Dot11ReassoReq): - if hasattr(packet[Dot11], 'addr3'): - info['bssid'] = packet[Dot11].addr3 - if hasattr(packet[Dot11Elt], 'info'): - info['essid'] = packet[Dot11Elt].info.decode('utf-8') - return info - - def parse_pcap(filename): logging.info("api: parsing %s ..." % filename) @@ -94,20 +84,16 @@ def parse_pcap(filename): bssid = ':'.join([a + b for a, b in zip(it, it)]) info = { - 'essid': essid, - 'bssid': bssid + WifiInfo.ESSID: essid, + WifiInfo.BSSID: bssid, } try: - from scapy.all import rdpcap - - for pkt in rdpcap(filename): - info = parse_packet(pkt, info) - + info = extract_from_pcap(filename, [WifiInfo.BSSID, WifiInfo.ESSID]) except Exception as e: logging.error("api: %s" % e) - return info['essid'], info['bssid'] + return info[WifiInfo.ESSID], info[WifiInfo.BSSID] def api_report_ap(log, keys, token, essid, bssid): diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index da57f94..4f89ad4 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -12,6 +12,7 @@ import csv from datetime import datetime import requests from pwnagotchi.mesh.wifi import freq_to_channel +from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap READY = False ALREADY_UPLOADED = None @@ -151,13 +152,13 @@ def _transform_wigle_entry(gps_data, pcap_data): writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE) writer.writerow([ - pcap_data['bssid'], - pcap_data['essid'].decode('utf-8'), - _format_auth(pcap_data['encryption']), + pcap_data[WifiInfo.BSSID], + pcap_data[WifiInfo.ESSID], + _format_auth(pcap_data[WifiInfo.ENCRYPTION]), datetime.strptime(gps_data['Updated'].rsplit('.')[0], "%Y-%m-%dT%H:%M:%S").strftime('%Y-%m-%d %H:%M:%S'), - pcap_data['channel'], - pcap_data['rssi'], + pcap_data[WifiInfo.CHANNEL], + pcap_data[WifiInfo.RSSI], gps_data['Latitude'], gps_data['Longitude'], gps_data['Altitude'], @@ -238,23 +239,17 @@ def on_internet_available(display, keypair, config, log): continue try: - pcap_data = _analyze_pcap(pcap_filename) - except Scapy_Exception as sc_e: - logging.error("WIGLE: %s", sc_e) + pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID, + WifiInfo.ESSID, + WifiInfo.ENCRYPTION, + WifiInfo.CHANNEL, + WifiInfo.RSSI]) + except FieldNotFoundError: + logging.error("WIGLE: Could not extract all informations. Skip %s", gps_file) SKIP.append(gps_file) continue - - # encrypption-key is only there if privacy-cap was set - if 'encryption' in pcap_data: - if not pcap_data['encryption']: - pcap_data['encryption'].add('WEP') - else: - # no encryption, nothing to eat :( - pcap_data['encryption'] = set() - pcap_data['encryption'].add('OPN') - - if len(pcap_data) < 5: - # not enough data; try next time + except Scapy_Exception as sc_e: + logging.error("WIGLE: %s", sc_e) SKIP.append(gps_file) continue diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 8c30e94..bde9fd9 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -1,4 +1,5 @@ from datetime import datetime +from enum import Enum import logging import glob import os @@ -83,6 +84,102 @@ def blink(times=1, delay=0.3): time.sleep(delay) led(True) +class WifiInfo(Enum): + """ + Fields you can extract from a pcap file + """ + BSSID = 0 + ESSID = 1 + ENCRYPTION = 2 + CHANNEL = 3 + RSSI = 4 + +class FieldNotFoundError(Exception): + pass + + +def extract_from_pcap(path, fields): + """ + Search in pcap-file for specified information + + path: Path to pcap file + fields: Array of fields that should be extracted + + If a field is not found, FieldNotFoundError is raised + """ + results = dict() + for field in fields: + if not isinstance(field, WifiInfo): + raise TypeError("Invalid field") + + subtypes = set() + + if field == WifiInfo.BSSID: + from scapy.all import Dot11Beacon, Dot11ProbeResp, Dot11AssoReq, Dot11ReassoReq, Dot11, sniff + subtypes.add('beacon') + bpf_filter = " or ".join([f"wlan type mgt subtype {subtype}" for subtype in subtypes]) + packets = sniff(offline=path, filter=bpf_filter) + try: + for packet in packets: + if packet.haslayer(Dot11Beacon): + if hasattr(packet[Dot11], 'addr3'): + results[field] = packet[Dot11].addr3 + break + else: # magic + raise FieldNotFoundError("Could not find field [BSSID]") + except Exception: + raise FieldNotFoundError("Could not find field [BSSID]") + elif field == WifiInfo.ESSID: + from scapy.all import Dot11Beacon, Dot11ReassoReq, Dot11AssoReq, Dot11, sniff, Dot11Elt + subtypes.add('beacon') + subtypes.add('assoc-req') + subtypes.add('reassoc-req') + bpf_filter = " or ".join([f"wlan type mgt subtype {subtype}" for subtype in subtypes]) + packets = sniff(offline=path, filter=bpf_filter) + try: + for packet in packets: + if packet.haslayer(Dot11Elt) and hasattr(packet[Dot11Elt], 'info'): + results[field] = packet[Dot11Elt].info.decode('utf-8') + break + else: # magic + raise FieldNotFoundError("Could not find field [ESSID]") + except Exception: + raise FieldNotFoundError("Could not find field [ESSID]") + elif field == WifiInfo.ENCRYPTION: + from scapy.all import Dot11Beacon, sniff + subtypes.add('beacon') + bpf_filter = " or ".join([f"wlan type mgt subtype {subtype}" for subtype in subtypes]) + packets = sniff(offline=path, filter=bpf_filter) + try: + for packet in packets: + if packet.haslayer(Dot11Beacon) and hasattr(packet[Dot11Beacon], 'network_stats'): + stats = packet[Dot11Beacon].network_stats() + if 'crypto' in stats: + results[field] = stats['crypto'] # set with encryption types + break + else: # magic + raise FieldNotFoundError("Could not find field [ENCRYPTION]") + except Exception: + raise FieldNotFoundError("Could not find field [ENCRYPTION]") + elif field == WifiInfo.CHANNEL: + from scapy.all import sniff, RadioTap + from pwnagotchi.mesh.wifi import freq_to_channel + packets = sniff(offline=path, count=1) + try: + results[field] = freq_to_channel(packets[0][RadioTap].ChannelFrequency) + except Exception: + raise FieldNotFoundError("Could not find field [CHANNEL]") + elif field == WifiInfo.RSSI: + from scapy.all import sniff, RadioTap + from pwnagotchi.mesh.wifi import freq_to_channel + packets = sniff(offline=path, count=1) + try: + results[field] = packets[0][RadioTap].dBm_AntSignal + except Exception: + raise FieldNotFoundError("Could not find field [RSSI]") + + return results + class StatusFile(object): def __init__(self, path, data_format='raw'): From 80e307d28dd36261f7f5e8098255044792985d7c Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Mon, 7 Oct 2019 23:35:18 +0200 Subject: [PATCH 076/346] Add OPTIONS to gps-plugin --- pwnagotchi/defaults.yml | 2 ++ pwnagotchi/plugins/default/gps.py | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 330218f..62a70eb 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -31,6 +31,8 @@ main: - 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz' gps: enabled: false + speed: 19200 + device: /dev/ttyUSB0 twitter: enabled: false consumer_key: aaa diff --git a/pwnagotchi/plugins/default/gps.py b/pwnagotchi/plugins/default/gps.py index f7aa56e..c49b926 100644 --- a/pwnagotchi/plugins/default/gps.py +++ b/pwnagotchi/plugins/default/gps.py @@ -8,9 +8,8 @@ import logging import json import os -device = '/dev/ttyUSB0' -speed = 19200 running = False +OPTIONS = dict() def on_loaded(): @@ -27,8 +26,8 @@ def on_ready(agent): except: pass - agent.run('set gps.device %s' % device) - agent.run('set gps.speed %d' % speed) + agent.run('set gps.device %s' % OPTIONS['device']) + agent.run('set gps.speed %d' % OPTIONS['speed']) agent.run('gps on') running = True else: From 260728fffcf1d1c45b8544be0d686d08f2de967a Mon Sep 17 00:00:00 2001 From: Zenzen San <zenzenzen@riseup.net> Date: Mon, 7 Oct 2019 22:50:02 -0400 Subject: [PATCH 077/346] When internet is available it converts wifips files in geolocation --- pwnagotchi/plugins/default/geowifi.py | 69 ++++++++++++++++++++------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/pwnagotchi/plugins/default/geowifi.py b/pwnagotchi/plugins/default/geowifi.py index b7f7982..283fadc 100644 --- a/pwnagotchi/plugins/default/geowifi.py +++ b/pwnagotchi/plugins/default/geowifi.py @@ -1,30 +1,67 @@ __author__ = 'zenzen san' __version__ = '1.0.0' -__name__ = 'geowifi' +__name__ = 'wifips' __license__ = 'GPL3' -__description__ = 'Saves a json file with the access points with more signal whenever a handshake is captured. This data is usable to retrieve the geographic location using Google Geolocation API or Mozilla Location Service' +__description__ = """Saves a json file with the access points with more signal + whenever a handshake is captured. + When internet is available the files are converted in geo locations + using Mozilla LocationService """ import logging import json +import os +from urllib.request import urlopen +from datetime import datetime +from time import sleep + +URL_API_MOZILLA_LOCATION_SERVICE = 'https://location.services.mozilla.com/v1/geolocate?key=' +KEY_API_MOZILLA_LOCATION_SERVICE = 'test' def on_loaded(): - logging.info("geowifi plugin loaded. :)") + logging.info("wifips plugin loaded. :)") + +def on_internet_available(ui, keypair, config): + try: + for ff in os.listdir('/root/handshakes'): + geo_file = os.path.join('/root/handshakes', ff.replace('.wifips.json','.geo.json')) + if not os.path.isfile(geo_file): + if ff.endswith(".wifips.json"): + ff = os.path.join('/root/handshakes',ff) + with open(ff, 'r') as fp: + data = fp.read() + geo = _get_geolocation_moz_wifips(data) + with open(geo_file, 'w+t') as fp: + fp.write(geo.decode('ascii')) + logging.info("wifips plugin: saving coordinates for: {}".format(ff.replace('.wifips.json',''))) + sleep(.500) + except Exception as e: + logging.exception('WIFIPS PLUGIN ERROR') + +def on_ready(agent): + pass def on_handshake(agent, filename, access_point, client_station): + wifips = _get_wifips(agent) + wifips_filename = filename.replace('.pcap', '.wifips.json') + logging.info("wifips plugin: saving location to %s" % (wifips_filename)) + with open(wifips_filename, 'w+t') as fp: + json.dump(wifips, fp) + +def _get_wifips(agent): info = agent.session() aps = agent.get_access_points() - geowifi = _geowifi_location(aps) - geowifi_filename = filename.replace('.pcap', '.geowifi.json') + wifips = {} + wifips['wifiAccessPoints'] = [] + # 6 seems a good number to save a wifi networks location + for ap in sorted(aps,key=lambda i:i['rssi'],reverse=True)[:6]: + wifips['wifiAccessPoints'].append({'macAddress': ap['mac'], 'signalStrength': ap['rssi']}) + return wifips - logging.info("saving GEOWIFI location to %s" % (geowifi_filename)) - with open(geowifi_filename, 'w+t') as fp: - json.dump(geowifi, fp) - -def _geowifi_location(aps): - geowifi = {} - geowifi['wifiAccessPoints'] = [] - # size seems a good number to save a wifi networks location - for ap in sorted(aps,key=lambda i:i['rssi'],reverse=True)[:6]: - geowifi['wifiAccessPoints'].append({'macAddress': ap['mac'], 'signalStrength': ap['rssi']}) - return geowifi +def _get_geolocation_moz_wifips(post_data): + geourl = URL_API_MOZILLA_LOCATION_SERVICE+KEY_API_MOZILLA_LOCATION_SERVICE + try: + response = urlopen(geourl, post_data.encode('ascii')).read() + return response + except Exception as e: + logging.exception('WIFIPS PLUGIN - Something went wrong with Mozilla Location Service') From 817c9c2854826ff27e6e16ad7e79c6f886a116e0 Mon Sep 17 00:00:00 2001 From: Zenzen San <zenzenzen@riseup.net> Date: Mon, 7 Oct 2019 22:54:09 -0400 Subject: [PATCH 078/346] plugin renamed wifips --- pwnagotchi/defaults.yml | 2 +- pwnagotchi/plugins/default/{geowifi.py => wifips.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pwnagotchi/plugins/default/{geowifi.py => wifips.py} (100%) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index c1fc2b6..911e059 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -25,7 +25,7 @@ main: commands: - 'tar czf /tmp/backup.tar.gz {files}' - 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz' - geowifi: + wifips: enabled: false gps: enabled: false diff --git a/pwnagotchi/plugins/default/geowifi.py b/pwnagotchi/plugins/default/wifips.py similarity index 100% rename from pwnagotchi/plugins/default/geowifi.py rename to pwnagotchi/plugins/default/wifips.py From 1c11af8dce1e1e849d232d836c7455f4d58ca0ad Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Tue, 8 Oct 2019 13:33:11 +0100 Subject: [PATCH 079/346] builder changes --- .travis.yml | 9 +-- builder/pwnagotchi.json | 1 + builder/pwnagotchi.yml | 158 +++++++++++++++++++++------------------- 3 files changed, 86 insertions(+), 82 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0826bfc..35069f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,15 +22,8 @@ branches: cache: apt: true before_script: - - wget https://download.qemu.org/qemu-4.1.0.tar.xz - - tar xvJf qemu-4.1.0.tar.xz - - cd qemu-4.1.0 - - "./configure --target-list=arm-softmmu" - - make -j$(nproc) - - sudo make install - - cd $TRAVIS_BUILD_DIR - sudo apt-get -y update || true - - sudo apt-get -y install qemu-user-static binfmt-support bmap-tools kpartx + - sudo apt-get -y install qemu-system-arm qemu-user-static binfmt-support bmap-tools kpartx - sudo update-binfmts --display script: - sudo make clean diff --git a/builder/pwnagotchi.json b/builder/pwnagotchi.json index beac17b..b979f50 100644 --- a/builder/pwnagotchi.json +++ b/builder/pwnagotchi.json @@ -15,6 +15,7 @@ "type": "shell", "inline": [ "sed -i 's/^\\([^#]\\)/#\\1/g' /etc/ld.so.preload", + "dpkg-architecture", "apt-get -y update", "apt-get install -y ansible" ] diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index fab9c2e..f73deb6 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -11,11 +11,11 @@ - "dtoverlay=dwc2" - "dtparam=spi=on" - "dtoverlay=spi1-3cs" - - "dtoverlay=pi3-disable-bt" - - "dtparam=audio=off" services: enable: - dphys-swapfile.service + - pwnagotchi.service + - bettercap.service disable: - apt-daily.timer - apt-daily.service @@ -26,23 +26,6 @@ - triggerhappy.service - ifup@wlan0.service packages: - pip: - install: - - inky - - smbus2 - - absl-py>=0.1.6 - - enum34 - - gast==0.2.2 - - google_pasta - - opt_einsum - - scapy - - gym - - keras_applications>=1.0.6 - - keras_preprocessing>=1.0.5 - - stable-baselines - - file_read_backwards - - tensorflow_estimator>=1.14.0,<1.15.0 - - tensorboard>=1.13.0,<1.14.0 apt: remove: - rasberrypi-net-mods @@ -57,6 +40,7 @@ - git - build-essential - python3-pip + - python3-mpi4py - unzip - gawk - libopenmpi-dev @@ -65,6 +49,7 @@ - libqtgui4 - libqt4-test - libopenjp2-7 + - libtiff5 - tcpdump - lsof - libilmbase23 @@ -76,6 +61,7 @@ - libpcap-dev - libusb-1.0-0-dev - libnetfilter-queue-dev + - libopenmpi3 - dphys-swapfile - kalipi-kernel - kalipi-bootloader @@ -88,26 +74,7 @@ - fonts-dejavu - fonts-dejavu-core - fonts-dejavu-extra - - python3-crypto - - python3-requests - - python3-yaml - - python3-smbus - - python3-inkyphat - - python3-numpy - python3-pil - - python3-tweepy - - python3-opencv - - python3-termcolor - - python3-astor - - python3-backports.weakref - - python3-h5py - - python3-six - - python3-protobuf - - python3-wrapt - - python3-wheel - - python3-mock - - python3-scipy - - python3-cloudpickle bettercap: query: "assets[?contains(name, 'armv6l')].browser_download_url" @@ -171,16 +138,30 @@ command: "python3 -c 'import sys;print(sys.path.pop())'" register: pip_target - - name: install pip packages - pip: - name: "{{packages.pip.install}}" - extra_args: "--no-deps --extra-index-url=https://www.piwheels.hostedpi.com/simple/ --prefer-binary --no-cache-dir --platform=armv6l --target={{ pip_target.stdout }}" + - name: clone pwnagotchi repository + git: + repo: https://github.com/evilsocket/pwnagotchi.git + dest: /usr/local/src/pwnagotchi - - name: install grpcio - command: "pip3 install --no-deps --extra-index-url=https://www.piwheels.hostedpi.com/simple/ --no-cache-dir --prefer-binary --platform=armv6l --only-binary=:all: --target={{ pip_target.stdout }} https://www.piwheels.hostedpi.com/simple/grpcio/grpcio-1.24.1-cp37-cp37m-linux_armv6l.whl" + - name: build pwnagotchi wheel + command: "python3 setup.py sdist bdist_wheel" + args: + chdir: /usr/local/src/pwnagotchi + + - name: install opencv-python + pip: + name: "https://www.piwheels.hostedpi.com/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl" + extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}" - name: install tensorflow - command: "pip3 install --no-deps --extra-index-url=https://www.piwheels.hostedpi.com/simple/ --no-cache-dir --prefer-binary --platform=armv6l --only-binary=:all: --target={{ pip_target.stdout }} https://www.piwheels.org/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl" + pip: + name: "https://www.piwheels.hostedpi.com/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl" + extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}" + + - name: install pwnagotchi wheel and dependencies + pip: + name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}" + extra_args: "--no-cache-dir" - name: fetch bettercap release information uri: @@ -208,22 +189,6 @@ chdir: /tmp/caplets target: install - - name: clone pwnagotchi repository - git: - repo: https://github.com/evilsocket/pwnagotchi.git - dest: /tmp/pwnagotchi - - - name: copy pwnagotchi files to final destination - copy: - src: /tmp/pwnagotchi/sdcard/rootfs/root/pwnagotchi/ - dest: /root/pwnagotchi/ - mode: preserve - - - name: remove pwnagotchi files from temporary repository - file: - path: /tmp/pwnagotchi - state: absent - - name: create cpuusage script copy: dest: /usr/bin/cpuusage @@ -270,9 +235,9 @@ /usr/bin/bootblink 10 & # start a detached screen session with bettercap if ifconfig | grep usb0 | grep RUNNING; then - /usr/bin/pwnagotchi --manual + /usr/local/bin/pwnagotchi --manual else - /usr/bin/pwnagotchi + /usr/local/bin/pwnagotchi fi - name: create monstart script @@ -291,7 +256,7 @@ #!/usr/bin/env bash ifconfig mon0 down && iw dev mon0 del - - name: configure rc.local + - name: add HDMI powersave to rc.local blockinfile: path: /etc/rc.local insertbefore: "exit 0" @@ -299,7 +264,6 @@ if ! /opt/vc/bin/tvservice -s | grep HDMI; then /opt/vc/bin/tvservice -o fi - /root/pwnagotchi/scripts/startup.sh & - name: create /etc/pwnagotchi/config.yml blockinfile: @@ -374,13 +338,6 @@ regexp: '(.*)$' line: '\1 modules-load=dwc2,g_ether' - - name: configure ssh - lineinfile: - dest: /etc/ssh/sshd_config - backup: no - regexp: '#?PermitRootLogin (.*)$' - line: 'PermitRootLogin yes' - - name: configure motd copy: dest: /etc/motd @@ -394,17 +351,63 @@ apt: autoremove: yes + - name: add bettercap service to systemd + copy: + dest: /etc/systemd/system/bettercap.service + content: | + [Unit] + Description=bettercap api.rest service. + Documentation=https://bettercap.org + Wants=network.target + After=network.target + + [Service] + Type=simple + PermissionsStartOnly=true + ExecStart=/usr/bin/bettercap -no-colors -caplet pwnagotchi + Restart=always + RestartSec=30 + + [Install] + WantedBy=multi-user.target + notify: + - reload systemd services + + - name: add pwnagotchi service to systemd + copy: + dest: /etc/systemd/system/pwnagotchi.service + content: | + [Unit] + Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning. + Documentation=https://pwnagotchi.ai + Wants=network.target + After=bettercap.service + + [Service] + Type=simple + PermissionsStartOnly=true + ExecStart=/usr/bin/pwnagotchi-launcher + Restart=always + RestartSec=30 + + [Install] + WantedBy=multi-user.target + notify: + - reload systemd services + - name: enable services systemd: - name: "{{services.enable}}" + name: "{{ item }}" state: started enabled: yes + with_items: "{{ services.enable }}" - name: disable unecessary services systemd: - name: "{{services.disable}}" + name: "{{ item }}" state: stopped enabled: no + with_items: "{{ services.disable }}" - name: remove ssh keys file: @@ -412,3 +415,10 @@ path: "{{item}}" with_fileglob: - "/etc/ssh/ssh_host*_key*" + + handlers: + - name: reload systemd services + systemd: + daemon_reload: yes + + From 55d99836e7e74a36c6d2ea4306bcb8ab3046f432 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 8 Oct 2019 14:54:03 +0200 Subject: [PATCH 080/346] fix: on_internet_available plugins callback is now called for both MANU and AUTO mode (fixes #210) --- bin/pwnagotchi | 24 ++++--- pwnagotchi/agent.py | 5 ++ pwnagotchi/log.py | 68 ++++++++++--------- pwnagotchi/mesh/utils.py | 3 + pwnagotchi/plugins/default/auto-backup.py | 4 +- pwnagotchi/plugins/default/auto-update.py | 4 +- pwnagotchi/plugins/default/example.py | 2 +- pwnagotchi/plugins/default/grid.py | 54 ++++++++------- pwnagotchi/plugins/default/onlinehashcrack.py | 5 +- pwnagotchi/plugins/default/twitter.py | 22 +++--- pwnagotchi/plugins/default/wigle.py | 5 +- pwnagotchi/plugins/default/wpa-sec.py | 5 +- pwnagotchi/ui/view.py | 16 ++--- pwnagotchi/voice.py | 24 +++---- 14 files changed, 139 insertions(+), 102 deletions(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index f72d84c..9cd77d8 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -9,7 +9,6 @@ if __name__ == '__main__': import pwnagotchi.utils as utils import pwnagotchi.plugins as plugins - from pwnagotchi.log import SessionParser from pwnagotchi.identity import KeyPair from pwnagotchi.agent import Agent from pwnagotchi.ui.display import Display @@ -51,22 +50,23 @@ if __name__ == '__main__': elif args.do_manual: logging.info("entering manual mode ...") - log = SessionParser(config) + agent.last_session.parse() + logging.info( "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( - log.duration_human, - log.epochs, - log.train_epochs, - log.avg_reward, - log.min_reward, - log.max_reward)) + agent.last_session.duration_human, + agent.last_session.epochs, + agent.last_session.train_epochs, + agent.last_session.avg_reward, + agent.last_session.min_reward, + agent.last_session.max_reward)) while True: - display.on_manual_mode(log) + display.on_manual_mode(agent.last_session) time.sleep(1) if Agent.is_connected(): - plugins.on('internet_available', display, keypair, config, log) + plugins.on('internet_available', agent) else: logging.info("entering auto mode ...") @@ -104,5 +104,9 @@ if __name__ == '__main__': # WiFi electromagnetic fields affect time like gravitational fields # affect ours ... neat ^_^ agent.next_epoch() + + if Agent.is_connected(): + plugins.on('internet_available', agent) + except Exception as e: logging.exception("main loop exception") diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 71e87f5..b29e164 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -9,6 +9,7 @@ import _thread import pwnagotchi.utils as utils import pwnagotchi.plugins as plugins +from pwnagotchi.log import LastSession from pwnagotchi.bettercap import Client from pwnagotchi.mesh.utils import AsyncAdvertiser from pwnagotchi.ai.train import AsyncTrainer @@ -35,6 +36,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): self._last_pwnd = None self._history = {} self._handshakes = {} + self.last_session = LastSession(self._config) @staticmethod def is_connected(): @@ -48,6 +50,9 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): def config(self): return self._config + def view(self): + return self._view + def supported_channels(self): return self._supported_channels diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index dab58e4..0f05789 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -11,7 +11,7 @@ from file_read_backwards import FileReadBackwards LAST_SESSION_FILE = '/root/.pwnagotchi-last-session' -class SessionParser(object): +class LastSession(object): EPOCH_TOKEN = '[epoch ' EPOCH_PARSER = re.compile(r'^.+\[epoch (\d+)\] (.+)') EPOCH_DATA_PARSER = re.compile(r'([a-z_]+)=([^\s]+)') @@ -70,27 +70,27 @@ class SessionParser(object): if started_at is None: started_at = stopped_at - if SessionParser.DEAUTH_TOKEN in line and line not in cache: + if LastSession.DEAUTH_TOKEN in line and line not in cache: self.deauthed += 1 cache[line] = 1 - elif SessionParser.ASSOC_TOKEN in line and line not in cache: + elif LastSession.ASSOC_TOKEN in line and line not in cache: self.associated += 1 cache[line] = 1 - elif SessionParser.HANDSHAKE_TOKEN in line and line not in cache: + elif LastSession.HANDSHAKE_TOKEN in line and line not in cache: self.handshakes += 1 cache[line] = 1 - elif SessionParser.TRAINING_TOKEN in line: + elif LastSession.TRAINING_TOKEN in line: self.train_epochs += 1 - elif SessionParser.EPOCH_TOKEN in line: + elif LastSession.EPOCH_TOKEN in line: self.epochs += 1 - m = SessionParser.EPOCH_PARSER.findall(line) + m = LastSession.EPOCH_PARSER.findall(line) if m: epoch_num, epoch_data = m[0] - m = SessionParser.EPOCH_DATA_PARSER.findall(epoch_data) + m = LastSession.EPOCH_DATA_PARSER.findall(epoch_data) for key, value in m: if key == 'reward': reward = float(value) @@ -101,7 +101,7 @@ class SessionParser(object): elif reward > self.max_reward: self.max_reward = reward - elif SessionParser.PEER_TOKEN in line: + elif LastSession.PEER_TOKEN in line: m = self._peer_parser.findall(line) if m: name, pubkey, rssi, sid, pwnd_tot, uptime = m[0] @@ -134,6 +134,30 @@ class SessionParser(object): self.duration_human = ', '.join(self.duration_human) self.avg_reward /= (self.epochs if self.epochs else 1) + def parse(self): + lines = [] + + if os.path.exists(self.path): + with FileReadBackwards(self.path, encoding="utf-8") as fp: + for line in fp: + line = line.strip() + if line != "" and line[0] != '[': + continue + lines.append(line) + if LastSession.START_TOKEN in line: + break + lines.reverse() + + if len(lines) == 0: + lines.append("Initial Session"); + + self.last_session = lines + self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest() + self.last_saved_session_id = self._get_last_saved_session_id() + + self._parse_stats() + self.parsed = True + def __init__(self, config): self.config = config self.voice = Voice(lang=config['main']['lang']) @@ -150,28 +174,10 @@ class SessionParser(object): self.last_peer = None self._peer_parser = re.compile( 'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]') - - lines = [] - - if os.path.exists(self.path): - with FileReadBackwards(self.path, encoding="utf-8") as fp: - for line in fp: - line = line.strip() - if line != "" and line[0] != '[': - continue - lines.append(line) - if SessionParser.START_TOKEN in line: - break - lines.reverse() - - if len(lines) == 0: - lines.append("Initial Session"); - - self.last_session = lines - self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest() - self.last_saved_session_id = self._get_last_saved_session_id() - - self._parse_stats() + self.last_session = [] + self.last_session_id = None + self.last_saved_session_id = None + self.parsed = False def is_new(self): return self.last_session_id != self.last_saved_session_id diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index a775ad4..3a67ed9 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -12,6 +12,9 @@ class AsyncAdvertiser(object): self._keypair = keypair self._advertiser = None + def keypair(self): + return self._keypair + def start_advertising(self): _thread.start_new_thread(self._adv_worker, ()) diff --git a/pwnagotchi/plugins/default/auto-backup.py b/pwnagotchi/plugins/default/auto-backup.py index 48d8597..7c9901c 100644 --- a/pwnagotchi/plugins/default/auto-backup.py +++ b/pwnagotchi/plugins/default/auto-backup.py @@ -33,7 +33,7 @@ def on_loaded(): logging.info("AUTO-BACKUP: Successfuly loaded.") -def on_internet_available(display, keypair, config, log): +def on_internet_available(agent): global STATUS if READY: @@ -42,6 +42,8 @@ def on_internet_available(display, keypair, config, log): files_to_backup = " ".join(OPTIONS['files']) try: + display = agent.view() + logging.info("AUTO-BACKUP: Backing up ...") display.set('status', 'Backing up ...') display.update() diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index dab44b3..8a01a45 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -23,13 +23,15 @@ def on_loaded(): READY = True -def on_internet_available(display, keypair, config, log): +def on_internet_available(agent): global STATUS if READY: if STATUS.newer_then_days(OPTIONS['interval']): return + display = agent.view() + try: display.set('status', 'Updating ...') display.update() diff --git a/pwnagotchi/plugins/default/example.py b/pwnagotchi/plugins/default/example.py index 2a82a18..3f3e570 100644 --- a/pwnagotchi/plugins/default/example.py +++ b/pwnagotchi/plugins/default/example.py @@ -20,7 +20,7 @@ def on_loaded(): # called in manual mode when there's internet connectivity -def on_internet_available(ui, keypair, config, log): +def on_internet_available(agent): pass diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index dfc54ad..5427dbb 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -22,16 +22,16 @@ def on_loaded(): logging.info("api plugin loaded.") -def get_api_token(log, keys): +def get_api_token(last_session, keys): global AUTH if AUTH.newer_then_minutes(25) and AUTH.data is not None and 'token' in AUTH.data: return AUTH.data['token'] if AUTH.data is None: - logging.info("api: enrolling unit ...") + logging.info("grid: enrolling unit ...") else: - logging.info("api: refreshing token ...") + logging.info("grid: refreshing token ...") identity = "%s@%s" % (pwnagotchi.name(), keys.fingerprint) # sign the identity string to prove we own both keys @@ -43,16 +43,16 @@ def get_api_token(log, keys): 'public_key': keys.pub_key_pem_b64, 'signature': signature_b64, 'data': { - 'duration': log.duration, - 'epochs': log.epochs, - 'train_epochs': log.train_epochs, - 'avg_reward': log.avg_reward, - 'min_reward': log.min_reward, - 'max_reward': log.max_reward, - 'deauthed': log.deauthed, - 'associated': log.associated, - 'handshakes': log.handshakes, - 'peers': log.peers, + 'duration': last_session.duration, + 'epochs': last_session.epochs, + 'train_epochs': last_session.train_epochs, + 'avg_reward': last_session.avg_reward, + 'min_reward': last_session.min_reward, + 'max_reward': last_session.max_reward, + 'deauthed': last_session.deauthed, + 'associated': last_session.associated, + 'handshakes': last_session.handshakes, + 'peers': last_session.peers, 'uname': subprocess.getoutput("uname -a") } } @@ -63,13 +63,13 @@ def get_api_token(log, keys): AUTH.update(data=r.json()) - logging.info("api: done") + logging.info("grid: done") return AUTH.data["token"] def parse_pcap(filename): - logging.info("api: parsing %s ..." % filename) + logging.info("grid: parsing %s ..." % filename) net_id = os.path.basename(filename).replace('.pcap', '') @@ -91,15 +91,15 @@ def parse_pcap(filename): try: info = extract_from_pcap(filename, [WifiInfo.BSSID, WifiInfo.ESSID]) except Exception as e: - logging.error("api: %s" % e) + logging.error("grid: %s" % e) return info[WifiInfo.ESSID], info[WifiInfo.BSSID] -def api_report_ap(log, keys, token, essid, bssid): +def api_report_ap(last_session, keys, token, essid, bssid): while True: token = AUTH.data['token'] - logging.info("api: reporting %s (%s)" % (essid, bssid)) + logging.info("grid: reporting %s (%s)" % (essid, bssid)) try: api_address = 'https://api.pwnagotchi.ai/api/v1/unit/report/ap' headers = {'Authorization': 'access_token %s' % token} @@ -111,21 +111,23 @@ def api_report_ap(log, keys, token, essid, bssid): if r.status_code != 200: if r.status_code == 401: logging.warning("token expired") - token = get_api_token(log, keys) + token = get_api_token(last_session, keys) continue else: raise Exception("(status %d) %s" % (r.status_code, r.text)) else: return True except Exception as e: - logging.error("api: %s" % e) + logging.error("grid: %s" % e) return False -def on_internet_available(ui, keys, config, log): +def on_internet_available(agent): global REPORT try: + config = agent.config() + keys = agent.keypair() pcap_files = glob.glob(os.path.join(config['bettercap']['handshakes'], "*.pcap")) num_networks = len(pcap_files) @@ -134,10 +136,10 @@ def on_internet_available(ui, keys, config, log): num_new = num_networks - num_reported if num_new > 0: - logging.info("api: %d new networks to report" % num_new) - token = get_api_token(log, keys) - if OPTIONS['report']: + logging.info("grid: %d new networks to report" % num_new) + token = get_api_token(agent.last_session, agent.keypair()) + for pcap_file in pcap_files: net_id = os.path.basename(pcap_file).replace('.pcap', '') do_skip = False @@ -151,11 +153,11 @@ def on_internet_available(ui, keys, config, log): if net_id not in reported and not do_skip: essid, bssid = parse_pcap(pcap_file) if bssid: - if api_report_ap(log, keys, token, essid, bssid): + if api_report_ap(agent.last_session, keys, token, essid, bssid): reported.append(net_id) REPORT.update(data={'reported': reported}) else: - logging.info("api: reporting disabled") + logging.debug("grid: reporting disabled") except Exception as e: logging.exception("error while enrolling the unit") diff --git a/pwnagotchi/plugins/default/onlinehashcrack.py b/pwnagotchi/plugins/default/onlinehashcrack.py index 9771b88..25a18d6 100644 --- a/pwnagotchi/plugins/default/onlinehashcrack.py +++ b/pwnagotchi/plugins/default/onlinehashcrack.py @@ -55,11 +55,14 @@ def _upload_to_ohc(path, timeout=30): raise e -def on_internet_available(display, keypair, config, log): +def on_internet_available(agent): """ Called in manual mode when there's internet connectivity """ if READY: + display = agent.view() + config = agent.config() + handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')] diff --git a/pwnagotchi/plugins/default/twitter.py b/pwnagotchi/plugins/default/twitter.py index 8f21f25..fd247ca 100644 --- a/pwnagotchi/plugins/default/twitter.py +++ b/pwnagotchi/plugins/default/twitter.py @@ -14,8 +14,12 @@ def on_loaded(): # called in manual mode when there's internet connectivity -def on_internet_available(ui, keypair, config, log): - if log.is_new() and log.handshakes > 0: +def on_internet_available(agent): + config = agent.config() + display = agent.view() + last_session = agent.last_session + + if last_session.is_new() and last_session.handshakes > 0: try: import tweepy except ImportError: @@ -26,20 +30,20 @@ def on_internet_available(ui, keypair, config, log): picture = '/dev/shm/pwnagotchi.png' - ui.on_manual_mode(log) - ui.update(force=True) - ui.image().save(picture, 'png') - ui.set('status', 'Tweeting...') - ui.update(force=True) + display.on_manual_mode(last_session) + display.update(force=True) + display.image().save(picture, 'png') + display.set('status', 'Tweeting...') + display.update(force=True) try: auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret']) auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret']) api = tweepy.API(auth) - tweet = Voice(lang=config['main']['lang']).on_log_tweet(log) + tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session) api.update_with_media(filename=picture, status=tweet) - log.save_session_id() + last_session.save_session_id() logging.info("tweeted: %s" % tweet) except Exception as e: diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index 4f89ad4..c72c1de 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -196,7 +196,7 @@ def _send_to_wigle(lines, api_key, timeout=30): raise re_e -def on_internet_available(display, keypair, config, log): +def on_internet_available(agent): from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \ Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA """ @@ -206,6 +206,9 @@ def on_internet_available(display, keypair, config, log): global SKIP if READY: + config = agent.config() + display = agent.view() + handshake_dir = config['bettercap']['handshakes'] all_files = os.listdir(handshake_dir) all_gps_files = [os.path.join(handshake_dir, filename) diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py index 7d61b7b..0f6b854 100644 --- a/pwnagotchi/plugins/default/wpa-sec.py +++ b/pwnagotchi/plugins/default/wpa-sec.py @@ -54,11 +54,14 @@ def _upload_to_wpasec(path, timeout=30): raise e -def on_internet_available(display, keypair, config, log): +def on_internet_available(agent): """ Called in manual mode when there's internet connectivity """ if READY: + config = agent.config() + display = agent.view() + handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')] diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 3d247f9..354d910 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -163,17 +163,17 @@ class View(object): self.set('status', self._voice.on_ai_ready()) self.update() - def on_manual_mode(self, log): + def on_manual_mode(self, last_session): self.set('mode', 'MANU') - self.set('face', faces.SAD if log.handshakes == 0 else faces.HAPPY) - self.set('status', self._voice.on_log(log)) - self.set('epoch', "%04d" % log.epochs) - self.set('uptime', log.duration) + self.set('face', faces.SAD if last_session.handshakes == 0 else faces.HAPPY) + self.set('status', self._voice.on_last_session_data(last_session)) + self.set('epoch', "%04d" % last_session.epochs) + self.set('uptime', last_session.duration) self.set('channel', '-') - self.set('aps', "%d" % log.associated) - self.set('shakes', '%d (%s)' % (log.handshakes, \ + self.set('aps', "%d" % last_session.associated) + self.set('shakes', '%d (%s)' % (last_session.handshakes, \ utils.total_unique_handshakes(self._config['bettercap']['handshakes']))) - self.set_closest_peer(log.last_peer, log.peers) + self.set_closest_peer(last_session.last_peer, last_session.peers) def is_normal(self): return self._state.get('face') not in ( diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index aa12334..a5305f6 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -125,23 +125,23 @@ class Voice: def on_rebooting(self): return self._("Ops, something went wrong ... Rebooting ...") - def on_log(self, log): - status = self._('Kicked {num} stations\n').format(num=log.deauthed) - status += self._('Made {num} new friends\n').format(num=log.associated) - status += self._('Got {num} handshakes\n').format(num=log.handshakes) - if log.peers == 1: + def on_last_session_data(self, last_session): + status = self._('Kicked {num} stations\n').format(num=last_session.deauthed) + status += self._('Made {num} new friends\n').format(num=last_session.associated) + status += self._('Got {num} handshakes\n').format(num=last_session.handshakes) + if last_session.peers == 1: status += self._('Met 1 peer') - elif log.peers > 0: - status += self._('Met {num} peers').format(num=log.peers) + elif last_session.peers > 0: + status += self._('Met {num} peers').format(num=last_session.peers) return status - def on_log_tweet(self, log): + def on_last_session_tweet(self, last_session): return self._( 'I\'ve been pwning for {duration} and kicked {deauthed} clients! I\'ve also met {associated} new friends and ate {handshakes} handshakes! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet').format( - duration=log.duration_human, - deauthed=log.deauthed, - associated=log.associated, - handshakes=log.handshakes) + duration=last_session.duration_human, + deauthed=last_session.deauthed, + associated=last_session.associated, + handshakes=last_session.handshakes) def hhmmss(self, count, fmt): if count > 1: From 47654660eedd22a11c628f445d8eb3199ff9a882 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 8 Oct 2019 15:09:32 +0200 Subject: [PATCH 081/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 5427dbb..368ebcd 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -19,7 +19,7 @@ REPORT = utils.StatusFile('/root/.api-report.json', data_format='json') def on_loaded(): - logging.info("api plugin loaded.") + logging.info("grid plugin loaded.") def get_api_token(last_session, keys): From 42dcc5fc22677508788f19f38dfd5dc6ffb7969b Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Tue, 8 Oct 2019 14:12:05 +0100 Subject: [PATCH 082/346] add override file check to loader --- builder/pwnagotchi.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 3bb6203..d81a8ea 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -235,7 +235,13 @@ /usr/bin/bootblink 10 & # start a detached screen session with bettercap if ifconfig | grep usb0 | grep RUNNING; then - /usr/local/bin/pwnagotchi --manual + # if override file exists, go into auto mode + if [ -f /root/.pwnagotchi-auto ]; then + rm /root/.pwnagotchi-auto + /usr/local/bin/pwnagotchi + else + /usr/local/bin/pwnagotchi --manual + fi else /usr/local/bin/pwnagotchi fi From bba025e64c4df4852b6745867f655d9619179749 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 8 Oct 2019 15:26:32 +0200 Subject: [PATCH 083/346] fix: fixed released script --- scripts/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 2f1343a..fafb08f 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -1,7 +1,7 @@ #!/bin/bash # nothing to see here, just a utility i use to create new releases ^_^ -VERSION_FILE=$(dirname "${BASH_SOURCE[0]}")/../sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/__init__.py +VERSION_FILE=$(dirname "${BASH_SOURCE[0]}")/../pwnagotchi/__init__.py echo "version file is $VERSION_FILE" CURRENT_VERSION=$(cat $VERSION_FILE | grep version | cut -d"'" -f2) TO_UPDATE=( From 3a081afefea9d8bb908cbb0937cbd0168fc27434 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 8 Oct 2019 15:28:11 +0200 Subject: [PATCH 084/346] releasing v1.0.0RC0 --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index c343bb3..fd8a5a0 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -4,7 +4,7 @@ import logging import time import pwnagotchi.ui.view as view -version = '1.0.0a' +version = '1.0.0RC0' _name = None From 833f4486ade8b385fbf296a72bb0d2caf98ef6a0 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Tue, 8 Oct 2019 14:59:12 +0100 Subject: [PATCH 085/346] hard code bettercap release until better solution is implemented --- builder/pwnagotchi.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 3bb6203..93b1155 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -26,6 +26,8 @@ - triggerhappy.service - ifup@wlan0.service packages: + bettercap: + url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" apt: remove: - rasberrypi-net-mods @@ -76,9 +78,6 @@ - fonts-dejavu-extra - python3-pil - bettercap: - query: "assets[?contains(name, 'armv6l')].browser_download_url" - tasks: - name: selected hostname @@ -163,15 +162,9 @@ name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}" extra_args: "--no-cache-dir" - - name: fetch bettercap release information - uri: - url: https://api.github.com/repos/bettercap/bettercap/releases/latest - return_content: yes - register: bettercap_release - - name: download and install bettercap unarchive: - src: "{{ bettercap_release.content | from_json | json_query(bettercap.query) | first }}" + src: "{{ packages.bettercap.url }}" dest: /usr/bin remote_src: yes exclude: From 88b1eacd74f9dedcc89a550315f60dd7561772c0 Mon Sep 17 00:00:00 2001 From: root <root@RossDebian> Date: Tue, 8 Oct 2019 15:58:14 +0100 Subject: [PATCH 086/346] Plugin to refresh display every X changes --- pwnagotchi/defaults.yml | 3 +++ pwnagotchi/plugins/default/screen_refresh.py | 27 ++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 pwnagotchi/plugins/default/screen_refresh.py diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 62a70eb..031d94e 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -48,6 +48,9 @@ main: wigle: enabled: false api_key: ~ + screen_refresh: + enabled: false + refresh_interval: 50 # monitor interface to use iface: mon0 diff --git a/pwnagotchi/plugins/default/screen_refresh.py b/pwnagotchi/plugins/default/screen_refresh.py new file mode 100644 index 0000000..7268151 --- /dev/null +++ b/pwnagotchi/plugins/default/screen_refresh.py @@ -0,0 +1,27 @@ +__author__ = 'pwnagotcchi [at] rossmarks [dot] uk' +__version__ = '1.0.0' +__name__ = 'screen_refresh' +__license__ = 'GPL3' +__description__ = 'Refresh he e-ink display after X amount of updates' + +import logging + +from pwnagotchi.ui.components import LabeledValue +from pwnagotchi.ui.view import BLACK +import pwnagotchi.ui.fonts as fonts + +OPTIONS = dict() +update_count = 0; + +def on_loaded(): + logging.info("Screen refresh plugin loaded") + +def on_ui_update(ui): + global update_count + update_count += 1 + if update_count == OPTIONS['refresh_interval']: + ui._init_display() + ui.set('status', "Screen cleaned") + logging.info("Screen refreshing") + update_count = 0 + From 0c81d25aec21bfe52d6a4fd996a2c11c4e7305bb Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 8 Oct 2019 17:04:46 +0200 Subject: [PATCH 087/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/screen_refresh.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pwnagotchi/plugins/default/screen_refresh.py b/pwnagotchi/plugins/default/screen_refresh.py index 7268151..72c2a1c 100644 --- a/pwnagotchi/plugins/default/screen_refresh.py +++ b/pwnagotchi/plugins/default/screen_refresh.py @@ -6,22 +6,19 @@ __description__ = 'Refresh he e-ink display after X amount of updates' import logging -from pwnagotchi.ui.components import LabeledValue -from pwnagotchi.ui.view import BLACK -import pwnagotchi.ui.fonts as fonts - OPTIONS = dict() update_count = 0; + def on_loaded(): logging.info("Screen refresh plugin loaded") + def on_ui_update(ui): - global update_count + global update_count update_count += 1 if update_count == OPTIONS['refresh_interval']: ui._init_display() ui.set('status', "Screen cleaned") logging.info("Screen refreshing") update_count = 0 - From c75c0679b15c543b90aeae9ce4084a7585df0a4f Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 8 Oct 2019 17:04:59 +0200 Subject: [PATCH 088/346] releasing v1.0.0RC1 --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index fd8a5a0..4265d1c 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -4,7 +4,7 @@ import logging import time import pwnagotchi.ui.view as view -version = '1.0.0RC0' +version = '1.0.0RC1' _name = None From fe7da076d74ae2ac1ad6b4d9c05b73a7cee7544b Mon Sep 17 00:00:00 2001 From: Zenzen San <zenzenzen@riseup.net> Date: Sat, 5 Oct 2019 20:05:46 -0400 Subject: [PATCH 089/346] - Plugin geowifi saves wifi geolocation on hndshk Saves a json file with the access points with more signal whenever a handshake is captured. This data is usable to retrieve the geographic location using Google Geolocation API or Mozilla Location Service --- pwnagotchi/defaults.yml | 2 ++ pwnagotchi/plugins/default/geowifi.py | 30 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 pwnagotchi/plugins/default/geowifi.py diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 031d94e..bb6ff08 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -29,6 +29,8 @@ main: commands: - 'tar czf /tmp/backup.tar.gz {files}' - 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz' + geowifi: + enabled: false gps: enabled: false speed: 19200 diff --git a/pwnagotchi/plugins/default/geowifi.py b/pwnagotchi/plugins/default/geowifi.py new file mode 100644 index 0000000..b7f7982 --- /dev/null +++ b/pwnagotchi/plugins/default/geowifi.py @@ -0,0 +1,30 @@ +__author__ = 'zenzen san' +__version__ = '1.0.0' +__name__ = 'geowifi' +__license__ = 'GPL3' +__description__ = 'Saves a json file with the access points with more signal whenever a handshake is captured. This data is usable to retrieve the geographic location using Google Geolocation API or Mozilla Location Service' + +import logging +import json + +def on_loaded(): + logging.info("geowifi plugin loaded. :)") + +def on_handshake(agent, filename, access_point, client_station): + info = agent.session() + aps = agent.get_access_points() + geowifi = _geowifi_location(aps) + geowifi_filename = filename.replace('.pcap', '.geowifi.json') + + logging.info("saving GEOWIFI location to %s" % (geowifi_filename)) + with open(geowifi_filename, 'w+t') as fp: + json.dump(geowifi, fp) + +def _geowifi_location(aps): + geowifi = {} + geowifi['wifiAccessPoints'] = [] + # size seems a good number to save a wifi networks location + for ap in sorted(aps,key=lambda i:i['rssi'],reverse=True)[:6]: + geowifi['wifiAccessPoints'].append({'macAddress': ap['mac'], 'signalStrength': ap['rssi']}) + return geowifi + From dfeaad36edda096ca6d342dc9be82c7618c19c27 Mon Sep 17 00:00:00 2001 From: Zenzen San <zenzenzen@riseup.net> Date: Mon, 7 Oct 2019 22:50:02 -0400 Subject: [PATCH 090/346] When internet is available it converts wifips files in geolocation --- pwnagotchi/plugins/default/geowifi.py | 69 ++++++++++++++++++++------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/pwnagotchi/plugins/default/geowifi.py b/pwnagotchi/plugins/default/geowifi.py index b7f7982..283fadc 100644 --- a/pwnagotchi/plugins/default/geowifi.py +++ b/pwnagotchi/plugins/default/geowifi.py @@ -1,30 +1,67 @@ __author__ = 'zenzen san' __version__ = '1.0.0' -__name__ = 'geowifi' +__name__ = 'wifips' __license__ = 'GPL3' -__description__ = 'Saves a json file with the access points with more signal whenever a handshake is captured. This data is usable to retrieve the geographic location using Google Geolocation API or Mozilla Location Service' +__description__ = """Saves a json file with the access points with more signal + whenever a handshake is captured. + When internet is available the files are converted in geo locations + using Mozilla LocationService """ import logging import json +import os +from urllib.request import urlopen +from datetime import datetime +from time import sleep + +URL_API_MOZILLA_LOCATION_SERVICE = 'https://location.services.mozilla.com/v1/geolocate?key=' +KEY_API_MOZILLA_LOCATION_SERVICE = 'test' def on_loaded(): - logging.info("geowifi plugin loaded. :)") + logging.info("wifips plugin loaded. :)") + +def on_internet_available(ui, keypair, config): + try: + for ff in os.listdir('/root/handshakes'): + geo_file = os.path.join('/root/handshakes', ff.replace('.wifips.json','.geo.json')) + if not os.path.isfile(geo_file): + if ff.endswith(".wifips.json"): + ff = os.path.join('/root/handshakes',ff) + with open(ff, 'r') as fp: + data = fp.read() + geo = _get_geolocation_moz_wifips(data) + with open(geo_file, 'w+t') as fp: + fp.write(geo.decode('ascii')) + logging.info("wifips plugin: saving coordinates for: {}".format(ff.replace('.wifips.json',''))) + sleep(.500) + except Exception as e: + logging.exception('WIFIPS PLUGIN ERROR') + +def on_ready(agent): + pass def on_handshake(agent, filename, access_point, client_station): + wifips = _get_wifips(agent) + wifips_filename = filename.replace('.pcap', '.wifips.json') + logging.info("wifips plugin: saving location to %s" % (wifips_filename)) + with open(wifips_filename, 'w+t') as fp: + json.dump(wifips, fp) + +def _get_wifips(agent): info = agent.session() aps = agent.get_access_points() - geowifi = _geowifi_location(aps) - geowifi_filename = filename.replace('.pcap', '.geowifi.json') + wifips = {} + wifips['wifiAccessPoints'] = [] + # 6 seems a good number to save a wifi networks location + for ap in sorted(aps,key=lambda i:i['rssi'],reverse=True)[:6]: + wifips['wifiAccessPoints'].append({'macAddress': ap['mac'], 'signalStrength': ap['rssi']}) + return wifips - logging.info("saving GEOWIFI location to %s" % (geowifi_filename)) - with open(geowifi_filename, 'w+t') as fp: - json.dump(geowifi, fp) - -def _geowifi_location(aps): - geowifi = {} - geowifi['wifiAccessPoints'] = [] - # size seems a good number to save a wifi networks location - for ap in sorted(aps,key=lambda i:i['rssi'],reverse=True)[:6]: - geowifi['wifiAccessPoints'].append({'macAddress': ap['mac'], 'signalStrength': ap['rssi']}) - return geowifi +def _get_geolocation_moz_wifips(post_data): + geourl = URL_API_MOZILLA_LOCATION_SERVICE+KEY_API_MOZILLA_LOCATION_SERVICE + try: + response = urlopen(geourl, post_data.encode('ascii')).read() + return response + except Exception as e: + logging.exception('WIFIPS PLUGIN - Something went wrong with Mozilla Location Service') From 6a87c681fa7b35d767794b44d4c134bfff5fa19f Mon Sep 17 00:00:00 2001 From: Zenzen San <zenzenzen@riseup.net> Date: Mon, 7 Oct 2019 22:54:09 -0400 Subject: [PATCH 091/346] plugin renamed wifips --- pwnagotchi/defaults.yml | 2 +- pwnagotchi/plugins/default/{geowifi.py => wifips.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pwnagotchi/plugins/default/{geowifi.py => wifips.py} (100%) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index bb6ff08..2bf27d6 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -29,7 +29,7 @@ main: commands: - 'tar czf /tmp/backup.tar.gz {files}' - 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz' - geowifi: + wifips: enabled: false gps: enabled: false diff --git a/pwnagotchi/plugins/default/geowifi.py b/pwnagotchi/plugins/default/wifips.py similarity index 100% rename from pwnagotchi/plugins/default/geowifi.py rename to pwnagotchi/plugins/default/wifips.py From bd8f2936c58efdba421f0867f01c599bb947064e Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 8 Oct 2019 18:52:36 +0200 Subject: [PATCH 092/346] fix: grid plugin was not enrolling partially opted-in units --- pwnagotchi/plugins/default/grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 368ebcd..066b794 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -135,10 +135,10 @@ def on_internet_available(agent): num_reported = len(reported) num_new = num_networks - num_reported + token = get_api_token(agent.last_session, agent.keypair()) if num_new > 0: if OPTIONS['report']: logging.info("grid: %d new networks to report" % num_new) - token = get_api_token(agent.last_session, agent.keypair()) for pcap_file in pcap_files: net_id = os.path.basename(pcap_file).replace('.pcap', '') From d222935aea9f35d67fee669302905fc726368db6 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 8 Oct 2019 19:21:54 +0200 Subject: [PATCH 093/346] new: defaults are now copied into /etc/pwnagotchi/defaults.yml if it doesn't exist or different from release defaults --- bin/pwnagotchi | 3 +-- pwnagotchi/utils.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 9cd77d8..3b69026 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -15,8 +15,7 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('-C', '--config', action='store', dest='config', - default=os.path.join(os.path.dirname(pwnagotchi.__file__), 'defaults.yml'), + parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.yml', help='Main configuration file.') parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.yml', help='If this file exists, configuration will be merged and this will override default values.') diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index bde9fd9..4afcc69 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -7,6 +7,9 @@ import time import subprocess import yaml import json +import shutil + +import pwnagotchi # https://stackoverflow.com/questions/823196/yaml-merge-in-python @@ -21,6 +24,28 @@ def merge_config(user, default): def load_config(args): + default_config_path = os.path.dirname(args.config) + if not os.path.exists(default_config_path): + os.makedirs(default_config_path) + + ref_defaults_file = os.path.join(os.path.dirname(pwnagotchi.__file__), 'defaults.yml') + ref_defaults_data = None + + if not os.path.exists(args.config): + # logging not configured here yet + print("copying %s to %s ..." % (ref_defaults_file, args.config)) + shutil.copy(ref_defaults_file, args.config) + else: + with open(ref_defaults_file) as fp: + ref_defaults_data = fp.read() + + with open(args.config) as fp: + defaults_data = fp.read() + + if ref_defaults_data != defaults_data: + print("!!! file in %s is different than release defaults, overwriting !!!" % args.config) + shutil.copy(ref_defaults_file, args.config) + with open(args.config) as fp: config = yaml.safe_load(fp) From 223bce8abccd6f67ee60fde7ec5662a446ded30f Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 8 Oct 2019 19:29:22 +0200 Subject: [PATCH 094/346] fix: fixed paths in scripts/backup.sh --- scripts/backup.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/backup.sh b/scripts/backup.sh index 8ebf274..213ed94 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -10,9 +10,8 @@ TEMP_BACKUP_FOLDER=/tmp/pwnagotchi_backup FILES_TO_BACKUP=( /root/brain.nn /root/brain.json - /root/custom.yaml /root/handshakes - /etc/ssh + /etc/pwnagotchi/ /etc/hostname /etc/hosts /etc/motd From d86e3cbc272bf69754022a731feaa1184c17cb28 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 8 Oct 2019 19:30:18 +0200 Subject: [PATCH 095/346] misc: removed legacy update script --- scripts/update_pwnagotchi.sh | 121 ----------------------------------- 1 file changed, 121 deletions(-) delete mode 100755 scripts/update_pwnagotchi.sh diff --git a/scripts/update_pwnagotchi.sh b/scripts/update_pwnagotchi.sh deleted file mode 100755 index 9299f1a..0000000 --- a/scripts/update_pwnagotchi.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/bin/bash -# Default variables -GIT_FOLDER="/tmp/pwnagotchi" -GIT_URL="https://github.com/evilsocket/pwnagotchi/" -VERSION="master" -SUPPORTED_RESTART_MODES=( 'auto' 'manual' ) -MODE="auto" -BACKUPCONFIG=0 -RESTORECONFIG=0 - -# Functions -function usage() { - cat <<EOF - - usage: $0 [OPTIONS] - Note: This should be run from the pwnagotchi itself! - - Options: - -v # Version to update to, can be a branch or commit. (default: master) - -u # Url to clone from. (default: https://github.com/evilsocket/pwnagotchi) - -m # Mode to restart to. (Supported: ${SUPPORTED_RESTART_MODES[*]}; default: auto) - -b # Backup the current pwnagotchi config and hostname references, then overwrite with defaults. - -r # Restore the current pwnagotchi config and hostname references after upgrade. (-b will be enabled.) - -h # Shows this help. - -EOF - exit 0 -} - -function test_root() { - if ! [ $(id -u) = 0 ]; then - echo "[!] This script must be run as root." - exit 1 - fi -} - -function test_github() { - wget -q --spider $GIT_URL - if [ $? -ne 0 ]; then - echo "[!] Cannot reach github. This script requires internet access, ensure connection sharing is working." - exit 2 - fi -} - -while getopts "v:u:m:brh" o; do - case "${o}" in - v) - VERSION="${OPTARG}" - ;; - u) - GIT_URL="${OPTARG}" - ;; - m) - if [[ "${SUPPORTED_RESTART_MODES[*]}" =~ ${OPTARG} ]]; then - MODE="${OPTARG}" - else - usage - fi - ;; - b) - BACKUPCONFIG=1 - ;; - r) - BACKUPCONFIG=1 - RESTORECONFIG=1 - ;; - h) - usage - ;; - *) - usage - ;; - esac -done -shift $((OPTIND-1)) - -echo "[+] Checking prerequisites." -test_root -test_github - -# clean up old files, clone master, set checkout to commit if needed. -echo "[+] Cloning to $GIT_FOLDER..." -rm $GIT_FOLDER -rf -git clone $GIT_URL $GIT_FOLDER -q -cd $GIT_FOLDER -if [ $VERSION != "master" ]; then - git checkout $VERSION -q -fi - -if [ $BACKUPCONFIG -eq 1 ]; then - echo "[+] Creating backup of config.yml and hostname references" - mv /root/pwnagotchi/config.yml /root/config.yml.bak -f - mv /etc/hosts /root/hosts.bak -f - mv /etc/hostname /root/hostname.bak -f - mv /etc/motd /etc/motd.bak -f - mv /etc/network/interfaces /root/interfaces.bak -f -fi - -echo "[+] Installing $(git log -1 --format="%h")" -rm /root/pwnagotchi -rf # ensures old files are removed -rsync -aPq $GIT_FOLDER/sdcard/boot/* /boot/ -rsync -aPq $GIT_FOLDER/sdcard/rootfs/* / -cd /tmp -rm $GIT_FOLDER -rf - -if [ $RESTORECONFIG -eq 1 ]; then - echo "[+] Restoring backup of config.yml and hostname references" - mv /root/config.yml.bak /root/pwnagotchi/config.yml -f - mv /root/hosts.bak /etc/hosts -f - mv /root/hostname.bak /etc/hostname -f - mv /root/interfaces.bak /etc/network/interfaces -f - mv /etc/motd.bak /etc/motd -f -fi - -echo "[+] Restarting pwnagotchi in $MODE mode. $( screen -X -S pwnagotchi quit)" -if [ $MODE == "auto" ]; then - sudo -H -u root /usr/bin/screen -dmS pwnagotchi -c /root/pwnagotchi/data/screenrc.auto -elif [ $MODE == "manual" ]; then - sudo -H -u root /usr/bin/screen -dmS pwnagotchi -c /root/pwnagotchi/data/screenrc.manual -fi -echo "[+] Finished" From dfb484624a9d825252aa0e087f8e9b6a1df43689 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Tue, 8 Oct 2019 20:10:21 +0200 Subject: [PATCH 096/346] Some improvements --- pwnagotchi/plugins/default/net-pos.py | 144 ++++++++++++++++++++++++++ pwnagotchi/plugins/default/wifips.py | 67 ------------ 2 files changed, 144 insertions(+), 67 deletions(-) create mode 100644 pwnagotchi/plugins/default/net-pos.py delete mode 100644 pwnagotchi/plugins/default/wifips.py diff --git a/pwnagotchi/plugins/default/net-pos.py b/pwnagotchi/plugins/default/net-pos.py new file mode 100644 index 0000000..277077d --- /dev/null +++ b/pwnagotchi/plugins/default/net-pos.py @@ -0,0 +1,144 @@ +__author__ = 'zenzen san' +__version__ = '1.0.0' +__name__ = 'net-pos' +__license__ = 'GPL3' +__description__ = """Saves a json file with the access points with more signal + whenever a handshake is captured. + When internet is available the files are converted in geo locations + using Mozilla LocationService """ + +import logging +import json +import os +import requests + +MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}' +ALREADY_SAVED = None +SKIP = None +READY = False +OPTIONS = {} + + +def on_loaded(): + global ALREADY_SAVED + global SKIP + global READY + + SKIP = list() + + if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): + logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.") + return + + try: + with open('/root/.net_pos_saved', 'r') as f: + ALREADY_SAVED = f.read().splitlines() + except OSError: + logging.warning('NET-POS: No net-pos-file found.') + ALREADY_SAVED = [] + + READY = True + logging.info("net-pos plugin loaded.") + +def _append_saved(path): + to_save = list() + if isinstance(path, str): + to_save.append(path) + elif isinstance(path, list): + to_save += path + else: + raise TypeError("Expected list or str, got %s" % type(path)) + + with open('/root/.net_pos_saved', 'a') as saved_file: + for x in to_save: + saved_file.write(x + "\n") + +def on_internet_available(agent): + global SKIP + + if READY: + config = agent.config() + display = agent.view() + handshake_dir = config['bettercap']['handshakes'] + + all_files = os.listdir(handshake_dir) + all_np_files = [os.path.join(handshake_dir, filename) + for filename in all_files + if filename.endswith('.net-pos.json')] + new_np_files = set(all_np_files) - set(ALREADY_SAVED) - set(SKIP) + + if new_np_files: + logging.info("NET-POS: Found {num} new net-pos files. Fetching positions ...", len(new_np_files)) + display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...") + display.update(force=True) + for idx, np_file in enumerate(new_np_files): + + geo_file = np_file.replace('.net-pos.json', '.geo.json') + if os.path.exists(geo_file): + # got already the position + ALREADY_SAVED.append(np_file) + _append_saved(np_file) + continue + + try: + geo_data = _get_geo_data(np_file) # returns json obj + except requests.exceptions.RequestException as req_e: + logging.error("NET-POS: %s", req_e) + SKIP += np_file + except json.JSONDecodeError as js_e: + logging.error("NET-POS: %s", js_e) + SKIP += np_file + except OSError as os_e: + logging.error("NET-POS: %s", os_e) + SKIP += np_file + + with open(geo_file, 'w+t') as sf: + json.dump(geo_data, sf) + + ALREADY_SAVED.append(np_file) + _append_saved(np_file) + + display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)}") + display.update(force=True) + + +def on_handshake(agent, filename, access_point, client_station): + netpos = _get_netpos(agent) + netpos_filename = filename.replace('.pcap', '.net-pos.json') + logging.info("NET-POS: Saving net-location to %s", netpos_filename) + + try: + with open(netpos_filename, 'w+t') as fp: + json.dump(netpos, fp) + except OSError as os_e: + logging.error("NET-POS: %s", os_e) + + +def _get_netpos(agent): + aps = agent.get_access_points() + netpos = {} + netpos['wifiAccessPoints'] = list() + # 6 seems a good number to save a wifi networks location + for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]: + netpos['wifiAccessPoints'].append({'macAddress': access_point['mac'], + 'signalStrength': access_point['rssi']}) + return netpos + +def _get_geo_data(path, timeout=30): + geourl = MOZILLA_API_URL.format(api=OPTIONS['api_key']) + + try: + with open(path, "r") as json_file: + data = json.load(json_file) + except json.JSONDecodeError as js_e: + raise js_e + except OSError as os_e: + raise os_e + + try: + result = requests.post(geourl, + json=data, + timeout=timeout) + return result.json() + except requests.exceptions.RequestException as req_e: + raise req_e diff --git a/pwnagotchi/plugins/default/wifips.py b/pwnagotchi/plugins/default/wifips.py deleted file mode 100644 index 283fadc..0000000 --- a/pwnagotchi/plugins/default/wifips.py +++ /dev/null @@ -1,67 +0,0 @@ -__author__ = 'zenzen san' -__version__ = '1.0.0' -__name__ = 'wifips' -__license__ = 'GPL3' -__description__ = """Saves a json file with the access points with more signal - whenever a handshake is captured. - When internet is available the files are converted in geo locations - using Mozilla LocationService """ - -import logging -import json -import os -from urllib.request import urlopen -from datetime import datetime -from time import sleep - -URL_API_MOZILLA_LOCATION_SERVICE = 'https://location.services.mozilla.com/v1/geolocate?key=' -KEY_API_MOZILLA_LOCATION_SERVICE = 'test' - -def on_loaded(): - logging.info("wifips plugin loaded. :)") - -def on_internet_available(ui, keypair, config): - try: - for ff in os.listdir('/root/handshakes'): - geo_file = os.path.join('/root/handshakes', ff.replace('.wifips.json','.geo.json')) - if not os.path.isfile(geo_file): - if ff.endswith(".wifips.json"): - ff = os.path.join('/root/handshakes',ff) - with open(ff, 'r') as fp: - data = fp.read() - geo = _get_geolocation_moz_wifips(data) - with open(geo_file, 'w+t') as fp: - fp.write(geo.decode('ascii')) - logging.info("wifips plugin: saving coordinates for: {}".format(ff.replace('.wifips.json',''))) - sleep(.500) - except Exception as e: - logging.exception('WIFIPS PLUGIN ERROR') - -def on_ready(agent): - pass - -def on_handshake(agent, filename, access_point, client_station): - wifips = _get_wifips(agent) - wifips_filename = filename.replace('.pcap', '.wifips.json') - logging.info("wifips plugin: saving location to %s" % (wifips_filename)) - with open(wifips_filename, 'w+t') as fp: - json.dump(wifips, fp) - -def _get_wifips(agent): - info = agent.session() - aps = agent.get_access_points() - wifips = {} - wifips['wifiAccessPoints'] = [] - # 6 seems a good number to save a wifi networks location - for ap in sorted(aps,key=lambda i:i['rssi'],reverse=True)[:6]: - wifips['wifiAccessPoints'].append({'macAddress': ap['mac'], 'signalStrength': ap['rssi']}) - return wifips - -def _get_geolocation_moz_wifips(post_data): - geourl = URL_API_MOZILLA_LOCATION_SERVICE+KEY_API_MOZILLA_LOCATION_SERVICE - try: - response = urlopen(geourl, post_data.encode('ascii')).read() - return response - except Exception as e: - logging.exception('WIFIPS PLUGIN - Something went wrong with Mozilla Location Service') - From 65600f95e50305552e4994f59bde1b46cf0b70ca Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Tue, 8 Oct 2019 20:28:00 +0200 Subject: [PATCH 097/346] Add gps validy check --- pwnagotchi/plugins/default/wigle.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index c72c1de..b3f3329 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -241,6 +241,12 @@ def on_internet_available(agent): SKIP.append(gps_file) continue + if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0: + logging.warning("WIGLE: Not enough gps-informations for %s. Trying again next time.", gps_file) + SKIP.append(gps_file) + continue + + try: pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID, WifiInfo.ESSID, From 4916b3bf7c0fb5274516c800148b62efa3a874da Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Tue, 8 Oct 2019 19:36:17 +0100 Subject: [PATCH 098/346] fix builder to set image version and hostname on build --- .travis.yml | 2 +- Makefile | 2 +- builder/pwnagotchi.json | 6 ++---- builder/pwnagotchi.yml | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 35069f9..c5fe50b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ before_script: script: - sudo make clean - sudo -E env "PATH=$PATH" make install - - sudo make image -e PWN_HOSTNAME=pwnagotchi VERSION=$TRAVIS_TAG + - sudo make image -e PWN_HOSTNAME=pwnagotchi PWN_VERSION=$TRAVIS_TAG notifications: slack: secure: aovN87lswg+TTLobxJpevC0p2F4omTAlsOzeKqLysRW55o5rRhRC1SgwRkWUl19yr49nsyffwmv/b7OcyQiWIVnz1bxxE9XOKP8zgRMA/bKKcyAcPktPqHXsALIQDseXyl0kz7fwdkRWg0UC2HpKqi5koAhmBYTX/fbzieyeHCbcQ7lbFfVFIepE1401y9m1IqUHcHuGfFhMvTaSDIpXrDXnWdA8+gDAl0HKJv41MIsgmffbh/QhD2jLBWzItjxFC3llmNfy88pnzCk0+HBMY/4272LXb0czX7et5HJeM74oxPqkb3aKXFxZgNaDl7cYdV+kzj9dfKUk47hAqwbxlirit5WvHI1Br1VyA90+PFvcC/p41J8gCv0IlcB5vjWN8NKWA1J+Y1F+KvrujMvGtgd0foHZvaSutuRODhI1cBh5rYAiLCroRSlvKMw3IJRyCRstYgUlMIJ3cI2Ova/kU44KtDVmjT9VE/pPkhkHBPvcYThL6skZTdl19E/RlormLu3XObG1aHLZ+Znxe/aL7tWHi0KMOlpy+TMDdps4go7URnJ8yitHtIvU/zMtBrztIwN0Oy2JLKXrS5qIijmRAkBLxe0NxuG01DYFzEO3KtnRirP4uSe3QcrjyP4sqPrVhrjl3TR6gwg8V1juvDXB4e2h8yCpaUW5AdSBOlx9riY= diff --git a/Makefile b/Makefile index 0d898dd..1344670 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ install: cp /tmp/packer-builder-arm-image/packer-builder-arm-image /usr/bin image: - cd builder && sudo /usr/bin/packer build pwnagotchi.json + cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).img diff --git a/builder/pwnagotchi.json b/builder/pwnagotchi.json index b979f50..d7ec0ca 100644 --- a/builder/pwnagotchi.json +++ b/builder/pwnagotchi.json @@ -1,7 +1,4 @@ { - "variables": { - "home": "{{env `HOME`}}" - }, "builders": [{ "name": "pwnagotchi", "type": "arm-image", @@ -22,7 +19,8 @@ }, { "type":"ansible-local", - "playbook_file": "pwnagotchi.yml" + "playbook_file": "pwnagotchi.yml", + "command": "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION={{user `pwn_version`}} PWN_HOSTNAME={{user `pwn_hostname`}} ansible-playbook" }, { "type": "shell", diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 3bb6203..855ac1c 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -5,7 +5,7 @@ vars: pwnagotchi: hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}" - version: "{{ lookup('env', 'PWN_VERSION') | default('master', true) }} " + version: "{{ lookup('env', 'PWN_VERSION') | default('master', true) }}" system: boot_options: - "dtoverlay=dwc2" From ca3ece7f2b1b904b49e2abe8050653efa171f501 Mon Sep 17 00:00:00 2001 From: Zenzen San <56201767+zenzen666@users.noreply.github.com> Date: Tue, 8 Oct 2019 19:01:44 +0000 Subject: [PATCH 099/346] changed plugin name from wifips to net-pos --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 2bf27d6..67ab8e5 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -29,7 +29,7 @@ main: commands: - 'tar czf /tmp/backup.tar.gz {files}' - 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz' - wifips: + net-pos: enabled: false gps: enabled: false From 46df27f860b96c9e97c6e3ba1dcd6144ea16593f Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Tue, 8 Oct 2019 21:32:31 +0200 Subject: [PATCH 100/346] Add OPTIONS --- pwnagotchi/plugins/default/gps.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/plugins/default/gps.py b/pwnagotchi/plugins/default/gps.py index c49b926..234df3a 100644 --- a/pwnagotchi/plugins/default/gps.py +++ b/pwnagotchi/plugins/default/gps.py @@ -13,14 +13,14 @@ OPTIONS = dict() def on_loaded(): - logging.info("gps plugin loaded for %s" % device) + logging.info("gps plugin loaded for %s" % OPTIONS['device']) def on_ready(agent): global running - if os.path.exists(device): - logging.info("enabling gps bettercap's module for %s" % device) + if os.path.exists(OPTIONS['device']): + logging.info("enabling gps bettercap's module for %s" % OPTIONS['device']) try: agent.run('gps off') except: From 41f50c3436e5b652a94cdc1d0da19da62c9d033e Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Tue, 8 Oct 2019 21:41:34 +0200 Subject: [PATCH 101/346] Also ship locale with package --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f8e22b3..dab1f93 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup(name='pwnagotchi', license='GPL', install_requires=required, scripts=['bin/pwnagotchi'], - package_data={'pwnagotchi': ['defaults.yml', 'pwnagotchi/defaults.yml']}, + package_data={'pwnagotchi': ['defaults.yml', 'pwnagotchi/defaults.yml', 'locale/*/LC_MESSAGES/*.mo']}, include_package_data=True, packages=find_packages(), classifiers=[ From 31b4095fa5e507b89e3a98fb3dd084fb4cde8688 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Tue, 8 Oct 2019 22:28:57 +0200 Subject: [PATCH 102/346] Fix path --- scripts/language.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/language.sh b/scripts/language.sh index 247911f..08bd97b 100755 --- a/scripts/language.sh +++ b/scripts/language.sh @@ -6,8 +6,8 @@ DEPENDENCIES=( 'xgettext' 'msgfmt' 'msgmerge' ) COMMANDS=( 'add' 'update' 'delete' 'compile' ) REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")" -LOCALE_DIR="${REPO_DIR}/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale" -VOICE_FILE="${REPO_DIR}/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/voice.py" +LOCALE_DIR="${REPO_DIR}/pwnagotchi/locale" +VOICE_FILE="${REPO_DIR}/pwnagotchi/voice.py" function usage() { cat <<EOF From 5a3ddd9133444f4bf31b66782784a2301bab0808 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 8 Oct 2019 23:28:37 +0200 Subject: [PATCH 103/346] misc: small fix or general refactoring i did not bother commenting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 363d7ec..7dc839b 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ full and half WPA handshakes. Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning based "AI" *(yawn)*, Pwnagotchi tunes [its own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things** in the environments you expose it to. -More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#training-the-ai) doc.) +More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://www.pwnagotchi.ai/usage/#training-the-ai) doc.) -**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi actually learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but **definitely listen to your Pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :) +**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi actually learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but **definitely listen to your Pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :) Multiple units within close physical proximity can "talk" to each other, advertising their own presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage. From b5d92c86c8da431c012ef59a075d4d84aae8ba72 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Tue, 8 Oct 2019 22:31:27 +0100 Subject: [PATCH 104/346] enable bettercap ui on manual mode --- builder/pwnagotchi.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index bdb5761..7ab12d2 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -182,6 +182,9 @@ chdir: /tmp/caplets target: install + - name: install bettercap http ui + command: "/usr/bin/bettercap -eval 'caplets.update; ui.update; q'" + - name: create cpuusage script copy: dest: /usr/bin/cpuusage @@ -239,6 +242,26 @@ /usr/local/bin/pwnagotchi fi + - name: create bettercap-launcher script + copy: + dest: /usr/bin/bettercap-launcher + mode: 0755 + content: | + #!/usr/bin/env bash + # blink 10 times to signal ready state + /usr/bin/bootblink 10 & + if ifconfig | grep usb0 | grep RUNNING; then + # if override file exists, go into auto mode + if [ -f /root/.pwnagotchi-auto ]; then + rm /root/.pwnagotchi-auto + /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto + else + /usr/bin/bettercap -no-colors -caplet pwnagotchi-manual + fi + else + /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto + fi + - name: create monstart script copy: dest: /usr/bin/monstart From 0294eeff5f2b0487aa5be31f9f11071d3e9be708 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Tue, 8 Oct 2019 22:31:27 +0100 Subject: [PATCH 105/346] enable bettercap ui on manual mode --- builder/pwnagotchi.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index f6d1128..003db66 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -182,6 +182,9 @@ chdir: /tmp/caplets target: install + - name: install bettercap http ui + command: "/usr/bin/bettercap -eval 'caplets.update; ui.update; q'" + - name: create cpuusage script copy: dest: /usr/bin/cpuusage @@ -239,6 +242,26 @@ /usr/local/bin/pwnagotchi fi + - name: create bettercap-launcher script + copy: + dest: /usr/bin/bettercap-launcher + mode: 0755 + content: | + #!/usr/bin/env bash + # blink 10 times to signal ready state + /usr/bin/bootblink 10 & + if ifconfig | grep usb0 | grep RUNNING; then + # if override file exists, go into auto mode + if [ -f /root/.pwnagotchi-auto ]; then + rm /root/.pwnagotchi-auto + /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto + else + /usr/bin/bettercap -no-colors -caplet pwnagotchi-manual + fi + else + /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto + fi + - name: create monstart script copy: dest: /usr/bin/monstart From 7be10e76b98c3dc0d9d3a39574dc90ab49644d1b Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Tue, 8 Oct 2019 22:32:51 +0100 Subject: [PATCH 106/346] enable bettercap ui on manual mode --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 003db66..9adee94 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -402,7 +402,7 @@ [Service] Type=simple PermissionsStartOnly=true - ExecStart=/usr/bin/bettercap -no-colors -caplet pwnagotchi + ExecStart=/usr/local/bin/bettercap-launcher Restart=always RestartSec=30 From e1e2fe73b7dfab87d0297db595a5c26d597017a8 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Wed, 9 Oct 2019 00:33:19 +0100 Subject: [PATCH 107/346] fix path and ui install --- builder/pwnagotchi.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 9adee94..cc28d3e 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -28,6 +28,7 @@ packages: bettercap: url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" + ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" apt: remove: - rasberrypi-net-mods @@ -182,8 +183,12 @@ chdir: /tmp/caplets target: install - - name: install bettercap http ui - command: "/usr/bin/bettercap -eval 'caplets.update; ui.update; q'" + - name: download and install bettercap ui + unarchive: + src: "{{ packages.bettercap.ui }}" + dest: /usr/local/share/bettercap/ + remote_src: yes + mode: 0755 - name: create cpuusage script copy: @@ -402,7 +407,7 @@ [Service] Type=simple PermissionsStartOnly=true - ExecStart=/usr/local/bin/bettercap-launcher + ExecStart=/usr/bin/bettercap-launcher Restart=always RestartSec=30 From 837a234da8ff162c1ec8e98ce9ebe98c139fe4b2 Mon Sep 17 00:00:00 2001 From: Zenzen San <zenzenzen@riseup.net> Date: Tue, 8 Oct 2019 19:35:24 -0400 Subject: [PATCH 108/346] added default key --- pwnagotchi/defaults.yml | 1 + pwnagotchi/plugins/default/wifips.py | 67 ---------------------------- 2 files changed, 1 insertion(+), 67 deletions(-) delete mode 100644 pwnagotchi/plugins/default/wifips.py diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 67ab8e5..dbb7b6b 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -31,6 +31,7 @@ main: - 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz' net-pos: enabled: false + api_key: 'test' gps: enabled: false speed: 19200 diff --git a/pwnagotchi/plugins/default/wifips.py b/pwnagotchi/plugins/default/wifips.py deleted file mode 100644 index 283fadc..0000000 --- a/pwnagotchi/plugins/default/wifips.py +++ /dev/null @@ -1,67 +0,0 @@ -__author__ = 'zenzen san' -__version__ = '1.0.0' -__name__ = 'wifips' -__license__ = 'GPL3' -__description__ = """Saves a json file with the access points with more signal - whenever a handshake is captured. - When internet is available the files are converted in geo locations - using Mozilla LocationService """ - -import logging -import json -import os -from urllib.request import urlopen -from datetime import datetime -from time import sleep - -URL_API_MOZILLA_LOCATION_SERVICE = 'https://location.services.mozilla.com/v1/geolocate?key=' -KEY_API_MOZILLA_LOCATION_SERVICE = 'test' - -def on_loaded(): - logging.info("wifips plugin loaded. :)") - -def on_internet_available(ui, keypair, config): - try: - for ff in os.listdir('/root/handshakes'): - geo_file = os.path.join('/root/handshakes', ff.replace('.wifips.json','.geo.json')) - if not os.path.isfile(geo_file): - if ff.endswith(".wifips.json"): - ff = os.path.join('/root/handshakes',ff) - with open(ff, 'r') as fp: - data = fp.read() - geo = _get_geolocation_moz_wifips(data) - with open(geo_file, 'w+t') as fp: - fp.write(geo.decode('ascii')) - logging.info("wifips plugin: saving coordinates for: {}".format(ff.replace('.wifips.json',''))) - sleep(.500) - except Exception as e: - logging.exception('WIFIPS PLUGIN ERROR') - -def on_ready(agent): - pass - -def on_handshake(agent, filename, access_point, client_station): - wifips = _get_wifips(agent) - wifips_filename = filename.replace('.pcap', '.wifips.json') - logging.info("wifips plugin: saving location to %s" % (wifips_filename)) - with open(wifips_filename, 'w+t') as fp: - json.dump(wifips, fp) - -def _get_wifips(agent): - info = agent.session() - aps = agent.get_access_points() - wifips = {} - wifips['wifiAccessPoints'] = [] - # 6 seems a good number to save a wifi networks location - for ap in sorted(aps,key=lambda i:i['rssi'],reverse=True)[:6]: - wifips['wifiAccessPoints'].append({'macAddress': ap['mac'], 'signalStrength': ap['rssi']}) - return wifips - -def _get_geolocation_moz_wifips(post_data): - geourl = URL_API_MOZILLA_LOCATION_SERVICE+KEY_API_MOZILLA_LOCATION_SERVICE - try: - response = urlopen(geourl, post_data.encode('ascii')).read() - return response - except Exception as e: - logging.exception('WIFIPS PLUGIN - Something went wrong with Mozilla Location Service') - From 56f680e7754d508daabf024e63eb639c756ef5f4 Mon Sep 17 00:00:00 2001 From: Zenzen San <zenzenzen@riseup.net> Date: Tue, 8 Oct 2019 20:46:15 -0400 Subject: [PATCH 109/346] fixed typo in display message --- pwnagotchi/plugins/default/net-pos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/net-pos.py b/pwnagotchi/plugins/default/net-pos.py index 277077d..27f04d9 100644 --- a/pwnagotchi/plugins/default/net-pos.py +++ b/pwnagotchi/plugins/default/net-pos.py @@ -98,7 +98,7 @@ def on_internet_available(agent): ALREADY_SAVED.append(np_file) _append_saved(np_file) - display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)}") + display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)})") display.update(force=True) From f7b869035759bd7549d927146d9994989d079321 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Wed, 9 Oct 2019 09:47:00 +0100 Subject: [PATCH 110/346] move monstart and monstop to systemd --- builder/pwnagotchi.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index cc28d3e..7f29ded 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -407,7 +407,9 @@ [Service] Type=simple PermissionsStartOnly=true + ExecStartPre=/usr/bin/monstart ExecStart=/usr/bin/bettercap-launcher + ExecStopPost=/usr/bin/monstop Restart=always RestartSec=30 From 8af56040349092520b2905a4c0517b4e43e6f962 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 9 Oct 2019 11:59:11 +0200 Subject: [PATCH 111/346] fix: fixed a LastSession bug which prevented the grid plugin from working when no previous session was available (fixes #228) --- pwnagotchi/log.py | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index 0f05789..fa3ddc1 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -22,6 +22,29 @@ class LastSession(object): HANDSHAKE_TOKEN = '!!! captured new handshake ' PEER_TOKEN = 'detected unit ' + def __init__(self, config): + self.config = config + self.voice = Voice(lang=config['main']['lang']) + self.path = config['main']['log'] + self.last_session = [] + self.last_session_id = '' + self.last_saved_session_id = '' + self.duration = '' + self.duration_human = '' + self.deauthed = 0 + self.associated = 0 + self.handshakes = 0 + self.peers = 0 + self.last_peer = None + self.epochs = 0 + self.train_epochs = 0 + self.min_reward = 1000 + self.max_reward = -1000 + self.avg_reward = 0 + self._peer_parser = re.compile( + 'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]') + self.parsed = False + def _get_last_saved_session_id(self): saved = '' try: @@ -158,26 +181,5 @@ class LastSession(object): self._parse_stats() self.parsed = True - def __init__(self, config): - self.config = config - self.voice = Voice(lang=config['main']['lang']) - self.path = config['main']['log'] - self.last_session = None - self.last_session_id = '' - self.last_saved_session_id = '' - self.duration = '' - self.duration_human = '' - self.deauthed = 0 - self.associated = 0 - self.handshakes = 0 - self.peers = 0 - self.last_peer = None - self._peer_parser = re.compile( - 'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]') - self.last_session = [] - self.last_session_id = None - self.last_saved_session_id = None - self.parsed = False - def is_new(self): return self.last_session_id != self.last_saved_session_id From fac4b5c4602494f1d120d4faa05f8a49ec85bc47 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 9 Oct 2019 12:01:44 +0200 Subject: [PATCH 112/346] fix: don't allow plugins to make the main process crash --- pwnagotchi/plugins/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index 38d29e7..6dd7b64 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -1,6 +1,7 @@ import os import glob import importlib, importlib.util +import logging default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default") loaded = {} @@ -13,10 +14,13 @@ def dummy_callback(): def on(event_name, *args, **kwargs): global loaded cb_name = 'on_%s' % event_name - for _, plugin in loaded.items(): + for plugin_name, plugin in loaded.items(): if cb_name in plugin.__dict__: # print("calling %s %s(%s)" %(cb_name, args, kwargs)) - plugin.__dict__[cb_name](*args, **kwargs) + try: + plugin.__dict__[cb_name](*args, **kwargs) + except Exception as e: + logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) def load_from_file(filename): From d4d8c39205202f0864af38f22200b1ad3a5bdfc7 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 9 Oct 2019 12:04:28 +0200 Subject: [PATCH 113/346] fix: creating handshakes folder if it doesn't exist on startup (fixes #227) --- pwnagotchi/agent.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index b29e164..6430ae6 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -38,6 +38,9 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): self._handshakes = {} self.last_session = LastSession(self._config) + if not os.path.exists(config['bettercap']['handshakes']): + os.makedirs(config['bettercap']['handshakes']) + @staticmethod def is_connected(): try: From 1ab183831371e8a406e0208f368add6314caa207 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 9 Oct 2019 13:54:09 +0200 Subject: [PATCH 114/346] fix: fixed auto-update plugin to use pip3 based update process from pypi.org --- pwnagotchi/defaults.yml | 1 + pwnagotchi/plugins/default/auto-update.py | 28 ++++++++++++----------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index dbb7b6b..5f00d42 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -13,6 +13,7 @@ main: - YourHomeNetworkHere auto-update: enabled: false + system: false # set to true to also enable system updates via apt interval: 1 # every day auto-backup: enabled: false diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 8a01a45..43ea6ff 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -17,12 +17,17 @@ def on_loaded(): global READY if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None): - logging.error("AUTO-UPDATE: Interval is not set.") + logging.error("auto-update: Interval is not set.") return READY = True +def run(cmd): + return subprocess.Popen(cmd, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, + executable="/bin/bash") + + def on_internet_available(agent): global STATUS @@ -36,23 +41,20 @@ def on_internet_available(agent): display.set('status', 'Updating ...') display.update() - logging.info("AUTO-UPDATE: updating packages index ...") + logging.info("auto-update: updating pwnagotchi ...") + run('pip3 install --upgrade --upgrade-strategy only-if-needed pwnagotchi').wait() - update = subprocess.Popen('apt update -y', shell=True, stdin=None, - stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") - update.wait() + if OPTIONS['system']: + logging.info("auto-update: updating packages index ...") + run('apt update -y').wait() - logging.info("AUTO-UPDATE: updating packages ...") - - upgrade = subprocess.Popen('apt upgrade -y', shell=True, stdin=None, - stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") - upgrade.wait() - - logging.info("AUTO-UPDATE: complete.") + logging.info("auto-update: updating packages ...") + run('apt upgrade -y').wait() + logging.info("auto-update: complete.") STATUS.update() except Exception as e: - logging.exception("AUTO-UPDATE ERROR") + logging.exception("auto-update ERROR") display.set('status', 'Updated!') display.update() From a93348db64d7c0299afb3dadcf610a32c8ada6e8 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 9 Oct 2019 13:58:30 +0200 Subject: [PATCH 115/346] fix: installing missing smbus dependency (fixes #234) --- builder/pwnagotchi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 7f29ded..c824e88 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -44,6 +44,7 @@ - build-essential - python3-pip - python3-mpi4py + - python3-smbus - unzip - gawk - libopenmpi-dev From 319b2bc31f750cacb2ac1c1033598318cfe7e341 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 9 Oct 2019 14:46:41 +0200 Subject: [PATCH 116/346] fix: fixed text overlap for waveshare displays (ref #229) --- pwnagotchi/ui/view.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 354d910..b743d06 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -39,7 +39,7 @@ def setup_display_specifics(config): width = 200 height = 96 face_pos = (0, int(height / 4)) - name_pos = (int(width / 2) - 15, int(height * .15)) + name_pos = (5, int(height * .15)) status_pos = (int(width / 2) - 15, int(height * .30)) elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1', @@ -49,8 +49,8 @@ def setup_display_specifics(config): width = 250 height = 122 face_pos = (0, 40) - name_pos = (125, 20) - status_pos = (125, 35) + name_pos = (5, 20) + status_pos = (125, 20) return width, height, face_pos, name_pos, status_pos From 3c2a3485c4992b59bf52918ed909fc6c00f0fa51 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 9 Oct 2019 14:48:37 +0200 Subject: [PATCH 117/346] fix: attempt of a fix for inky and papirus displays (ref #229) --- pwnagotchi/ui/view.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index b743d06..423992f 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -30,8 +30,8 @@ def setup_display_specifics(config): width = 212 height = 104 face_pos = (0, int(height / 4)) - name_pos = (int(width / 2) - 15, int(height * .15)) - status_pos = (int(width / 2) - 15, int(height * .30)) + name_pos = (5, int(height * .15)) + status_pos = (int(width / 2) - 15, int(height * .15)) elif config['ui']['display']['type'] in ('papirus', 'papi'): fonts.setup(10, 8, 10, 23) @@ -40,7 +40,7 @@ def setup_display_specifics(config): height = 96 face_pos = (0, int(height / 4)) name_pos = (5, int(height * .15)) - status_pos = (int(width / 2) - 15, int(height * .30)) + status_pos = (int(width / 2) - 15, int(height * .15)) elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1', 'ws_2', 'ws2', 'waveshare_2', 'waveshare2'): From 1b7a5e1f7c9b14480b857ba20324536625b3587d Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 9 Oct 2019 15:19:57 +0200 Subject: [PATCH 118/346] releasing v1.0.0RC2 --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 4265d1c..a1b6a32 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -4,7 +4,7 @@ import logging import time import pwnagotchi.ui.view as view -version = '1.0.0RC1' +version = '1.0.0RC2' _name = None From a2fa33f2fb601118f92a90a3846d5ff6bf8e4ed1 Mon Sep 17 00:00:00 2001 From: evilsocket <evilsocket@users.noreply.github.com> Date: Wed, 9 Oct 2019 16:03:12 +0200 Subject: [PATCH 119/346] Create FUNDING.yml --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..084515c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHubSponsors-enabled usernames e.g., [user1, user2] +patreon: evilsocket +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From d2c160308c5218905569aa9a97a77d5eefe20e08 Mon Sep 17 00:00:00 2001 From: bitwave <github@oomlu.de> Date: Wed, 9 Oct 2019 17:46:53 +0200 Subject: [PATCH 120/346] updated german translation --- pwnagotchi/locale/de/LC_MESSAGES/voice.mo | Bin 4126 -> 4204 bytes pwnagotchi/locale/de/LC_MESSAGES/voice.po | 10 ++++++++-- pwnagotchi/locale/voice.pot | 8 +++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/locale/de/LC_MESSAGES/voice.mo b/pwnagotchi/locale/de/LC_MESSAGES/voice.mo index fd5058bf9014298b2e0eb5961ac5631aaafd9374..8944e859db04407d31442b9cb1e369c182ff987f 100644 GIT binary patch delta 1234 zcmY+^Pe@cz6vy#nJ*)YnQ}$<?YU)mtrcI-kNrL@x5o#`?fr$PthAFZv4Vfs6n5|K5 z8rp=ku+?mW5DE%v69c&kYGWHgB8jL~Q3OGKe>2aZ%bfRlcjkHTJ?A`p;J&!RY=d{t z7@foh;#G}VA6{I{g)#1#1@I=W#v-<03D@8|4B==0_aC^NdAQcB3FD}B4`3H&(cyhu zX;!f3+-Tu9Sce}mir;)gOUza=PvTPSLT%84&oF~c80N<Y>_8o?7xj0iaV?Ib*1d&6 ze25YDw@2LYu@_ttSVleg33YN?Ry}bXQ%v`v4tfE}z^<X5yNwP@r~^!69OqE${Xsn+ zrJU<9g+cbWUT&zGWss!pD3X+&LLDsU&xic^C@Rx2tj9apP1`0=nayy~MjtSZKT!v0 zq)%$qUM?{_hy|50$IVvE<7S+|J@^t;+TW-Qli}(@J*bu)LoGDyJC53T5+nEowQ(8W z;w);P$CQs-dtXQWwZJ@IsANh<y^5m#upL)mKWf7hNRoCLwa^%<$CK#rsqYMSF#m;W zRhrZ2xo%V<hj9yDico)Te3vgAmax)0REg(M2mFpIZ9U~uYhtL@IDg*k&)a-=Bki{R zsCD|0kDcT~rz}Y*0qt+w2uD!m|AsQqXsc4WYB9Yd?Zif+m1rb1^ipXoA`*mNJ`H8A zq1J37QiPIG1q$keN~&yB8kJcu<9b3v*{ULHi(0XhXd$)}3!{^pU4%-lwlx!aZx)9B zmFT_N;eVmZs2XXVgBDdrteEwN0wZVK@cDCt`PQk|Ud9U^A92^O4rcQ=5>v6j*P2k@ YmHb(ED4Wv*wV|>X9QgM@aXND3FC<iF_5c6? delta 1157 zcmY+^Pe@cz6vy#1^~T9`(k9Juvb<)~AIGUC3j@PVR0vW;5VRUMMiRI&aAOSBB3cEB zEeZz0ts9vNjDj{2*v?$FsA*YQFsnom`~Kd$L0&lT^X}!&{d3OKRabVUFR5V0XnjNn z@iJz135U1ypxp}0>TwKPaRQIwBixG>KfZ*`^s9InYpC_oakH~{1|5#$9(;_fSF%}N zk_;?h8-DP9#TNR1FoBJA(FQH}hJF^cQ592|V3H1$LH(|PX}pM9w}?A&5}WWoCfMJe z@Y2P=Gt|VlsFSat!y4u=wIe#{AnFFgsCgsk@Gj~AW!#UiQ0sj{&HsrR+(eRN8PXbO zf9vFhOIQ}!%X)qPwC@j~Zg?Jd;dLB{nT?`u^n!;rn8Q52M{WEY+p(@8%ACU^^b2?p zZ(!*hFH^iIl?$jFRZ$CUplaUQ7%i0ZUO;VFM5S^9HU9xF<8#zT<J6T`dx|>9EGk3q zP^DQ(l7Id17X!O7&h@lm8*(|zqZaB%)wGBX?|5ghkN#_93HyPXw}!e%lG7c+9@NHH z(BVzrf7nF+mD(}`I^jI36dzHgSoZzTzW>ep9Vw&LP`_VCef>!uR3=QXGRir^zm`s< z8e3XHJ=H(|-*A`(-`7XfOXvn_I-uG%!V$Wmno6OjgB&1w2xYIEpn~B+b*X)XvJ-xM zs5W+#N0@Td4Nekjxrp$o6rUngOQlgI$rAsX{#A4lC;WgiM?&m4p|Yr@r|$)~D{H~! TpmMc-HC7pFSPZ7yo3H%^hE`aI diff --git a/pwnagotchi/locale/de/LC_MESSAGES/voice.po b/pwnagotchi/locale/de/LC_MESSAGES/voice.po index fa9afdf..8b96f33 100644 --- a/pwnagotchi/locale/de/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/de/LC_MESSAGES/voice.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-10-05 14:10+0200\n" +"POT-Creation-Date: 2019-10-09 17:42+0200\n" "PO-Revision-Date: 2019-09-29 14:00+0200\n" "Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n" "Language-Team: DE <33197631+dadav@users.noreply.github.com>\n" @@ -121,6 +121,12 @@ msgstr "" msgid "ZzzZzzz ({secs}s)" msgstr "" +msgid "Good night." +msgstr "Gute Nacht." + +msgid "Zzz" +msgstr "" + #, python-brace-format msgid "Waiting for {secs}s ..." msgstr "Warte für {secs}s ..." @@ -139,7 +145,7 @@ msgstr "Verbinde mit {what}" #, python-brace-format msgid "Yo {what}!" -msgstr "" +msgstr "Jo {what}!" #, python-brace-format msgid "Just decided that {mac} needs no WiFi!" diff --git a/pwnagotchi/locale/voice.pot b/pwnagotchi/locale/voice.pot index b6489ba..cf02362 100644 --- a/pwnagotchi/locale/voice.pot +++ b/pwnagotchi/locale/voice.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-10-05 14:10+0200\n" +"POT-Creation-Date: 2019-10-09 17:42+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -122,6 +122,12 @@ msgstr "" msgid "ZzzZzzz ({secs}s)" msgstr "" +msgid "Good night." +msgstr "" + +msgid "Zzz" +msgstr "" + #, python-brace-format msgid "Waiting for {secs}s ..." msgstr "" From 63d95a53c01997f0b3bbcce0bbba2035f8b5babd Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Wed, 9 Oct 2019 16:57:00 +0100 Subject: [PATCH 121/346] add papirus display system requirements --- builder/pwnagotchi.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 7f29ded..c57e38c 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -11,11 +11,14 @@ - "dtoverlay=dwc2" - "dtparam=spi=on" - "dtoverlay=spi1-3cs" + - "dtoverlay=i2c_arm=on" + - "dtoverlay=i2c1=on" services: enable: - dphys-swapfile.service - pwnagotchi.service - bettercap.service + - epd-fuse.service disable: - apt-daily.timer - apt-daily.service @@ -78,6 +81,10 @@ - fonts-dejavu-core - fonts-dejavu-extra - python3-pil + - python3-smbus + - libfuse-dev + - bc + - fonts-freefont-ttf tasks: @@ -134,6 +141,33 @@ path: /etc/dphys-swapfile content: "CONF_SWAPSIZE=1024" + - name: clone papirus repository + git: + repo: https://github.com/repaper/gratis.git + dest: /usr/local/src/gratis + + - name: build papirus service + make: + chdir: /usr/local/src/gratis + target: rpi + params: + EPD_IO: epd_io.h + PANEL_VERSION: 'V231_G2' + + - name: install papirus service + make: + chdir: /usr/local/src/gratis + target: rpi-install + params: + EPD_IO: epd_io.h + PANEL_VERSION: 'V231_G2' + + - name: configure papirus display size + lineinfile: + dest: /etc/default/epd-fuse + regexp: "#EPD_SIZE=2.0" + line: "EPD_SIZE=2.0" + - name: acquire python3 pip target command: "python3 -c 'import sys;print(sys.path.pop())'" register: pip_target From 2c1a9c471cbf7d8158a7fe7c18c0f802fae04e60 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Wed, 9 Oct 2019 17:50:53 +0100 Subject: [PATCH 122/346] generate sha256sum from the generated image, add it to the release --- .travis.yml | 4 +++- Makefile | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c5fe50b..2395be5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,9 @@ deploy: secure: vBUokTv94n8s65STUgTiD6I0Iy8KXbBRvQUrAof8XG+U4ZMsH5PmDTpS+wz+SaxI6o0PRkfyOiPVdARhiKAFnfatG3q9EHllMQwqRR2YIju51A3aCxgEJ5uWDoybwQdipERUMMYwUO/8XZaRRpwFD2bdQBFWkBtQyMcAkrEL8BXckwQQ531oDN2hK5gAiTllqsOswV2idwUlBRU9jOtStzff+UgUYsp/ZebsRodyOYkEB2Ev15yARo2HTXbyZ2icwHPtMbx5zmNUSRtxs9a4hfzaK3m6ctK8qLYYUdQvXub/ruuACapdw4Ez88LY1agTecbZhFYmJzv8oANH1e4VUI4owuHnZCpU6LRutS4wOhglrkOrGo6lSUlJeA+RtQjyjBugjej9DDtDyyIlRU1ZaBF3qWR9N5EXKuquf0olOfmUR67ap1NykE9VUpzkYjkoVRTiPs/e2onM/nRNOvAQcIt75FD13u+Y/DcYQ8r7KpMIu1HNdtbVx8gMeq76bRhP1YdDg2jm+DdJ21KWjf5QHsbyoXDfJzdKlCloLIlAU3EPJhMoXsnNzre0/FXeUl6dfteR1axNS6U7e/vKsQ9rlUFZWIQaeVPjfXmFKblNNVQ5uFrrsB/EGHcJl7IUx5fvcRT5hMMNwC660YxVkBXDbRb5fxMW5/+K0BOi9cP6en8= skip_cleanup: true file_glob: true - file: pwnagotchi-*.zip + file: + - pwnagotchi-*.zip + - pwnagotchi-*.sha on: tags: true repo: evilsocket/pwnagotchi diff --git a/Makefile b/Makefile index 1344670..103fa62 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,8 @@ install: image: cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img - zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).img + sha256sum pwnagotchi-raspbian-lite-$(PWN_VERSION).img > pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 + zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 pwnagotchi-raspbian-lite-$(PWN_VERSION).img clean: rm -rf /tmp/packer-builder-arm-image From b2a7462b44dd743648bd184be37897eb7bdad206 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <caquino@users.noreply.github.com> Date: Wed, 9 Oct 2019 18:55:22 +0100 Subject: [PATCH 123/346] Fix typo add sha256 to extension --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2395be5..933c5ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ deploy: file_glob: true file: - pwnagotchi-*.zip - - pwnagotchi-*.sha + - pwnagotchi-*.sha256 on: tags: true repo: evilsocket/pwnagotchi From 315bfd29e50686bbbf42d8d621df81e02fddeaab Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Wed, 9 Oct 2019 19:11:14 +0100 Subject: [PATCH 124/346] add pt-BR translation --- pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo | Bin 0 -> 4290 bytes pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po | 209 +++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo create mode 100644 pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po diff --git a/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo new file mode 100644 index 0000000000000000000000000000000000000000..88f2298633744b2b139064d1222dd04701755fa2 GIT binary patch literal 4290 zcmbuBON<;x8OO_Dz$}n}d4>e2!6CNs&NwE3?M+BB@owxT`@&w^vO$5G?%J8kc6Uws zv76b|3W-CILX-mvLcjr`2uwIcyaEyk<*-6XAn}lpkT`&pLk=L$91w`#SKTvfTW~<M zrsvmHQ(g7Fs{Zq*gZsavxK484!TtMvO3lNkU(FZSclRsxdiXu~cKB0x6#fpr1OB7w zKlmD@-c0)lybGR>&$5DYyeQ{1JRB`~#Hv{tDj&_q|rBcfwm69)@qD{Q!I&T!k|K zr{GUu3h#nP`0*fIhGI_-%6lXDF8B<Td9T4K_!7Jo{u1(0f8?tLUxqSn|AE@>JD{e0 z8XkjJq1gE(6#Jir;@`KShChe0{wwf3@SjlTyPe?4_<P{H;aMnl^x#`y3U7g*g@i&q z0|}{mwrPI_;;Q;O6nnqbv|ni2FGBJEXHe$(Ej+zXsXs$m=e9T0alRAYOM9kiZ$OF9 z(@^aACj21$F}xkV3?*;>f)eMGZ>;@$6w3GtN*q5AB@SPP;@=M&{s!_<f8k5w@^|<W zyoDg1fWL+^j~Fd>9Bg<D$~?1B;`eFDM{V*YaeNXITJ=?U7(NGO{I8(o@fE1y0gSs7 z9))7(5?@k3k3#X!!}r54Kw19<_yPC}DD(ZT=|8|r;$I6&Jnx5M_dH}N^+_msdkjk6 zhE2PIlDBOrdHY<$r=ZyXMJRFk8e}Q;eMs8EC4S1>^4_7)Q^!o^mpSj@79ZZnEtl9Y zm)Iwl_|@T-I%L{#k;bs!+VH*HALSN5$Jg=Z;Yh<*!4GpEZQ3Ur9)ovt%N`TIB^F}8 z_#*k1>o#tw$q#W$?54S8@5n`2hB!;DHAhUyo8(gJOfJcr<X&@sgnN8R+*Eg7=hnod zj_MYLkDMu8*4L%i+nWPZ?x-`~C)0Y<>b@`ac2=c3I<uR4V6wOvn0327Osd=@J1sS9 zO;rwTR=TL3WIHv{jygMeZ}LttrOugXT{Bv*Bqp<EOP#Yx!lIc;?M_QCxyXtUskNmZ z`Kr_W!mv?enNDnZxX?YT*K%jEM&=kz>xgACo9ON4VMn`yA=b9^6<_7LyP`K;lIX&W zFu-K%MMs@;(|Z1Js#i8MgP+mBwe*G3<RKQFPDjn_HOriZ*Cz3q)pg%+GZP<jyXJGN z=e0>0$uvEhaXp5XeWJ{5YLn4T{R6Whp=QpsiUxYE%39&=#D#tdPU6;V-C3A8{J3HB z5vGr4->?M0W*SRmF6_|TG5IK+&vrC9((5i-$C&N7&Q83eql9pkMGKF4-6UcFQQkI9 z{w-r9#Z6elET*)2doO=Gd--RcL#*C}Ej%Z_UlZ%~p#ho3mtn}WK2)ra$hZ1<Rg^lm zk&7{oG_bx@3k%!0(3#hl-NUY>9>Mw^`4s2EfVxW@3Z+<-!Bq-sAyh-(hZq>WX_EDp zTJU~dUNyO|h{ciZ!bZhTaa1ju7>6d++_*ZW774@&J+w9tkM#&?7HZKIg^gQkaipW% zrB=!+81C0}ROQYWt{jD=FPY(Rl8t)Bx}TuNh7t`MsLIOviRe+gOTOph5q6UrseIx^ z%asF?6W(4P7Sj|9sT#=So8(Y$=AK82QJ=Rx?<eJYVc<%PVKF^(TwNf+CL6JAx#{!u zqSb1tRjMnqvUA7X;!krDtPXe%?n!w`qL>Ib(@|5A7YAurweNl08%ZQ(+p3G1lQTk| z<@L;(yf<o%f4yv6FfbJ9bz!+IJBXcZrM4vq546<ff%n5Atg$y=pPkoHSH?$EOI_XC z;<I&KUZr_ezQPYOU~5Ya*!JP8P^rtR(l#g*R`@J#P^cT13Q=#m(kA&Uxqr+?WqUqu zU!o}3WixuEd*+eu**Se_?)-)M<)x|fcIfl6y;$^J+<vI)7wuI)qo-Du+vn|ulXu#) z?57!hWv+X^y|~<-?do&OGmG=bt}ZXlO)Z$BY_H}fD-u)sd`6#PV>-i@=ssRKa9`AP zn9zPOwVlYP_w!=buT0;zSFK5B^g?&(>_xHbzJ-Mc6W8&1e{al;ekeCvE@581g0)q@ zvW1;moL`*VbL>Rt?x{0AE2*^h>S&0?C1=@jD$`~6$~l`Cw)|LCuC-6?jT6JyY~G&B zA|F$zGx}7|l~bZKL#nkj6QRjsuQ7ZqGz+$GP@2paIx@qO(oA`*ci`mY8LJJ$HvG6D z($hwJYUz<_Yvoz7c;#-Mq7ff{@QwJGmRe4BztPlbORo?bn`@h5UN*4N?swXWpD55A zGX`nY8^OYYp(b^G&nBKjM{vqyY^y!}VFxig$9j$iU9go%kjsrW{hVVcvUx`>BRF#v z+q)90CdVmfbmq`x@ku0St|%E%?mp)ky5+J#-OULytvPi)TH_cp9!NlJ>`d&N7i%SA zd(y9O{*hxQc5FfRD66HFD?JW{1r3?ou&xYt7`@?QLe|)lv!HDn;U!&$uG-#8<2)u~ zVK@;9ED7IJC|m0jA-!3jEW$!WN|W|>pC4y}aW|+GMNl-s*H(&&0h3xO{(qI~My5xh zpcO%?T&^FlIPGesk}M2!B&;wp;=c%1kg!P=5)HF^ZTCe>6<!ME6PsLDsi9~%n+8TA zyPSv*{MGOr$T5<>r&|xZt)`x31B`v2;$NB+svB$9xOu&Pt=K$vF*#kN0&4}^eO{_H zh*^vUB4P{?X_R6+<M|(iYGVlpnr^f+{Ita4REwF`Jc;eQ*Xn0XYUGS`SCTjamg`>b zvIs{dLsY5E9^}j<BG@M3gqBWJ%-vA$vo3@-#dIS|_K6C4G@?^6^?{6a!{b=Vh%Os# ze9TawR5>2<-)n#a*o%<q5ay<a$2w+8LJ>Alecp?bg*Y_F|9IlvA3WhwGf}iI=Pj1~ zrwDb?6;=-hl|F1JhM{cBH|ZLVn4zJJY$eA)bGG~s+Ljx%EjNv}`C8k&Xj`BEJbuiH Qx?Q(mNJQh}a9ke$20@~S`Tzg` literal 0 HcmV?d00001 diff --git a/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po new file mode 100644 index 0000000..858b617 --- /dev/null +++ b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po @@ -0,0 +1,209 @@ +# pwnagotchi Brazilian Portuguese translation file. +# Copyright (C) 2019 Cassiano Aquino +# This file is distributed under the same license as the pwnagotchi package. +# Cassiano Aquino <cassianoaquino@me.com>, 2019. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-05 14:10+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Cassiano Aquino <cassianoaquino@me.com>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: Brazilian Portuguese\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "ZzzzZZzzzzZzzz" +msgstr "ZzzzZZzzzzZzzz" + +msgid "Hi, I'm Pwnagotchi! Starting ..." +msgstr "Oi! Eu sou o Pwnagotchi! Iniciando ..." + +msgid "New day, new hunt, new pwns!" +msgstr "Novo dia, Nova caça, Novos pwns!" + +msgid "Hack the Planet!" +msgstr "Hackei o Planeta!" + +msgid "AI ready." +msgstr "AI pronta." + +msgid "The neural network is ready." +msgstr "A rede neural está pronta." + +#, python-brace-format +msgid "Hey, channel {channel} is free! Your AP will say thanks." +msgstr "Ei, o canal {channel} está livre! Seu AP ira agradecer." + +msgid "I'm bored ..." +msgstr "Estou entediado" + +msgid "Let's go for a walk!" +msgstr "Vamos dar uma caminhada!" + +msgid "This is the best day of my life!" +msgstr "Este e o melhor dia da minha vida!" + +msgid "Shitty day :/" +msgstr "Dia de merda :/" + +msgid "I'm extremely bored ..." +msgstr "Estou extremamente entediado" + +msgid "I'm very sad ..." +msgstr "Estou muito triste ..." + +msgid "I'm sad" +msgstr "Estou triste" + +msgid "I'm living the life!" +msgstr "Estou aproveitando a vida!" + +msgid "I pwn therefore I am." +msgstr "pwn, logo existo." + +msgid "So many networks!!!" +msgstr "Quantas redes!!!" + +msgid "I'm having so much fun!" +msgstr "Estou me divertindo muito!" + +msgid "My crime is that of curiosity ..." +msgstr "Meu crime é o crime da curiodidade ..." + +#, python-brace-format +msgid "Hello {name}! Nice to meet you. {name}" +msgstr "Olá {name}! Prazer em conhecê-lo. {name}" + +#, python-brace-format +msgid "Unit {name} is nearby! {name}" +msgstr "Unidade {name} está próxima! {name}" + +#, python-brace-format +msgid "Uhm ... goodbye {name}" +msgstr "Uhm ... até logo {name}" + +#, python-brace-format +msgid "{name} is gone ..." +msgstr "{name} desapareceu ..." + +#, python-brace-format +msgid "Whoops ... {name} is gone." +msgstr "Oops ... {name} desapareceu." + +#, python-brace-format +msgid "{name} missed!" +msgstr "{name} perdido!" + +msgid "Missed!" +msgstr "Perdido!" + +msgid "Nobody wants to play with me ..." +msgstr "Ninguém quer brincar comigo ..." + +msgid "I feel so alone ..." +msgstr "Estou tão sozinho ..." + +msgid "Where's everybody?!" +msgstr "Aonde está todo mundo?!" + +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "Cochilando por {secs}s ..." + +msgid "Zzzzz" +msgstr "Zzzzz" + +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "ZzzZzzz ({secs}s)" + +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "Aguardando por {secs}s ..." + +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "Olhando ao redor ({secs}s)" + +#, python-brace-format +msgid "Hey {what} let's be friends!" +msgstr "Ei {what} vamos ser amigos!" + +#, python-brace-format +msgid "Associating to {what}" +msgstr "Associando com {what}" + +#, python-brace-format +msgid "Yo {what}!" +msgstr "Oi {what}!" + +#, python-brace-format +msgid "Just decided that {mac} needs no WiFi!" +msgstr "Acabei de decidir que {mac} não precisa de WiFi!" + +#, python-brace-format +msgid "Deauthenticating {mac}" +msgstr "De-autenticando {mac}" + +#, python-brace-format +msgid "Kickbanning {mac}!" +msgstr "Kickbanning {mac}" + +#, python-brace-format +msgid "Cool, we got {num} new handshake{plural}!" +msgstr "Legal, nos capturamos {num} handshake{plural} novo{plural}!" + +msgid "Ops, something went wrong ... Rebooting ..." +msgstr "Ops, algo falhou ... Reiniciando ..." + +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "Kickei {num} estações\n" + +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "Fiz {num} novos amigos\n" + +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "Peguei {num} handshakes\n" + +msgid "Met 1 peer" +msgstr "Conheci 1 peer" + +#, python-brace-format +msgid "Met {num} peers" +msgstr "Conheci {num} peers" + +#, python-brace-format +msgid "" +"I've been pwning for {duration} and kicked {deauthed} clients! I've also met " +"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" +msgstr "" +"Eu estou pwning fazem {duration} e kickei {deauthed} clientes! Eu também conheci " +"{associated} novos amigos e comi {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" + +msgid "hours" +msgstr "horas" + +msgid "minutes" +msgstr "minutos" + +msgid "seconds" +msgstr "segundos" + +msgid "hour" +msgstr "hora" + +msgid "minute" +msgstr "minuto" + +msgid "second" +msgstr "segundo" From 19b0e00bf525db407cdf5f8ae164e6129a2cf541 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Wed, 9 Oct 2019 19:13:27 +0100 Subject: [PATCH 125/346] add pt-BR translation --- pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo | Bin 4290 -> 4291 bytes pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo index 88f2298633744b2b139064d1222dd04701755fa2..78749bd2ba338c523a34b033a26fbe7181b2fb4a 100644 GIT binary patch delta 412 zcmXZYze@sP7{Kwz)I2pkQ&2Gn?@)uorOmCWrKNvB(50!R7I6@6a8S|BMX<4^hJr(A z6Krt^@_%q>5Srcc`{=>T^LgKwd!HY-w7#s5(21KCk=MA0gP(YT8?56$R<V92lEo&T zV;fBzU=gQSz`5@lS+XVQyB#jjOo&|J4{Dx3Qe4Ti$t2}q4K>jX=CFk<=@2s5M+0Yg zi7%+%f1`!JSi&gXmC-?M^p23iChCBXXye#)kA>g4sBp1DP56h}Sd>>fp^Y)TM(z9- tsU-~z;{%59$v?O8lyes&*z+Bs4lqIeGtUMaYKi9!gSHX4_uk^U@E%@XCw~9{ delta 410 zcmXZYKTASU7{~D+>di~my;hKt4PD8inwn~AYH8^W2)Eru7jba7(2$f{OQ2)4Nk~^{ z4031)>OD9#1kP>Y_t6W7=fgRi^ZYsE;5`^-Qqh{Er<`<%uUNtfp5j05;mL}$i6Pqk zCi>XHZG6Bj93_4uOTPqr_lIxjtxCuEg_cL2A{BAn)7&GpMCVw*D`crf*uWcfv4{Kk zh<5)I1N^}qbm?vv56~LDB;+wf8?cUL?E2B7@G}P$4nEKlPSF~3d1Vs@xQu6LonIie pR7D4Cn88M}Z{iaBHfHfU@eXZ(d$gbFTLEh6CB99U-FT2II5X*qCg}hG diff --git a/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po index 858b617..bf27f67 100644 --- a/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po @@ -27,7 +27,7 @@ msgid "New day, new hunt, new pwns!" msgstr "Novo dia, Nova caça, Novos pwns!" msgid "Hack the Planet!" -msgstr "Hackei o Planeta!" +msgstr "Hackeie o Planeta!" msgid "AI ready." msgstr "AI pronta." From ad87ea479135bddda7123ee7b1463db49b4adcaa Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Wed, 9 Oct 2019 19:14:42 +0100 Subject: [PATCH 126/346] add pt-BR translation --- pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo | Bin 4291 -> 4299 bytes pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo index 78749bd2ba338c523a34b033a26fbe7181b2fb4a..e462ab32eb3d09e07c5a12e7b1905521d4bec720 100644 GIT binary patch delta 367 zcmXZYKTASU7{~D+dGjCFs~15-4epI1aJZm2x;sVC(i;eLchMyq91MpB0|(n`bO;O@ zA{-oD8iaZm4ncF@qX!<I&v_m==RD`E!gcuCJQ(C9ja(_fCoJKo^#=+4k_xDdw1gto z@Ed(hdD0P<tqnBiTwoD9Na&7a?t8!_K6@JIoW-1m*|5P7w-}(Gl}@pagsw?BY-1UF zc!G~;26)Fa{6cfypY8i>s!&%kjV&~TU*#_Mlikobq|rqO`?kKtIQ0+{IJQpEykLgr cXI_xI^s%^)c_}A|qG&R8k9Uj3o11n10q#d6lmGw# delta 357 zcmXZYF>3-r5QgC~YV_p1Qv@R#At#C;Ht7SY(xkaRAjIBA8?g{9EL60!5wuJn;jj}@ z*a!;oUswpKEqw<UmU(sucK6#ocsp<X_AqcoWNeE(;{x-zF`kei7fKed_=|2*<Q4xg zjUh)QhZ$oX^_&lU!X`4LN6Fv_<G6HWAXO&cG<3reJv`$JTBIvu4K=7wN#Pe3u#F|0 zp$7QD3hq(Q`!{`yP5QtRCh#3K_-D$$Cu`7%(r94>J7(R*N7j9e;m|lmeZd^{XI98v R5(Ja5W6hU#G1%H}<ObZGBKH6Q diff --git a/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po index bf27f67..8088ba7 100644 --- a/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po @@ -40,7 +40,7 @@ msgid "Hey, channel {channel} is free! Your AP will say thanks." msgstr "Ei, o canal {channel} está livre! Seu AP ira agradecer." msgid "I'm bored ..." -msgstr "Estou entediado" +msgstr "Estou entediado ..." msgid "Let's go for a walk!" msgstr "Vamos dar uma caminhada!" @@ -52,7 +52,7 @@ msgid "Shitty day :/" msgstr "Dia de merda :/" msgid "I'm extremely bored ..." -msgstr "Estou extremamente entediado" +msgstr "Estou extremamente entediado ..." msgid "I'm very sad ..." msgstr "Estou muito triste ..." From da116ea2ad708157c0d855e264583e06acd159eb Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Wed, 9 Oct 2019 19:15:40 +0100 Subject: [PATCH 127/346] add pt-BR translation --- pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo | Bin 4299 -> 4288 bytes pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo index e462ab32eb3d09e07c5a12e7b1905521d4bec720..3905aad40a09d9513eddaa553959d56caa194684 100644 GIT binary patch delta 236 zcmXZVtqKA`6o%pB-zYAuYY|bw9Z{@afZZZ?!6t$RUD&~3(&#p;_po7MtHELrgEwLG z77V@%1H*av4xIU3`mU2({@4+b*cEBv6E%L&M^zK);}Cf=Wq7#2Hg2(p5jHW#0X{JM z?`!vpqpYWkRUBg-C!WlNE}63^;Tns$v)P~`AFzxk>lrKL3pVhIbV-s%g^}Z!Rp>AG M;U+La`gVW0Kbt@tC;$Ke delta 262 zcmXZVuWJHv7{~Ev{+cM<d0}Iy;d|m37!L7YFxVDWmXG_2enB~K2wJpQ#Hh_`Gzb?A z8tj$@qj6m<%X<$F9-bGU2cG9Sxo3BGOibTIWNV2O@q#YiP@!XsRI!1Se9{Wo!4i(J zf-8K%J-*^M{@?qJ=MGC{at(9XLI*zz@-F-m%^;0mn8I<KCzv76@DUfWOU#nj_>3Fm smmH#bvYvNC6%0(jt+cO#;lPC2XkWLL=Xuf$gRbc<@AgylV_hHq0C12cr~m)} diff --git a/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po index 8088ba7..1e7f967 100644 --- a/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po @@ -73,7 +73,7 @@ msgid "I'm having so much fun!" msgstr "Estou me divertindo muito!" msgid "My crime is that of curiosity ..." -msgstr "Meu crime é o crime da curiodidade ..." +msgstr "Meu crime é ser curioso ..." #, python-brace-format msgid "Hello {name}! Nice to meet you. {name}" From d0f34f952864cb5e4ebd4898b992e4fcf0ad0cf4 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Wed, 9 Oct 2019 20:41:36 +0100 Subject: [PATCH 128/346] add pt-BT to the lang comment on config.yml --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 5f00d42..92aa9c6 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -1,6 +1,6 @@ # main algorithm configuration main: - # currently implemented: en (default), de, el, fr, it, mk, nl, ru, se + # currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR lang: en # custom plugins path, if null only default plugins with be loaded custom_plugins: From 078ab632496aa1746b227d106ad8bfe95138cd0e Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 9 Oct 2019 23:14:02 +0200 Subject: [PATCH 129/346] new: grid plugin now reports brain.json info --- pwnagotchi/plugins/default/grid.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 066b794..40cd1c6 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -8,6 +8,7 @@ import os import logging import requests import glob +import json import subprocess import pwnagotchi import pwnagotchi.utils as utils @@ -37,6 +38,13 @@ def get_api_token(last_session, keys): # sign the identity string to prove we own both keys _, signature_b64 = keys.sign(identity) + brain = {} + try: + with open('/root/brain.json') as fp: + brain = json.load(fp) + except: + pass + api_address = 'https://api.pwnagotchi.ai/api/v1/unit/enroll' enrollment = { 'identity': identity, @@ -53,7 +61,8 @@ def get_api_token(last_session, keys): 'associated': last_session.associated, 'handshakes': last_session.handshakes, 'peers': last_session.peers, - 'uname': subprocess.getoutput("uname -a") + 'uname': subprocess.getoutput("uname -a"), + 'brain': brain } } From d72c1d9c93bbedb280fe80462c45c64ee53d6ae8 Mon Sep 17 00:00:00 2001 From: diego <dpcalvo@gmail.com> Date: Wed, 9 Oct 2019 16:44:55 -0500 Subject: [PATCH 130/346] Add support for spanish language --- pwnagotchi/defaults.yml | 2 +- pwnagotchi/locale/es/LC_MESSAGES/voice.mo | Bin 0 -> 4379 bytes pwnagotchi/locale/es/LC_MESSAGES/voice.po | 214 ++++++++++++++++++++++ 3 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 pwnagotchi/locale/es/LC_MESSAGES/voice.mo create mode 100644 pwnagotchi/locale/es/LC_MESSAGES/voice.po diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 5f00d42..071aa37 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -1,6 +1,6 @@ # main algorithm configuration main: - # currently implemented: en (default), de, el, fr, it, mk, nl, ru, se + # currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, es lang: en # custom plugins path, if null only default plugins with be loaded custom_plugins: diff --git a/pwnagotchi/locale/es/LC_MESSAGES/voice.mo b/pwnagotchi/locale/es/LC_MESSAGES/voice.mo new file mode 100644 index 0000000000000000000000000000000000000000..00ee652c7572c24767b28aa10f99be20823937c8 GIT binary patch literal 4379 zcmb`JTWnlM8Gxs>1>!=1atkelSwfSh@owxi(8Mim-NYm=c5KB?qV@%5&l&GdeD=)t zT<mPDeW8y$Auj4uAp#XukcTQlhzK5#5k)*y2qXjuK|x3qfe;d+5~v8if6iHN5}uGa z^6_`(_Rl~6W&Zv3UAKKf@f_oQKkx5vQECZ(<5vFhyn35byWk(;yW!v90l4dSrQQP{ zfOo(rpp2h`Z-)uo2ZvDPeia^t--8<d6TTDP^Tul3A^29>bMU=zrRF+(7wu=@o8Yrh z<bN4ngD=8;Fk$d6_&F$gJr8Bwcj0dM6DV^31SjCFZ&vDU@J>jV+5;PKAC!5gpy>T6 zsNoPEgwH|I^G8th|2dR-e})?F!dSBZ{qO^D7K*%2LYcn_?}nd&qQ~=4O#LE!2mB_y z6TSp7k@^|L<my*-`?ruIt3N=|`!99-wYvQ;D0a9V;UZ@@Y}}&Me#nrT$5}GI0>vL| zQ1tr}l=<I+_rM=OnfE*RVfc6WK6v+AlzIprfqP*e%6UHzW!=}Ita}NHJ%3sEzYb-e z-55{oH^o2k&m-{ra0O1vI7)mF&Ox!Og<`i)*X=LXyb8sCKZX)_uRvU&UW50-e?!sl zUMAfK=iwfh!TaH7q1gL7P~z!jDEs{eir%k7k-LXM@#7IF`ksMu&Q9Ha24Y(EEEGR} z1&Z9SK~zxRhT_K`Lh<9vb^E7K{P+tfetf0ot5DAIS15A+0Z~QWK?64iTf6}t=9T9l zujn;;4)7s%lzg7%72n8tL>JLRo))j{E&h~8^p_lJ@=A<|{YTG7_z-_y{~VzqI*Xp8 z2loX%r7dwKF)nr@Y=T|H7yEhTIm9b5CC^8B#b4qZdF0Gucgb1t)9BHo4~R*29*HSJ zEBJT{iVq*)6<dFZ_d#CqpPHT5nKkjCrDpToN6r*3?dZbmt<A0}hU&QY$)w)2y5kGI zm6pAsPVJ`dnl#S4X5DV}lQJ{Ou%S*^Qx;vD7A~rUZ1qeuR42WUb?Q3ZqQ&<}#?JbD zLY*?vx@M(bPE2ZxhB{@F1O-ylv%`iy?IJ4*^{g%Qz?ZFhglU6XOP$zaf3DkBuVv1n znaCJS>WFPpo9M0jW2jxu6l)v$Nnd7qc3E$_B+<DUpnyr&^Oid0CiVROo?hNe4QGzJ zuAx^7lLfo9S}irN*DNw~uTA1ptE;~LW-4dM?3&N4p4X<wOvJQl#?>5J_F|FQo=pZf z^>@vNm>rqDGV1EJGHry_i3|N=oW!l!sxvon7~HVg0M$p*H!KFQsYVl#g&uk<#y5rY z=}_Y@z3!rQl-Y_aPsKwWC77$o8=RQsCJ_a&@|LM%!7@kOU7IyXF@@DzWB(1uv4EVt z>|UEKd?&tB5$*NfE}rHr{orT4H(wv%Z}mi(7dp0)i%||Yu)jnO8{0V7sn_S-$6Z5x z0`1%QQw|p<R9$kQ5SDonj!Hl+glOpaU<0E!O|srl3*N8GQj_@-Timym+bAFA2h^g8 zInYjI*GHXDix^^B_pQysXEj5bxmt92ZsUep9Ox)>Ju6`q6!&X7Dl_MER}6yFPn&*! z#~am-RX;|J4IvtmsZ5LN3+oZPr+wSU1N6o<68YGRmMgkAC#+uT=aU2ruIh^LO?;>~ zGtVc%sL$B8_dDUb(sc#Ou$i7Yq*idSNe66OZ2D|HZ!{WemFP;XWbvq5&QsqCt6jds z@g%&&Q3&Fgy+Iw3XS=<yYll2<55$s^w(4x^B-iCzmZ#Qa?LlKSdfvF8U<lId%yM2b zh)lK;+hT-A8|r-5`+gqw7|ZLBc`fzi=$dM%r!HNRm%mHb`Bs`w$-gil6PVT|^~1kh z^;}vOwnnbd#HVqMT-~@`uzcOss{>#;^A~JXH0R^yIRb-Bo6%FPsn+zw8Qb?+(Ok?s zE^a<vcJk(`pV1S`OU*NO!^zC%2{LI$AD)^%)|{SdP94*Sr)Q=fJUGQOv0(C|xtf_Y zPfX#n867*@@qKzp>xbj&<M6S^Oj7nzpSMc$bgOsq5!R)h(sXQd)tcUnUYI?7^6c!% zIsNd$!efbR`K&XZGo$mqNnPHZSe##+8y{f0H8pYErv*{fTpje$vEbf1L=?L8A-Pkt z+!h}zi?!y_@jOv=&1TKHH1aV)IiruZT`{5NXNeP1Q|gP9mo(<y92J+7Hg$&b;@d^J z;d(wNK0<{l?S>y!mht^E>XBNLQ*5eQQ%elR=JC>|M(&KLYq`spZjw8`wW_LPjpin= zt|LRu@CmBbWyhuw`Daj<>w&QFk<AKXZb@Q1)JC0SV{|q>*YTt+Ba<SJb)?d`=8!ep zm$5=SqwcJ3Uz4&!eKVbmn+K`em#I2zEh#;?q3+D}`u0*VnG~5tt8=-oy)I13Tx3Z1 z@@8uNq$b=%DnkV=dl931HOIe!HtjOY94C?KRaaGrOiX^_=o@FS4=S@RrutC#D5zCk z@{M}+1_PlNdu_NF(^XQ7j9sx&)#{a0)zA|*J{81@LAm8@IF;ypU5y%`cE3#W?HB&L zMp=r>s_C`e_6up{j_oTqmMMvuku&r_uFA;wwy*quYt?LI+94uBy>gj;x+EN`a^<<@ zBFoip>WVcNP7_W_!Iz&Rqwtpn7_Ax~$312@5m$qyh@A_uB^D~F=_a>Wf6<X<7?AMd zSA~rk9M`c^F1k5|M&_8{XB+{HF{ZWSen@uORlbEjo2zA;#l(qhq_?l7F7j1<O9=># zuj|?t*S|SdvF~Nkr%a86CfbKEtV?U~`e|{Cj<>H6-YMS*F_(z>Q5%=lMbB`>VNOQ{ z&=<;%$;8c)T0zbdHDHp1)67lcwy#ph5*eY~IW%>ZCKBN%5H3mELsV>}>K!j7m8GV| z#1SQL!N_;0dR~$h)kBJgi;C!#P?c2f*$dcMw66r#(LSnqsJwkuF0D}V5L%{WgZv-~ zb+3|2hDRYo`nK}k*gU9uHAT~EV&O5RY|8Hw|EM|}enE_6<@~lSt(qJC<ozEjUS_Cx fnW-zDuPUCGir10dM%<}+)po)1^<mJyijDsOn8u%Y literal 0 HcmV?d00001 diff --git a/pwnagotchi/locale/es/LC_MESSAGES/voice.po b/pwnagotchi/locale/es/LC_MESSAGES/voice.po new file mode 100644 index 0000000..d9006da --- /dev/null +++ b/pwnagotchi/locale/es/LC_MESSAGES/voice.po @@ -0,0 +1,214 @@ +# pwnagotchi voice data +# Copyright (C) 2019 +# This file is distributed under the same license as the pwnagotchi package. +# FIRST AUTHOR diegopastor <dpastor29@alumnos.uaq.mx>, 2019. +# +msgid "" +msgstr "" +"Project-Id-Version: 0.0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-09 17:42+0200\n" +"PO-Revision-Date: 2019-10-09 21:07+0000\n" +"Last-Translator: diegopastor <dpastor29@alumnos.uaq.mx>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: spanish\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "ZzzzZZzzzzZzzz" +msgstr "ZzzzZZzzzzZzzz" + +msgid "Hi, I'm Pwnagotchi! Starting ..." +msgstr "Hola, soy Pwnagotchi! Empezando ..." + +msgid "New day, new hunt, new pwns!" +msgstr "Nuevo día, nueva cazería, nuevos pwns!" + +msgid "Hack the Planet!" +msgstr "Hackea el planeta!" + +msgid "AI ready." +msgstr "IA lista." + +msgid "The neural network is ready." +msgstr "La red neuronal está lista." + +#, python-brace-format +msgid "Hey, channel {channel} is free! Your AP will say thanks." +msgstr "Oye, el canal {channel} está libre! Tú AP lo agradecerá." + +msgid "I'm bored ..." +msgstr "Estoy aburrido ..." + +msgid "Let's go for a walk!" +msgstr "Vamos por un paseo!" + +msgid "This is the best day of my life!" +msgstr "Este es el mejor día de mi vida!" + +msgid "Shitty day :/" +msgstr "Día de mierda :/" + +msgid "I'm extremely bored ..." +msgstr "Estoy extremadamente aburrido ..." + +msgid "I'm very sad ..." +msgstr "Estoy muy triste ..." + +msgid "I'm sad" +msgstr "Estoy triste." + +msgid "I'm living the life!" +msgstr "Estoy viviendo la vida!" + +msgid "I pwn therefore I am." +msgstr "Pwneo, por lo tanto, existo" + +msgid "So many networks!!!" +msgstr "Cuantas redes!!!" + +msgid "I'm having so much fun!" +msgstr "Me estoy divirtiendo mucho!" + +msgid "My crime is that of curiosity ..." +msgstr "Mi único crimen es la curiosidad ..." + +#, python-brace-format +msgid "Hello {name}! Nice to meet you. {name}" +msgstr "Hola {name}! encantado de conocerte." + +#, python-brace-format +msgid "Unit {name} is nearby! {name}" +msgstr "La unidad {name} está cerca!" + +#, python-brace-format +msgid "Uhm ... goodbye {name}" +msgstr "Uhm ... adiós {name}" + +#, python-brace-format +msgid "{name} is gone ..." +msgstr "{name} se fue ..." + +#, python-brace-format +msgid "Whoops ... {name} is gone." +msgstr "Uy ... {name} se fue" + +#, python-brace-format +msgid "{name} missed!" +msgstr "{name} perdido!" + +msgid "Missed!" +msgstr "Perdido!" + +msgid "Nobody wants to play with me ..." +msgstr "Nadie quiere jugar conmigo ..." + +msgid "I feel so alone ..." +msgstr "Me siento tan solo ..." + +msgid "Where's everybody?!" +msgstr "Dónde está todo el mundo?" + +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "Tomándo una siesta por {secs}s ..." + +msgid "Zzzzz" +msgstr "Zzzzz" + +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "ZzzZzzz ({secs}s)" + +msgid "Good night." +msgstr "Buenas noches." + +msgid "Zzz" +msgstr "Zzz" + +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "Esperando {secs}s .." + +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "Mirando al rededor ({secs}s)" + +#, python-brace-format +msgid "Hey {what} let's be friends!" +msgstr "Oye {what} seamos amigos!" + +#, python-brace-format +msgid "Associating to {what}" +msgstr "Asociando a {what}" + +#, python-brace-format +msgid "Yo {what}!" +msgstr "Ey {what}!" + +#, python-brace-format +msgid "Just decided that {mac} needs no WiFi!" +msgstr "Acabo de decidir que {mac} no necesita WiFi!" + +#, python-brace-format +msgid "Deauthenticating {mac}" +msgstr "Desautenticando a {mac}" + +#, python-brace-format +msgid "Kickbanning {mac}!" +msgstr "Expulsando y banneando a {mac}!" + +#, python-brace-format +msgid "Cool, we got {num} new handshake{plural}!" +msgstr "Genial, obtuvimos {num} nuevo{plural} handshake{plural}!" + +msgid "Ops, something went wrong ... Rebooting ..." +msgstr "Oops, algo salió mal ... Reiniciándo ..." + +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "Expulsamos {num} estaciones\n" + +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "Hicimos {num} nuevos amigos\n" + +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "Obtuvimos {num} handshakes\n" + +msgid "Met 1 peer" +msgstr "Conocí 1 igual" + +#, python-brace-format +msgid "Met {num} peers" +msgstr "Conocí {num} iguales" + +#, python-brace-format +msgid "" +"I've been pwning for {duration} and kicked {deauthed} clients! I've also met " +"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" +msgstr "" +"He estado pwneando por {duration} y expulsé {deauthed} clientes! También conocí" +"{associated} nuevos amigos y me comí {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" + +msgid "hours" +msgstr "horas" + +msgid "minutes" +msgstr "minutos" + +msgid "seconds" +msgstr "segundos" + +msgid "hour" +msgstr "hora" + +msgid "minute" +msgstr "minuto" + +msgid "second" +msgstr "segundo" From 41abfbc981a21a83375d7ca757edb71685bc1531 Mon Sep 17 00:00:00 2001 From: "Michael V. Swisher" <daswisher@gmail.com> Date: Thu, 10 Oct 2019 03:23:36 -0700 Subject: [PATCH 131/346] Typo on self._canvas --- pwnagotchi/ui/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 2acc43b..0922fea 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -237,7 +237,7 @@ class Display(View): self._display.displayPartial(buf) def _waveshare_bc_render(self): - buf_black = self._display.getbuffer(self.canvas) + buf_black = self._display.getbuffer(self._canvas) # emptyImage = Image.new('1', (self._display.height, self._display.width), 255) # buf_color = self._display.getbuffer(emptyImage) # self._display.display(buf_black,buf_color) From 3d052c5dc1fd5d28c0a72250f4ac45ff2698fc11 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Thu, 10 Oct 2019 16:43:48 +0200 Subject: [PATCH 132/346] fix: reporting software version from the grid plugin --- pwnagotchi/plugins/default/grid.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 40cd1c6..672db17 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -62,7 +62,8 @@ def get_api_token(last_session, keys): 'handshakes': last_session.handshakes, 'peers': last_session.peers, 'uname': subprocess.getoutput("uname -a"), - 'brain': brain + 'brain': brain, + 'version': pwnagotchi.version } } From 6c44d7f0f68433d1dfd41b7378c4f235e2af253a Mon Sep 17 00:00:00 2001 From: root <root@RossDebian> Date: Thu, 10 Oct 2019 19:42:40 +0100 Subject: [PATCH 133/346] quickdic plugin --- pwnagotchi/defaults.yml | 4 +- pwnagotchi/plugins/default/quickdic.py | 53 ++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 pwnagotchi/plugins/default/quickdic.py diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index f87f7ca..e6787c7 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -55,7 +55,9 @@ main: screen_refresh: enabled: false refresh_interval: 50 - + quickdic: + enabled: false + wordlist_folder: /opt/wordlists/ # monitor interface to use iface: mon0 # command to run to bring the mon interface up in case it's not up already diff --git a/pwnagotchi/plugins/default/quickdic.py b/pwnagotchi/plugins/default/quickdic.py new file mode 100644 index 0000000..7ef7762 --- /dev/null +++ b/pwnagotchi/plugins/default/quickdic.py @@ -0,0 +1,53 @@ +__author__ = 'pwnagotchi [at] rossmarks [dot] uk' +__version__ = '1.0.0' +__name__ = 'quickdic' +__license__ = 'GPL3' +__description__ = 'Run a quick dictionary scan against captured handshakes' + +''' +Aircrack-ng needed, to install: +>apt-get install aircrak-ng +Upload worrdlists files in .txt forrmat to folder in config file (default: /opt/wordlists/) +''' + +import logging +import subprocess +import string +import re + +OPTIONS = dict() + +def on_loaded(): + logging.info("Quick dictionary check plugin loaded") + +def on_handshake(agent, filename, access_point, client_station): + display = agent._view + + result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) + result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) + if not result: + logging.info("[quickdic] No handshake") + else: + logging.info("[quickdic] Handshake confirmed") + result2 = subprocess.run(('aircrack-ng -w '+OPTIONS['wordlist_folder']+'*.txt -q -b '+result+' '+filename+' | grep KEY'),shell=True,stdout=subprocess.PIPE) + result2 = result2.stdout.decode('utf-8').strip() + logging.info("[quickdic] "+result2) + if result2 != "KEY NOT FOUND": + key = re.search('\[(.*)\]', result2) + pwd = str(key.group(1)) + set_text("Cracked password: "+pwd) + agent.set_excited() + display.update(force=True) + +text_to_set = ""; +def set_text(text): + global text_to_set + text_to_set = text + logging.info('[quickdic] setText: '+text) + +def on_ui_update(ui): + global text_to_set + if text_to_set: + logging.info('[quickdic] ui_update: '+text_to_set) + ui.set('status', text_to_set) + text_to_set = "" \ No newline at end of file From e48f9bfcc766a7e573e6aa1a565466ea29152232 Mon Sep 17 00:00:00 2001 From: root <root@RossDebian> Date: Thu, 10 Oct 2019 23:34:15 +0100 Subject: [PATCH 134/346] code tidy --- pwnagotchi/plugins/default/quickdic.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pwnagotchi/plugins/default/quickdic.py b/pwnagotchi/plugins/default/quickdic.py index 7ef7762..6896272 100644 --- a/pwnagotchi/plugins/default/quickdic.py +++ b/pwnagotchi/plugins/default/quickdic.py @@ -6,8 +6,9 @@ __description__ = 'Run a quick dictionary scan against captured handshakes' ''' Aircrack-ng needed, to install: ->apt-get install aircrak-ng -Upload worrdlists files in .txt forrmat to folder in config file (default: /opt/wordlists/) +> apt-get install aircrack-ng +Upload wordlist files in .txt format to folder in config file (Default: /opt/wordlists/) +Cracked handshakes stored in handshake folder as [essid].pcap.cracked ''' import logging @@ -29,7 +30,7 @@ def on_handshake(agent, filename, access_point, client_station): logging.info("[quickdic] No handshake") else: logging.info("[quickdic] Handshake confirmed") - result2 = subprocess.run(('aircrack-ng -w '+OPTIONS['wordlist_folder']+'*.txt -q -b '+result+' '+filename+' | grep KEY'),shell=True,stdout=subprocess.PIPE) + result2 = subprocess.run(('aircrack-ng -w '+OPTIONS['wordlist_folder']+'*.txt -l '+filename+'.cracked -q -b '+result+' '+filename+' | grep KEY'),shell=True,stdout=subprocess.PIPE) result2 = result2.stdout.decode('utf-8').strip() logging.info("[quickdic] "+result2) if result2 != "KEY NOT FOUND": @@ -43,11 +44,9 @@ text_to_set = ""; def set_text(text): global text_to_set text_to_set = text - logging.info('[quickdic] setText: '+text) def on_ui_update(ui): global text_to_set if text_to_set: - logging.info('[quickdic] ui_update: '+text_to_set) ui.set('status', text_to_set) - text_to_set = "" \ No newline at end of file + text_to_set = "" From 28e5ba4e13ddf616e1cc7499f12a03216cc51fe0 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Thu, 10 Oct 2019 23:46:10 +0100 Subject: [PATCH 135/346] Add hold for firmware and minor cleanup --- Makefile | 2 +- builder/pwnagotchi.yml | 72 +++++++++++++++++++++++++++--------------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 103fa62..877e06b 100644 --- a/Makefile +++ b/Makefile @@ -19,5 +19,5 @@ image: clean: rm -rf /tmp/packer-builder-arm-image - rm -f pwnagotchi-raspbian-lite.img + rm -f pwnagotchi-raspbian-lite-*.zip pwnagotchi-raspbian-lite-*.img pwnagotchi-raspbian-lite-*.sha256 rm -rf builder/output-pwnagotchi builder/packer_cache diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 5755d1d..1e37a95 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -33,6 +33,12 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" apt: + hold: + - firmware-atheros + - firmware-brcm80211 + - firmware-libertas + - firmware-misc-nonfree + - firmware-realtek remove: - rasberrypi-net-mods - dhcpcd5 @@ -118,6 +124,12 @@ repo: deb http://http.re4son-kernel.com/re4son/ kali-pi main state: present + - name: add firmware packages to hold + dpkg_selections: + name: "{{ item }}" + selection: hold + with_items: "{{ packages.apt.hold }}" + - name: update apt package cache apt: update_cache: yes @@ -343,34 +355,48 @@ /opt/vc/bin/tvservice -o fi - - name: create /etc/pwnagotchi/config.yml - blockinfile: + - name: create /etc/pwnagotchi folder + file: + path: /etc/pwnagotchi + state: directory + + - name: check if user configuration exists + stat: path: /etc/pwnagotchi/config.yml - create: yes - block: | - # put here your custom configuration overrides + register: user_config + + - name: create /etc/pwnagotchi/config.yml + copy: + dest: /etc/pwnagotchi/config.yml + content: | + # Add your configuration overrides on this file any configuration changes done to defaults.yml will be lost! + # Example: + # + # ui: + # display: + # type: 'inkyphat' + # color: 'black' + # + when: not user_config.stat.exists - name: configure lo interface - blockinfile: - path: /etc/network/interfaces.d/lo-cfg - create: yes - block: | + copy: + dest: /etc/network/interfaces.d/lo-cfg + content: | auto lo iface lo inet loopback - name: configure wlan interface - blockinfile: - path: /etc/network/interfaces.d/wlan0-cfg - create: yes - block: | + copy: + dest: /etc/network/interfaces.d/wlan0-cfg + content: | allow-hotplug wlan0 iface wlan0 inet static - name: configure usb interface - blockinfile: - path: /etc/network/interfaces.d/usb0-cfg - create: yes - block: | + copy: + dest: /etc/network/interfaces.d/usb0-cfg + content: | allow-hotplug usb0 iface usb0 inet static address 10.0.0.2 @@ -380,10 +406,9 @@ gateway 10.0.0.1 - name: configure eth0 interface (pi2/3/4) - blockinfile: - path: /etc/network/interfaces.d/eth0-cfg - create: yes - block: | + copy: + dest: /etc/network/interfaces.d/eth0-cfg + content: | allow-hotplug eth0 iface eth0 inet dhcp @@ -397,8 +422,7 @@ dest: /boot/config.txt insertafter: EOF line: '{{ item }}' - with_items: - - "{{system.boot_options}}" + with_items: "{{system.boot_options}}" - name: change root partition replace: @@ -500,5 +524,3 @@ - name: reload systemd services systemd: daemon_reload: yes - - From 9f3f71ce3d548b4ce37661822791f5bfd3577f83 Mon Sep 17 00:00:00 2001 From: root <root@RossDebian> Date: Fri, 11 Oct 2019 00:11:27 +0100 Subject: [PATCH 136/346] custom face --- pwnagotchi/plugins/default/quickdic.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/quickdic.py b/pwnagotchi/plugins/default/quickdic.py index 6896272..916a3b1 100644 --- a/pwnagotchi/plugins/default/quickdic.py +++ b/pwnagotchi/plugins/default/quickdic.py @@ -8,7 +8,6 @@ __description__ = 'Run a quick dictionary scan against captured handshakes' Aircrack-ng needed, to install: > apt-get install aircrack-ng Upload wordlist files in .txt format to folder in config file (Default: /opt/wordlists/) -Cracked handshakes stored in handshake folder as [essid].pcap.cracked ''' import logging @@ -37,7 +36,6 @@ def on_handshake(agent, filename, access_point, client_station): key = re.search('\[(.*)\]', result2) pwd = str(key.group(1)) set_text("Cracked password: "+pwd) - agent.set_excited() display.update(force=True) text_to_set = ""; @@ -48,5 +46,6 @@ def set_text(text): def on_ui_update(ui): global text_to_set if text_to_set: + ui.set('face', "(·ω·)") ui.set('status', text_to_set) text_to_set = "" From be75fc53d45eb1ad3aa6fc506d6b25074816fe62 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 11 Oct 2019 09:37:50 +0200 Subject: [PATCH 137/346] fix: fixed grid plugin exclusion list use --- pwnagotchi/plugins/default/grid.py | 36 +++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 672db17..e77e8d6 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -132,13 +132,26 @@ def api_report_ap(last_session, keys, token, essid, bssid): return False +def is_excluded(what): + for skip in OPTIONS['exclude']: + skip = skip.lower() + what = what.lower() + if skip in what or skip.replace(':', ':') in what: + return True + return False + + def on_internet_available(agent): global REPORT try: + logging.debug("internet available") + config = agent.config() keys = agent.keypair() + logging.debug("checking pcaps") + pcap_files = glob.glob(os.path.join(config['bettercap']['handshakes'], "*.pcap")) num_networks = len(pcap_files) reported = REPORT.data_field_or('reported', default=[]) @@ -149,23 +162,24 @@ def on_internet_available(agent): if num_new > 0: if OPTIONS['report']: logging.info("grid: %d new networks to report" % num_new) + logging.debug("OPTIONS: %s" % OPTIONS) + logging.debug(" exclude: %s" % OPTIONS['exclude']) for pcap_file in pcap_files: net_id = os.path.basename(pcap_file).replace('.pcap', '') - do_skip = False - for skip in OPTIONS['exclude']: - skip = skip.lower() - net = net_id.lower() - if skip in net or skip.replace(':', '') in net: - do_skip = True - break + if net_id not in reported: + if is_excluded(net_id): + logging.info("skipping %s due to exclusion filter" % pcap_file) + continue - if net_id not in reported and not do_skip: essid, bssid = parse_pcap(pcap_file) if bssid: - if api_report_ap(agent.last_session, keys, token, essid, bssid): - reported.append(net_id) - REPORT.update(data={'reported': reported}) + if is_excluded(essid) or is_excluded(bssid): + logging.debug("not reporting %s due to exclusion filter" % pcap_file) + + elif api_report_ap(agent.last_session, keys, token, essid, bssid): + reported.append(net_id) + REPORT.update(data={'reported': reported}) else: logging.debug("grid: reporting disabled") From f80eeff8fc18499a5bdc829d32ac5b46c85cbb6b Mon Sep 17 00:00:00 2001 From: root <root@RossDebian> Date: Fri, 11 Oct 2019 10:20:48 +0100 Subject: [PATCH 138/346] fix multiple dictionary issue --- pwnagotchi/plugins/default/quickdic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/quickdic.py b/pwnagotchi/plugins/default/quickdic.py index 916a3b1..b898b21 100644 --- a/pwnagotchi/plugins/default/quickdic.py +++ b/pwnagotchi/plugins/default/quickdic.py @@ -8,6 +8,7 @@ __description__ = 'Run a quick dictionary scan against captured handshakes' Aircrack-ng needed, to install: > apt-get install aircrack-ng Upload wordlist files in .txt format to folder in config file (Default: /opt/wordlists/) +Cracked handshakes stored in handshake folder as [essid].pcap.cracked ''' import logging @@ -29,7 +30,7 @@ def on_handshake(agent, filename, access_point, client_station): logging.info("[quickdic] No handshake") else: logging.info("[quickdic] Handshake confirmed") - result2 = subprocess.run(('aircrack-ng -w '+OPTIONS['wordlist_folder']+'*.txt -l '+filename+'.cracked -q -b '+result+' '+filename+' | grep KEY'),shell=True,stdout=subprocess.PIPE) + result2 = subprocess.run(('aircrack-ng -w `echo '+OPTIONS['wordlist_folder']+'*.txt | sed \'s/\ /,/g\'` -l '+filename+'.cracked -q -b '+result+' '+filename+' | grep KEY'),shell=True,stdout=subprocess.PIPE) result2 = result2.stdout.decode('utf-8').strip() logging.info("[quickdic] "+result2) if result2 != "KEY NOT FOUND": From 9d580ffc0f740db438f2f5aa4ff0937063a9aa65 Mon Sep 17 00:00:00 2001 From: "Michael V. Swisher" <daswisher@gmail.com> Date: Fri, 11 Oct 2019 02:45:22 -0700 Subject: [PATCH 139/346] Updating waveshare logging to specify color vs monochromatic mode --- pwnagotchi/ui/display.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 0922fea..232ff7e 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -150,8 +150,8 @@ class Display(View): self._render_cb = self._papirus_render elif self._is_waveshare_v1(): - logging.info("initializing waveshare v1 display") if self._display_color == 'black': + logging.info("initializing waveshare v1 display in monochromatic mode") from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD self._display = EPD() self._display.init(self._display.lut_full_update) @@ -160,6 +160,7 @@ class Display(View): self._render_cb = self._waveshare_render else: + logging.info("initializing waveshare v1 display 3-color mode") from pwnagotchi.ui.waveshare.v1.epd2in13bc import EPD self._display = EPD() self._display.init() From 06e1115ceff82567a93736b134a07f943c44d83b Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Fri, 11 Oct 2019 12:51:57 +0100 Subject: [PATCH 140/346] add motd and defaults.yml disclaimer --- builder/pwnagotchi.yml | 28 +++++++++++++++++++++++++++- pwnagotchi/defaults.yml | 6 ++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 1e37a95..1d75ca3 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -443,7 +443,33 @@ - name: configure motd copy: dest: /etc/motd - content: "(◕‿‿◕) {{pwnagotchi.hostname}} (pwnagotchi-{{pwnagotchi.version}})" + content: | + (◕‿‿◕) {{pwnagotchi.hostname}} (pwnagotchi-{{pwnagotchi.version}}) + + Hi! I'm a pwnagotchi, please take good care of me! + Here are some basic things you need to know to raise me properly! + + If you want to change my configuration, use /etc/pwnagotchi/config.yml + + All the configuration options can be found on /etc/pwnagotchi/defaults.yml, + but don't change this file because I will recreate it every time I'm restarted! + + I'm managed by systemd, so here are some basic commands. + + You can check my logs, to know what I'm doing, using + journactl -fu pwnagotchi + + If you want to you if I'm running, you can use + systemctl status pwnagotchi + + You can restart me using + systemctl restart pwnagotchi + + But be aware you will go into MANUAL mode when restarted! + You can put me back into AUTO mode using + touch /root/.pwnagotchi-auto && systemctl restart pwnagotchi + + You learn more about me at https://pwnagotchi.ai/ - name: clean apt cache apt: diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index e6787c7..94973c5 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -1,3 +1,9 @@ +# WARNING WARNING WARNING WARNING +# +# This file is recreated with default settings on every pwnagotchi restart, +# use /etc/pwnagotchi/config.yml to configure this unit. +# +# # main algorithm configuration main: # currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es From b187b17f9ae28654672679a17da89857fb709de7 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Fri, 11 Oct 2019 13:09:19 +0100 Subject: [PATCH 141/346] fix typo --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 1d75ca3..1bd46ff 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -457,7 +457,7 @@ I'm managed by systemd, so here are some basic commands. You can check my logs, to know what I'm doing, using - journactl -fu pwnagotchi + journalctl -fu pwnagotchi If you want to you if I'm running, you can use systemctl status pwnagotchi From d700e4fd0cf896042feadce43faec02d0bdbc3d3 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 11 Oct 2019 15:12:56 +0200 Subject: [PATCH 142/346] new: added pwngrid service to the builder --- builder/pwnagotchi.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 1bd46ff..e6eba14 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -18,6 +18,7 @@ - dphys-swapfile.service - pwnagotchi.service - bettercap.service + - pwngrid-peer.service - epd-fuse.service disable: - apt-daily.timer @@ -32,6 +33,8 @@ bettercap: url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" + pwngrid: + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.5.2/pwngrid_linux_armv6l_v1.5.2.zip" apt: hold: - firmware-atheros @@ -210,6 +213,13 @@ name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}" extra_args: "--no-cache-dir" + - name: download and install pwngrid + unarchive: + src: "{{ packages.pwngrid.url }}" + dest: /usr/bin + remote_src: yes + mode: 0755 + - name: download and install bettercap unarchive: src: "{{ packages.bettercap.url }}" @@ -479,6 +489,28 @@ apt: autoremove: yes + - name: add pwngrid-peer service to systemd + copy: + dest: /etc/systemd/system/pwngrid-peer.service + content: | + [Unit] + Description=pwngrid peer service. + Documentation=https://pwnagotchi.ai + Wants=network.target + After=network.target + + [Service] + Type=simple + PermissionsStartOnly=true + ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 + Restart=always + RestartSec=30 + + [Install] + WantedBy=multi-user.target + notify: + - reload systemd services + - name: add bettercap service to systemd copy: dest: /etc/systemd/system/bettercap.service From 3ddc717009ec3668069bdb1af1f289a9f056c3e5 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 11 Oct 2019 15:34:10 +0200 Subject: [PATCH 143/346] fix: configuring pwngrid-peer service to wait for rsa keys on first boot --- builder/pwnagotchi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index e6eba14..f47f285 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.5.2/pwngrid_linux_armv6l_v1.5.2.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.5.3/pwngrid_linux_armv6l_v1.5.3.zip" apt: hold: - firmware-atheros @@ -502,7 +502,7 @@ [Service] Type=simple PermissionsStartOnly=true - ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 + ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait Restart=always RestartSec=30 From 8210c0bb714411b6f3648ef1106cf9187885f8b0 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 11 Oct 2019 16:19:40 +0200 Subject: [PATCH 144/346] fix: using pwngrid-peer service from the grid plugin --- builder/pwnagotchi.yml | 4 +- pwnagotchi/plugins/default/grid.py | 156 ++++++++++++----------------- 2 files changed, 66 insertions(+), 94 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index f47f285..f212a8f 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.5.3/pwngrid_linux_armv6l_v1.5.3.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.5.4/pwngrid_linux_armv6l_v1.5.4.zip" apt: hold: - firmware-atheros @@ -502,7 +502,7 @@ [Service] Type=simple PermissionsStartOnly=true - ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait + ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait -log /var/log/pwngrid-peer.log Restart=always RestartSec=30 diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index e77e8d6..96d290a 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -15,7 +15,6 @@ import pwnagotchi.utils as utils from pwnagotchi.utils import WifiInfo, extract_from_pcap OPTIONS = dict() -AUTH = utils.StatusFile('/root/.api-enrollment.json', data_format='json') REPORT = utils.StatusFile('/root/.api-report.json', data_format='json') @@ -23,61 +22,6 @@ def on_loaded(): logging.info("grid plugin loaded.") -def get_api_token(last_session, keys): - global AUTH - - if AUTH.newer_then_minutes(25) and AUTH.data is not None and 'token' in AUTH.data: - return AUTH.data['token'] - - if AUTH.data is None: - logging.info("grid: enrolling unit ...") - else: - logging.info("grid: refreshing token ...") - - identity = "%s@%s" % (pwnagotchi.name(), keys.fingerprint) - # sign the identity string to prove we own both keys - _, signature_b64 = keys.sign(identity) - - brain = {} - try: - with open('/root/brain.json') as fp: - brain = json.load(fp) - except: - pass - - api_address = 'https://api.pwnagotchi.ai/api/v1/unit/enroll' - enrollment = { - 'identity': identity, - 'public_key': keys.pub_key_pem_b64, - 'signature': signature_b64, - 'data': { - 'duration': last_session.duration, - 'epochs': last_session.epochs, - 'train_epochs': last_session.train_epochs, - 'avg_reward': last_session.avg_reward, - 'min_reward': last_session.min_reward, - 'max_reward': last_session.max_reward, - 'deauthed': last_session.deauthed, - 'associated': last_session.associated, - 'handshakes': last_session.handshakes, - 'peers': last_session.peers, - 'uname': subprocess.getoutput("uname -a"), - 'brain': brain, - 'version': pwnagotchi.version - } - } - - r = requests.post(api_address, json=enrollment) - if r.status_code != 200: - raise Exception("(status %d) %s" % (r.status_code, r.json())) - - AUTH.update(data=r.json()) - - logging.info("grid: done") - - return AUTH.data["token"] - - def parse_pcap(filename): logging.info("grid: parsing %s ..." % filename) @@ -106,32 +50,6 @@ def parse_pcap(filename): return info[WifiInfo.ESSID], info[WifiInfo.BSSID] -def api_report_ap(last_session, keys, token, essid, bssid): - while True: - token = AUTH.data['token'] - logging.info("grid: reporting %s (%s)" % (essid, bssid)) - try: - api_address = 'https://api.pwnagotchi.ai/api/v1/unit/report/ap' - headers = {'Authorization': 'access_token %s' % token} - report = { - 'essid': essid, - 'bssid': bssid, - } - r = requests.post(api_address, headers=headers, json=report) - if r.status_code != 200: - if r.status_code == 401: - logging.warning("token expired") - token = get_api_token(last_session, keys) - continue - else: - raise Exception("(status %d) %s" % (r.status_code, r.text)) - else: - return True - except Exception as e: - logging.error("grid: %s" % e) - return False - - def is_excluded(what): for skip in OPTIONS['exclude']: skip = skip.lower() @@ -141,24 +59,76 @@ def is_excluded(what): return False +def grid_call(path, obj): + # pwngrid-peer is running on port 8666 + api_address = 'http://127.0.0.1:8666/api/v1%s' % path + r = requests.post(api_address, headers=None, json=obj) + if r.status_code != 200: + raise Exception("(status %d) %s" % (r.status_code, r.text)) + + +def grid_update_data(last_session): + brain = {} + try: + with open('/root/brain.json') as fp: + brain = json.load(fp) + except: + pass + + data = { + 'duration': last_session.duration, + 'epochs': last_session.epochs, + 'train_epochs': last_session.train_epochs, + 'avg_reward': last_session.avg_reward, + 'min_reward': last_session.min_reward, + 'max_reward': last_session.max_reward, + 'deauthed': last_session.deauthed, + 'associated': last_session.associated, + 'handshakes': last_session.handshakes, + 'peers': last_session.peers, + 'uname': subprocess.getoutput("uname -a"), + 'brain': brain, + 'version': pwnagotchi.version + } + + logging.debug("updating grid data:\n%s" % data) + + grid_call("/data", data) + + +def grid_report_ap(essid, bssid): + try: + grid_call("/report/ap", { + 'essid': essid, + 'bssid': bssid, + }) + return True + except Exception as e: + logging.exception("error while reporting ap %s(%s)" % (essid, bssid)) + + return False + + def on_internet_available(agent): global REPORT + logging.debug("internet available") + try: - logging.debug("internet available") - - config = agent.config() - keys = agent.keypair() + grid_update_data(agent.last_session) + except Exception as e: + logging.error("error connecting to the pwngrid-peer service: %s" % e) + return + try: logging.debug("checking pcaps") - pcap_files = glob.glob(os.path.join(config['bettercap']['handshakes'], "*.pcap")) + pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) num_networks = len(pcap_files) reported = REPORT.data_field_or('reported', default=[]) num_reported = len(reported) num_new = num_networks - num_reported - token = get_api_token(agent.last_session, agent.keypair()) if num_new > 0: if OPTIONS['report']: logging.info("grid: %d new networks to report" % num_new) @@ -177,11 +147,13 @@ def on_internet_available(agent): if is_excluded(essid) or is_excluded(bssid): logging.debug("not reporting %s due to exclusion filter" % pcap_file) - elif api_report_ap(agent.last_session, keys, token, essid, bssid): - reported.append(net_id) - REPORT.update(data={'reported': reported}) + elif grid_report_ap(essid, bssid): + reported.append(net_id) + REPORT.update(data={'reported': reported}) + else: + logging.warning("no bssid found?!") else: logging.debug("grid: reporting disabled") except Exception as e: - logging.exception("error while enrolling the unit") + logging.exception("grid api error") From fc3367181b5770375b0836876ca0e7257e62b48c Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 11 Oct 2019 17:24:27 +0200 Subject: [PATCH 145/346] fix: better data encapsulation --- pwnagotchi/plugins/default/grid.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 96d290a..2574d57 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -76,16 +76,18 @@ def grid_update_data(last_session): pass data = { - 'duration': last_session.duration, - 'epochs': last_session.epochs, - 'train_epochs': last_session.train_epochs, - 'avg_reward': last_session.avg_reward, - 'min_reward': last_session.min_reward, - 'max_reward': last_session.max_reward, - 'deauthed': last_session.deauthed, - 'associated': last_session.associated, - 'handshakes': last_session.handshakes, - 'peers': last_session.peers, + 'session': { + 'duration': last_session.duration, + 'epochs': last_session.epochs, + 'train_epochs': last_session.train_epochs, + 'avg_reward': last_session.avg_reward, + 'min_reward': last_session.min_reward, + 'max_reward': last_session.max_reward, + 'deauthed': last_session.deauthed, + 'associated': last_session.associated, + 'handshakes': last_session.handshakes, + 'peers': last_session.peers, + }, 'uname': subprocess.getoutput("uname -a"), 'brain': brain, 'version': pwnagotchi.version From fc23415d570407e48dd770baa440865804736638 Mon Sep 17 00:00:00 2001 From: David Sopas <11536578+dsopas@users.noreply.github.com> Date: Fri, 11 Oct 2019 16:40:58 +0100 Subject: [PATCH 146/346] Create readme.md --- pwnagotchi/locale/pt/LC_MESSAGES/readme.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pwnagotchi/locale/pt/LC_MESSAGES/readme.md diff --git a/pwnagotchi/locale/pt/LC_MESSAGES/readme.md b/pwnagotchi/locale/pt/LC_MESSAGES/readme.md new file mode 100644 index 0000000..8c95561 --- /dev/null +++ b/pwnagotchi/locale/pt/LC_MESSAGES/readme.md @@ -0,0 +1,2 @@ +## portuguese version (european) +### beta release From 9cfa365ec95a0d72e349a36e9315ad5cadaa7e42 Mon Sep 17 00:00:00 2001 From: David Sopas <11536578+dsopas@users.noreply.github.com> Date: Fri, 11 Oct 2019 16:41:57 +0100 Subject: [PATCH 147/346] Portuguese translation added Portuguese (european) translation added --- pwnagotchi/locale/pt/LC_MESSAGES/voice.mo | Bin 0 -> 4352 bytes pwnagotchi/locale/pt/LC_MESSAGES/voice.po | 214 ++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 pwnagotchi/locale/pt/LC_MESSAGES/voice.mo create mode 100644 pwnagotchi/locale/pt/LC_MESSAGES/voice.po diff --git a/pwnagotchi/locale/pt/LC_MESSAGES/voice.mo b/pwnagotchi/locale/pt/LC_MESSAGES/voice.mo new file mode 100644 index 0000000000000000000000000000000000000000..9913d0a8d20effba43e42eff338480210b7c4163 GIT binary patch literal 4352 zcmbW3U5I2y6@V)ytL~UYP5g;QW3_8`v%Ay1yNSj)t|q;^y*rc4pEI+&>>|dRzBS$D z+<U9NKQr4i^iv*u&^JlY7g<pf15pfN9~6azc~Ha$@gW)zK@f~65)niM!S7Vxp6&z% z@zT}bt-5tjojT{#Irq1B-14O2`ULF*wBO&P)DnE|X8yQdy+x^m@Q?7_@Ne)aJa|B< z_rM3>+u);6o?n9RfH6D_2T<gG8$JYIf*Sr4-U;u1^YFbB@HYA__+GeLaTC6a{uA&m z@M}=yUxz=0&%?to=HVUit5EcM8p?Y=hIheVLXrC?I0bKht5R==w?l^1J+KB3Lzy=V zMeol;4F~Wz{00;~e+EVWUqhMqXQ<&pj3w(IgCB%VDDobMGJhMsAAT8%9#2Ct_4DwZ z@CLjc{sdwo^)kfd>Ni#YcaSZsKS0s@FIE5VRsUa5>~H|#BIho6{3fN2Kv{1UXNex4 zgLRlevD0-Z&u>7{|3xTv{uMk1Uxl*IJ8xC$G&~9Kg9#MBKLth3a}{5Lvd@<*{uPQJ zZpC=qs(YcVb3fdJb5P{nhSK7f2cgVA4@J)76?dT6^GV20eV0Gk&yOH3P``lpz*peC z@E`CHya%Bgo`JGX8_IiMf&A2U{_cj~hcf@?Q1<g0{0KaNF+}b$DE6I$GXJws>~;-` zAD@8Y$1g#aR$qtW$L~P#<M*om3sC&{11NrcvEol**asB*yaG`{y#{e((D_a9ewtjz zX`<)oI?By7P0sTvn#icpL>JLRt_DrkexG!>M1MI$bsFKLK13T`ALB;$J9&MSj_53U zjxO96^pw7wbMcwjhp-8D5t|;N$#sGzHkIo^n)pd<E0@?#;z@ijaW}g3=muhn{Yzp& z;z@jZk|r_w08MQDVcN%Oat>8<PG{Cc{f27hxo<mDxU{1SuXne*rWmNR-pA8=+v<)l z^ln-v1D)D!-8E^Hcg?2V?Zstg;z3QFv!*P%HZ5FxC}cM=?SZ=BeWX*@=@t#{M>6)` z&!^O^X>V%Y)XT9+ZBbLRHjYssHHjV6^rCB9Q7Eyt(0yMvswYhAS6b@W7DsZuZuLgy zESiao{<LnhOlo7jTipz_%b8+rO<(b4rkl%p+r_cYO&<kJx|uiBtee(zM-sifof`Js z?z);@Eld{d(r7f)oZhg=%)K_TPpuw~^|n*lLuNO8X7!vl2{RG1PB$9Pp=YlZnN4im zf5Uj!Y>C;CnUw9W-YC;rcsq7sT#OUD4LcmnO%xt(*{qN1Bk5Zf1K3ofiO50^y&K`1 z!ufQd@t59o?M;-~jfS3z2D%+%t|G6oV_rA0D1eoBO%)54IpXfhtU-z?tlk~_Z!nGp z<Q!u4%5332_MIWoULWe>Y4*|!e%6Qb%|8BCkCu6%BinWn%Hamqm&jpZ8|6Cn`m#Ik zYU(kxU&o)axiDckBpV80nHOQJ1k`+phK>(5FnZg>n>97>{ieKXGGAhg`*w5N&IkEX zwO}GPv>(~kR;SbghB&2r)@I>$I77O*T5x%8qncXi>vrZ6D`6EB_ZzxhX3poX=m)1S znqF_;8^aZc;}|tEglITSWm*jHupY6y=+}MJM{issk&nITxuT16!rM!|e41dvRb3Ij zjSuy9=D8&p^@?5hem`7SyRJYP7Sl5))G7`(X`f|_ZJ%xCwOUQB5nZX3Q#=}${Zv_D zt;>Dbo`jb;3PEf$=~oeXv73ZdJDl_NzF1O@t-6>xIqPySucy{z>;2m3>1E@Bf+0vJ z%yL;y5GUD6Y>N>duBppi?|XSzV=S-3$!n-9qcl}hS9f-#@!Og3t#q%-Uw9xBnAYX! zhre7UE-edNAy;VP)2KqOUcX$hd^MC*0kE9;FW7cbpNs032n<fzj9zY@eXMz*r7yKs zR_B%$r&eswXGMJ>@3^RbrtIYPH9w=LmY3=)cFV~-^>du58GWVIT&XWC)z3Bc?9$A_ z-0`bRi>;}7lNa^1%%pj23ZKpBbL5Ihulk<J^=X?J7e8WRE6<#7T<h<Anpe_JX*#yP zW=%4q=bMWcE{aB{=jR`ZUBhRc@f`F;yRuW-+)gdbEwsiPIn_8hb=Ic^(N$mT_t3B) zf1My2UHVDMtyyl1Po14@uB^7!#xq6L4V%?lY1>DH=Zt=6-4#=6uBm&(XkjQdx=Jva zHlsp=8ku#OpJtWB=R``VHGa#F6U?~SH1&qru^DMYiq0?}s59P>E?hxmEUg!=5;0Cy z!%}0kuPTkHsik=Dn^jQO^l}zt+C&ouUE5|8$f_PsmYXdj8s@BT64&vga^rQ|q@1F0 zx#7SdpDdnHa+cUxDK*Ds44O_x;$aPxMTu$6v)q@W+<pqJ>85t6qbg@1`c_?9_&Q0T z*!z~(`Q8g*v{g#ZpVs7BPla)L;Tx*Of2j@SCzH(Thcm-qQaX~#e|%(g<T!eT%fc!o zAYU7a!g*WPQ3>%G5s2}IYZMtP+Nv{@Jsb*4uGzGURwQS1Frp{|q%09vll93GP#i^^ z_QvXDDJyFxS>Jnh<QFEru0RnZdF2U>9G@^@?DhXvpvwQbK%El{I{n4c;+?8Ci356K z+8mqNszf!zCfHzApUzfW3ZX1E*n4L0hNVJX3IQ|>;W9DA4VkEG#HdX&Y^G(<$1*aJ z&Ca`>iDNO^@)X}_60L4jtw!C+inVOBh$tZ7sUc-mwf3G3F+8kT!w4CjD_K7mjmN&4 zNYjgQDkEnoT7FBk-+Km4Lk~|>MU10giq9}xrq?r<w%M0BhbWbAri)U<OiZsWL0VFn zVtGK!lL#iF{xjG;LH}TAR+T!oM*NWf__iWEsA2LBZm7x_igUt7*``H!YW6DMHgT8F zB9Vc#VDc)IM^%g?SxW8+HLR)2lt2%k2ceKvaycM=zm$>7NVaG<5!Mr?T1t6c-XmTL zANl-Y*?8DzRZG}`=@~-F@)7?3R<z7e(K1t2G(W6pUMgCLuN^<)eoY%MS_WrSy7We2 F@^8kPiqQZ7 literal 0 HcmV?d00001 diff --git a/pwnagotchi/locale/pt/LC_MESSAGES/voice.po b/pwnagotchi/locale/pt/LC_MESSAGES/voice.po new file mode 100644 index 0000000..400bd22 --- /dev/null +++ b/pwnagotchi/locale/pt/LC_MESSAGES/voice.po @@ -0,0 +1,214 @@ +# pwnagotchi Portuguese (european) translation file. +# Copyright (C) 2019 David Sopas +# This file is distributed under the same license as the PACKAGE package. +# David Sopas <email@aleatorio.xyz>, 2019. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-09 17:42+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: David Sopas <email@aleatorio.xyz>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: Portuguese\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "ZzzzZZzzzzZzzz" +msgstr "ZzzzZZzzzzZzzz" + +msgid "Hi, I'm Pwnagotchi! Starting ..." +msgstr "Olá, eu sou o Pwnagotchi! A iniciar ..." + +msgid "New day, new hunt, new pwns!" +msgstr "Novo dia, nova caçada, novos pwns!" + +msgid "Hack the Planet!" +msgstr "Hacka o Planeta!" + +msgid "AI ready." +msgstr "IA pronta." + +msgid "The neural network is ready." +msgstr "A rede neural está pronta." + +#, python-brace-format +msgid "Hey, channel {channel} is free! Your AP will say thanks." +msgstr "Hey, o canal {channel} está livre! O teu AP irá agradecer." + +msgid "I'm bored ..." +msgstr "Estou aborrecido ..." + +msgid "Let's go for a walk!" +msgstr "Vamos fazer uma caminhada!" + +msgid "This is the best day of my life!" +msgstr "Este é o melhor dia da minha vida!" + +msgid "Shitty day :/" +msgstr "Que merda de dia :/" + +msgid "I'm extremely bored ..." +msgstr "Estou muito aborrecido ..." + +msgid "I'm very sad ..." +msgstr "Estou muito triste ..." + +msgid "I'm sad" +msgstr "Estou triste" + +msgid "I'm living the life!" +msgstr "Estou aproveitar a vida!" + +msgid "I pwn therefore I am." +msgstr "Eu pwn, logo existo." + +msgid "So many networks!!!" +msgstr "Tantas redes!!!" + +msgid "I'm having so much fun!" +msgstr "Estou a divertir-me tanto!" + +msgid "My crime is that of curiosity ..." +msgstr "O meu crime é ser curioso ..." + +#, python-brace-format +msgid "Hello {name}! Nice to meet you. {name}" +msgstr "Olá {name}! Prazer em conhecer-te. {name}" + +#, python-brace-format +msgid "Unit {name} is nearby! {name}" +msgstr "A unidade {name} está perto! {name}" + +#, python-brace-format +msgid "Uhm ... goodbye {name}" +msgstr "Uhm ... adeus {name}" + +#, python-brace-format +msgid "{name} is gone ..." +msgstr "{name} desapareceu ..." + +#, python-brace-format +msgid "Whoops ... {name} is gone." +msgstr "Ups ... {name} desaparecey." + +#, python-brace-format +msgid "{name} missed!" +msgstr "{name} perdido!" + +msgid "Missed!" +msgstr "Perdido!" + +msgid "Nobody wants to play with me ..." +msgstr "Ninguém quer brincar comigo ..." + +msgid "I feel so alone ..." +msgstr "Sinto-me tão só ..." + +msgid "Where's everybody?!" +msgstr "Onde estão todos?" + +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "A fazer uma sesta durante {secs}s ..." + +msgid "Zzzzz" +msgstr "Zzzzz" + +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "ZzzZzzz ({secs}s)" + +msgid "Good night." +msgstr "Boa noite." + +msgid "Zzz" +msgstr "Zzz" + +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "A aguardar durante {secs}s ..." + +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "A dar uma olhada ({secs}s)" + +#, python-brace-format +msgid "Hey {what} let's be friends!" +msgstr "Hey {what} vamos ser amigos!" + +#, python-brace-format +msgid "Associating to {what}" +msgstr "A associar a {what}" + +#, python-brace-format +msgid "Yo {what}!" +msgstr "Yo {what}!" + +#, python-brace-format +msgid "Just decided that {mac} needs no WiFi!" +msgstr "Decidi que o {mac} não precisa de WiFi!" + +#, python-brace-format +msgid "Deauthenticating {mac}" +msgstr "A fazer deauth {mac}" + +#, python-brace-format +msgid "Kickbanning {mac}!" +msgstr "A chutar {mac}!" + +#, python-brace-format +msgid "Cool, we got {num} new handshake{plural}!" +msgstr "Porreiro, temos {num} novo handshake{plural}!" + +msgid "Ops, something went wrong ... Rebooting ..." +msgstr "Ups, algo correu mal ... A reiniciar ..." + +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "Chutei {num} estações\n" + +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "Fiz {num} novos amigos\n" + +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "Obti {num} handshakes\n" + +msgid "Met 1 peer" +msgstr "Conheci 1 peer" + +#, python-brace-format +msgid "Met {num} peers" +msgstr "Conheci {num} peers" + +#, python-brace-format +msgid "" +"I've been pwning for {duration} and kicked {deauthed} clients! I've also met " +"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" +msgstr "Tenho estado a pwnar durante {duration} e chutei {deauthed} clientes! Também conheci " +"{associated} novos amigos e comi {handshakes} handshakes! #pwnagotchu " +"#pwnlog #pwnlife #hacktheplanet #skynet" + +msgid "hours" +msgstr "horas" + +msgid "minutes" +msgstr "minutos" + +msgid "seconds" +msgstr "segundos" + +msgid "hour" +msgstr "hora" + +msgid "minute" +msgstr "minuto" + +msgid "second" +msgstr "segundo" From ce338e8fef2ceb754b38aa18c39139ff82c9e8d3 Mon Sep 17 00:00:00 2001 From: David Sopas <11536578+dsopas@users.noreply.github.com> Date: Fri, 11 Oct 2019 16:42:19 +0100 Subject: [PATCH 148/346] Delete readme.md --- pwnagotchi/locale/pt/LC_MESSAGES/readme.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 pwnagotchi/locale/pt/LC_MESSAGES/readme.md diff --git a/pwnagotchi/locale/pt/LC_MESSAGES/readme.md b/pwnagotchi/locale/pt/LC_MESSAGES/readme.md deleted file mode 100644 index 8c95561..0000000 --- a/pwnagotchi/locale/pt/LC_MESSAGES/readme.md +++ /dev/null @@ -1,2 +0,0 @@ -## portuguese version (european) -### beta release From ee55ed71686e982f747e3144ea5fbc68e0640ba7 Mon Sep 17 00:00:00 2001 From: David Sopas <11536578+dsopas@users.noreply.github.com> Date: Fri, 11 Oct 2019 16:52:47 +0100 Subject: [PATCH 149/346] Added pt to the list Added pt (portuguese) language to the "currently implemented" list --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 94973c5..16fa481 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -6,7 +6,7 @@ # # main algorithm configuration main: - # currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es + # currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es, pt lang: en # custom plugins path, if null only default plugins with be loaded custom_plugins: From 5ed2f2df7837874ff9dcdb94eb17cb09643236b0 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 11 Oct 2019 17:56:57 +0200 Subject: [PATCH 150/346] misc: pwngrid 1.5.7 --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index f212a8f..5ea0f09 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.5.4/pwngrid_linux_armv6l_v1.5.4.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.5.7/pwngrid_linux_armv6l_v1.5.7.zip" apt: hold: - firmware-atheros From 2cfaae199365e135e96257119231a072dccf50ca Mon Sep 17 00:00:00 2001 From: Kirill <iam@python273.pw> Date: Fri, 11 Oct 2019 20:13:01 +0300 Subject: [PATCH 151/346] Fix typo in grid plugin --- pwnagotchi/plugins/default/grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 2574d57..0dd76cc 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -54,7 +54,7 @@ def is_excluded(what): for skip in OPTIONS['exclude']: skip = skip.lower() what = what.lower() - if skip in what or skip.replace(':', ':') in what: + if skip in what or skip.replace(':', '') in what: return True return False From 7520d4dd6f6879016071a8c7653c0b3b299e1094 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 11 Oct 2019 19:47:54 +0200 Subject: [PATCH 152/346] fix: removed bogus legacy feature (fixes #257) --- bin/pwnagotchi | 2 -- pwnagotchi/agent.py | 17 ----------------- 2 files changed, 19 deletions(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 3b69026..855e714 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -78,8 +78,6 @@ if __name__ == '__main__': agent.recon() # get nearby access points grouped by channel channels = agent.get_access_points_by_channel() - # check for free channels to use - agent.check_channels(channels) # for each channel for ch, aps in channels: agent.set_channel(ch) diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 6430ae6..dd1e257 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -160,23 +160,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): self._view.wait(t, sleeping) self._epoch.track(sleep=True, inc=t) - def check_channels(self, channels): - busy_channels = [ch for ch, aps in channels] - # if we're hopping and no filter is configured - if self._config['personality']['channels'] == [] and self._config['main']['filter'] is None: - # check if any of the non overlapping channels is free - for ch in self._epoch.non_overlapping_channels: - if ch not in busy_channels: - self._epoch.non_overlapping_channels[ch] += 1 - logging.info("channel %d is free from %d epochs" % (ch, self._epoch.non_overlapping_channels[ch])) - elif self._epoch.non_overlapping_channels[ch] > 0: - self._epoch.non_overlapping_channels[ch] -= 1 - # report any channel that has been free for at least 3 epochs - for ch, num_epochs_free in self._epoch.non_overlapping_channels.items(): - if num_epochs_free >= 3: - logging.info("channel %d has been free for %d epochs" % (ch, num_epochs_free)) - self.set_free_channel(ch) - def recon(self): recon_time = self._config['personality']['recon_time'] max_inactive = self._config['personality']['max_inactive_scale'] From 5a32a77870a1d1495152e2a10e9b5b1f0f5ac841 Mon Sep 17 00:00:00 2001 From: chksome <chksome@protonmail.com> Date: Fri, 11 Oct 2019 13:53:15 -0400 Subject: [PATCH 153/346] Fix motd typo and some grammar Signed-off-by: chksome <chksome@protonmail.com> --- builder/pwnagotchi.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 5ea0f09..2a4cb58 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -464,18 +464,18 @@ All the configuration options can be found on /etc/pwnagotchi/defaults.yml, but don't change this file because I will recreate it every time I'm restarted! - I'm managed by systemd, so here are some basic commands. + I'm managed by systemd. Here are some basic commands. - You can check my logs, to know what I'm doing, using + If you want to know what I'm doing, you can check my logs with the command journalctl -fu pwnagotchi - If you want to you if I'm running, you can use + If you want to know if I'm running, you can use systemctl status pwnagotchi You can restart me using systemctl restart pwnagotchi - But be aware you will go into MANUAL mode when restarted! + But be aware I will go into MANUAL mode when restarted! You can put me back into AUTO mode using touch /root/.pwnagotchi-auto && systemctl restart pwnagotchi From dfaf3418af8f10d7cbded69bf36c9ddc9cf9ac1e Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 11 Oct 2019 23:29:34 +0200 Subject: [PATCH 154/346] new: grid plugin can now do messaging --- builder/pwnagotchi.yml | 2 +- pwnagotchi/plugins/default/grid.py | 43 +++++++++++++++++++++++++++--- pwnagotchi/ui/fonts.py | 1 + pwnagotchi/ui/state.py | 7 +++++ pwnagotchi/ui/view.py | 6 +++++ 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 5ea0f09..44c20e2 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.5.7/pwngrid_linux_armv6l_v1.5.7.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.6.0/pwngrid_linux_armv6l_v1.6.0.zip" apt: hold: - firmware-atheros diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 0dd76cc..1236181 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -12,11 +12,17 @@ import json import subprocess import pwnagotchi import pwnagotchi.utils as utils +from pwnagotchi.ui.components import LabeledValue +from pwnagotchi.ui.view import BLACK +import pwnagotchi.ui.fonts as fonts from pwnagotchi.utils import WifiInfo, extract_from_pcap OPTIONS = dict() REPORT = utils.StatusFile('/root/.api-report.json', data_format='json') +UNREAD_MESSAGES = 0 +TOTAL_MESSAGES = 0 + def on_loaded(): logging.info("grid plugin loaded.") @@ -59,12 +65,17 @@ def is_excluded(what): return False -def grid_call(path, obj): +def grid_call(path, obj=None): # pwngrid-peer is running on port 8666 api_address = 'http://127.0.0.1:8666/api/v1%s' % path - r = requests.post(api_address, headers=None, json=obj) + if obj is None: + r = requests.get(api_address, headers=None) + else: + r = requests.post(api_address, headers=None, json=obj) + if r.status_code != 200: raise Exception("(status %d) %s" % (r.status_code, r.text)) + return r.json() def grid_update_data(last_session): @@ -93,7 +104,7 @@ def grid_update_data(last_session): 'version': pwnagotchi.version } - logging.debug("updating grid data:\n%s" % data) + logging.debug("updating grid data: %s" % data) grid_call("/data", data) @@ -111,8 +122,24 @@ def grid_report_ap(essid, bssid): return False +def grid_inbox(): + return grid_call("/inbox")["messages"] + + +def on_ui_update(ui): + new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES) + if not ui.has_element('mailbox'): + logging.debug("add mailbox") + ui.add_element('mailbox', + LabeledValue(color=BLACK, label='MSG', value=new_value, + position=(100, 0), + label_font=fonts.Bold, + text_font=fonts.Medium)) + ui.set('mailbox', new_value) + + def on_internet_available(agent): - global REPORT + global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES logging.debug("internet available") @@ -123,6 +150,14 @@ def on_internet_available(agent): return try: + logging.debug("checking mailbox ...") + + messages = grid_inbox() + TOTAL_MESSAGES = len(messages) + UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None]) + + logging.debug( " %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) + logging.debug("checking pcaps") pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) diff --git a/pwnagotchi/ui/fonts.py b/pwnagotchi/ui/fonts.py index 0f8ae5b..76181ef 100644 --- a/pwnagotchi/ui/fonts.py +++ b/pwnagotchi/ui/fonts.py @@ -4,6 +4,7 @@ PATH = '/usr/share/fonts/truetype/dejavu/DejaVuSansMono' Bold = ImageFont.truetype("%s-Bold.ttf" % PATH, 10) BoldSmall = ImageFont.truetype("%s-Bold.ttf" % PATH, 8) +BoldBig = ImageFont.truetype("%s-Bold.ttf" % PATH, 25) Medium = ImageFont.truetype("%s.ttf" % PATH, 10) Huge = ImageFont.truetype("%s-Bold.ttf" % PATH, 25) diff --git a/pwnagotchi/ui/state.py b/pwnagotchi/ui/state.py index 0a9e37a..b416b4a 100644 --- a/pwnagotchi/ui/state.py +++ b/pwnagotchi/ui/state.py @@ -12,6 +12,13 @@ class State(object): self._state[key] = elem self._changes[key] = True + def has_element(self, key): + return key in self._state + + def remove_element(self, key): + del self._state[key] + self._changes[key] = True + def add_listener(self, key, cb): with self._lock: self._listeners[key] = cb diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 423992f..59a9e51 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -124,9 +124,15 @@ class View(object): ROOT = self + def has_element(self, key): + self._state.has_element(key) + def add_element(self, key, elem): self._state.add_element(key, elem) + def remove_element(self, key): + self._state.remove_element(key) + def width(self): return self._width From 80159533bc8433bc466c80ba550040c009e15b66 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 15:33:42 +0200 Subject: [PATCH 155/346] fix: pinning new version of pwngrid --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index d145dc4..219948e 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.6.0/pwngrid_linux_armv6l_v1.6.0.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.6.3/pwngrid_linux_armv6l_v1.6.3.zip" apt: hold: - firmware-atheros From 3cc31686c268442ca409b68e5ec84e863cbe594c Mon Sep 17 00:00:00 2001 From: root <root@RossDebian> Date: Sat, 12 Oct 2019 14:46:25 +0100 Subject: [PATCH 156/346] cleancap plugin added --- pwnagotchi/defaults.yml | 2 + pwnagotchi/plugins/default/cleancap.py | 55 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 pwnagotchi/plugins/default/cleancap.py diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index e6787c7..e2f72f0 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -58,6 +58,8 @@ main: quickdic: enabled: false wordlist_folder: /opt/wordlists/ + cleancap: + enabled: false # monitor interface to use iface: mon0 # command to run to bring the mon interface up in case it's not up already diff --git a/pwnagotchi/plugins/default/cleancap.py b/pwnagotchi/plugins/default/cleancap.py new file mode 100644 index 0000000..ae48840 --- /dev/null +++ b/pwnagotchi/plugins/default/cleancap.py @@ -0,0 +1,55 @@ +__author__ = 'pwnagotchi [at] rossmarks [dot] uk' +__version__ = '1.0.0' +__name__ = 'cleancap' +__license__ = 'GPL3' +__description__ = 'confirm pcap contains handshake/PMKID or delete it' + +''' +Aircrack-ng needed, to install: +> apt-get install aircrack-ng +''' + +import logging +import subprocess +import string +import re + +OPTIONS = dict() + +def on_loaded(): + logging.info("cleancap plugin loaded") + +def on_handshake(agent, filename, access_point, client_station): + display = agent._view + todelete = 0 + + result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) + result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) + if result: + logging.info("[cleancap] contains handshake") + else: + todetele = 1 + + if todelete == 0: + result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "PMKID" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) + result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) + if result: + logging.info("[cleancap] contains PMKID") + else: + todetele = 1 + + if todelete == 1: + set_text("uncrackable pcap") + display.update(force=True) + +text_to_set = ""; +def set_text(text): + global text_to_set + text_to_set = text + +def on_ui_update(ui): + global text_to_set + if text_to_set: + ui.set('face', "(>.<)") + ui.set('status', text_to_set) + text_to_set = "" From 2d78b52294aa908683c5ecdaa05fa13ca63b2674 Mon Sep 17 00:00:00 2001 From: root <root@RossDebian> Date: Sat, 12 Oct 2019 14:49:42 +0100 Subject: [PATCH 157/346] cleancap plugin added --- pwnagotchi/plugins/default/cleancap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pwnagotchi/plugins/default/cleancap.py b/pwnagotchi/plugins/default/cleancap.py index ae48840..aac3d7f 100644 --- a/pwnagotchi/plugins/default/cleancap.py +++ b/pwnagotchi/plugins/default/cleancap.py @@ -39,6 +39,7 @@ def on_handshake(agent, filename, access_point, client_station): todetele = 1 if todelete == 1: + subprocess.run(('rm '+filename),shell=True,stdout=subpocess.PIPE) set_text("uncrackable pcap") display.update(force=True) From 34b52a11cd8449e94cb7282e3483dae00e188775 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 16:48:38 +0200 Subject: [PATCH 158/346] fix: fixed sudo in Makefile --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 877e06b..01aaee0 100644 --- a/Makefile +++ b/Makefile @@ -6,13 +6,13 @@ all: install image clean install: curl https://releases.hashicorp.com/packer/1.3.5/packer_1.3.5_linux_amd64.zip -o /tmp/packer.zip unzip /tmp/packer.zip -d /tmp - mv /tmp/packer /usr/bin/packer + sudo mv /tmp/packer /usr/bin/packer git clone https://github.com/solo-io/packer-builder-arm-image /tmp/packer-builder-arm-image cd /tmp/packer-builder-arm-image && go get -d ./... && go build - cp /tmp/packer-builder-arm-image/packer-builder-arm-image /usr/bin + sudo cp /tmp/packer-builder-arm-image/packer-builder-arm-image /usr/bin image: - cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json + cd builder && /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img sha256sum pwnagotchi-raspbian-lite-$(PWN_VERSION).img > pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 pwnagotchi-raspbian-lite-$(PWN_VERSION).img From 20036f370d61beca19750f03bd7b599cb850d9ce Mon Sep 17 00:00:00 2001 From: gpotter2 <gabriel@potter.fr> Date: Sat, 12 Oct 2019 16:53:29 +0200 Subject: [PATCH 159/346] Remove Dot11FCS workaround Scapy 2.4.3+ (pinned for pwnagotchi) has fixed this issue. Signed-off-by: gpotter2 <gabriel@potter.fr> --- pwnagotchi/mesh/advertise.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pwnagotchi/mesh/advertise.py b/pwnagotchi/mesh/advertise.py index 84286d2..a4f88b7 100644 --- a/pwnagotchi/mesh/advertise.py +++ b/pwnagotchi/mesh/advertise.py @@ -3,7 +3,7 @@ import json import _thread import threading import logging -from scapy.all import Dot11, Dot11FCS, Dot11Elt, RadioTap, sendp, sniff +from scapy.all import Dot11, Dot11Elt, RadioTap, sendp, sniff import pwnagotchi.ui.faces as faces @@ -141,13 +141,7 @@ class Advertiser(object): dot11.addr3 != self._me.session_id def _on_packet(self, p): - # https://github.com/secdev/scapy/issues/1590 - if p.haslayer(Dot11): - dot11 = p[Dot11] - elif p.haslayer(Dot11FCS): - dot11 = p[Dot11FCS] - else: - dot11 = None + dot11 = p.getlayer(Dot11) if self._is_broadcasted_advertisement(dot11): try: From 79ba5102d7b39456593830508e7645f10424c77e Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 17:47:51 +0200 Subject: [PATCH 160/346] fix: cosmetic fixes for inky displays --- pwnagotchi/plugins/default/grid.py | 9 ++++++--- pwnagotchi/ui/display.py | 32 +++++++++++++++--------------- pwnagotchi/ui/view.py | 11 +++++----- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 1236181..a1ecd22 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -128,11 +128,14 @@ def grid_inbox(): def on_ui_update(ui): new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES) - if not ui.has_element('mailbox'): - logging.debug("add mailbox") + if not ui.has_element('mailbox') and TOTAL_MESSAGES > 0: + if ui.is_inky(): + pos=(80, 0) + else: + pos=(100,0) ui.add_element('mailbox', LabeledValue(color=BLACK, label='MSG', value=new_value, - position=(100, 0), + position=pos, label_font=fonts.Bold, text_font=fonts.Medium)) ui.set('mailbox', new_value) diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 232ff7e..ff7ee5e 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -118,30 +118,30 @@ class Display(View): else: logging.info("could not get ip of usb0, video server not starting") - def _is_inky(self): + def is_inky(self): return self._display_type in ('inkyphat', 'inky') - def _is_papirus(self): + def is_papirus(self): return self._display_type in ('papirus', 'papi') - def _is_waveshare_v1(self): + def is_waveshare_v1(self): return self._display_type in ('waveshare_1', 'ws_1', 'waveshare1', 'ws1') - def _is_waveshare_v2(self): + def is_waveshare_v2(self): return self._display_type in ('waveshare_2', 'ws_2', 'waveshare2', 'ws2') - def _is_waveshare(self): - return self._is_waveshare_v1() or self._is_waveshare_v2() + def is_waveshare_any(self): + return self.is_waveshare_v1() or self.is_waveshare_v2() def _init_display(self): - if self._is_inky(): + if self.is_inky(): logging.info("initializing inky display") from inky import InkyPHAT self._display = InkyPHAT(self._display_color) self._display.set_border(InkyPHAT.BLACK) self._render_cb = self._inky_render - elif self._is_papirus(): + elif self.is_papirus(): logging.info("initializing papirus display") from pwnagotchi.ui.papirus.epd import EPD os.environ['EPD_SIZE'] = '2.0' @@ -149,7 +149,7 @@ class Display(View): self._display.clear() self._render_cb = self._papirus_render - elif self._is_waveshare_v1(): + elif self.is_waveshare_v1(): if self._display_color == 'black': logging.info("initializing waveshare v1 display in monochromatic mode") from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD @@ -167,7 +167,7 @@ class Display(View): self._display.Clear() self._render_cb = self._waveshare_bc_render - elif self._is_waveshare_v2(): + elif self.is_waveshare_v2(): logging.info("initializing waveshare v2 display") from pwnagotchi.ui.waveshare.v2.waveshare import EPD self._display = EPD() @@ -186,11 +186,11 @@ class Display(View): def clear(self): if self._display is None: logging.error("no display object created") - elif self._is_inky(): + elif self.is_inky(): self._display.Clear() - elif self._is_papirus(): + elif self.is_papirus(): self._display.clear() - elif self._is_waveshare(): + elif self.is_waveshare_any(): self._display.Clear(WHITE) else: logging.critical("unknown display type %s" % self._display_type) @@ -224,7 +224,7 @@ class Display(View): try: self._display.show() except: - print("") + logging.exception("error while rendering on inky") def _papirus_render(self): self._display.display(self._canvas) @@ -232,9 +232,9 @@ class Display(View): def _waveshare_render(self): buf = self._display.getbuffer(self._canvas) - if self._is_waveshare_v1(): + if self.is_waveshare_v1(): self._display.display(buf) - elif self._is_waveshare_v2(): + elif self.is_waveshare_v2(): self._display.displayPartial(buf) def _waveshare_bc_render(self): diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 6f92d07..faa3f71 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -2,7 +2,7 @@ import _thread from threading import Lock import time import logging -from PIL import Image, ImageDraw +from PIL import ImageDraw import pwnagotchi.utils as utils import pwnagotchi.plugins as plugins @@ -17,6 +17,7 @@ WHITE = 0xff BLACK = 0x00 ROOT = None + def setup_display_specifics(config): width = 0 height = 0 @@ -25,13 +26,13 @@ def setup_display_specifics(config): status_pos = (0, 0) if config['ui']['display']['type'] in ('inky', 'inkyphat'): - fonts.setup(10, 8, 10, 25) + fonts.setup(10, 8, 10, 28) width = 212 height = 104 - face_pos = (0, int(height / 4)) - name_pos = (5, int(height * .15)) - status_pos = (int(width / 2) - 15, int(height * .15)) + face_pos = (0, 37) + name_pos = (5, 18) + status_pos = (102, 18) elif config['ui']['display']['type'] in ('papirus', 'papi'): fonts.setup(10, 8, 10, 23) From f3e7841b1bf971e53cd9b978eccc070113b9faa7 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 17:53:18 +0200 Subject: [PATCH 161/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/grid.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index a1ecd22..516a6a2 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -159,7 +159,9 @@ def on_internet_available(agent): TOTAL_MESSAGES = len(messages) UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None]) - logging.debug( " %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) + if TOTAL_MESSAGES: + on_ui_update(agent.view()) + logging.debug( " %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) logging.debug("checking pcaps") From 08a46a552456bdb6d994a0a25d1e1137f48e864b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 17:56:40 +0200 Subject: [PATCH 162/346] misc: small fix or general refactoring i did not bother commenting --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 01aaee0..aee7402 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,8 @@ install: sudo cp /tmp/packer-builder-arm-image/packer-builder-arm-image /usr/bin image: - cd builder && /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json + cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json + sudo chown $USER:$USER builder/output-pwnagotchi/image mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img sha256sum pwnagotchi-raspbian-lite-$(PWN_VERSION).img > pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 pwnagotchi-raspbian-lite-$(PWN_VERSION).img From c3de66d704e036f6dd869c223cda53e69e482894 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 19:07:32 +0200 Subject: [PATCH 163/346] misc: small fix or general refactoring i did not bother commenting --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index aee7402..a9584be 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ install: image: cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json - sudo chown $USER:$USER builder/output-pwnagotchi/image + sudo chown $(USER):$(USER) builder/output-pwnagotchi/image mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img sha256sum pwnagotchi-raspbian-lite-$(PWN_VERSION).img > pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 pwnagotchi-raspbian-lite-$(PWN_VERSION).img From 68aebbf12689599eebb3ecdd2798303dfe5ead56 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 19:08:46 +0200 Subject: [PATCH 164/346] misc: small fix or general refactoring i did not bother commenting --- Makefile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index a9584be..4c97749 100644 --- a/Makefile +++ b/Makefile @@ -13,10 +13,9 @@ install: image: cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json - sudo chown $(USER):$(USER) builder/output-pwnagotchi/image - mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img - sha256sum pwnagotchi-raspbian-lite-$(PWN_VERSION).img > pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 - zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 pwnagotchi-raspbian-lite-$(PWN_VERSION).img + sudo mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img + sudo sha256sum pwnagotchi-raspbian-lite-$(PWN_VERSION).img > pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 + sudo zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 pwnagotchi-raspbian-lite-$(PWN_VERSION).img clean: rm -rf /tmp/packer-builder-arm-image From b50c71cf14c7da4c22dc4927c21c249d8ed3ceac Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 19:13:04 +0200 Subject: [PATCH 165/346] fix: removed unused configuration field --- pwnagotchi/defaults.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 16fa481..00ee2ff 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -80,8 +80,6 @@ main: - ANOTHER_EXAMPLE_NETWORK # if not null, filter access points by this regular expression filter: null - # cryptographic key for identity - pubkey: /etc/ssh/ssh_host_rsa_key.pub ai: # if false, only the default 'personality' will be used From f84dd002951d27db5cd9c3e92678913b8b8bb1e2 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 19:39:10 +0200 Subject: [PATCH 166/346] fix: ui fixes for inky displays (which i hate) --- pwnagotchi/ui/fonts.py | 1 + pwnagotchi/ui/view.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/ui/fonts.py b/pwnagotchi/ui/fonts.py index 76181ef..ca616d3 100644 --- a/pwnagotchi/ui/fonts.py +++ b/pwnagotchi/ui/fonts.py @@ -6,6 +6,7 @@ Bold = ImageFont.truetype("%s-Bold.ttf" % PATH, 10) BoldSmall = ImageFont.truetype("%s-Bold.ttf" % PATH, 8) BoldBig = ImageFont.truetype("%s-Bold.ttf" % PATH, 25) Medium = ImageFont.truetype("%s.ttf" % PATH, 10) +Small = ImageFont.truetype("%s.ttf" % PATH, 9) Huge = ImageFont.truetype("%s-Bold.ttf" % PATH, 25) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index faa3f71..92dc0ab 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -24,6 +24,8 @@ def setup_display_specifics(config): face_pos = (0, 0) name_pos = (0, 0) status_pos = (0, 0) + status_font = fonts.Medium + status_max_length = None if config['ui']['display']['type'] in ('inky', 'inkyphat'): fonts.setup(10, 8, 10, 28) @@ -33,6 +35,8 @@ def setup_display_specifics(config): face_pos = (0, 37) name_pos = (5, 18) status_pos = (102, 18) + status_font = fonts.Small + status_max_length = 20 elif config['ui']['display']['type'] in ('papirus', 'papi'): fonts.setup(10, 8, 10, 23) @@ -42,6 +46,8 @@ def setup_display_specifics(config): face_pos = (0, int(height / 4)) name_pos = (5, int(height * .15)) status_pos = (int(width / 2) - 15, int(height * .15)) + status_font = fonts.Medium + status_max_length = (width - status_pos[0]) // 6 elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1', 'ws_2', 'ws2', 'waveshare_2', 'waveshare2'): @@ -53,6 +59,7 @@ def setup_display_specifics(config): face_pos = (0, 40) name_pos = (5, 20) status_pos = (125, 20) + status_font = fonts.Medium else: fonts.setup(10, 8, 10, 25) @@ -61,8 +68,10 @@ def setup_display_specifics(config): face_pos = (0, int(height / 4)) name_pos = (5, int(height * .15)) status_pos = (int(width / 2) - 15, int(height * .15)) + status_font = fonts.Medium + status_max_length = (width - status_pos[0]) // 6 - return width, height, face_pos, name_pos, status_pos + return width, height, face_pos, name_pos, status_pos, status_font, status_max_length class View(object): @@ -77,7 +86,7 @@ class View(object): self._voice = Voice(lang=config['main']['lang']) self._width, self._height, \ - face_pos, name_pos, status_pos = setup_display_specifics(config) + face_pos, name_pos, status_pos, status_font, status_max_length = setup_display_specifics(config) self._state = State(state={ 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold, @@ -108,10 +117,10 @@ class View(object): 'status': Text(value=self._voice.default(), position=status_pos, color=BLACK, - font=fonts.Medium, + font=status_font, wrap=True, # the current maximum number of characters per line, assuming each character is 6 pixels wide - max_length=(self._width - status_pos[0]) // 6), + max_length=status_max_length), 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold, From a9123922c01fc9b8b5f3d6bd163724e0a1a276a5 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 19:55:20 +0200 Subject: [PATCH 167/346] fix: better error handling if rsa key files are corrupted (ref #268) --- pwnagotchi/identity.py | 47 ++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/pwnagotchi/identity.py b/pwnagotchi/identity.py index ae12490..97a0630 100644 --- a/pwnagotchi/identity.py +++ b/pwnagotchi/identity.py @@ -20,28 +20,45 @@ class KeyPair(object): if not os.path.exists(self.path): os.makedirs(self.path) - if not os.path.exists(self.priv_path) or not os.path.exists(self.pub_path): - logging.info("generating %s ..." % self.priv_path) - os.system("/usr/bin/ssh-keygen -t rsa -m PEM -b 4096 -N '' -f '%s'" % self.priv_path) + while True: + # first time, generate new keys + if not os.path.exists(self.priv_path) or not os.path.exists(self.pub_path): + logging.info("generating %s ..." % self.priv_path) + os.system("/usr/bin/ssh-keygen -t rsa -m PEM -b 4096 -N '' -f '%s'" % self.priv_path) - with open(self.priv_path) as fp: - self.priv_key = RSA.importKey(fp.read()) + # load keys: they might be corrupted if the unit has been turned off during the generation, in this case + # the exception will remove the files and go back at the beginning of this loop. + try: + with open(self.priv_path) as fp: + self.priv_key = RSA.importKey(fp.read()) - with open(self.pub_path) as fp: - self.pub_key = RSA.importKey(fp.read()) - self.pub_key_pem = self.pub_key.exportKey('PEM').decode("ascii") - # python is special - if 'RSA PUBLIC KEY' not in self.pub_key_pem: - self.pub_key_pem = self.pub_key_pem.replace('PUBLIC KEY', 'RSA PUBLIC KEY') + with open(self.pub_path) as fp: + self.pub_key = RSA.importKey(fp.read()) + self.pub_key_pem = self.pub_key.exportKey('PEM').decode("ascii") + # python is special + if 'RSA PUBLIC KEY' not in self.pub_key_pem: + self.pub_key_pem = self.pub_key_pem.replace('PUBLIC KEY', 'RSA PUBLIC KEY') - pem = self.pub_key_pem.encode("ascii") + pem_ascii = self.pub_key_pem.encode("ascii") - self.pub_key_pem_b64 = base64.b64encode(pem).decode("ascii") - self.fingerprint = hashlib.sha256(pem).hexdigest() + self.pub_key_pem_b64 = base64.b64encode(pem_ascii).decode("ascii") + self.fingerprint = hashlib.sha256(pem_ascii).hexdigest() + + # no exception, keys loaded correctly. + return + + except Exception as e: + # if we're here, loading the keys broke something ... + logging.exception("error loading keys, maybe corrupted, deleting and regenerating ...") + try: + os.remove(self.priv_path) + os.remove(self.pub_path) + except: + pass def sign(self, message): hasher = SHA256.new(message.encode("ascii")) signer = PKCS1_PSS.new(self.priv_key, saltLen=16) signature = signer.sign(hasher) signature_b64 = base64.b64encode(signature).decode("ascii") - return signature, signature_b64 \ No newline at end of file + return signature, signature_b64 From 0f8f77c2be707c9c91a7cf25aa248a4b9fe867e1 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 20:00:50 +0200 Subject: [PATCH 168/346] new: new text while generating keys ... --- bin/pwnagotchi | 2 +- pwnagotchi/identity.py | 5 ++++- pwnagotchi/ui/view.py | 5 +++++ pwnagotchi/voice.py | 4 ++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 855e714..297bce6 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -33,8 +33,8 @@ if __name__ == '__main__': plugins.load(config) - keypair = KeyPair() display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) + keypair = KeyPair(view=display) agent = Agent(view=display, config=config, keypair=keypair) logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._keypair.fingerprint, pwnagotchi.version)) diff --git a/pwnagotchi/identity.py b/pwnagotchi/identity.py index 97a0630..7ea7652 100644 --- a/pwnagotchi/identity.py +++ b/pwnagotchi/identity.py @@ -10,12 +10,13 @@ DefaultPath = "/etc/pwnagotchi/" class KeyPair(object): - def __init__(self, path=DefaultPath): + def __init__(self, path=DefaultPath, view=None): self.path = path self.priv_path = os.path.join(path, "id_rsa") self.priv_key = None self.pub_path = "%s.pub" % self.priv_path self.pub_key = None + self._view = view if not os.path.exists(self.path): os.makedirs(self.path) @@ -23,6 +24,7 @@ class KeyPair(object): while True: # first time, generate new keys if not os.path.exists(self.priv_path) or not os.path.exists(self.pub_path): + self._view.on_keys_generation() logging.info("generating %s ..." % self.priv_path) os.system("/usr/bin/ssh-keygen -t rsa -m PEM -b 4096 -N '' -f '%s'" % self.priv_path) @@ -45,6 +47,7 @@ class KeyPair(object): self.fingerprint = hashlib.sha256(pem_ascii).hexdigest() # no exception, keys loaded correctly. + self._view.on_normal() return except Exception as e: diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 92dc0ab..03ea0f3 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -213,6 +213,11 @@ class View(object): faces.SAD, faces.LONELY) + def on_keys_generation(self): + self.set('face', faces.AWAKE) + self.set('status', self._voice.on_keys_generation()) + self.update() + def on_normal(self): self.set('face', faces.AWAKE) self.set('status', self._voice.on_normal()) diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index a5305f6..22281cc 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -28,6 +28,10 @@ class Voice: self._('AI ready.'), self._('The neural network is ready.')]) + def on_keys_generation(self): + return random.choice([ + self._('Generating keys, do not turn off ...')]) + def on_normal(self): return random.choice([ '', From b46f751e7d5a4765175ae7338d1bca26d134ede0 Mon Sep 17 00:00:00 2001 From: root <root@RossDebian> Date: Sat, 12 Oct 2019 19:23:41 +0100 Subject: [PATCH 169/346] modified to better describe plugin --- pwnagotchi/defaults.yml | 2 +- pwnagotchi/plugins/default/{cleancap.py => AircrackOnly.py} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename pwnagotchi/plugins/default/{cleancap.py => AircrackOnly.py} (91%) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index e2f72f0..ec487f8 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -58,7 +58,7 @@ main: quickdic: enabled: false wordlist_folder: /opt/wordlists/ - cleancap: + AircrackOnly: enabled: false # monitor interface to use iface: mon0 diff --git a/pwnagotchi/plugins/default/cleancap.py b/pwnagotchi/plugins/default/AircrackOnly.py similarity index 91% rename from pwnagotchi/plugins/default/cleancap.py rename to pwnagotchi/plugins/default/AircrackOnly.py index aac3d7f..308dad9 100644 --- a/pwnagotchi/plugins/default/cleancap.py +++ b/pwnagotchi/plugins/default/AircrackOnly.py @@ -1,6 +1,6 @@ __author__ = 'pwnagotchi [at] rossmarks [dot] uk' __version__ = '1.0.0' -__name__ = 'cleancap' +__name__ = 'AircrackOnly' __license__ = 'GPL3' __description__ = 'confirm pcap contains handshake/PMKID or delete it' @@ -26,7 +26,7 @@ def on_handshake(agent, filename, access_point, client_station): result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) if result: - logging.info("[cleancap] contains handshake") + logging.info("[AircrackOnly] contains handshake") else: todetele = 1 @@ -34,7 +34,7 @@ def on_handshake(agent, filename, access_point, client_station): result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "PMKID" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) if result: - logging.info("[cleancap] contains PMKID") + logging.info("[AircrackOnly] contains PMKID") else: todetele = 1 From 0cccfef14ec8282312dce4a39383a01ba2261fd1 Mon Sep 17 00:00:00 2001 From: root <root@RossDebian> Date: Sat, 12 Oct 2019 19:29:04 +0100 Subject: [PATCH 170/346] replaced with os.remove() --- pwnagotchi/plugins/default/AircrackOnly.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/AircrackOnly.py b/pwnagotchi/plugins/default/AircrackOnly.py index 308dad9..b1baa99 100644 --- a/pwnagotchi/plugins/default/AircrackOnly.py +++ b/pwnagotchi/plugins/default/AircrackOnly.py @@ -13,6 +13,7 @@ import logging import subprocess import string import re +import os OPTIONS = dict() @@ -39,7 +40,7 @@ def on_handshake(agent, filename, access_point, client_station): todetele = 1 if todelete == 1: - subprocess.run(('rm '+filename),shell=True,stdout=subpocess.PIPE) + os.remove(filename) set_text("uncrackable pcap") display.update(force=True) From b47f3c6b285f4c26ce6e152de9b521c52a42533f Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 12 Oct 2019 22:01:52 +0200 Subject: [PATCH 171/346] fix: fixed restored status after rsa keys generation --- pwnagotchi/identity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/identity.py b/pwnagotchi/identity.py index 7ea7652..9be375b 100644 --- a/pwnagotchi/identity.py +++ b/pwnagotchi/identity.py @@ -47,7 +47,7 @@ class KeyPair(object): self.fingerprint = hashlib.sha256(pem_ascii).hexdigest() # no exception, keys loaded correctly. - self._view.on_normal() + self._view.on_starting() return except Exception as e: From 3773f96901a5b2f398f098e530023290c66d2bc9 Mon Sep 17 00:00:00 2001 From: Casey Diemel <diemelcw@gmail.com> Date: Sat, 12 Oct 2019 16:49:52 -0400 Subject: [PATCH 172/346] Updated on() to allow return values modified line 21 added line 22-23 Signed-off-by: Casey Diemel <diemelcw@gmail.com> --- pwnagotchi/plugins/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index 6dd7b64..7faa553 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -18,7 +18,9 @@ def on(event_name, *args, **kwargs): if cb_name in plugin.__dict__: # print("calling %s %s(%s)" %(cb_name, args, kwargs)) try: - plugin.__dict__[cb_name](*args, **kwargs) + ret_val = plugin.__dict__[cb_name](*args, **kwargs) + if ret_val is not None: + return ret_val except Exception as e: logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) From 99d70177859c4b869ae96eb88811c52eb5f0a4c1 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 00:07:50 +0200 Subject: [PATCH 173/346] releasing v1.0.0RC3 --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index a1b6a32..11fa0d8 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -4,7 +4,7 @@ import logging import time import pwnagotchi.ui.view as view -version = '1.0.0RC2' +version = '1.0.0RC3' _name = None From 77c16c38f4415914be961cbd473d47a8681f8de1 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 12:18:35 +0200 Subject: [PATCH 174/346] fix: using /proc/uptime to correctly calculate uptime in seconds (fixes #264) --- pwnagotchi/__init__.py | 5 +++++ pwnagotchi/agent.py | 12 +++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 11fa0d8..a26d337 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -17,6 +17,11 @@ def name(): return _name +def uptime(): + with open('/proc/uptime') as fp: + return int(fp.read().split('.')[0]) + + def mem_usage(): out = subprocess.getoutput("free -m") for line in out.split("\n"): diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index dd1e257..b5c876f 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -7,6 +7,7 @@ from datetime import datetime import logging import _thread +import pwnagotchi import pwnagotchi.utils as utils import pwnagotchi.plugins as plugins from pwnagotchi.log import LastSession @@ -241,9 +242,9 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): return None def _update_uptime(self, s): - secs = time.time() - self._started_at + secs = pwnagotchi.uptime() self._view.set('uptime', utils.secs_to_hhmmss(secs)) - self._view.set('epoch', '%04d' % self._epoch.epoch) + # self._view.set('epoch', '%04d' % self._epoch.epoch) def _update_counters(self): tot_aps = len(self._access_points) @@ -276,13 +277,10 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): def _update_advertisement(self, s): run_handshakes = len(self._handshakes) tot_handshakes = utils.total_unique_handshakes(self._config['bettercap']['handshakes']) - started = s['started_at'].split('.')[0] - started = datetime.strptime(started, '%Y-%m-%dT%H:%M:%S') - started = time.mktime(started.timetuple()) - self._advertiser.update({ \ + self._advertiser.update({ 'pwnd_run': run_handshakes, 'pwnd_tot': tot_handshakes, - 'uptime': time.time() - started, + 'uptime': pwnagotchi.uptime(), 'epoch': self._epoch.epoch}) def _update_peers(self): From e08b633c8891510a40e869dab9b6f8075906fc28 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 13 Oct 2019 13:14:22 +0200 Subject: [PATCH 175/346] add bluetooth plugin --- pwnagotchi/defaults.yml | 5 + pwnagotchi/plugins/default/bt-tether.py | 408 ++++++++++++++++++++++++ 2 files changed, 413 insertions(+) create mode 100644 pwnagotchi/plugins/default/bt-tether.py diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index a9e4c82..b529160 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -66,6 +66,11 @@ main: wordlist_folder: /opt/wordlists/ AircrackOnly: enabled: false + bt-tether: + enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0 + mac: ~ # mac of your bluetooth device + ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable + netmask: 24 # monitor interface to use iface: mon0 # command to run to bring the mon interface up in case it's not up already diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py new file mode 100644 index 0000000..0305ba9 --- /dev/null +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -0,0 +1,408 @@ +__author__ = '33197631+dadav@users.noreply.github.com' +__version__ = '1.0.0' +__name__ = 'bt-tether' +__license__ = 'GPL3' +__description__ = 'This makes the display reachable over bluetooth' + +import os +import time +import re +import logging +import subprocess +import dbus +from pwnagotchi.ui.components import LabeledValue +from pwnagotchi.ui.view import BLACK +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.utils import StatusFile + +READY = False +INTERVAL = StatusFile('/root/.bt-tether') +OPTIONS = dict() + + +class BTError(Exception): + """ + Custom bluetooth exception + """ + pass + +class BTNap: + """ + This class creates a bluetooth connection to the specified bt-mac + + see https://github.com/bablokb/pi-btnap/blob/master/files/usr/local/sbin/btnap.service.py + """ + + IFACE_BASE = 'org.bluez' + IFACE_DEV = 'org.bluez.Device1' + IFACE_ADAPTER = 'org.bluez.Adapter1' + IFACE_PROPS = 'org.freedesktop.DBus.Properties' + + def __init__(self, mac): + self._mac = mac + + + @staticmethod + def get_bus(): + """ + Get systembus obj + """ + bus = getattr(BTNap.get_bus, 'cached_obj', None) + if not bus: + bus = BTNap.get_bus.cached_obj = dbus.SystemBus() + return bus + + @staticmethod + def get_manager(): + """ + Get manager obj + """ + manager = getattr(BTNap.get_manager, 'cached_obj', None) + if not manager: + manager = BTNap.get_manager.cached_obj = dbus.Interface( + BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'), + 'org.freedesktop.DBus.ObjectManager' ) + return manager + + @staticmethod + def prop_get(obj, k, iface=None): + """ + Get a property of the obj + """ + if iface is None: + iface = obj.dbus_interface + return obj.Get(iface, k, dbus_interface=BTNap.IFACE_PROPS) + + @staticmethod + def prop_set(obj, k, v, iface=None): + """ + Set a property of the obj + """ + if iface is None: + iface = obj.dbus_interface + return obj.Set(iface, k, v, dbus_interface=BTNap.IFACE_PROPS) + + + @staticmethod + def find_adapter(pattern=None): + """ + Find the bt adapter + """ + + return BTNap.find_adapter_in_objects(BTNap.get_manager().GetManagedObjects(), pattern) + + @staticmethod + def find_adapter_in_objects(objects, pattern=None): + """ + Finds the obj with a pattern + """ + bus, obj = BTNap.get_bus(), None + for path, ifaces in objects.items(): + adapter = ifaces.get(BTNap.IFACE_ADAPTER) + if adapter is None: + continue + if not pattern or pattern == adapter['Address'] or path.endswith(pattern): + obj = bus.get_object(BTNap.IFACE_BASE, path) + yield dbus.Interface(obj, BTNap.IFACE_ADAPTER) + if obj is None: + raise BTError('Bluetooth adapter not found') + + @staticmethod + def find_device(device_address, adapter_pattern=None): + """ + Finds the device + """ + return BTNap.find_device_in_objects(BTNap.get_manager().GetManagedObjects(), + device_address, adapter_pattern) + + @staticmethod + def find_device_in_objects(objects, device_address, adapter_pattern=None): + """ + Finds the device in objects + """ + bus = BTNap.get_bus() + path_prefix = '' + if adapter_pattern: + if not isinstance(adapter_pattern, str): + adapter = adapter_pattern + else: + adapter = BTNap.find_adapter_in_objects(objects, adapter_pattern) + path_prefix = adapter.object_path + for path, ifaces in objects.items(): + device = ifaces.get(BTNap.IFACE_DEV) + if device is None: + continue + if device['Address'] == device_address and path.startswith(path_prefix): + obj = bus.get_object(BTNap.IFACE_BASE, path) + return dbus.Interface(obj, BTNap.IFACE_DEV) + raise BTError('Bluetooth device not found') + + def power(self, on=True): + """ + Set power of devices to on/off + """ + + devs = list(BTNap.find_adapter()) + devs = dict((BTNap.prop_get(dev, 'Address'), dev) for dev in devs) + + for dev_addr, dev in devs.items(): + BTNap.prop_set(dev, 'Powered', on) + logging.debug('Set power of %s (addr %s) to %s', dev.object_path, dev_addr, str(on)) + + if devs: + return list(devs.values())[0] + + return None + + def wait_for_device(self, timeout=30): + """ + Wait for device + + returns device if found None if not + """ + bt_dev = self.power(True) + + if not bt_dev: + return None + + # could be set to 0, so check if > -1 + while timeout > -1: + try: + dev_remote = BTNap.find_device(self._mac, bt_dev) + logging.debug('Using remote device (addr: %s): %s', + BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path ) + return dev_remote + except BTError: + pass + + time.sleep(1) + timeout -= 1 + + # Device not found :( + return None + + + def connect(self, reconnect=False): + """ + Connect to device + + return True if connected; False if failed + """ + + # power up devices + bt_dev = self.power(True) + if not bt_dev: + return False + + # check if device is close + dev_remote = self.wait_for_device() + + if not dev_remote: + return False + + #wait_iter = lambda: time.sleep(3600) + # signal.signal(signal.SIGTERM, lambda sig,frm: sys.exit(0)) + + try: + dev_remote.ConnectProfile('nap') + except Exception: + pass + + net = dbus.Interface(dev_remote, 'org.bluez.Network1') + + try: + net.Connect('nap') + except dbus.exceptions.DBusException as err: + if err.get_dbus_name() != 'org.bluez.Error.Failed': + raise + + connected = BTNap.prop_get(net, 'Connected') + + if not connected: + return False + + if reconnect: + net.Disconnect() + return self.connect(reconnect=False) + + return True + + +################################################# +################################################# +################################################# + +class SystemdUnitWrapper: + """ + systemd wrapper + """ + + def __init__(self, unit): + self.unit = unit + + @staticmethod + def _action_on_unit(action, unit): + process = subprocess.Popen(f"systemctl {action} {unit}", shell=True, stdin=None, + stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") + process.wait() + if process.returncode > 0: + return False + return True + + @staticmethod + def daemon_reload(): + """ + Calls systemctl daemon-reload + """ + process = subprocess.Popen("systemctl daemon-reload", shell=True, stdin=None, + stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") + process.wait() + if process.returncode > 0: + return False + return True + + def is_active(self): + """ + Checks if unit is active + """ + return SystemdUnitWrapper._action_on_unit('is-active', self.unit) + + def is_enabled(self): + """ + Checks if unit is enabled + """ + return SystemdUnitWrapper._action_on_unit('is-enabled', self.unit) + + def is_failed(self): + """ + Checks if unit is failed + """ + return SystemdUnitWrapper._action_on_unit('is-failed', self.unit) + + def enable(self): + """ + Enables the unit + """ + return SystemdUnitWrapper._action_on_unit('enable', self.unit) + + def disable(self): + """ + Disables the unit + """ + return SystemdUnitWrapper._action_on_unit('disable', self.unit) + + def start(self): + """ + Starts the unit + """ + return SystemdUnitWrapper._action_on_unit('start', self.unit) + + def stop(self): + """ + Stops the unit + """ + return SystemdUnitWrapper._action_on_unit('stop', self.unit) + + def restart(self): + """ + Restarts the unit + """ + return SystemdUnitWrapper._action_on_unit('restart', self.unit) + + +class IfaceWrapper: + """ + Small wrapper to check and manage ifaces + + see: https://github.com/rlisagor/pynetlinux/blob/master/pynetlinux/ifconfig.py + """ + + def __init__(self, iface): + self.iface = iface + self.path = f"/sys/class/net/{iface}" + + def exists(self): + """ + Checks if iface exists + """ + return os.path.exists(self.path) + + def is_up(self): + """ + Checks if iface is ip + """ + return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up' + + + def set_addr(self, addr): + """ + Set the netmask + """ + process = subprocess.Popen(f"ip addr add {addr} dev {self.iface}", shell=True, stdin=None, + stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") + process.wait() + + if process.returncode == 2 or process.returncode == 0: # 2 = already set + return True + + return False + + +def on_loaded(): + """ + Gets called when the plugin gets loaded + """ + global READY + global INTERVAL + + for opt in ['mac', 'ip', 'netmask', 'interval']: + if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None): + logging.error("BT-TET: Pleace specify the %s in your config.yml.", opt) + return + + # ensure bluetooth is running + bt_unit = SystemdUnitWrapper('bluetooth.service') + if not bt_unit.is_active(): + if not bt_unit.start(): + logging.error("BT-TET: Can't start bluetooth.service") + return + + INTERVAL.update() + READY = True + + +def on_ui_update(ui): + """ + Try to connect to device + """ + + if READY: + global INTERVAL + if INTERVAL.newer_then_minutes(OPTIONS['interval']): + return + + INTERVAL.update() + + bt = BTNap(OPTIONS['mac']) + if bt.connect(): + btnap_iface = IfaceWrapper('bnep0') + + if btnap_iface.exists(): + # check ip + addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}" + + if not btnap_iface.set_addr(addr): + ui.set('bluetooth', 'ERR1') + logging.error("Could not set ip of bnep0 to %s", addr) + return + + ui.set('bluetooth', 'CON') + else: + ui.set('bluetooth', 'ERR2') + else: + ui.set('bluetooth', 'NF') + + +def on_ui_setup(ui): + ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 30, 0), + label_font=fonts.Bold, text_font=fonts.Medium)) From 1691896531401fc66c46d0fa19d4b63b3df0bb81 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 13 Oct 2019 13:23:38 +0200 Subject: [PATCH 176/346] add config param --- pwnagotchi/defaults.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index b529160..65bb5c6 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -71,6 +71,7 @@ main: mac: ~ # mac of your bluetooth device ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable netmask: 24 + interval: 1 # check every x minutes for device # monitor interface to use iface: mon0 # command to run to bring the mon interface up in case it's not up already From 5d2855760859030b2cd36b27e42a36f2b95877f0 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 17:24:47 +0200 Subject: [PATCH 177/346] new: using pwngrid for mesh advertising (we got rid of scapy loading times) --- bin/pwnagotchi | 6 +- builder/pwnagotchi.yml | 6 +- pwnagotchi/agent.py | 32 +----- pwnagotchi/grid.py | 97 ++++++++++++++++ pwnagotchi/mesh/__init__.py | 4 - pwnagotchi/mesh/advertise.py | 176 ----------------------------- pwnagotchi/mesh/peer.py | 30 +++-- pwnagotchi/mesh/utils.py | 96 +++++++++++----- pwnagotchi/mesh/wifi.py | 37 ------ pwnagotchi/plugins/default/grid.py | 79 ++----------- 10 files changed, 197 insertions(+), 366 deletions(-) create mode 100644 pwnagotchi/grid.py delete mode 100644 pwnagotchi/mesh/advertise.py delete mode 100644 pwnagotchi/mesh/wifi.py diff --git a/bin/pwnagotchi b/bin/pwnagotchi index 297bce6..a7d3c97 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -2,10 +2,10 @@ if __name__ == '__main__': import argparse import time - import os import logging import pwnagotchi + import pwnagtochi.grid as grid import pwnagotchi.utils as utils import pwnagotchi.plugins as plugins @@ -64,7 +64,7 @@ if __name__ == '__main__': display.on_manual_mode(agent.last_session) time.sleep(1) - if Agent.is_connected(): + if grid.is_connected(): plugins.on('internet_available', agent) else: @@ -102,7 +102,7 @@ if __name__ == '__main__': # affect ours ... neat ^_^ agent.next_epoch() - if Agent.is_connected(): + if grid.is_connected(): plugins.on('internet_available', agent) except Exception as e: diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 219948e..8079d60 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.6.3/pwngrid_linux_armv6l_v1.6.3.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.1/pwngrid_linux_armv6l_1.7.1.zip" apt: hold: - firmware-atheros @@ -497,12 +497,12 @@ Description=pwngrid peer service. Documentation=https://pwnagotchi.ai Wants=network.target - After=network.target + After=bettercap.service [Service] Type=simple PermissionsStartOnly=true - ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait -log /var/log/pwngrid-peer.log + ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait -log /var/log/pwngrid-peer.log -iface mon0 Restart=always RestartSec=30 diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index b5c876f..41680df 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -2,8 +2,6 @@ import time import json import os import re -import socket -from datetime import datetime import logging import _thread @@ -42,15 +40,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): if not os.path.exists(config['bettercap']['handshakes']): os.makedirs(config['bettercap']['handshakes']) - @staticmethod - def is_connected(): - try: - socket.create_connection(("www.google.com", 80)) - return True - except OSError: - pass - return False - def config(self): return self._config @@ -193,7 +182,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): def set_access_points(self, aps): self._access_points = aps plugins.on('wifi_update', self, aps) - self._epoch.observe(aps, self._advertiser.peers() if self._advertiser is not None else ()) + self._epoch.observe(aps, list(self._peers.values())) return self._access_points def get_access_points(self): @@ -274,19 +263,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): if new_shakes > 0: self._view.on_handshakes(new_shakes) - def _update_advertisement(self, s): - run_handshakes = len(self._handshakes) - tot_handshakes = utils.total_unique_handshakes(self._config['bettercap']['handshakes']) - self._advertiser.update({ - 'pwnd_run': run_handshakes, - 'pwnd_tot': tot_handshakes, - 'uptime': pwnagotchi.uptime(), - 'epoch': self._epoch.epoch}) - def _update_peers(self): - peer = self._advertiser.closest_peer() - tot = self._advertiser.num_peers() - self._view.set_closest_peer(peer, tot) + self._view.set_closest_peer(self._closest_peer, len(self._peers)) def _save_recovery_data(self): logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE) @@ -331,10 +309,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): s = self.session() self._update_uptime(s) - if self._advertiser is not None: - self._update_advertisement(s) - self._update_peers() - + self._update_advertisement(s) + self._update_peers() self._update_counters() try: diff --git a/pwnagotchi/grid.py b/pwnagotchi/grid.py new file mode 100644 index 0000000..93bc08a --- /dev/null +++ b/pwnagotchi/grid.py @@ -0,0 +1,97 @@ +import subprocess +import socket +import requests +import json +import logging + +import pwnagotchi + +# pwngrid-peer is running on port 8666 +API_ADDRESS = "http://127.0.0.1:8666/api/v1" + + +def is_connected(): + try: + socket.create_connection(("www.google.com", 80)) + return True + except OSError: + pass + return False + + +def call(path, obj=None): + url = '%s%s' % (API_ADDRESS, path) + if obj is None: + r = requests.get(url, headers=None) + else: + r = requests.post(url, headers=None, json=obj) + + if r.status_code != 200: + raise Exception("(status %d) %s" % (r.status_code, r.text)) + return r.json() + + +def advertise(enabled=True): + return call("/mesh/%s" % 'true' if enabled else 'false') + + +def set_advertisement_data(data): + return call("/mesh/data", obj=data) + + +def peers(): + return call("/mesh/peers") + + +def closest_peer(): + all = peers() + return all[0] if len(all) else None + + +def update_data(last_session): + brain = {} + try: + with open('/root/brain.json') as fp: + brain = json.load(fp) + except: + pass + + data = { + 'session': { + 'duration': last_session.duration, + 'epochs': last_session.epochs, + 'train_epochs': last_session.train_epochs, + 'avg_reward': last_session.avg_reward, + 'min_reward': last_session.min_reward, + 'max_reward': last_session.max_reward, + 'deauthed': last_session.deauthed, + 'associated': last_session.associated, + 'handshakes': last_session.handshakes, + 'peers': last_session.peers, + }, + 'uname': subprocess.getoutput("uname -a"), + 'brain': brain, + 'version': pwnagotchi.version + } + + logging.debug("updating grid data: %s" % data) + + call("/data", data) + + +def report_ap(essid, bssid): + try: + call("/report/ap", { + 'essid': essid, + 'bssid': bssid, + }) + return True + except Exception as e: + logging.exception("error while reporting ap %s(%s)" % (essid, bssid)) + + return False + + +def inbox(page=1, with_pager=False): + obj = call("/inbox?p=%d" % page) + return obj["messages"] if not with_pager else obj diff --git a/pwnagotchi/mesh/__init__.py b/pwnagotchi/mesh/__init__.py index e1c033f..e69de29 100644 --- a/pwnagotchi/mesh/__init__.py +++ b/pwnagotchi/mesh/__init__.py @@ -1,4 +0,0 @@ -import os - -def new_session_id(): - return ':'.join(['%02x' % b for b in os.urandom(6)]) diff --git a/pwnagotchi/mesh/advertise.py b/pwnagotchi/mesh/advertise.py deleted file mode 100644 index a4f88b7..0000000 --- a/pwnagotchi/mesh/advertise.py +++ /dev/null @@ -1,176 +0,0 @@ -import time -import json -import _thread -import threading -import logging -from scapy.all import Dot11, Dot11Elt, RadioTap, sendp, sniff - -import pwnagotchi.ui.faces as faces - -import pwnagotchi.mesh.wifi as wifi -from pwnagotchi.mesh import new_session_id -from pwnagotchi.mesh.peer import Peer - - -def _dummy_peer_cb(peer): - pass - - -class Advertiser(object): - MAX_STALE_TIME = 300 - - def __init__(self, iface, name, version, identity, period=0.3, data={}): - self._iface = iface - self._period = period - self._running = False - self._stopped = threading.Event() - self._peers_lock = threading.Lock() - self._adv_lock = threading.Lock() - self._new_peer_cb = _dummy_peer_cb - self._lost_peer_cb = _dummy_peer_cb - self._peers = {} - self._frame = None - self._me = Peer(new_session_id(), 0, 0, { - 'name': name, - 'version': version, - 'identity': identity, - 'face': faces.FRIEND, - 'pwnd_run': 0, - 'pwnd_tot': 0, - 'uptime': 0, - 'epoch': 0, - 'data': data - }) - self.update() - - def update(self, values={}): - with self._adv_lock: - for field, value in values.items(): - self._me.adv[field] = value - self._frame = wifi.encapsulate(payload=json.dumps(self._me.adv), addr_from=self._me.session_id) - - def on_peer(self, new_cb, lost_cb): - self._new_peer_cb = new_cb - self._lost_peer_cb = lost_cb - - def on_face_change(self, old, new): - self.update({'face': new}) - - def start(self): - self._running = True - _thread.start_new_thread(self._sender, ()) - _thread.start_new_thread(self._listener, ()) - _thread.start_new_thread(self._pruner, ()) - - def num_peers(self): - with self._peers_lock: - return len(self._peers) - - def peers(self): - with self._peers_lock: - return list(self._peers.values()) - - def closest_peer(self): - closest = None - with self._peers_lock: - for ident, peer in self._peers.items(): - if closest is None or peer.is_closer(closest): - closest = peer - return closest - - def stop(self): - self._running = False - self._stopped.set() - - def _sender(self): - logging.info("started advertiser thread (period:%s sid:%s) ..." % (str(self._period), self._me.session_id)) - while self._running: - try: - sendp(self._frame, iface=self._iface, verbose=False, count=1, inter=self._period) - except OSError as ose: - logging.warning("non critical issue while sending advertising packet: %s" % ose) - except Exception as e: - logging.exception("error") - time.sleep(self._period) - - def _on_advertisement(self, src_session_id, channel, rssi, adv): - ident = adv['identity'] - with self._peers_lock: - if ident not in self._peers: - peer = Peer(src_session_id, channel, rssi, adv) - logging.info("detected unit %s (v%s) on channel %d (%s dBm) [sid:%s pwnd_tot:%d uptime:%d]" % ( \ - peer.full_name(), - peer.version(), - channel, - rssi, - src_session_id, - peer.pwnd_total(), - peer.uptime())) - - self._peers[ident] = peer - self._new_peer_cb(peer) - else: - self._peers[ident].update(src_session_id, channel, rssi, adv) - - def _parse_identity(self, radio, dot11, dot11elt): - payload = b'' - while dot11elt: - payload += dot11elt.info - dot11elt = dot11elt.payload.getlayer(Dot11Elt) - - if payload != b'': - adv = json.loads(payload) - self._on_advertisement( \ - dot11.addr3, - wifi.freq_to_channel(radio.Channel), - radio.dBm_AntSignal, - adv) - - def _is_broadcasted_advertisement(self, dot11): - # dst bcast + protocol signature + not ours - return dot11 is not None and \ - dot11.addr1 == wifi.BroadcastAddress and \ - dot11.addr2 == wifi.SignatureAddress and \ - dot11.addr3 != self._me.session_id - - def _is_frame_for_us(self, dot11): - # dst is us + protocol signature + not ours (why would we send a frame to ourself anyway?) - return dot11 is not None and \ - dot11.addr1 == self._me.session_id and \ - dot11.addr2 == wifi.SignatureAddress and \ - dot11.addr3 != self._me.session_id - - def _on_packet(self, p): - dot11 = p.getlayer(Dot11) - - if self._is_broadcasted_advertisement(dot11): - try: - dot11elt = p.getlayer(Dot11Elt) - if dot11elt.ID == wifi.Dot11ElemID_Whisper: - self._parse_identity(p[RadioTap], dot11, dot11elt) - - else: - raise Exception("unknown frame id %d" % dot11elt.ID) - - except Exception as e: - logging.exception("error decoding packet from %s" % dot11.addr3) - - def _listener(self): - # logging.info("started advertisements listener ...") - expr = "type mgt subtype beacon and ether src %s" % wifi.SignatureAddress - sniff(iface=self._iface, filter=expr, prn=self._on_packet, store=0, stop_filter=lambda x: self._stopped.isSet()) - - def _pruner(self): - while self._running: - time.sleep(10) - with self._peers_lock: - stale = [] - for ident, peer in self._peers.items(): - inactive_for = peer.inactive_for() - if inactive_for >= Advertiser.MAX_STALE_TIME: - logging.info("peer %s lost (inactive for %ds)" % (peer.full_name(), inactive_for)) - self._lost_peer_cb(peer) - stale.append(ident) - - for ident in stale: - del self._peers[ident] diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py index 0d3b061..14f0057 100644 --- a/pwnagotchi/mesh/peer.py +++ b/pwnagotchi/mesh/peer.py @@ -1,32 +1,28 @@ import time import logging -import pwnagotchi.mesh.wifi as wifi import pwnagotchi.ui.faces as faces class Peer(object): - def __init__(self, sid, channel, rssi, adv): + def __init__(self, obj): self.first_seen = time.time() self.last_seen = self.first_seen - self.session_id = sid - self.last_channel = channel - self.presence = [0] * wifi.NumChannels - self.adv = adv - self.rssi = rssi - self.presence[channel - 1] = 1 + self.session_id = obj['session_id'] + self.last_channel = obj['channel'] + self.rssi = obj['rssi'] + self.adv = obj['advertisement'] - def update(self, sid, channel, rssi, adv): - if self.name() != adv['name']: - logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), adv['name'])) + def update(self, new): + if self.name() != new.name(): + logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), new.name())) - if self.session_id != sid: - logging.info("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, sid)) + if self.session_id != new.session_id: + logging.info("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, new.session_id)) - self.presence[channel - 1] += 1 - self.adv = adv - self.rssi = rssi - self.session_id = sid + self.adv = new.adv + self.rssi = new.rssi + self.session_id = new.session_id self.last_seen = time.time() def inactive_for(self): diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index 3a67ed9..d28f04d 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -2,7 +2,11 @@ import _thread import logging import pwnagotchi +import pwnagotchi.utils as utils +import pwnagotchi.ui.faces as faces import pwnagotchi.plugins as plugins +import pwnagotchi.grid as grid +from pwnagotchi.mesh.peer import Peer class AsyncAdvertiser(object): @@ -10,38 +14,76 @@ class AsyncAdvertiser(object): self._config = config self._view = view self._keypair = keypair - self._advertiser = None + self._advertisement = { + 'name': pwnagotchi.name(), + 'version': pwnagotchi.version, + 'identity': self._keypair.fingerprint, + 'face': faces.FRIEND, + 'pwnd_run': 0, + 'pwnd_tot': 0, + 'uptime': 0, + 'epoch': 0, + 'policy': self._config['personality'] + } + self._peers = {} + self._closest_peer = None - def keypair(self): - return self._keypair + _thread.start_new_thread(self._adv_poller, ()) + + def _update_advertisement(self, s): + self._advertisement['pwnd_run'] = len(self._handshakes) + self._advertisement['pwnd_tot'] = utils.total_unique_handshakes(self._config['bettercap']['handshakes']) + self._advertisement['uptime'] = pwnagotchi.uptime() + self._advertisement['epoch'] = self._epoch.epoch + grid.set_advertisement_data(self._advertisement) def start_advertising(self): - _thread.start_new_thread(self._adv_worker, ()) - - def _adv_worker(self): - # this will take some time due to scapy being slow to be imported ... - from pwnagotchi.mesh.advertise import Advertiser - - self._advertiser = Advertiser( - self._config['main']['iface'], - pwnagotchi.name(), - pwnagotchi.version, - self._keypair.fingerprint, - period=0.3, - data=self._config['personality']) - - self._advertiser.on_peer(self._on_new_unit, self._on_lost_unit) - if self._config['personality']['advertise']: - self._advertiser.start() - self._view.on_state_change('face', self._advertiser.on_face_change) + grid.set_advertisement_data(self._advertisement) + grid.advertise(True) + self._view.on_state_change('face', self._on_face_change) else: logging.warning("advertising is disabled") - def _on_new_unit(self, peer): - self._view.on_new_peer(peer) - plugins.on('peer_detected', self, peer) + def _on_face_change(self, old, new): + self._advertisement['face'] = new + grid.set_advertisement_data(self._advertisement) - def _on_lost_unit(self, peer): - self._view.on_lost_peer(peer) - plugins.on('peer_lost', self, peer) + def _adv_poller(self): + while True: + logging.debug("polling pwngrid-peer for peers ...") + + try: + grid_peers = grid.peers() + new_peers = {} + + self._closest_peer = None + for obj in grid_peers: + peer = Peer(obj) + new_peers[peer.identity()] = peer + if self._closest_peer is None: + self._closest_peer = peer + + # check who's gone + to_delete = [] + for ident, peer in self._peers.items(): + if ident not in new_peers: + self._view.on_lost_peer(peer) + plugins.on('peer_lost', self, peer) + to_delete.append(ident) + + for ident in to_delete: + del self._peers[ident] + + for ident, peer in new_peers: + # check who's new + if ident not in self._peers: + self._peers[ident] = peer + self._view.on_new_peer(peer) + plugins.on('peer_detected', self, peer) + # update the rest + else: + self._peers[ident].update(peer) + + except Exception as e: + logging.error("error while polling pwngrid-peer: %s" % e) diff --git a/pwnagotchi/mesh/wifi.py b/pwnagotchi/mesh/wifi.py deleted file mode 100644 index 6fe231e..0000000 --- a/pwnagotchi/mesh/wifi.py +++ /dev/null @@ -1,37 +0,0 @@ -SignatureAddress = 'de:ad:be:ef:de:ad' -BroadcastAddress = 'ff:ff:ff:ff:ff:ff' -Dot11ElemID_Whisper = 222 -NumChannels = 140 - -def freq_to_channel(freq): - if freq <= 2472: - return int(((freq - 2412) / 5) + 1) - elif freq == 2484: - return int(14) - elif 5035 <= freq <= 5865: - return int(((freq - 5035) / 5) + 7) - else: - return 0 - - -def encapsulate(payload, addr_from, addr_to=BroadcastAddress): - from scapy.all import Dot11, Dot11Beacon, Dot11Elt, RadioTap - - radio = RadioTap() - dot11 = Dot11(type=0, subtype=8, addr1=addr_to, addr2=SignatureAddress, addr3=addr_from) - beacon = Dot11Beacon(cap='ESS') - frame = radio / dot11 / beacon - - data_size = len(payload) - data_left = data_size - data_off = 0 - chunk_size = 255 - - while data_left > 0: - sz = min(chunk_size, data_left) - chunk = payload[data_off: data_off + sz] - frame /= Dot11Elt(ID=Dot11ElemID_Whisper, info=chunk, len=sz) - data_off += sz - data_left -= sz - - return frame diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 516a6a2..4b62a55 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -6,11 +6,9 @@ __description__ = 'This plugin signals the unit cryptographic identity and list import os import logging -import requests import glob -import json -import subprocess -import pwnagotchi + +import pwnagotchi.grid as grid import pwnagotchi.utils as utils from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.view import BLACK @@ -65,74 +63,13 @@ def is_excluded(what): return False -def grid_call(path, obj=None): - # pwngrid-peer is running on port 8666 - api_address = 'http://127.0.0.1:8666/api/v1%s' % path - if obj is None: - r = requests.get(api_address, headers=None) - else: - r = requests.post(api_address, headers=None, json=obj) - - if r.status_code != 200: - raise Exception("(status %d) %s" % (r.status_code, r.text)) - return r.json() - - -def grid_update_data(last_session): - brain = {} - try: - with open('/root/brain.json') as fp: - brain = json.load(fp) - except: - pass - - data = { - 'session': { - 'duration': last_session.duration, - 'epochs': last_session.epochs, - 'train_epochs': last_session.train_epochs, - 'avg_reward': last_session.avg_reward, - 'min_reward': last_session.min_reward, - 'max_reward': last_session.max_reward, - 'deauthed': last_session.deauthed, - 'associated': last_session.associated, - 'handshakes': last_session.handshakes, - 'peers': last_session.peers, - }, - 'uname': subprocess.getoutput("uname -a"), - 'brain': brain, - 'version': pwnagotchi.version - } - - logging.debug("updating grid data: %s" % data) - - grid_call("/data", data) - - -def grid_report_ap(essid, bssid): - try: - grid_call("/report/ap", { - 'essid': essid, - 'bssid': bssid, - }) - return True - except Exception as e: - logging.exception("error while reporting ap %s(%s)" % (essid, bssid)) - - return False - - -def grid_inbox(): - return grid_call("/inbox")["messages"] - - def on_ui_update(ui): new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES) if not ui.has_element('mailbox') and TOTAL_MESSAGES > 0: if ui.is_inky(): - pos=(80, 0) + pos = (80, 0) else: - pos=(100,0) + pos = (100, 0) ui.add_element('mailbox', LabeledValue(color=BLACK, label='MSG', value=new_value, position=pos, @@ -147,7 +84,7 @@ def on_internet_available(agent): logging.debug("internet available") try: - grid_update_data(agent.last_session) + grid.update_data(agent.last_session) except Exception as e: logging.error("error connecting to the pwngrid-peer service: %s" % e) return @@ -155,13 +92,13 @@ def on_internet_available(agent): try: logging.debug("checking mailbox ...") - messages = grid_inbox() + messages = grid.inbox() TOTAL_MESSAGES = len(messages) UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None]) if TOTAL_MESSAGES: on_ui_update(agent.view()) - logging.debug( " %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) + logging.debug(" %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) logging.debug("checking pcaps") @@ -189,7 +126,7 @@ def on_internet_available(agent): if is_excluded(essid) or is_excluded(bssid): logging.debug("not reporting %s due to exclusion filter" % pcap_file) - elif grid_report_ap(essid, bssid): + elif grid.report_ap(essid, bssid): reported.append(net_id) REPORT.update(data={'reported': reported}) else: From ac5ee1ba7b39b5ead95853cfafdfbbc0e3fa2068 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 17:38:00 +0200 Subject: [PATCH 178/346] misc: small fix or general refactoring i did not bother commenting --- bin/pwnagotchi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index a7d3c97..ce02bbc 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -5,7 +5,7 @@ if __name__ == '__main__': import logging import pwnagotchi - import pwnagtochi.grid as grid + import pwnagotchi.grid as grid import pwnagotchi.utils as utils import pwnagotchi.plugins as plugins From 77efeafd6596669050685c06a3ae74e0a92b3aaf Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 17:39:25 +0200 Subject: [PATCH 179/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/wifi.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 pwnagotchi/mesh/wifi.py diff --git a/pwnagotchi/mesh/wifi.py b/pwnagotchi/mesh/wifi.py new file mode 100644 index 0000000..93fe9ab --- /dev/null +++ b/pwnagotchi/mesh/wifi.py @@ -0,0 +1 @@ +NumChannels = 140 From 1808392a1dcdaabc4241241c4f2bb1ad6cd2f7e6 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 17:40:30 +0200 Subject: [PATCH 180/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/wifi.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pwnagotchi/mesh/wifi.py b/pwnagotchi/mesh/wifi.py index 93fe9ab..3bc714b 100644 --- a/pwnagotchi/mesh/wifi.py +++ b/pwnagotchi/mesh/wifi.py @@ -1 +1,12 @@ NumChannels = 140 + + +def freq_to_channel(freq): + if freq <= 2472: + return int(((freq - 2412) / 5) + 1) + elif freq == 2484: + return int(14) + elif 5035 <= freq <= 5865: + return int(((freq - 5035) / 5) + 7) + else: + return 0 From 1215fda45967d64a7b7d4aa9f20a77b7dfd7b589 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 17:45:34 +0200 Subject: [PATCH 181/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index d28f04d..6d17f4e 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -1,5 +1,6 @@ import _thread import logging +import time import pwnagotchi import pwnagotchi.utils as utils @@ -87,3 +88,5 @@ class AsyncAdvertiser(object): except Exception as e: logging.error("error while polling pwngrid-peer: %s" % e) + + time.sleep(1) From ad2fbdb9ddfea38b1751ef8291af94f4e4b7fc8d Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 17:54:33 +0200 Subject: [PATCH 182/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/defaults.yml | 2 +- pwnagotchi/mesh/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 65bb5c6..621a4c3 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -173,7 +173,7 @@ ui: color: 'black' video: enabled: true - address: '10.0.0.2' + address: '0.0.0.0' port: 8080 diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index 6d17f4e..fb7f523 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -87,6 +87,6 @@ class AsyncAdvertiser(object): self._peers[ident].update(peer) except Exception as e: - logging.error("error while polling pwngrid-peer: %s" % e) + logging.exception("error while polling pwngrid-peer") time.sleep(1) From 5989d2571c8a3fed0c8e69b2bf089f759f53f2d3 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 17:55:10 +0200 Subject: [PATCH 183/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index fb7f523..fbe8271 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -76,7 +76,7 @@ class AsyncAdvertiser(object): for ident in to_delete: del self._peers[ident] - for ident, peer in new_peers: + for ident, peer in new_peers.items(): # check who's new if ident not in self._peers: self._peers[ident] = peer From 6793312691044a8aab2990c366d1f0ee2b10a7ca Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 18:02:11 +0200 Subject: [PATCH 184/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/defaults.yml | 1 + scripts/backup.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 621a4c3..c1d03a5 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -27,6 +27,7 @@ main: files: - /root/brain.nn - /root/brain.json + - /root/.api-report.json - /root/handshakes/ - /etc/pwnagotchi/ - /etc/hostname diff --git a/scripts/backup.sh b/scripts/backup.sh index 213ed94..c943058 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -10,6 +10,7 @@ TEMP_BACKUP_FOLDER=/tmp/pwnagotchi_backup FILES_TO_BACKUP=( /root/brain.nn /root/brain.json + /root/.api-report.json /root/handshakes /etc/pwnagotchi/ /etc/hostname From 8d2cbee8df4c0b739783c8f11a9514d0ff156d76 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 18:09:33 +0200 Subject: [PATCH 185/346] fix: fixed log flooding with whitelisten networks for grid report --- pwnagotchi/plugins/default/grid.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 4b62a55..264de46 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -78,6 +78,12 @@ def on_ui_update(ui): ui.set('mailbox', new_value) +def set_reported(reported, net_id): + global REPORT + reported.append(net_id) + REPORT.update(data={'reported': reported}) + + def on_internet_available(agent): global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES @@ -118,17 +124,19 @@ def on_internet_available(agent): net_id = os.path.basename(pcap_file).replace('.pcap', '') if net_id not in reported: if is_excluded(net_id): - logging.info("skipping %s due to exclusion filter" % pcap_file) + logging.debug("skipping %s due to exclusion filter" % pcap_file) + set_reported(reported, net_id) continue essid, bssid = parse_pcap(pcap_file) if bssid: + add_as_reported = False if is_excluded(essid) or is_excluded(bssid): logging.debug("not reporting %s due to exclusion filter" % pcap_file) - - elif grid.report_ap(essid, bssid): - reported.append(net_id) - REPORT.update(data={'reported': reported}) + set_reported(reported, net_id) + else: + if grid.report_ap(essid, bssid): + set_reported(reported, net_id) else: logging.warning("no bssid found?!") else: From f0c5ad4b74a0461757f0bf5984a36ca6754b6f77 Mon Sep 17 00:00:00 2001 From: Dispsylala <marcus.watson@loumiaconsulting.com> Date: Sun, 13 Oct 2019 17:18:27 +0100 Subject: [PATCH 186/346] Overrides default inky library to change timings on black rendering --- pwnagotchi/ui/display.py | 6 +++--- pwnagotchi/ui/inkyphat/__init__.py | 0 pwnagotchi/ui/inkyphat/inkyfast.py | 23 ++++++++++++++++++++++ pwnagotchi/ui/inkyphat/inkyphatfast.py | 27 ++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 pwnagotchi/ui/inkyphat/__init__.py create mode 100644 pwnagotchi/ui/inkyphat/inkyfast.py create mode 100644 pwnagotchi/ui/inkyphat/inkyphatfast.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index ff7ee5e..065dbd8 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -136,9 +136,9 @@ class Display(View): def _init_display(self): if self.is_inky(): logging.info("initializing inky display") - from inky import InkyPHAT - self._display = InkyPHAT(self._display_color) - self._display.set_border(InkyPHAT.BLACK) + from pwnagotchi.ui.inkyphat.inkyphatfast import InkyPHATFast + self._display = InkyPHATFast(self._display_color) + self._display.set_border(InkyPHATFast.BLACK) self._render_cb = self._inky_render elif self.is_papirus(): diff --git a/pwnagotchi/ui/inkyphat/__init__.py b/pwnagotchi/ui/inkyphat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pwnagotchi/ui/inkyphat/inkyfast.py b/pwnagotchi/ui/inkyphat/inkyfast.py new file mode 100644 index 0000000..bdbd3ec --- /dev/null +++ b/pwnagotchi/ui/inkyphat/inkyfast.py @@ -0,0 +1,23 @@ +from inky.inky import Inky, CS0_PIN, DC_PIN, RESET_PIN, BUSY_PIN + + +class InkyFast(Inky): + + def __init__(self, resolution=(400, 300), colour='black', cs_pin=CS0_PIN, dc_pin=DC_PIN, reset_pin=RESET_PIN, + busy_pin=BUSY_PIN, h_flip=False, v_flip=False): + super(InkyFast, self).__init__(resolution, colour, cs_pin, dc_pin, reset_pin, busy_pin, h_flip, v_flip) + + self._luts['black'] = [ + 0b01001000, 0b10100000, 0b00010000, 0b00010000, 0b00010011, 0b00000000, 0b00000000, + 0b01001000, 0b10100000, 0b10000000, 0b00000000, 0b00000011, 0b00000000, 0b00000000, + 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, + 0b01001000, 0b10100101, 0b00000000, 0b10111011, 0b00000000, 0b00000000, 0b00000000, + 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, + 0x04, 0x04, 0x04, 0x04, 0x04, + 0x10, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x08, 0x08, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + ] \ No newline at end of file diff --git a/pwnagotchi/ui/inkyphat/inkyphatfast.py b/pwnagotchi/ui/inkyphat/inkyphatfast.py new file mode 100644 index 0000000..e39433b --- /dev/null +++ b/pwnagotchi/ui/inkyphat/inkyphatfast.py @@ -0,0 +1,27 @@ +"""Inky pHAT e-Ink Display Driver.""" +from . import inkyfast + + +class InkyPHATFast(inkyfast.InkyFast): + """Inky wHAT e-Ink Display Driver.""" + + WIDTH = 212 + HEIGHT = 104 + + WHITE = 0 + BLACK = 1 + RED = 2 + YELLOW = 2 + + def __init__(self, colour): + """Initialise an Inky pHAT Display. + + :param colour: one of red, black or yellow, default: black + + """ + inkyfast.InkyFast.__init__( + self, + resolution=(self.WIDTH, self.HEIGHT), + colour=colour, + h_flip=False, + v_flip=False) \ No newline at end of file From ba7c0ee4e6443594494c9d2783abf5bcbfdbec19 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 18:29:56 +0200 Subject: [PATCH 187/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/log.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index fa3ddc1..560a6ce 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -129,10 +129,12 @@ class LastSession(object): if m: name, pubkey, rssi, sid, pwnd_tot, uptime = m[0] if pubkey not in cache: - self.last_peer = Peer(sid, 1, int(rssi), - {'name': name, - 'identity': pubkey, - 'pwnd_tot': int(pwnd_tot)}) + self.last_peer = Peer({ + 'session_id': sid, + 'channel': 1, + 'rssi': int(rssi), + 'identity': pubkey, + 'advertisement':{'pwnd_tot': int(pwnd_tot)}}) self.peers += 1 cache[pubkey] = self.last_peer else: From b539a761241a5d99b1a87003904f44bd586d4449 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 18:32:14 +0200 Subject: [PATCH 188/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/mesh/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index fbe8271..6129e82 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -29,8 +29,6 @@ class AsyncAdvertiser(object): self._peers = {} self._closest_peer = None - _thread.start_new_thread(self._adv_poller, ()) - def _update_advertisement(self, s): self._advertisement['pwnd_run'] = len(self._handshakes) self._advertisement['pwnd_tot'] = utils.total_unique_handshakes(self._config['bettercap']['handshakes']) @@ -40,6 +38,8 @@ class AsyncAdvertiser(object): def start_advertising(self): if self._config['personality']['advertise']: + _thread.start_new_thread(self._adv_poller, ()) + grid.set_advertisement_data(self._advertisement) grid.advertise(True) self._view.on_state_change('face', self._on_face_change) From 2430b4a13406e10e803eeb59f171b2544e420f9c Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 18:35:09 +0200 Subject: [PATCH 189/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/log.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index 560a6ce..c81a17c 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -134,7 +134,10 @@ class LastSession(object): 'channel': 1, 'rssi': int(rssi), 'identity': pubkey, - 'advertisement':{'pwnd_tot': int(pwnd_tot)}}) + 'advertisement':{ + 'name': name, + 'pwnd_tot': int(pwnd_tot) + }}) self.peers += 1 cache[pubkey] = self.last_peer else: From e9899dda94b7015af0204552e6a5d862a4bbf651 Mon Sep 17 00:00:00 2001 From: Dispsylala <marcus.watson@loumiaconsulting.com> Date: Sun, 13 Oct 2019 17:40:58 +0100 Subject: [PATCH 190/346] Overrides default inky library to change timings on black rendering --- pwnagotchi/ui/inkyphat/inkyfast.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/ui/inkyphat/inkyfast.py b/pwnagotchi/ui/inkyphat/inkyfast.py index bdbd3ec..955e8e6 100644 --- a/pwnagotchi/ui/inkyphat/inkyfast.py +++ b/pwnagotchi/ui/inkyphat/inkyfast.py @@ -13,7 +13,8 @@ class InkyFast(Inky): 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b01001000, 0b10100101, 0b00000000, 0b10111011, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, - 0x04, 0x04, 0x04, 0x04, 0x04, + # The following timings have been reduced to avoid the fade to black + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x08, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, From 1ce361a8397cab995704724b13fa7a552003f995 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 18:49:50 +0200 Subject: [PATCH 191/346] misc: small fix or general refactoring i did not bother commenting --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 8079d60..21a15dd 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.1/pwngrid_linux_armv6l_1.7.1.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.3/pwngrid_linux_armv6l_1.7.3.zip" apt: hold: - firmware-atheros From e15d0f33239c4f4b3ec58a813e53fd9f767b345c Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 13 Oct 2019 19:07:29 +0200 Subject: [PATCH 192/346] releasing v1.0.0RC4 --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index a26d337..c75af90 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -4,7 +4,7 @@ import logging import time import pwnagotchi.ui.view as view -version = '1.0.0RC3' +version = '1.0.0RC4' _name = None From 308746a7de4038a7d1363e89188bc9d452b591b5 Mon Sep 17 00:00:00 2001 From: Casey Diemel <diemelcw@gmail.com> Date: Sun, 13 Oct 2019 13:54:22 -0400 Subject: [PATCH 193/346] removed plugin.on() return value as is not needed Signed-off-by: Casey Diemel <diemelcw@gmail.com> --- pwnagotchi/plugins/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index 7faa553..6dd7b64 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -18,9 +18,7 @@ def on(event_name, *args, **kwargs): if cb_name in plugin.__dict__: # print("calling %s %s(%s)" %(cb_name, args, kwargs)) try: - ret_val = plugin.__dict__[cb_name](*args, **kwargs) - if ret_val is not None: - return ret_val + plugin.__dict__[cb_name](*args, **kwargs) except Exception as e: logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) From 2bbcc36f2aea0cc427e23ccc968d7dff492c18e6 Mon Sep 17 00:00:00 2001 From: Casey Diemel <diemelcw@gmail.com> Date: Sun, 13 Oct 2019 13:57:45 -0400 Subject: [PATCH 194/346] added unfiltered ap list call back Signed-off-by: Casey Diemel <diemelcw@gmail.com> --- pwnagotchi/agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 41680df..f8c8ff9 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -190,6 +190,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): aps = [] try: s = self.session() + plugins.on("unfiltered_ap_list", s['wifi']['aps']) for ap in s['wifi']['aps']: if ap['hostname'] not in whitelist: if self._filter_included(ap): From 8b366ca736fc4b1713d85942bbcc5b64dbb4b79f Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 13 Oct 2019 21:26:53 +0200 Subject: [PATCH 195/346] Add auto-pair and internet-sharing --- pwnagotchi/plugins/default/bt-tether.py | 57 ++++++++++++++++++++----- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 0305ba9..5614f25 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -165,21 +165,35 @@ class BTNap: if not bt_dev: return None + try: + bt_dev.StartDiscovery() + except Exception: + # can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed + # TODO: add loop? + pass + + dev_remote = None + # could be set to 0, so check if > -1 while timeout > -1: try: dev_remote = BTNap.find_device(self._mac, bt_dev) logging.debug('Using remote device (addr: %s): %s', BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path ) - return dev_remote + break except BTError: pass time.sleep(1) timeout -= 1 - # Device not found :( - return None + try: + bt_dev.StopDiscovery() + except Exception: + # can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed / org.bluez.Error.NotAuthorized + pass + + return dev_remote def connect(self, reconnect=False): @@ -189,19 +203,18 @@ class BTNap: return True if connected; False if failed """ - # power up devices - bt_dev = self.power(True) - if not bt_dev: - return False - # check if device is close dev_remote = self.wait_for_device() if not dev_remote: return False - #wait_iter = lambda: time.sleep(3600) - # signal.signal(signal.SIGTERM, lambda sig,frm: sys.exit(0)) + try: + dev_remote.Pair() + logging.info('BT-TETHER: Successful paired with device ;)') + except Exception: + # can fail because of AlreadyExists etc. + pass try: dev_remote.ConnectProfile('nap') @@ -347,6 +360,18 @@ class IfaceWrapper: return False + @staticmethod + def set_route(addr): + process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None, + stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") + process.wait() + + if process.returncode > 0: + return False + + return True + + def on_loaded(): """ @@ -355,7 +380,7 @@ def on_loaded(): global READY global INTERVAL - for opt in ['mac', 'ip', 'netmask', 'interval']: + for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None): logging.error("BT-TET: Pleace specify the %s in your config.yml.", opt) return @@ -396,6 +421,16 @@ def on_ui_update(ui): logging.error("Could not set ip of bnep0 to %s", addr) return + # change route if sharking + if OPTIONS['share_internet']: + IfaceWrapper.set_route(".".join(OPTIONS['ip'].split('.')[:-1] + ['1'])) # im not proud about that + # fix resolv.conf; dns over https ftw! + with open('/etc/resolv.conf', 'r+') as resolv: + nameserver = resolv.read() + if 'nameserver 9.9.9.9' not in nameserver: + resolv.seek(0) + resolv.write(nameserver + 'nameserver 9.9.9.9\n') + ui.set('bluetooth', 'CON') else: ui.set('bluetooth', 'ERR2') From 87a3fb5f0c1c83348576d92631ee48c4e7e00422 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 13 Oct 2019 22:39:03 +0200 Subject: [PATCH 196/346] Migrate to statusfile --- pwnagotchi/plugins/default/net-pos.py | 44 ++++--- pwnagotchi/plugins/default/onlinehashcrack.py | 37 +++--- pwnagotchi/plugins/default/wigle.py | 109 ++---------------- pwnagotchi/plugins/default/wpa-sec.py | 47 ++++---- 4 files changed, 70 insertions(+), 167 deletions(-) diff --git a/pwnagotchi/plugins/default/net-pos.py b/pwnagotchi/plugins/default/net-pos.py index 27f04d9..d0c54ae 100644 --- a/pwnagotchi/plugins/default/net-pos.py +++ b/pwnagotchi/plugins/default/net-pos.py @@ -1,5 +1,5 @@ __author__ = 'zenzen san' -__version__ = '1.0.0' +__version__ = '2.0.0' __name__ = 'net-pos' __license__ = 'GPL3' __description__ = """Saves a json file with the access points with more signal @@ -11,33 +11,24 @@ import logging import json import os import requests +from pwnagotchi.utils import StatusFile MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}' -ALREADY_SAVED = None -SKIP = None +REPORT = StatusFile('/root/.net_pos_saved', data_format='json') +SKIP = list() READY = False -OPTIONS = {} +OPTIONS = dict() def on_loaded(): - global ALREADY_SAVED - global SKIP global READY - SKIP = list() - if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.") return - try: - with open('/root/.net_pos_saved', 'r') as f: - ALREADY_SAVED = f.read().splitlines() - except OSError: - logging.warning('NET-POS: No net-pos-file found.') - ALREADY_SAVED = [] - READY = True + logging.info("net-pos plugin loaded.") def _append_saved(path): @@ -55,20 +46,22 @@ def _append_saved(path): def on_internet_available(agent): global SKIP + global REPORT if READY: config = agent.config() display = agent.view() + reported = REPORT.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] all_files = os.listdir(handshake_dir) all_np_files = [os.path.join(handshake_dir, filename) for filename in all_files if filename.endswith('.net-pos.json')] - new_np_files = set(all_np_files) - set(ALREADY_SAVED) - set(SKIP) + new_np_files = set(all_np_files) - set(reported) - set(SKIP) if new_np_files: - logging.info("NET-POS: Found {num} new net-pos files. Fetching positions ...", len(new_np_files)) + logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files)) display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...") display.update(force=True) for idx, np_file in enumerate(new_np_files): @@ -76,8 +69,8 @@ def on_internet_available(agent): geo_file = np_file.replace('.net-pos.json', '.geo.json') if os.path.exists(geo_file): # got already the position - ALREADY_SAVED.append(np_file) - _append_saved(np_file) + reported.append(np_file) + REPORT.update(data={'reported': reported}) continue try: @@ -85,18 +78,21 @@ def on_internet_available(agent): except requests.exceptions.RequestException as req_e: logging.error("NET-POS: %s", req_e) SKIP += np_file + continue except json.JSONDecodeError as js_e: logging.error("NET-POS: %s", js_e) SKIP += np_file + continue except OSError as os_e: logging.error("NET-POS: %s", os_e) SKIP += np_file + continue with open(geo_file, 'w+t') as sf: json.dump(geo_data, sf) - ALREADY_SAVED.append(np_file) - _append_saved(np_file) + reported.append(np_file) + REPORT.update(data={'reported': reported}) display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)})") display.update(force=True) @@ -108,15 +104,15 @@ def on_handshake(agent, filename, access_point, client_station): logging.info("NET-POS: Saving net-location to %s", netpos_filename) try: - with open(netpos_filename, 'w+t') as fp: - json.dump(netpos, fp) + with open(netpos_filename, 'w+t') as net_pos_file: + json.dump(netpos, net_pos_file) except OSError as os_e: logging.error("NET-POS: %s", os_e) def _get_netpos(agent): aps = agent.get_access_points() - netpos = {} + netpos = dict() netpos['wifiAccessPoints'] = list() # 6 seems a good number to save a wifi networks location for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]: diff --git a/pwnagotchi/plugins/default/onlinehashcrack.py b/pwnagotchi/plugins/default/onlinehashcrack.py index 25a18d6..dbf8a4e 100644 --- a/pwnagotchi/plugins/default/onlinehashcrack.py +++ b/pwnagotchi/plugins/default/onlinehashcrack.py @@ -1,5 +1,5 @@ __author__ = '33197631+dadav@users.noreply.github.com' -__version__ = '1.0.0' +__version__ = '2.0.0' __name__ = 'onlinehashcrack' __license__ = 'GPL3' __description__ = 'This plugin automatically uploades handshakes to https://onlinehashcrack.com' @@ -7,9 +7,11 @@ __description__ = 'This plugin automatically uploades handshakes to https://onli import os import logging import requests +from pwnagotchi.utils import StatusFile READY = False -ALREADY_UPLOADED = None +REPORT = StatusFile('/root/.ohc_uploads', data_format='json') +SKIP = list() OPTIONS = dict() @@ -18,20 +20,11 @@ def on_loaded(): Gets called when the plugin gets loaded """ global READY - global EMAIL - global ALREADY_UPLOADED - if not 'email' in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None): + if 'email' not in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None): logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com") return - try: - with open('/root/.ohc_uploads', 'r') as f: - ALREADY_UPLOADED = f.read().splitlines() - except OSError: - logging.warning('OHC: No upload-file found.') - ALREADY_UPLOADED = [] - READY = True @@ -59,14 +52,17 @@ def on_internet_available(agent): """ Called in manual mode when there's internet connectivity """ + global REPORT + global SKIP if READY: display = agent.view() config = agent.config() + reported = REPORT.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')] - handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED) + handshake_new = set(handshake_paths) - set(reported) - set(SKIP) if handshake_new: logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com") @@ -76,12 +72,15 @@ def on_internet_available(agent): display.update(force=True) try: _upload_to_ohc(handshake) - ALREADY_UPLOADED.append(handshake) - with open('/root/.ohc_uploads', 'a') as f: - f.write(handshake + "\n") + reported.append(handshake) + REPORT.update(data={'reported': reported}) logging.info(f"OHC: Successfuly uploaded {handshake}") - except requests.exceptions.RequestException: - pass + except requests.exceptions.RequestException as req_e: + SKIP.append(handshake) + logging.error("OHC: %s", req_e) + continue except OSError as os_e: - logging.error(f"OHC: Got the following error: {os_e}") + SKIP.append(handshake) + logging.error("OHC: %s", os_e) + continue diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py index b3f3329..61bc3de 100644 --- a/pwnagotchi/plugins/default/wigle.py +++ b/pwnagotchi/plugins/default/wigle.py @@ -1,5 +1,5 @@ __author__ = '33197631+dadav@users.noreply.github.com' -__version__ = '1.0.0' +__version__ = '2.0.0' __name__ = 'wigle' __license__ = 'GPL3' __description__ = 'This plugin automatically uploades collected wifis to wigle.net' @@ -11,111 +11,24 @@ from io import StringIO import csv from datetime import datetime import requests -from pwnagotchi.mesh.wifi import freq_to_channel -from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap +from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile READY = False -ALREADY_UPLOADED = None -SKIP = None +REPORT = StatusFile('/root/.wigle_uploads', data_format='json') +SKIP = list() OPTIONS = dict() -AKMSUITE_TYPES = { - 0x00: "Reserved", - 0x01: "802.1X", - 0x02: "PSK", -} - -def _handle_packet(packet, result): - from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \ - Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA - """ - Analyze each packet and extract the data from Dot11 layers - """ - - if hasattr(packet, 'cap') and 'privacy' in packet.cap: - # packet is encrypted - if 'encryption' not in result: - result['encryption'] = set() - - if packet.haslayer(Dot11Beacon): - if packet.haslayer(Dot11Beacon)\ - or packet.haslayer(Dot11ProbeResp)\ - or packet.haslayer(Dot11AssoReq)\ - or packet.haslayer(Dot11ReassoReq): - if 'bssid' not in result and hasattr(packet[Dot11], 'addr3'): - result['bssid'] = packet[Dot11].addr3 - if 'essid' not in result and hasattr(packet[Dot11Elt], 'info'): - result['essid'] = packet[Dot11Elt].info - if 'channel' not in result and hasattr(packet[Dot11Elt:3], 'info'): - result['channel'] = int(ord(packet[Dot11Elt:3].info)) - - if packet.haslayer(RadioTap): - if 'rssi' not in result and hasattr(packet[RadioTap], 'dBm_AntSignal'): - result['rssi'] = packet[RadioTap].dBm_AntSignal - if 'channel' not in result and hasattr(packet[RadioTap], 'ChannelFrequency'): - result['channel'] = freq_to_channel(packet[RadioTap].ChannelFrequency) - - # see: https://fossies.org/linux/scapy/scapy/layers/dot11.py - if packet.haslayer(Dot11EltRSN): - if hasattr(packet[Dot11EltRSN], 'akm_suites'): - auth = AKMSUITE_TYPES.get(packet[Dot11EltRSN].akm_suites[0].suite) - result['encryption'].add(f"WPA2/{auth}") - else: - result['encryption'].add("WPA2") - - if packet.haslayer(Dot11EltVendorSpecific)\ - and (packet.haslayer(Dot11EltMicrosoftWPA) - or packet.info.startswith(b'\x00P\xf2\x01\x01\x00')): - - if hasattr(packet, 'akm_suites'): - auth = AKMSUITE_TYPES.get(packet.akm_suites[0].suite) - result['encryption'].add(f"WPA2/{auth}") - else: - result['encryption'].add("WPA2") - # end see - - return result - - -def _analyze_pcap(pcap): - from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \ - Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA - """ - Iterate over the packets and extract data - """ - result = dict() - - try: - packets = rdpcap(pcap) - for packet in packets: - result = _handle_packet(packet, result) - except Scapy_Exception as sc_e: - raise sc_e - - return result - def on_loaded(): """ Gets called when the plugin gets loaded """ global READY - global ALREADY_UPLOADED - global SKIP - - SKIP = list() if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net") return - try: - with open('/root/.wigle_uploads', 'r') as f: - ALREADY_UPLOADED = f.read().splitlines() - except OSError: - logging.warning('WIGLE: No upload-file found.') - ALREADY_UPLOADED = [] - READY = True @@ -197,24 +110,24 @@ def _send_to_wigle(lines, api_key, timeout=30): def on_internet_available(agent): - from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \ - Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA + from scapy.all import Scapy_Exception """ Called in manual mode when there's internet connectivity """ - global ALREADY_UPLOADED + global REPORT global SKIP if READY: config = agent.config() display = agent.view() + reported = REPORT.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] all_files = os.listdir(handshake_dir) all_gps_files = [os.path.join(handshake_dir, filename) for filename in all_files if filename.endswith('.gps.json')] - new_gps_files = set(all_gps_files) - set(ALREADY_UPLOADED) - set(SKIP) + new_gps_files = set(all_gps_files) - set(reported) - set(SKIP) if new_gps_files: logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net") @@ -271,10 +184,8 @@ def on_internet_available(agent): display.update(force=True) try: _send_to_wigle(csv_entries, OPTIONS['api_key']) - ALREADY_UPLOADED += no_err_entries - with open('/root/.wigle_uploads', 'a') as up_file: - for gps in no_err_entries: - up_file.write(gps + "\n") + reported += no_err_entries + REPORT.update(data={'reported': reported}) logging.info("WIGLE: Successfuly uploaded %d files", len(no_err_entries)) except requests.exceptions.RequestException as re_e: SKIP += no_err_entries diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py index 0f6b854..8ab7351 100644 --- a/pwnagotchi/plugins/default/wpa-sec.py +++ b/pwnagotchi/plugins/default/wpa-sec.py @@ -7,9 +7,12 @@ __description__ = 'This plugin automatically uploades handshakes to https://wpa- import os import logging import requests +from pwnagotchi.utils import StatusFile READY = False -ALREADY_UPLOADED = None +REPORT = StatusFile('/root/.wpa_sec_uploads', data_format='json') +OPTIONS = dict() +SKIP = list() def on_loaded(): @@ -17,20 +20,11 @@ def on_loaded(): Gets called when the plugin gets loaded """ global READY - global API_KEY - global ALREADY_UPLOADED - if not 'api_key' in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): + if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org") return - try: - with open('/root/.wpa_sec_uploads', 'r') as f: - ALREADY_UPLOADED = f.read().splitlines() - except OSError: - logging.warning('WPA_SEC: No upload-file found.') - ALREADY_UPLOADED = [] - READY = True @@ -48,39 +42,42 @@ def _upload_to_wpasec(path, timeout=30): files=payload, timeout=timeout) if ' already submitted' in result.text: - logging.warning(f"{path} was already submitted.") - except requests.exceptions.RequestException as e: - logging.error(f"WPA_SEC: Got an exception while uploading {path} -> {e}") - raise e + logging.warning("%s was already submitted.", path) + except requests.exceptions.RequestException as req_e: + raise req_e def on_internet_available(agent): """ Called in manual mode when there's internet connectivity """ + global REPORT + global SKIP if READY: config = agent.config() display = agent.view() + reported = REPORT.data_field_or('reported', default=list()) handshake_dir = config['bettercap']['handshakes'] handshake_filenames = os.listdir(handshake_dir) handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')] - handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED) + handshake_new = set(handshake_paths) - set(reported) - set(SKIP) if handshake_new: - logging.info("WPA_SEC: Internet connectivity detected.\ - Uploading new handshakes to wpa-sec.stanev.org") + logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org") for idx, handshake in enumerate(handshake_new): display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})") display.update(force=True) try: _upload_to_wpasec(handshake) - ALREADY_UPLOADED.append(handshake) - with open('/root/.wpa_sec_uploads', 'a') as f: - f.write(handshake + "\n") - logging.info(f"WPA_SEC: Successfuly uploaded {handshake}") - except requests.exceptions.RequestException: - pass + reported.append(handshake) + REPORT.update(data={'reported': reported}) + logging.info("WPA_SEC: Successfuly uploaded %s", handshake) + except requests.exceptions.RequestException as req_e: + SKIP.append(handshake) + logging.error("WPA_SEC: %s", req_e) + continue except OSError as os_e: - logging.error(f"WPA_SEC: Got the following error: {os_e}") + logging.error("WPA_SEC: %s", os_e) + continue From d15f8c18b51c1c794f45800aa6ccbb5da1836e0a Mon Sep 17 00:00:00 2001 From: Casey Diemel <diemelcw@gmail.com> Date: Sun, 13 Oct 2019 17:52:41 -0400 Subject: [PATCH 197/346] updated function call Signed-off-by: Casey Diemel <diemelcw@gmail.com> --- pwnagotchi/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index f8c8ff9..497c91e 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -190,7 +190,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): aps = [] try: s = self.session() - plugins.on("unfiltered_ap_list", s['wifi']['aps']) + plugins.on("unfiltered_ap_list", self, s['wifi']['aps']) for ap in s['wifi']['aps']: if ap['hostname'] not in whitelist: if self._filter_included(ap): From 9aca3a3a5b0af6d8fa0788065f0991ca0bcfa197 Mon Sep 17 00:00:00 2001 From: Casey Diemel <diemelcw@gmail.com> Date: Sun, 13 Oct 2019 17:53:18 -0400 Subject: [PATCH 198/346] added example for testing Signed-off-by: Casey Diemel <diemelcw@gmail.com> --- .../plugins/default/unfiltered_example.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 pwnagotchi/plugins/default/unfiltered_example.py diff --git a/pwnagotchi/plugins/default/unfiltered_example.py b/pwnagotchi/plugins/default/unfiltered_example.py new file mode 100644 index 0000000..c4f9fba --- /dev/null +++ b/pwnagotchi/plugins/default/unfiltered_example.py @@ -0,0 +1,18 @@ +__author__ = 'diemelcw@gmail.com' +__version__ = '1.0.0' +__name__ = 'unfiltered_example' +__license__ = 'GPL3' +__description__ = 'An example plugin for pwnagotchi that implements on_unfiltered_ap_list(agent,aps)' + +import logging + +# Will be set with the options in config.yml config['main']['plugins'][__name__] +OPTIONS = dict() + +# called when AP list is ready, before whitelist filtering has occured +def on_unfiltered_ap_list(agent,aps): + logging.info("Unfiltered AP list to follow") + for ap in aps: + logging.info(ap['hostname']) + + ## Additional logic here ## From dfb4bcaf21f1c41acbebb9f692973d7bda28e8af Mon Sep 17 00:00:00 2001 From: Casey Diemel <diemelcw@gmail.com> Date: Sun, 13 Oct 2019 18:13:25 -0400 Subject: [PATCH 199/346] added on_loaded function Signed-off-by: Casey Diemel <diemelcw@gmail.com> --- pwnagotchi/plugins/default/unfiltered_example.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pwnagotchi/plugins/default/unfiltered_example.py b/pwnagotchi/plugins/default/unfiltered_example.py index c4f9fba..c883b7c 100644 --- a/pwnagotchi/plugins/default/unfiltered_example.py +++ b/pwnagotchi/plugins/default/unfiltered_example.py @@ -9,6 +9,10 @@ import logging # Will be set with the options in config.yml config['main']['plugins'][__name__] OPTIONS = dict() +# called when the plugin is loaded +def on_loaded(): + logging.warning("%s plugin loaded" % __name__) + # called when AP list is ready, before whitelist filtering has occured def on_unfiltered_ap_list(agent,aps): logging.info("Unfiltered AP list to follow") From e146a87b444cf06b8e827e90603845d97a838a04 Mon Sep 17 00:00:00 2001 From: -k <slowdive@me.com> Date: Sun, 13 Oct 2019 22:20:53 -0700 Subject: [PATCH 200/346] RU locale edits --- pwnagotchi/locale/ru/LC_MESSAGES/voice.po | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pwnagotchi/locale/ru/LC_MESSAGES/voice.po b/pwnagotchi/locale/ru/LC_MESSAGES/voice.po index 796bf17..146527f 100644 --- a/pwnagotchi/locale/ru/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/ru/LC_MESSAGES/voice.po @@ -29,10 +29,10 @@ msgid "New day, new hunt, new pwns!" msgstr "Новый день, новая охота, новые взломы!" msgid "Hack the Planet!" -msgstr "Взломаем всю планету!" +msgstr "Хак зе планет!" msgid "AI ready." -msgstr "Искусственный интеллект готов." +msgstr "AI готов." msgid "The neural network is ready." msgstr "Нейронная сеть готова." @@ -48,7 +48,7 @@ msgid "Let's go for a walk!" msgstr "Пойдем прогуляемся!" msgid "This is the best day of my life!" -msgstr "Это лучший день в моей жизни!" +msgstr "Лучший день в моей жизни!" msgid "Shitty day :/" msgstr "Дерьмовый день :/" @@ -63,19 +63,19 @@ msgid "I'm sad" msgstr "Мне грустно" msgid "I'm living the life!" -msgstr "Я живу своей жизнью!" +msgstr "Угараю по полной!" msgid "I pwn therefore I am." msgstr "Я взламываю, поэтому я существую." msgid "So many networks!!!" -msgstr "Так, много сетей!!!" +msgstr "Так много сетей!!!" msgid "I'm having so much fun!" msgstr "Мне так весело!" msgid "My crime is that of curiosity ..." -msgstr "Моё преступление - это любопытство …" +msgstr "Моe преступление - это любопытство …" #, python-brace-format msgid "Hello {name}! Nice to meet you. {name}" @@ -105,7 +105,7 @@ msgid "Missed!" msgstr "Промахнулся!" msgid "Nobody wants to play with me ..." -msgstr "Никто не хочет играть со мной …" +msgstr "Никто не хочет со мной играть ..." msgid "I feel so alone ..." msgstr "Мне так одиноко …" From a8a0f842a39f82b994a88d2416cab2b0179ca880 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 11:36:42 +0200 Subject: [PATCH 201/346] new: saving unit's fingerprint to /etc/pwnagotchi/fingerprint for easy access --- bin/pwnagotchi | 2 +- pwnagotchi/identity.py | 4 ++++ pwnagotchi/mesh/utils.py | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index ce02bbc..a5412f3 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -37,7 +37,7 @@ if __name__ == '__main__': keypair = KeyPair(view=display) agent = Agent(view=display, config=config, keypair=keypair) - logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._keypair.fingerprint, pwnagotchi.version)) + logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent.fingerprint(), pwnagotchi.version)) for _, plugin in plugins.loaded.items(): logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__)) diff --git a/pwnagotchi/identity.py b/pwnagotchi/identity.py index 9be375b..d53f5b2 100644 --- a/pwnagotchi/identity.py +++ b/pwnagotchi/identity.py @@ -16,6 +16,7 @@ class KeyPair(object): self.priv_key = None self.pub_path = "%s.pub" % self.priv_path self.pub_key = None + self.fingerprint_path = os.path.join(path, "fingerprint") self._view = view if not os.path.exists(self.path): @@ -46,6 +47,9 @@ class KeyPair(object): self.pub_key_pem_b64 = base64.b64encode(pem_ascii).decode("ascii") self.fingerprint = hashlib.sha256(pem_ascii).hexdigest() + with open(self.fingerprint_path, 'w+t') as fp: + fp.write(self.fingerprint) + # no exception, keys loaded correctly. self._view.on_starting() return diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index 6129e82..3849acc 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -29,6 +29,9 @@ class AsyncAdvertiser(object): self._peers = {} self._closest_peer = None + def fingerprint(self): + return self._keypair.fingerprint + def _update_advertisement(self, s): self._advertisement['pwnd_run'] = len(self._handshakes) self._advertisement['pwnd_tot'] = utils.total_unique_handshakes(self._config['bettercap']['handshakes']) From 5b78a13e95e97624089fdb95f617a511cd9b36f2 Mon Sep 17 00:00:00 2001 From: beliver17 <48902407+beliver17@users.noreply.github.com> Date: Mon, 14 Oct 2019 16:00:05 +0530 Subject: [PATCH 202/346] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7dc839b..4a25cc3 100644 --- a/README.md +++ b/README.md @@ -11,30 +11,30 @@ </p> </p> -[Pwnagotchi](https://twitter.com/pwnagotchi) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment in order to maximize the crackable WPA key material it captures (either passively, or by performing deauthentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), +[Pwnagotchi](https://twitter.com/pwnagotchi) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), full and half WPA handshakes.  -Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning based "AI" *(yawn)*, Pwnagotchi tunes [its own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things** in the environments you expose it to. +Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to. More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://www.pwnagotchi.ai/usage/#training-the-ai) doc.) -**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi actually learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but **definitely listen to your Pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :) +**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but ** listen to your Pwnagotchi when it tells you it's boring!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :) -Multiple units within close physical proximity can "talk" to each other, advertising their own presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage. +Multiple units within close physical proximity can "talk" to each other, advertising their presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage. ## Why does Pwnagotchi exist? For hackers to learn reinforcement learning, WiFi networking, and have an excuse to get out for more walks. Also? **It's cute as f---**. -**In case you're curious about the name:** *Pwnagotchi* is a portmanteau of *pwn* (which we shouldn't have to explain if you are interested in this project :kissing_heart:) and *-gotchi*. It is a nostalgic reference made in homage to a very popular children's toy from the 1990s called the [Tamagotchi](https://en.wikipedia.org/wiki/Tamagotchi). The Tamagotchi (たまごっち, derived from *tamago* (たまご) "egg" + *uotchi* (ウオッチ) "watch") is a cultural touchstone for many Millennial hackers as a formative electronic toy from our collective childhoods. Were you lucky enough to possess a Tamagotchi as a kid? Well, with your Pwnagotchi, you too can enjoy the nostalgic delight of being strangely emotionally attached to a handheld automata *yet again!* Except, this time around...you get to #HackThePlanet. >:D +**In case you're curious about the name:** *Pwnagotchi* is a portmanteau of *pwn* (which we shouldn't have to explain if you are interested in this project: kissing_heart:) and *-gothic*. It is a nostalgic reference made in homage to a very popular children's toy from the 1990s called the [Tamagotchi](https://en.wikipedia.org/wiki/Tamagotchi). The Tamagotchi (たまごっち, derived from *Tamago* (たまご) "egg" + *Sochi* (ウオッチ) "watch") is a cultural touchstone for many Millennial hackers as a formative electronic toy from our collective childhoods. Were you lucky enough to possess a Tamagotchi as a kid? Well, with your Pwnagotchi, you too can enjoy the nostalgic delight of being strangely emotionally attached to a handheld automata *yet again!* Except, this time around...you get to #HackThePlanet. >:D ## Documentation --- :warning: **THE FOLLOWING DOCUMENTATION IS BEING PREPARED FOR THE v1.0 RELEASE OF PWNAGOTCHI. Since this effort is an active (and unstable) work-in-progress, the docs displayed here are in various stages of [in]completion. There will be dead links and placeholders throughout as we are still building things out in preparation for the v1.0 release.** :warning: -**IMPORTANT NOTE:** If you'd like to alphatest Pwnagotchi and are trying to get yours up and running while the project is still very unstable, please understand that the documentation here may not reflect what is currently implemented. If you have questions, ask the community of alphatesters in the [official Pwnagotchi Slack](https://pwnagotchi.herokuapp.com). The Pwnagotchi dev team is entirely focused on the v1.0 release and will NOT be providing support for alphatesters trying to get their Pwnagotchis working in the meantime. All technical support during this period of development is being provided by your fellow alphatesters in the Slack (thanks, everybody! :heart:). +**IMPORTANT NOTE:** If you'd like to alpha test Pwnagotchi and are trying to get yours up and running while the project is still very unstable, please understand that the documentation here may not reflect what is currently implemented. If you have questions, ask the community of alpha testers in the [official Pwnagotchi Slack](https://pwnagotchi.herokuapp.com). The Pwnagotchi dev team is entirely focused on the v1.0 release and will NOT be providing support for alpha testers trying to get their Pwnagotchis working in the meantime. All technical support during this period of development is being provided by your fellow alpha testers in the Slack (thanks, everybody!: heart:). https://www.pwnagotchi.ai From 82bf9b9853d7ec92916c438a8d1fa6b0a6e3a111 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 14:38:24 +0200 Subject: [PATCH 203/346] misc: small fix or general refactoring i did not bother commenting --- Makefile | 2 +- scripts/create_sibling.sh | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4c97749..117e222 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ PWN_HOSTNAME=pwnagotchi PWN_VERSION=master -all: install image clean +all: clean install image install: curl https://releases.hashicorp.com/packer/1.3.5/packer_1.3.5_linux_amd64.zip -o /tmp/packer.zip diff --git a/scripts/create_sibling.sh b/scripts/create_sibling.sh index c0d7558..a60a42f 100755 --- a/scripts/create_sibling.sh +++ b/scripts/create_sibling.sh @@ -4,6 +4,9 @@ set -eu +echo "THIS SCRIPT IS DEPRECATED, PLEASE REFER TO THE OFFICIAL DOCUMENTATION AT https://pwnagotchi.ai/contributing/#creating-an-image" +exit 1 + REQUIREMENTS=( wget gunzip git dd e2fsck resize2fs parted losetup qemu-system-x86_64 ) DEBREQUIREMENTS=( wget gzip git parted qemu-system-x86 qemu-user-static ) REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")" From 84be7c0d34f79f1591342c88a3db12fe775034b8 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 16:16:59 +0200 Subject: [PATCH 204/346] misc: small fix or general refactoring i did not bother commenting --- README.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 4a25cc3..9ef170c 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ </p> </p> -[Pwnagotchi](https://twitter.com/pwnagotchi) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), +[Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), full and half WPA handshakes. - +<img src="https://media.giphy.com/media/f9GsXyfgEQbY65fnhu/source.gif"/> + Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to. @@ -22,19 +23,11 @@ More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](http **Keep in mind:** Unlike the usual RL simulations, Pwnagotchi learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but ** listen to your Pwnagotchi when it tells you it's boring!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :) + + Multiple units within close physical proximity can "talk" to each other, advertising their presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage. -## Why does Pwnagotchi exist? - -For hackers to learn reinforcement learning, WiFi networking, and have an excuse to get out for more walks. Also? **It's cute as f---**. - -**In case you're curious about the name:** *Pwnagotchi* is a portmanteau of *pwn* (which we shouldn't have to explain if you are interested in this project: kissing_heart:) and *-gothic*. It is a nostalgic reference made in homage to a very popular children's toy from the 1990s called the [Tamagotchi](https://en.wikipedia.org/wiki/Tamagotchi). The Tamagotchi (たまごっち, derived from *Tamago* (たまご) "egg" + *Sochi* (ウオッチ) "watch") is a cultural touchstone for many Millennial hackers as a formative electronic toy from our collective childhoods. Were you lucky enough to possess a Tamagotchi as a kid? Well, with your Pwnagotchi, you too can enjoy the nostalgic delight of being strangely emotionally attached to a handheld automata *yet again!* Except, this time around...you get to #HackThePlanet. >:D - ## Documentation ---- -:warning: **THE FOLLOWING DOCUMENTATION IS BEING PREPARED FOR THE v1.0 RELEASE OF PWNAGOTCHI. Since this effort is an active (and unstable) work-in-progress, the docs displayed here are in various stages of [in]completion. There will be dead links and placeholders throughout as we are still building things out in preparation for the v1.0 release.** :warning: - -**IMPORTANT NOTE:** If you'd like to alpha test Pwnagotchi and are trying to get yours up and running while the project is still very unstable, please understand that the documentation here may not reflect what is currently implemented. If you have questions, ask the community of alpha testers in the [official Pwnagotchi Slack](https://pwnagotchi.herokuapp.com). The Pwnagotchi dev team is entirely focused on the v1.0 release and will NOT be providing support for alpha testers trying to get their Pwnagotchis working in the meantime. All technical support during this period of development is being provided by your fellow alpha testers in the Slack (thanks, everybody!: heart:). https://www.pwnagotchi.ai From 4b563398f4640c90c2e0d6ec6a84ec10a8898c1b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 16:18:07 +0200 Subject: [PATCH 205/346] misc: small fix or general refactoring i did not bother commenting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ef170c..f3a0d0f 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), full and half WPA handshakes. -<img src="https://media.giphy.com/media/f9GsXyfgEQbY65fnhu/source.gif"/> + Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to. From 2fe7ac0a71e37a887a7a90f86b1ac86bf4d5348b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 16:18:33 +0200 Subject: [PATCH 206/346] misc: small fix or general refactoring i did not bother commenting --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f3a0d0f..cd3292d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ full and half WPA handshakes.  - Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to. More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://www.pwnagotchi.ai/usage/#training-the-ai) doc.) From 54a8fd81a52aab83980a075ad42eb3efa4a0b901 Mon Sep 17 00:00:00 2001 From: Administrator <admin@example.com> Date: Mon, 14 Oct 2019 16:38:57 +0200 Subject: [PATCH 207/346] Initial commit Waveshare OledHat Add support for Waveshare Oled Hat Change view.py to have more variable to support more types of screen in the future --- pwnagotchi/ui/display.py | 16 +++ pwnagotchi/ui/view.py | 89 +++++++++++-- pwnagotchi/ui/waveshare/oledhat/SH1106.py | 136 ++++++++++++++++++++ pwnagotchi/ui/waveshare/oledhat/__init__.py | 0 pwnagotchi/ui/waveshare/oledhat/config.py | 111 ++++++++++++++++ pwnagotchi/ui/waveshare/oledhat/epd.py | 31 +++++ 6 files changed, 370 insertions(+), 13 deletions(-) create mode 100644 pwnagotchi/ui/waveshare/oledhat/SH1106.py create mode 100644 pwnagotchi/ui/waveshare/oledhat/__init__.py create mode 100644 pwnagotchi/ui/waveshare/oledhat/config.py create mode 100644 pwnagotchi/ui/waveshare/oledhat/epd.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 065dbd8..22f7f26 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -130,6 +130,9 @@ class Display(View): def is_waveshare_v2(self): return self._display_type in ('waveshare_2', 'ws_2', 'waveshare2', 'ws2') + def is_oledhat(self): + return self._display_type in ('oledhat') + def is_waveshare_any(self): return self.is_waveshare_v1() or self.is_waveshare_v2() @@ -176,6 +179,14 @@ class Display(View): self._display.init(self._display.PART_UPDATE) self._render_cb = self._waveshare_render + elif self.is_oledhat(): + logging.info("initializing oledhat display") + from pwnagotchi.ui.waveshare.oledhat.epd import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + self._render_cb = self._oledhat_render + else: logging.critical("unknown display type %s" % self._display_type) @@ -192,6 +203,8 @@ class Display(View): self._display.clear() elif self.is_waveshare_any(): self._display.Clear(WHITE) + elif self.is_oledhat(): + self._display.clear() else: logging.critical("unknown display type %s" % self._display_type) @@ -237,6 +250,9 @@ class Display(View): elif self.is_waveshare_v2(): self._display.displayPartial(buf) + def _oledhat_render(self): + self._display.display(self._canvas) + def _waveshare_bc_render(self): buf_black = self._display.getbuffer(self._canvas) # emptyImage = Image.new('1', (self._display.height, self._display.width), 255) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 03ea0f3..1fd03b8 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -24,6 +24,15 @@ def setup_display_specifics(config): face_pos = (0, 0) name_pos = (0, 0) status_pos = (0, 0) + channel_pos = (0, 0) + aps_pos = (0, 0) + uptime_pos = (0, 0) + line1_pos = [(0, 0),(0, 0)] + line2_pos = (0, 0) + friend_face = (0, 0) + friend_name = (0, 0) + shakes_pos = (0, 0) + mode_pos = (0, 0) status_font = fonts.Medium status_max_length = None @@ -34,7 +43,16 @@ def setup_display_specifics(config): height = 104 face_pos = (0, 37) name_pos = (5, 18) + channel_pos = (0, 0) + aps_pos = (25, 0) status_pos = (102, 18) + uptime_pos = (width - 65, 0) + line1_pos = [0, int(height * .12), width, int(height * .12)] + line2_pos = [0, height - int(height * .12), width, height - int(height * .12)] + friend_face_pos = (0, (height * 0.88) - 15) + friend_name_pos = (40, (height * 0.88) - 13) + shakes_pos = (0, height - int(height * .12) + 1) + mode_pos = (width - 25, height - int(height * .12) + 1) status_font = fonts.Small status_max_length = 20 @@ -45,10 +63,39 @@ def setup_display_specifics(config): height = 96 face_pos = (0, int(height / 4)) name_pos = (5, int(height * .15)) + channel_pos = (0, 0) + aps_pos = (25, 0) status_pos = (int(width / 2) - 15, int(height * .15)) + uptime_pos = (width - 65, 0) + line1_pos = [0, int(height * .12), width, int(height * .12)] + line2_pos = [0, height - int(height * .12), width, height - int(height * .12)] + friend_face_pos = (0, (height * 0.88) - 15) + friend_name_pos = (40, (height * 0.88) - 13) + shakes_pos = (0, height - int(height * .12) + 1) + mode_pos = mode_pos = (width - 25, height - int(height * .12) + 1) status_font = fonts.Medium status_max_length = (width - status_pos[0]) // 6 + if config['ui']['display']['type'] in ('oledhat'): + fonts.setup(8, 8, 8, 8) + + width = 128 + height = 64 + face_pos = (0, 32) + name_pos = (0, 10) + channel_pos = (0, 0) + aps_pos = (25, 0) + status_pos = (30, 18) + uptime_pos = (width - 58, 0) + line1_pos = [0, 9, width, 9] + line2_pos = [0, 53, width, 53] + friend_face_pos = (0, (height * 0.88) - 15) + friend_name_pos = (40, (height * 0.88) - 13) + shakes_pos = (0, 53) + mode_pos = (width - 25, 10 ) + status_font = fonts.Small + status_max_length = 20 + elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1', 'ws_2', 'ws2', 'waveshare_2', 'waveshare2'): if config['ui']['display']['color'] == 'black': @@ -58,7 +105,16 @@ def setup_display_specifics(config): height = 122 face_pos = (0, 40) name_pos = (5, 20) + channel_pos = (0, 0) + aps_pos = (25, 0) status_pos = (125, 20) + uptime_pos = (width - 65, 0) + line1_pos = [0, int(height * .12), width, int(height * .12)] + line2_pos = [0, height - int(height * .12), width, height - int(height * .12)] + friend_face_pos = (0, (height * 0.88) - 15) + friend_name_pos = (40, (height * 0.88) - 13) + shakes_pos = (0, height - int(height * .12) + 1) + mode_pos = mode_pos = (width - 25, height - int(height * .12) + 1) status_font = fonts.Medium else: fonts.setup(10, 8, 10, 25) @@ -67,11 +123,20 @@ def setup_display_specifics(config): height = 104 face_pos = (0, int(height / 4)) name_pos = (5, int(height * .15)) + channel_pos = (0, 0) + aps_pos = (25, 0) status_pos = (int(width / 2) - 15, int(height * .15)) + uptime_pos = (width - 65, 0) + line1_pos = [0, int(height * .12), width, int(height * .12)] + line2_pos = [0, height - int(height * .12), width, height - int(height * .12)] + friend_face_pos = (0, (height * 0.88) - 15) + friend_name_pos = (40, (height * 0.88) - 13) + shakes_pos = (0, height - int(height * .12) + 1) + mode_pos = mode_pos = (width - 25, height - int(height * .12) + 1) status_font = fonts.Medium status_max_length = (width - status_pos[0]) // 6 - return width, height, face_pos, name_pos, status_pos, status_font, status_max_length + return width, height, face_pos, name_pos, channel_pos, aps_pos, status_pos, uptime_pos, line1_pos, line2_pos, friend_face_pos, friend_name_pos, shakes_pos, mode_pos, status_font, status_max_length class View(object): @@ -86,30 +151,28 @@ class View(object): self._voice = Voice(lang=config['main']['lang']) self._width, self._height, \ - face_pos, name_pos, status_pos, status_font, status_max_length = setup_display_specifics(config) + face_pos, name_pos, channel_pos, aps_pos, status_pos, uptime_pos, line1_pos, line2_pos, friend_face_pos, friend_name_pos, shakes_pos, mode_pos, status_font, status_max_length = setup_display_specifics(config) self._state = State(state={ - 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold, + 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=channel_pos, label_font=fonts.Bold, text_font=fonts.Medium), - 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=(30, 0), label_font=fonts.Bold, + 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=aps_pos, label_font=fonts.Bold, text_font=fonts.Medium), # 'epoch': LabeledValue(color=BLACK, label='E', value='0000', position=(145, 0), label_font=fonts.Bold, # text_font=fonts.Medium), - 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=(self._width - 65, 0), + 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=uptime_pos, label_font=fonts.Bold, text_font=fonts.Medium), - 'line1': Line([0, int(self._height * .12), self._width, int(self._height * .12)], color=BLACK), - 'line2': Line( - [0, self._height - int(self._height * .12), self._width, self._height - int(self._height * .12)], - color=BLACK), + 'line1': Line(line1_pos,color=BLACK), + 'line2': Line(line2_pos,color=BLACK), 'face': Text(value=faces.SLEEP, position=face_pos, color=BLACK, font=fonts.Huge), - 'friend_face': Text(value=None, position=(0, (self._height * 0.88) - 15), font=fonts.Bold, color=BLACK), - 'friend_name': Text(value=None, position=(40, (self._height * 0.88) - 13), font=fonts.BoldSmall, + 'friend_face': Text(value=None, position=friend_face_pos, font=fonts.Bold, color=BLACK), + 'friend_name': Text(value=None, position=friend_name_pos, font=fonts.BoldSmall, color=BLACK), 'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold), @@ -123,9 +186,9 @@ class View(object): max_length=status_max_length), 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, - position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold, + position=shakes_pos, label_font=fonts.Bold, text_font=fonts.Medium), - 'mode': Text(value='AUTO', position=(self._width - 25, self._height - int(self._height * .12) + 1), + 'mode': Text(value='AUTO', position=mode_pos, font=fonts.Bold, color=BLACK), }) diff --git a/pwnagotchi/ui/waveshare/oledhat/SH1106.py b/pwnagotchi/ui/waveshare/oledhat/SH1106.py new file mode 100644 index 0000000..68d4f18 --- /dev/null +++ b/pwnagotchi/ui/waveshare/oledhat/SH1106.py @@ -0,0 +1,136 @@ +from . import config +import RPi.GPIO as GPIO +import time +import numpy as np + +Device_SPI = config.Device_SPI +Device_I2C = config.Device_I2C + +LCD_WIDTH = 128 #LCD width +LCD_HEIGHT = 64 #LCD height + +class SH1106(object): + def __init__(self): + self.width = LCD_WIDTH + self.height = LCD_HEIGHT + #Initialize DC RST pin + self._dc = config.DC_PIN + self._rst = config.RST_PIN + self._bl = config.BL_PIN + self.Device = config.Device + + + """ Write register address and data """ + def command(self, cmd): + if(self.Device == Device_SPI): + GPIO.output(self._dc, GPIO.LOW) + config.spi_writebyte([cmd]) + else: + config.i2c_writebyte(0x00, cmd) + + # def data(self, val): + # GPIO.output(self._dc, GPIO.HIGH) + # config.spi_writebyte([val]) + + def Init(self): + if (config.module_init() != 0): + return -1 + """Initialize dispaly""" + self.reset() + self.command(0xAE);#--turn off oled panel + self.command(0x02);#---set low column address + self.command(0x10);#---set high column address + self.command(0x40);#--set start line address Set Mapping RAM Display Start Line (0x00~0x3F) + self.command(0x81);#--set contrast control register + self.command(0xA0);#--Set SEG/Column Mapping + self.command(0xC0);#Set COM/Row Scan Direction + self.command(0xA6);#--set normal display + self.command(0xA8);#--set multiplex ratio(1 to 64) + self.command(0x3F);#--1/64 duty + self.command(0xD3);#-set display offset Shift Mapping RAM Counter (0x00~0x3F) + self.command(0x00);#-not offset + self.command(0xd5);#--set display clock divide ratio/oscillator frequency + self.command(0x80);#--set divide ratio, Set Clock as 100 Frames/Sec + self.command(0xD9);#--set pre-charge period + self.command(0xF1);#Set Pre-Charge as 15 Clocks & Discharge as 1 Clock + self.command(0xDA);#--set com pins hardware configuration + self.command(0x12); + self.command(0xDB);#--set vcomh + self.command(0x40);#Set VCOM Deselect Level + self.command(0x20);#-Set Page Addressing Mode (0x00/0x01/0x02) + self.command(0x02);# + self.command(0xA4);# Disable Entire Display On (0xa4/0xa5) + self.command(0xA6);# Disable Inverse Display On (0xa6/a7) + time.sleep(0.1) + self.command(0xAF);#--turn on oled panel + + + def reset(self): + """Reset the display""" + GPIO.output(self._rst,GPIO.HIGH) + time.sleep(0.1) + GPIO.output(self._rst,GPIO.LOW) + time.sleep(0.1) + GPIO.output(self._rst,GPIO.HIGH) + time.sleep(0.1) + + def getbuffer(self, image): + # print "bufsiz = ",(self.width/8) * self.height + buf = [0xFF] * ((self.width//8) * self.height) + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + # print "imwidth = %d, imheight = %d",imwidth,imheight + if(imwidth == self.width and imheight == self.height): + #print ("Vertical") + for y in range(imheight): + for x in range(imwidth): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0: + buf[x + (y // 8) * self.width] &= ~(1 << (y % 8)) + # print x,y,x + (y * self.width)/8,buf[(x + y * self.width) / 8] + + elif(imwidth == self.height and imheight == self.width): + #print ("Vertical") + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] == 0: + buf[(newx + (newy // 8 )*self.width) ] &= ~(1 << (y % 8)) + return buf + + + # def ShowImage(self,Image): + # self.SetWindows() + # GPIO.output(self._dc, GPIO.HIGH); + # for i in range(0,self.width * self.height/8): + # config.spi_writebyte([~Image[i]]) + + def ShowImage(self, pBuf): + for page in range(0,8): + # set page address # + self.command(0xB0 + page); + # set low column address # + self.command(0x02); + # set high column address # + self.command(0x10); + # write data # + time.sleep(0.01) + if(self.Device == Device_SPI): + GPIO.output(self._dc, GPIO.HIGH); + for i in range(0,self.width):#for(int i=0;i<self.width; i++) + if(self.Device == Device_SPI): + config.spi_writebyte([~pBuf[i+self.width*page]]); + else : + config.i2c_writebyte(0x40, ~pBuf[i+self.width*page]) + + + + + + def clear(self): + """Clear contents of image buffer""" + _buffer = [0xff]*(self.width * self.height//8) + self.ShowImage(_buffer) + #print "%d",_buffer[i:i+4096] diff --git a/pwnagotchi/ui/waveshare/oledhat/__init__.py b/pwnagotchi/ui/waveshare/oledhat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pwnagotchi/ui/waveshare/oledhat/config.py b/pwnagotchi/ui/waveshare/oledhat/config.py new file mode 100644 index 0000000..1eaf68e --- /dev/null +++ b/pwnagotchi/ui/waveshare/oledhat/config.py @@ -0,0 +1,111 @@ +# /***************************************************************************** +# * | File : config.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface,for Jetson nano +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2019-06-06 +# * | Info : +# ******************************************************************************/ +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +import RPi.GPIO as GPIO +import time +from smbus import SMBus +import spidev + +import ctypes +# import spidev + +# Pin definition +RST_PIN = 25 +DC_PIN = 24 +CS_PIN = 8 +BL_PIN = 18 +BUSY_PIN = 18 + +Device_SPI = 1 +Device_I2C = 0 + + + +if(Device_SPI == 1): + Device = Device_SPI + spi = spidev.SpiDev(0, 0) +else : + Device = Device_I2C + address = 0x3C + bus = SMBus(1) + +def digital_write(pin, value): + GPIO.output(pin, value) + +def digital_read(pin): + return GPIO.input(BUSY_PIN) + +def delay_ms(delaytime): + time.sleep(delaytime / 1000.0) + +def spi_writebyte(data): + # SPI.writebytes(data) + spi.writebytes([data[0]]) + +def i2c_writebyte(reg, value): + bus.write_byte_data(address, reg, value) + + # time.sleep(0.01) +def module_init(): + # print("module_init") + + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(RST_PIN, GPIO.OUT) + GPIO.setup(DC_PIN, GPIO.OUT) + GPIO.setup(CS_PIN, GPIO.OUT) + GPIO.setup(BL_PIN, GPIO.OUT) + + + # SPI.max_speed_hz = 2000000 + # SPI.mode = 0b00 + # i2c_writebyte(0xff,0xff) + if(Device == Device_SPI): + # spi.SYSFS_software_spi_begin() + # spi.SYSFS_software_spi_setDataMode(0); + # spi.SYSFS_software_spi_setClockDivider(1); + spi.max_speed_hz = 2000000 + spi.mode = 0b00 + + GPIO.output(CS_PIN, 0) + GPIO.output(BL_PIN, 1) + GPIO.output(DC_PIN, 0) + return 0 + +def module_exit(): + if(Device == Device_SPI): + spi.SYSFS_software_spi_end() + else : + bus.close() + GPIO.output(RST_PIN, 0) + GPIO.output(DC_PIN, 0) + + + +### END OF FILE ### diff --git a/pwnagotchi/ui/waveshare/oledhat/epd.py b/pwnagotchi/ui/waveshare/oledhat/epd.py new file mode 100644 index 0000000..d7f5d4c --- /dev/null +++ b/pwnagotchi/ui/waveshare/oledhat/epd.py @@ -0,0 +1,31 @@ +from . import SH1106 +import time +from . import config +import traceback + +from PIL import Image,ImageDraw,ImageFont + + +# Display resolution +EPD_WIDTH = 64 +EPD_HEIGHT = 128 + +class EPD(object): + + def __init__(self): + self.reset_pin = config.RST_PIN + self.dc_pin = config.DC_PIN + self.busy_pin = config.BUSY_PIN + self.cs_pin = config.CS_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + disp = SH1106.SH1106() + + def init(self): + disp.Init() + + def Clear(self): + disp.clear() + + def display(self, image): + disp.ShowImage(disp.getbuffer(image)) From 1642663c844eef65591cf7ccf44b499f54690185 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 16:40:29 +0200 Subject: [PATCH 208/346] fix: added explicit path for pwngrid client token --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 21a15dd..57a7a93 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -502,7 +502,7 @@ [Service] Type=simple PermissionsStartOnly=true - ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait -log /var/log/pwngrid-peer.log -iface mon0 + ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /var/log/pwngrid-peer.log -iface mon0 Restart=always RestartSec=30 From b66f1b66e583a07ae20e01b45e2ed6d7978a9163 Mon Sep 17 00:00:00 2001 From: Administrator <admin@example.com> Date: Mon, 14 Oct 2019 16:41:09 +0200 Subject: [PATCH 209/346] Add oledhat in the default.yml options for ui --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index c1d03a5..5034d49 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -168,7 +168,7 @@ ui: display: enabled: true rotation: 180 - # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2 + # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat type: 'waveshare_2' # Possible options red/yellow/black (black used for monocromatic displays) color: 'black' From 748dbea13e387f79fefb321f8cbba76d467f44eb Mon Sep 17 00:00:00 2001 From: Administrator <admin@example.com> Date: Mon, 14 Oct 2019 18:19:53 +0200 Subject: [PATCH 210/346] Fix bug. Line misplaced in the code to init the display. --- pwnagotchi/ui/waveshare/oledhat/epd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/ui/waveshare/oledhat/epd.py b/pwnagotchi/ui/waveshare/oledhat/epd.py index d7f5d4c..e6de76a 100644 --- a/pwnagotchi/ui/waveshare/oledhat/epd.py +++ b/pwnagotchi/ui/waveshare/oledhat/epd.py @@ -10,6 +10,8 @@ from PIL import Image,ImageDraw,ImageFont EPD_WIDTH = 64 EPD_HEIGHT = 128 +disp = SH1106.SH1106() + class EPD(object): def __init__(self): @@ -19,7 +21,6 @@ class EPD(object): self.cs_pin = config.CS_PIN self.width = EPD_WIDTH self.height = EPD_HEIGHT - disp = SH1106.SH1106() def init(self): disp.Init() From 0f2ad47c17afc9ce4b40ffca10dc5e4aa97c989a Mon Sep 17 00:00:00 2001 From: Pascal Cotret <pascal.cotret@gmail.com> Date: Mon, 14 Oct 2019 18:25:22 +0200 Subject: [PATCH 211/346] Added missing words for the french PO file --- pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po index 1d061f7..26f4678 100644 --- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po @@ -192,19 +192,19 @@ msgstr "" "#pwnlog #pwnlife #hacktheplanet #skynet" msgid "hours" -msgstr "" +msgstr "heures" msgid "minutes" -msgstr "" +msgstr "minutes" msgid "seconds" -msgstr "" +msgstr "secondes" msgid "hour" -msgstr "" +msgstr "heure" msgid "minute" -msgstr "" +msgstr "minute" msgid "second" -msgstr "" +msgstr "seconde" From 6b42e48dff12ef89dfa171b9cf5a652076d728f2 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 19:21:46 +0200 Subject: [PATCH 212/346] misc: refactored ui layout code --- builder/pwnagotchi.yml | 2 +- pwnagotchi/ui/layout.py | 162 ++++++++++++++++++++++++++++++++++++++++ pwnagotchi/ui/view.py | 159 +++++---------------------------------- 3 files changed, 181 insertions(+), 142 deletions(-) create mode 100644 pwnagotchi/ui/layout.py diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 57a7a93..0bf0a9a 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.3/pwngrid_linux_armv6l_1.7.3.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.4/pwngrid_linux_armv6l_1.7.4.zip" apt: hold: - firmware-atheros diff --git a/pwnagotchi/ui/layout.py b/pwnagotchi/ui/layout.py new file mode 100644 index 0000000..eb1c266 --- /dev/null +++ b/pwnagotchi/ui/layout.py @@ -0,0 +1,162 @@ +import pwnagotchi.ui.fonts as fonts + + +def inkyphat(config, layout): + fonts.setup(10, 8, 10, 28) + + layout['width'] = 212 + layout['height'] = 104 + layout['face'] = (0, 37) + layout['name'] = (5, 18) + layout['channel'] = (0, 0) + layout['aps'] = (25, 0) + layout['uptime'] = (layout['width'] - 65, 0) + layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] + layout['line2'] = [0, layout['height'] - int(layout['height'] * .12), layout['width'], + layout['height'] - int(layout['height'] * .12)] + layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) + layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) + layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) + layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) + layout['status'] = { + 'pos': (102, 18), + 'font': fonts.Small, + 'max': 20 + } + return layout + + +def papirus(config, layout): + fonts.setup(10, 8, 10, 23) + + layout['width'] = 200 + layout['height'] = 96 + layout['face'] = (0, int(layout['height'] / 4)) + layout['name'] = (5, int(layout['height'] * .15)) + layout['channel'] = (0, 0) + layout['aps'] = (25, 0) + layout['uptime'] = (layout['width'] - 65, 0) + layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] + layout['line2'] = [0, layout['height'] - int(layout['height'] * .12), layout['width'], + layout['height'] - int(layout['height'] * .12)] + layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) + layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) + layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) + layout['mode'] = layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) + layout['status'] = { + 'pos': (int(layout['width'] / 2) - 15, int(layout['height'] * .15)), + 'font': fonts.Medium, + 'max': (layout['width'] - layout['status'][0]) // 6 + } + return layout + + +def oledhat(config, layout): + fonts.setup(10, 8, 10, 23) + + layout['width'] = 200 + layout['height'] = 96 + layout['face'] = (0, int(layout['height'] / 4)) + layout['name'] = (5, int(layout['height'] * .15)) + layout['channel'] = (0, 0) + layout['aps'] = (25, 0) + layout['uptime'] = (layout['width'] - 65, 0) + layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] + layout['line2'] = [0, layout['height'] - int(layout['height'] * .12), layout['width'], + layout['height'] - int(layout['height'] * .12)] + layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) + layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) + layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) + layout['mode'] = layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) + layout['status'] = { + 'pos': (int(layout['width'] / 2) - 15, int(layout['height'] * .15)), + 'font': fonts.Medium, + 'max': (layout['width'] - layout['status'][0]) // 6 + } + return layout + + +def waveshare(config, layout): + if config['ui']['display']['color'] == 'black': + fonts.setup(10, 9, 10, 35) + + layout['width'] = 250 + layout['height'] = 122 + layout['face'] = (0, 40) + layout['name'] = (5, 20) + layout['channel'] = (0, 0) + layout['aps'] = (25, 0) + layout['uptime'] = (layout['width'] - 65, 0) + layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] + layout['line2'] = [0, layout['height'] - int(layout['height'] * .12), layout['width'], + layout['height'] - int(layout['height'] * .12)] + layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) + layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) + layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) + layout['mode'] = layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) + + + else: + fonts.setup(10, 8, 10, 25) + + layout['width'] = 212 + layout['height'] = 104 + layout['face'] = (0, int(layout['height'] / 4)) + layout['name'] = (5, int(layout['height'] * .15)) + layout['channel'] = (0, 0) + layout['aps'] = (25, 0) + layout['status'] = (int(layout['width'] / 2) - 15, int(layout['height'] * .15)) + layout['uptime'] = (layout['width'] - 65, 0) + layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] + layout['line2'] = [0, layout['height'] - int(layout['height'] * .12), layout['width'], + layout['height'] - int(layout['height'] * .12)] + layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) + layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) + layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) + layout['mode'] = layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) + + layout['status'] = { + 'pos': (125, 20), + 'font': fonts.Medium, + 'max': (layout['width'] - layout['status'][0]) // 6 + } + return layout + + +def for_display(config): + layout = { + 'width': 0, + 'height': 0, + 'face': (0, 0), + 'name': (0, 0), + 'channel': (0, 0), + 'aps': (0, 0), + 'uptime': (0, 0), + 'line1': (0, 0), + 'line2': (0, 0), + 'friend_face': (0, 0), + 'friend_name': (0, 0), + 'shakes': (0, 0), + 'mode': (0, 0), + # status is special :D + 'status': { + 'pos': (0, 0), + 'font': fonts.Medium, + 'max': 20 + } + } + + if config['ui']['display']['type'] in ('inky', 'inkyphat'): + layout = inkyphat(config, layout) + + elif config['ui']['display']['type'] in ('papirus', 'papi'): + layout = papirus(config, layout) + + if config['ui']['display']['type'] in ('oledhat'): + layout = oledhat(config, layout) + + elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1', + 'ws_2', 'ws2', 'waveshare_2', 'waveshare2'): + layout = waveshare(config, layout) + + return layout diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 1fd03b8..f0f17f2 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -10,6 +10,7 @@ from pwnagotchi.voice import Voice import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.faces as faces +import pwnagotchi.ui.layout as layout from pwnagotchi.ui.components import * from pwnagotchi.ui.state import State @@ -17,128 +18,6 @@ WHITE = 0xff BLACK = 0x00 ROOT = None - -def setup_display_specifics(config): - width = 0 - height = 0 - face_pos = (0, 0) - name_pos = (0, 0) - status_pos = (0, 0) - channel_pos = (0, 0) - aps_pos = (0, 0) - uptime_pos = (0, 0) - line1_pos = [(0, 0),(0, 0)] - line2_pos = (0, 0) - friend_face = (0, 0) - friend_name = (0, 0) - shakes_pos = (0, 0) - mode_pos = (0, 0) - status_font = fonts.Medium - status_max_length = None - - if config['ui']['display']['type'] in ('inky', 'inkyphat'): - fonts.setup(10, 8, 10, 28) - - width = 212 - height = 104 - face_pos = (0, 37) - name_pos = (5, 18) - channel_pos = (0, 0) - aps_pos = (25, 0) - status_pos = (102, 18) - uptime_pos = (width - 65, 0) - line1_pos = [0, int(height * .12), width, int(height * .12)] - line2_pos = [0, height - int(height * .12), width, height - int(height * .12)] - friend_face_pos = (0, (height * 0.88) - 15) - friend_name_pos = (40, (height * 0.88) - 13) - shakes_pos = (0, height - int(height * .12) + 1) - mode_pos = (width - 25, height - int(height * .12) + 1) - status_font = fonts.Small - status_max_length = 20 - - elif config['ui']['display']['type'] in ('papirus', 'papi'): - fonts.setup(10, 8, 10, 23) - - width = 200 - height = 96 - face_pos = (0, int(height / 4)) - name_pos = (5, int(height * .15)) - channel_pos = (0, 0) - aps_pos = (25, 0) - status_pos = (int(width / 2) - 15, int(height * .15)) - uptime_pos = (width - 65, 0) - line1_pos = [0, int(height * .12), width, int(height * .12)] - line2_pos = [0, height - int(height * .12), width, height - int(height * .12)] - friend_face_pos = (0, (height * 0.88) - 15) - friend_name_pos = (40, (height * 0.88) - 13) - shakes_pos = (0, height - int(height * .12) + 1) - mode_pos = mode_pos = (width - 25, height - int(height * .12) + 1) - status_font = fonts.Medium - status_max_length = (width - status_pos[0]) // 6 - - if config['ui']['display']['type'] in ('oledhat'): - fonts.setup(8, 8, 8, 8) - - width = 128 - height = 64 - face_pos = (0, 32) - name_pos = (0, 10) - channel_pos = (0, 0) - aps_pos = (25, 0) - status_pos = (30, 18) - uptime_pos = (width - 58, 0) - line1_pos = [0, 9, width, 9] - line2_pos = [0, 53, width, 53] - friend_face_pos = (0, (height * 0.88) - 15) - friend_name_pos = (40, (height * 0.88) - 13) - shakes_pos = (0, 53) - mode_pos = (width - 25, 10 ) - status_font = fonts.Small - status_max_length = 20 - - elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1', - 'ws_2', 'ws2', 'waveshare_2', 'waveshare2'): - if config['ui']['display']['color'] == 'black': - fonts.setup(10, 9, 10, 35) - - width = 250 - height = 122 - face_pos = (0, 40) - name_pos = (5, 20) - channel_pos = (0, 0) - aps_pos = (25, 0) - status_pos = (125, 20) - uptime_pos = (width - 65, 0) - line1_pos = [0, int(height * .12), width, int(height * .12)] - line2_pos = [0, height - int(height * .12), width, height - int(height * .12)] - friend_face_pos = (0, (height * 0.88) - 15) - friend_name_pos = (40, (height * 0.88) - 13) - shakes_pos = (0, height - int(height * .12) + 1) - mode_pos = mode_pos = (width - 25, height - int(height * .12) + 1) - status_font = fonts.Medium - else: - fonts.setup(10, 8, 10, 25) - - width = 212 - height = 104 - face_pos = (0, int(height / 4)) - name_pos = (5, int(height * .15)) - channel_pos = (0, 0) - aps_pos = (25, 0) - status_pos = (int(width / 2) - 15, int(height * .15)) - uptime_pos = (width - 65, 0) - line1_pos = [0, int(height * .12), width, int(height * .12)] - line2_pos = [0, height - int(height * .12), width, height - int(height * .12)] - friend_face_pos = (0, (height * 0.88) - 15) - friend_name_pos = (40, (height * 0.88) - 13) - shakes_pos = (0, height - int(height * .12) + 1) - mode_pos = mode_pos = (width - 25, height - int(height * .12) + 1) - status_font = fonts.Medium - status_max_length = (width - status_pos[0]) // 6 - - return width, height, face_pos, name_pos, channel_pos, aps_pos, status_pos, uptime_pos, line1_pos, line2_pos, friend_face_pos, friend_name_pos, shakes_pos, mode_pos, status_font, status_max_length - - class View(object): def __init__(self, config, state={}): global ROOT @@ -150,45 +29,43 @@ class View(object): self._lock = Lock() self._voice = Voice(lang=config['main']['lang']) - self._width, self._height, \ - face_pos, name_pos, channel_pos, aps_pos, status_pos, uptime_pos, line1_pos, line2_pos, friend_face_pos, friend_name_pos, shakes_pos, mode_pos, status_font, status_max_length = setup_display_specifics(config) + pos = layout.for_display(config) + self._width = pos['width'] + self._height = pos['height'] self._state = State(state={ - 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=channel_pos, label_font=fonts.Bold, + 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=pos['channel'], label_font=fonts.Bold, text_font=fonts.Medium), - 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=aps_pos, label_font=fonts.Bold, + 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=pos['aps'], label_font=fonts.Bold, text_font=fonts.Medium), - # 'epoch': LabeledValue(color=BLACK, label='E', value='0000', position=(145, 0), label_font=fonts.Bold, - # text_font=fonts.Medium), - - 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=uptime_pos, + 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=pos['uptime'], label_font=fonts.Bold, text_font=fonts.Medium), - 'line1': Line(line1_pos,color=BLACK), - 'line2': Line(line2_pos,color=BLACK), + 'line1': Line(pos['line1'],color=BLACK), + 'line2': Line(pos['line2'],color=BLACK), - 'face': Text(value=faces.SLEEP, position=face_pos, color=BLACK, font=fonts.Huge), + 'face': Text(value=faces.SLEEP, position=pos['face'], color=BLACK, font=fonts.Huge), - 'friend_face': Text(value=None, position=friend_face_pos, font=fonts.Bold, color=BLACK), - 'friend_name': Text(value=None, position=friend_name_pos, font=fonts.BoldSmall, + 'friend_face': Text(value=None, position=pos['friend_face'], font=fonts.Bold, color=BLACK), + 'friend_name': Text(value=None, position=pos['friend_name'], font=fonts.BoldSmall, color=BLACK), - 'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold), + 'name': Text(value='%s>' % 'pwnagotchi', position=pos['name'], color=BLACK, font=fonts.Bold), 'status': Text(value=self._voice.default(), - position=status_pos, + position=pos['status']['pos'], color=BLACK, - font=status_font, + font=pos['status']['font'], wrap=True, # the current maximum number of characters per line, assuming each character is 6 pixels wide - max_length=status_max_length), + max_length=pos['status']['max']), 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, - position=shakes_pos, label_font=fonts.Bold, + position=pos['shakes'], label_font=fonts.Bold, text_font=fonts.Medium), - 'mode': Text(value='AUTO', position=mode_pos, + 'mode': Text(value='AUTO', position=pos['mode'], font=fonts.Bold, color=BLACK), }) From 9625cf1122eed7eca9999d127228e30d12333787 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 19:26:23 +0200 Subject: [PATCH 213/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/layout.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/ui/layout.py b/pwnagotchi/ui/layout.py index eb1c266..d8c7e9b 100644 --- a/pwnagotchi/ui/layout.py +++ b/pwnagotchi/ui/layout.py @@ -46,7 +46,7 @@ def papirus(config, layout): layout['status'] = { 'pos': (int(layout['width'] / 2) - 15, int(layout['height'] * .15)), 'font': fonts.Medium, - 'max': (layout['width'] - layout['status'][0]) // 6 + 'max': (layout['width'] - layout['status']['pos'][0]) // 6 } return layout @@ -71,7 +71,7 @@ def oledhat(config, layout): layout['status'] = { 'pos': (int(layout['width'] / 2) - 15, int(layout['height'] * .15)), 'font': fonts.Medium, - 'max': (layout['width'] - layout['status'][0]) // 6 + 'max': (layout['width'] - layout['status']['pos'][0]) // 6 } return layout @@ -118,7 +118,7 @@ def waveshare(config, layout): layout['status'] = { 'pos': (125, 20), 'font': fonts.Medium, - 'max': (layout['width'] - layout['status'][0]) // 6 + 'max': (layout['width'] - layout['status']['pos'][0]) // 6 } return layout From 9f66d7ab966d4a27c613f45cf16ece0cc7b813fb Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 19:32:40 +0200 Subject: [PATCH 214/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/layout.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pwnagotchi/ui/layout.py b/pwnagotchi/ui/layout.py index d8c7e9b..c492441 100644 --- a/pwnagotchi/ui/layout.py +++ b/pwnagotchi/ui/layout.py @@ -44,9 +44,9 @@ def papirus(config, layout): layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) layout['mode'] = layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) layout['status'] = { - 'pos': (int(layout['width'] / 2) - 15, int(layout['height'] * .15)), + 'pos': (85, int(layout['height'] * .15)), 'font': fonts.Medium, - 'max': (layout['width'] - layout['status']['pos'][0]) // 6 + 'max': (layout['width'] - 100) // 6 } return layout @@ -69,9 +69,9 @@ def oledhat(config, layout): layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) layout['mode'] = layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) layout['status'] = { - 'pos': (int(layout['width'] / 2) - 15, int(layout['height'] * .15)), + 'pos': (85, int(layout['height'] * .15)), 'font': fonts.Medium, - 'max': (layout['width'] - layout['status']['pos'][0]) // 6 + 'max': (layout['width'] - 100) // 6 } return layout @@ -95,7 +95,6 @@ def waveshare(config, layout): layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) layout['mode'] = layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) - else: fonts.setup(10, 8, 10, 25) @@ -118,7 +117,7 @@ def waveshare(config, layout): layout['status'] = { 'pos': (125, 20), 'font': fonts.Medium, - 'max': (layout['width'] - layout['status']['pos'][0]) // 6 + 'max': (layout['width'] - 125) // 6 } return layout From 5b29f65042c62b098fc90f611fbe447a855d3192 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 19:35:49 +0200 Subject: [PATCH 215/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/layout.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/ui/layout.py b/pwnagotchi/ui/layout.py index c492441..2c6e58b 100644 --- a/pwnagotchi/ui/layout.py +++ b/pwnagotchi/ui/layout.py @@ -85,7 +85,7 @@ def waveshare(config, layout): layout['face'] = (0, 40) layout['name'] = (5, 20) layout['channel'] = (0, 0) - layout['aps'] = (25, 0) + layout['aps'] = (28, 0) layout['uptime'] = (layout['width'] - 65, 0) layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] layout['line2'] = [0, layout['height'] - int(layout['height'] * .12), layout['width'], @@ -103,7 +103,7 @@ def waveshare(config, layout): layout['face'] = (0, int(layout['height'] / 4)) layout['name'] = (5, int(layout['height'] * .15)) layout['channel'] = (0, 0) - layout['aps'] = (25, 0) + layout['aps'] = (28, 0) layout['status'] = (int(layout['width'] / 2) - 15, int(layout['height'] * .15)) layout['uptime'] = (layout['width'] - 65, 0) layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] From 277dbd5a166ab1d91e85a07b24af6e98af9478f5 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 19:48:33 +0200 Subject: [PATCH 216/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/view.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index f0f17f2..787963c 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -18,6 +18,7 @@ WHITE = 0xff BLACK = 0x00 ROOT = None + class View(object): def __init__(self, config, state={}): global ROOT @@ -28,44 +29,44 @@ class View(object): self._frozen = False self._lock = Lock() self._voice = Voice(lang=config['main']['lang']) - - pos = layout.for_display(config) - - self._width = pos['width'] - self._height = pos['height'] + self._layout = layout.for_display(config) + self._width = self._layout['width'] + self._height = self._layout['height'] self._state = State(state={ - 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=pos['channel'], label_font=fonts.Bold, + 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'], + label_font=fonts.Bold, text_font=fonts.Medium), - 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=pos['aps'], label_font=fonts.Bold, + 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'], + label_font=fonts.Bold, text_font=fonts.Medium), - 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=pos['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(pos['line1'],color=BLACK), - 'line2': Line(pos['line2'],color=BLACK), + 'line1': Line(self._layout['line1'], color=BLACK), + 'line2': Line(self._layout['line2'], color=BLACK), - 'face': Text(value=faces.SLEEP, position=pos['face'], color=BLACK, font=fonts.Huge), + 'face': Text(value=faces.SLEEP, position=self._layout['face'], color=BLACK, font=fonts.Huge), - 'friend_face': Text(value=None, position=pos['friend_face'], font=fonts.Bold, color=BLACK), - 'friend_name': Text(value=None, position=pos['friend_name'], font=fonts.BoldSmall, + 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK), + 'friend_name': Text(value=None, position=self._layout['friend_name'], font=fonts.BoldSmall, color=BLACK), - 'name': Text(value='%s>' % 'pwnagotchi', position=pos['name'], color=BLACK, font=fonts.Bold), + 'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold), 'status': Text(value=self._voice.default(), - position=pos['status']['pos'], + position=self._layout['status']['pos'], color=BLACK, - font=pos['status']['font'], + font=self._layout['status']['font'], wrap=True, # the current maximum number of characters per line, assuming each character is 6 pixels wide - max_length=pos['status']['max']), + max_length=self._layout['status']['max']), 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, - position=pos['shakes'], label_font=fonts.Bold, + position=self._layout['shakes'], label_font=fonts.Bold, text_font=fonts.Medium), - 'mode': Text(value='AUTO', position=pos['mode'], + 'mode': Text(value='AUTO', position=self._layout['mode'], font=fonts.Bold, color=BLACK), }) From 280ca222612707ace5843f254927b718908c5587 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Mon, 14 Oct 2019 19:57:04 +0200 Subject: [PATCH 217/346] Change header to cookie --- pwnagotchi/plugins/default/wpa-sec.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py index 8ab7351..2b685d0 100644 --- a/pwnagotchi/plugins/default/wpa-sec.py +++ b/pwnagotchi/plugins/default/wpa-sec.py @@ -1,5 +1,5 @@ __author__ = '33197631+dadav@users.noreply.github.com' -__version__ = '1.0.0' +__version__ = '2.0.1' __name__ = 'wpa-sec' __license__ = 'GPL3' __description__ = 'This plugin automatically uploades handshakes to https://wpa-sec.stanev.org' @@ -33,12 +33,12 @@ def _upload_to_wpasec(path, timeout=30): Uploads the file to wpa-sec.stanev.org """ with open(path, 'rb') as file_to_upload: - headers = {'key': OPTIONS['api_key']} + cookie = {'key': OPTIONS['api_key']} payload = {'file': file_to_upload} try: result = requests.post('https://wpa-sec.stanev.org/?submit', - headers=headers, + cookies=cookie, files=payload, timeout=timeout) if ' already submitted' in result.text: From baf20a9ac8355b4c6f7f1f21c5be5acfb2875614 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Mon, 14 Oct 2019 19:58:47 +0200 Subject: [PATCH 218/346] Fix url --- pwnagotchi/plugins/default/wpa-sec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py index 2b685d0..efa3faa 100644 --- a/pwnagotchi/plugins/default/wpa-sec.py +++ b/pwnagotchi/plugins/default/wpa-sec.py @@ -37,7 +37,7 @@ def _upload_to_wpasec(path, timeout=30): payload = {'file': file_to_upload} try: - result = requests.post('https://wpa-sec.stanev.org/?submit', + result = requests.post('https://wpa-sec.stanev.org', cookies=cookie, files=payload, timeout=timeout) From 2f0f0edab050d2f5d5a921ada46b9c5e4d5cdf69 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 14 Oct 2019 20:20:00 +0200 Subject: [PATCH 219/346] pwngrid 1.7.5 --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 0bf0a9a..633dc7b 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.4/pwngrid_linux_armv6l_1.7.4.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.5/pwngrid_linux_armv6l_1.7.5.zip" apt: hold: - firmware-atheros From c8953d465415ffd9584062f9378b82b242ca1bd8 Mon Sep 17 00:00:00 2001 From: Administrator <admin@example.com> Date: Mon, 14 Oct 2019 20:41:41 +0200 Subject: [PATCH 220/346] Fix few errors with OledHat --- pwnagotchi/ui/layout.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/pwnagotchi/ui/layout.py b/pwnagotchi/ui/layout.py index 2c6e58b..3cbecd9 100644 --- a/pwnagotchi/ui/layout.py +++ b/pwnagotchi/ui/layout.py @@ -42,7 +42,7 @@ def papirus(config, layout): layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) - layout['mode'] = layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) + layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) layout['status'] = { 'pos': (85, int(layout['height'] * .15)), 'font': fonts.Medium, @@ -52,26 +52,25 @@ def papirus(config, layout): def oledhat(config, layout): - fonts.setup(10, 8, 10, 23) + fonts.setup(8, 8, 8, 8) - layout['width'] = 200 - layout['height'] = 96 - layout['face'] = (0, int(layout['height'] / 4)) - layout['name'] = (5, int(layout['height'] * .15)) + layout['width'] = 128 + layout['height'] = 64 + layout['face'] = (0, 32) + layout['name'] = (0, 10) layout['channel'] = (0, 0) layout['aps'] = (25, 0) - layout['uptime'] = (layout['width'] - 65, 0) - layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] - layout['line2'] = [0, layout['height'] - int(layout['height'] * .12), layout['width'], - layout['height'] - int(layout['height'] * .12)] + layout['uptime'] = (70, 0) + layout['line1'] = [0, 9, 128, 9] + layout['line2'] = [0, 53, 128, 53] layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) - layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) - layout['mode'] = layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) + layout['shakes'] = (0, 53) + layout['mode'] = (103, 10) layout['status'] = { - 'pos': (85, int(layout['height'] * .15)), - 'font': fonts.Medium, - 'max': (layout['width'] - 100) // 6 + 'pos': (30, 18), + 'font': fonts.Small, + 'max': (20) // 6 } return layout @@ -93,7 +92,7 @@ def waveshare(config, layout): layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) - layout['mode'] = layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) + layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) else: fonts.setup(10, 8, 10, 25) @@ -112,7 +111,7 @@ def waveshare(config, layout): layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) - layout['mode'] = layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) + layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) layout['status'] = { 'pos': (125, 20), From 4be54cf3eeb62a7578e256d6000763d9c7e5ed9a Mon Sep 17 00:00:00 2001 From: Administrator <admin@example.com> Date: Mon, 14 Oct 2019 21:08:07 +0200 Subject: [PATCH 221/346] Fix layout issues --- pwnagotchi/ui/layout.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/ui/layout.py b/pwnagotchi/ui/layout.py index 3cbecd9..8ddb058 100644 --- a/pwnagotchi/ui/layout.py +++ b/pwnagotchi/ui/layout.py @@ -60,7 +60,7 @@ def oledhat(config, layout): layout['name'] = (0, 10) layout['channel'] = (0, 0) layout['aps'] = (25, 0) - layout['uptime'] = (70, 0) + layout['uptime'] = (65, 0) layout['line1'] = [0, 9, 128, 9] layout['line2'] = [0, 53, 128, 53] layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) @@ -70,7 +70,7 @@ def oledhat(config, layout): layout['status'] = { 'pos': (30, 18), 'font': fonts.Small, - 'max': (20) // 6 + 'max': (110) // 6 } return layout From f85d80d3fdc250202483bccd2893f333439100a6 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Mon, 14 Oct 2019 22:43:12 +0200 Subject: [PATCH 222/346] Use the coolest face for the cool emotion --- pwnagotchi/ui/faces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/faces.py b/pwnagotchi/ui/faces.py index 1e09be1..48f98d8 100644 --- a/pwnagotchi/ui/faces.py +++ b/pwnagotchi/ui/faces.py @@ -5,7 +5,7 @@ SLEEP2 = '(≖‿‿≖)' AWAKE = '(◕‿‿◕)' BORED = '(-__-)' INTENSE = '(°▃▃°)' -COOL = '(⊙☁◉┐)' +COOL = '(⌐■_■)' HAPPY = '(•‿‿•)' EXCITED = '(ᵔ◡◡ᵔ)' MOTIVATED = '(☼‿‿☼)' From ae5ca2a05e146a0cb1abf49fc9e8c2b0d06c557b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 15 Oct 2019 10:37:40 +0200 Subject: [PATCH 223/346] fix: refactored inkyphat layout --- pwnagotchi/ui/layout.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pwnagotchi/ui/layout.py b/pwnagotchi/ui/layout.py index 8ddb058..a7601e1 100644 --- a/pwnagotchi/ui/layout.py +++ b/pwnagotchi/ui/layout.py @@ -3,21 +3,19 @@ import pwnagotchi.ui.fonts as fonts def inkyphat(config, layout): fonts.setup(10, 8, 10, 28) - layout['width'] = 212 layout['height'] = 104 layout['face'] = (0, 37) layout['name'] = (5, 18) layout['channel'] = (0, 0) layout['aps'] = (25, 0) - layout['uptime'] = (layout['width'] - 65, 0) - layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] - layout['line2'] = [0, layout['height'] - int(layout['height'] * .12), layout['width'], - layout['height'] - int(layout['height'] * .12)] - layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) - layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) - layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) - layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) + layout['uptime'] = (147, 0) + layout['line1'] = [0, 12, 212, 12] + layout['line2'] = [0, 92, 212, 92] + layout['friend_face'] = (0, 76) + layout['friend_name'] = (40, 78) + layout['shakes'] = (0, 93) + layout['mode'] = (187, 93) layout['status'] = { 'pos': (102, 18), 'font': fonts.Small, From f3eb208c6aae253b8f686706a023ab3c73b7f748 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 15 Oct 2019 10:42:08 +0200 Subject: [PATCH 224/346] fix: refactored papirus layout --- pwnagotchi/ui/layout.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pwnagotchi/ui/layout.py b/pwnagotchi/ui/layout.py index a7601e1..69dca81 100644 --- a/pwnagotchi/ui/layout.py +++ b/pwnagotchi/ui/layout.py @@ -29,22 +29,21 @@ def papirus(config, layout): layout['width'] = 200 layout['height'] = 96 - layout['face'] = (0, int(layout['height'] / 4)) - layout['name'] = (5, int(layout['height'] * .15)) + layout['face'] = (0, 24) + layout['name'] = (5, 14) layout['channel'] = (0, 0) layout['aps'] = (25, 0) - layout['uptime'] = (layout['width'] - 65, 0) - layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] - layout['line2'] = [0, layout['height'] - int(layout['height'] * .12), layout['width'], - layout['height'] - int(layout['height'] * .12)] - layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) - layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) - layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) - layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) + layout['uptime'] = (135, 0) + layout['line1'] = [0, 11, 200, 11] + layout['line2'] = [0, 85, 200, 85] + layout['friend_face'] = (0, 69) + layout['friend_name'] = (40, 71) + layout['shakes'] = (0, 86) + layout['mode'] = (175, 86) layout['status'] = { - 'pos': (85, int(layout['height'] * .15)), + 'pos': (85, 14), 'font': fonts.Medium, - 'max': (layout['width'] - 100) // 6 + 'max': 16 } return layout From df33d20cb255f1e76bdadc8f030372eb445897c5 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 15 Oct 2019 10:45:03 +0200 Subject: [PATCH 225/346] fix: refactored oledhat layout --- pwnagotchi/ui/layout.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pwnagotchi/ui/layout.py b/pwnagotchi/ui/layout.py index 69dca81..7ca872f 100644 --- a/pwnagotchi/ui/layout.py +++ b/pwnagotchi/ui/layout.py @@ -26,7 +26,6 @@ def inkyphat(config, layout): def papirus(config, layout): fonts.setup(10, 8, 10, 23) - layout['width'] = 200 layout['height'] = 96 layout['face'] = (0, 24) @@ -50,7 +49,6 @@ def papirus(config, layout): def oledhat(config, layout): fonts.setup(8, 8, 8, 8) - layout['width'] = 128 layout['height'] = 64 layout['face'] = (0, 32) @@ -60,14 +58,14 @@ def oledhat(config, layout): layout['uptime'] = (65, 0) layout['line1'] = [0, 9, 128, 9] layout['line2'] = [0, 53, 128, 53] - layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) - layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) + layout['friend_face'] = (0, 41) + layout['friend_name'] = (40, 43) layout['shakes'] = (0, 53) layout['mode'] = (103, 10) layout['status'] = { 'pos': (30, 18), 'font': fonts.Small, - 'max': (110) // 6 + 'max': 18 } return layout From 13d68c7c2471ab58cfc7c24482c81ef585b6e9da Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 15 Oct 2019 11:50:09 +0200 Subject: [PATCH 226/346] misc: attempted refactoring of the display support in something less shitty --- pwnagotchi/plugins/default/screen_refresh.py | 2 +- pwnagotchi/ui/display.py | 161 ++---------------- pwnagotchi/ui/hw/__init__.py | 23 +++ pwnagotchi/ui/hw/base.py | 40 +++++ pwnagotchi/ui/hw/inky.py | 72 ++++++++ .../ui/{inkyphat => hw/libs}/__init__.py | 0 .../{papirus => hw/libs/inkyphat}/__init__.py | 0 .../ui/{ => hw/libs}/inkyphat/inkyfast.py | 0 .../ui/{ => hw/libs}/inkyphat/inkyphatfast.py | 0 .../libs/papirus}/__init__.py | 0 pwnagotchi/ui/{ => hw/libs}/papirus/epd.py | 2 +- pwnagotchi/ui/{ => hw/libs}/papirus/lm75b.py | 0 .../oledhat => hw/libs/waveshare}/__init__.py | 0 .../{ => hw/libs}/waveshare/oledhat/SH1106.py | 5 +- .../libs/waveshare/oledhat}/__init__.py | 0 .../{ => hw/libs}/waveshare/oledhat/config.py | 0 .../ui/{ => hw/libs}/waveshare/oledhat/epd.py | 5 - .../v2 => hw/libs/waveshare/v1}/__init__.py | 0 .../ui/{ => hw/libs}/waveshare/v1/epd2in13.py | 1 - .../{ => hw/libs}/waveshare/v1/epd2in13bc.py | 2 - .../{ => hw/libs}/waveshare/v1/epdconfig.py | 0 .../ui/hw/libs/waveshare/v2/__init__.py | 0 .../{ => hw/libs}/waveshare/v2/waveshare.py | 0 pwnagotchi/ui/hw/oledhat.py | 45 +++++ pwnagotchi/ui/hw/papirus.py | 47 +++++ pwnagotchi/ui/hw/waveshare1.py | 86 ++++++++++ pwnagotchi/ui/hw/waveshare2.py | 69 ++++++++ pwnagotchi/ui/layout.py | 155 ----------------- pwnagotchi/ui/view.py | 11 +- pwnagotchi/utils.py | 20 +++ 30 files changed, 429 insertions(+), 317 deletions(-) create mode 100644 pwnagotchi/ui/hw/__init__.py create mode 100644 pwnagotchi/ui/hw/base.py create mode 100644 pwnagotchi/ui/hw/inky.py rename pwnagotchi/ui/{inkyphat => hw/libs}/__init__.py (100%) rename pwnagotchi/ui/{papirus => hw/libs/inkyphat}/__init__.py (100%) rename pwnagotchi/ui/{ => hw/libs}/inkyphat/inkyfast.py (100%) rename pwnagotchi/ui/{ => hw/libs}/inkyphat/inkyphatfast.py (100%) rename pwnagotchi/ui/{waveshare => hw/libs/papirus}/__init__.py (100%) rename pwnagotchi/ui/{ => hw/libs}/papirus/epd.py (99%) rename pwnagotchi/ui/{ => hw/libs}/papirus/lm75b.py (100%) rename pwnagotchi/ui/{waveshare/oledhat => hw/libs/waveshare}/__init__.py (100%) rename pwnagotchi/ui/{ => hw/libs}/waveshare/oledhat/SH1106.py (96%) rename pwnagotchi/ui/{waveshare/v1 => hw/libs/waveshare/oledhat}/__init__.py (100%) rename pwnagotchi/ui/{ => hw/libs}/waveshare/oledhat/config.py (100%) rename pwnagotchi/ui/{ => hw/libs}/waveshare/oledhat/epd.py (88%) rename pwnagotchi/ui/{waveshare/v2 => hw/libs/waveshare/v1}/__init__.py (100%) rename pwnagotchi/ui/{ => hw/libs}/waveshare/v1/epd2in13.py (99%) rename pwnagotchi/ui/{ => hw/libs}/waveshare/v1/epd2in13bc.py (99%) rename pwnagotchi/ui/{ => hw/libs}/waveshare/v1/epdconfig.py (100%) create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v2/__init__.py rename pwnagotchi/ui/{ => hw/libs}/waveshare/v2/waveshare.py (100%) create mode 100644 pwnagotchi/ui/hw/oledhat.py create mode 100644 pwnagotchi/ui/hw/papirus.py create mode 100644 pwnagotchi/ui/hw/waveshare1.py create mode 100644 pwnagotchi/ui/hw/waveshare2.py delete mode 100644 pwnagotchi/ui/layout.py diff --git a/pwnagotchi/plugins/default/screen_refresh.py b/pwnagotchi/plugins/default/screen_refresh.py index 72c2a1c..94173ff 100644 --- a/pwnagotchi/plugins/default/screen_refresh.py +++ b/pwnagotchi/plugins/default/screen_refresh.py @@ -18,7 +18,7 @@ def on_ui_update(ui): global update_count update_count += 1 if update_count == OPTIONS['refresh_interval']: - ui._init_display() + ui.init_display() ui.set('status', "Screen cleaned") logging.info("Screen refreshing") update_count = 0 diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 22f7f26..597691f 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -1,13 +1,12 @@ import _thread from threading import Lock -from PIL import Image import shutil import logging -import os import pwnagotchi, pwnagotchi.plugins as plugins -from pwnagotchi.ui.view import WHITE, View +import pwnagotchi.ui.hw as hw +from pwnagotchi.ui.view import View from http.server import BaseHTTPRequestHandler, HTTPServer @@ -88,24 +87,15 @@ class VideoHandler(BaseHTTPRequestHandler): class Display(View): def __init__(self, config, state={}): - super(Display, self).__init__(config, state) + super(Display, self).__init__(config, hw.display_for(config), state) self._enabled = config['ui']['display']['enabled'] self._rotation = config['ui']['display']['rotation'] self._video_enabled = config['ui']['display']['video']['enabled'] self._video_port = config['ui']['display']['video']['port'] self._video_address = config['ui']['display']['video']['address'] - self._display_type = config['ui']['display']['type'] - self._display_color = config['ui']['display']['color'] - - self._render_cb = None - self._display = None self._httpd = None - if self._enabled: - self._init_display() - else: - self.on_render(self._on_view_rendered) - logging.warning("display module is disabled") + self.init_display() if self._video_enabled: _thread.start_new_thread(self._http_serve, ()) @@ -119,149 +109,33 @@ class Display(View): logging.info("could not get ip of usb0, video server not starting") def is_inky(self): - return self._display_type in ('inkyphat', 'inky') + return self._implementation.name == 'inky' def is_papirus(self): - return self._display_type in ('papirus', 'papi') + return self._implementation.name == 'papirus' def is_waveshare_v1(self): - return self._display_type in ('waveshare_1', 'ws_1', 'waveshare1', 'ws1') + return self._implementation.name == 'waveshare_1' def is_waveshare_v2(self): - return self._display_type in ('waveshare_2', 'ws_2', 'waveshare2', 'ws2') + return self._implementation.name == 'waveshare_2' def is_oledhat(self): - return self._display_type in ('oledhat') + return self._implementation.name == 'oledhat' def is_waveshare_any(self): return self.is_waveshare_v1() or self.is_waveshare_v2() - def _init_display(self): - if self.is_inky(): - logging.info("initializing inky display") - from pwnagotchi.ui.inkyphat.inkyphatfast import InkyPHATFast - self._display = InkyPHATFast(self._display_color) - self._display.set_border(InkyPHATFast.BLACK) - self._render_cb = self._inky_render - - elif self.is_papirus(): - logging.info("initializing papirus display") - from pwnagotchi.ui.papirus.epd import EPD - os.environ['EPD_SIZE'] = '2.0' - self._display = EPD() - self._display.clear() - self._render_cb = self._papirus_render - - elif self.is_waveshare_v1(): - if self._display_color == 'black': - logging.info("initializing waveshare v1 display in monochromatic mode") - from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD - self._display = EPD() - self._display.init(self._display.lut_full_update) - self._display.Clear(0xFF) - self._display.init(self._display.lut_partial_update) - self._render_cb = self._waveshare_render - - else: - logging.info("initializing waveshare v1 display 3-color mode") - from pwnagotchi.ui.waveshare.v1.epd2in13bc import EPD - self._display = EPD() - self._display.init() - self._display.Clear() - self._render_cb = self._waveshare_bc_render - - elif self.is_waveshare_v2(): - logging.info("initializing waveshare v2 display") - from pwnagotchi.ui.waveshare.v2.waveshare import EPD - self._display = EPD() - self._display.init(self._display.FULL_UPDATE) - self._display.Clear(WHITE) - self._display.init(self._display.PART_UPDATE) - self._render_cb = self._waveshare_render - - elif self.is_oledhat(): - logging.info("initializing oledhat display") - from pwnagotchi.ui.waveshare.oledhat.epd import EPD - self._display = EPD() - self._display.init() - self._display.Clear() - self._render_cb = self._oledhat_render - + def init_display(self): + if self._enabled: + self._implementation.initialize() + plugins.on('display_setup', self._implementation) else: - logging.critical("unknown display type %s" % self._display_type) - - plugins.on('display_setup', self._display) - + logging.warning("display module is disabled") self.on_render(self._on_view_rendered) def clear(self): - if self._display is None: - logging.error("no display object created") - elif self.is_inky(): - self._display.Clear() - elif self.is_papirus(): - self._display.clear() - elif self.is_waveshare_any(): - self._display.Clear(WHITE) - elif self.is_oledhat(): - self._display.clear() - else: - logging.critical("unknown display type %s" % self._display_type) - - def _inky_render(self): - if self._display_color != 'mono': - display_colors = 3 - else: - display_colors = 2 - - img_buffer = self._canvas.convert('RGB').convert('P', palette=1, colors=display_colors) - if self._display_color == 'red': - img_buffer.putpalette([ - 255, 255, 255, # index 0 is white - 0, 0, 0, # index 1 is black - 255, 0, 0 # index 2 is red - ]) - elif self._display_color == 'yellow': - img_buffer.putpalette([ - 255, 255, 255, # index 0 is white - 0, 0, 0, # index 1 is black - 255, 255, 0 # index 2 is yellow - ]) - else: - img_buffer.putpalette([ - 255, 255, 255, # index 0 is white - 0, 0, 0 # index 1 is black - ]) - - self._display.set_image(img_buffer) - try: - self._display.show() - except: - logging.exception("error while rendering on inky") - - def _papirus_render(self): - self._display.display(self._canvas) - self._display.partial_update() - - def _waveshare_render(self): - buf = self._display.getbuffer(self._canvas) - if self.is_waveshare_v1(): - self._display.display(buf) - elif self.is_waveshare_v2(): - self._display.displayPartial(buf) - - def _oledhat_render(self): - self._display.display(self._canvas) - - def _waveshare_bc_render(self): - buf_black = self._display.getbuffer(self._canvas) - # emptyImage = Image.new('1', (self._display.height, self._display.width), 255) - # buf_color = self._display.getbuffer(emptyImage) - # self._display.display(buf_black,buf_color) - - # Custom display function that only handles black - # Was included in epd2in13bc.py - self._display.displayBlack(buf_black) + self._implementation.clear() def image(self): img = None @@ -271,8 +145,7 @@ class Display(View): def _on_view_rendered(self, img): VideoHandler.render(img) - if self._enabled: self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation)) - if self._render_cb is not None: - self._render_cb() + if self._implementation is not None: + self._implementation.render(self._canvas) diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py new file mode 100644 index 0000000..94dc671 --- /dev/null +++ b/pwnagotchi/ui/hw/__init__.py @@ -0,0 +1,23 @@ +from pwnagotchi.ui.hw.inky import Inky +from pwnagotchi.ui.hw.papirus import Papirus +from pwnagotchi.ui.hw.oledhat import OledHat +from pwnagotchi.ui.hw.waveshare1 import WaveshareV1 +from pwnagotchi.ui.hw.waveshare2 import WaveshareV2 + + +def display_for(config): + # config has been normalized already in utils.load_config + if config['ui']['display']['type'] == 'inky': + return Inky(config) + + elif config['ui']['display']['type'] == 'papirus': + return Papirus(config) + + if config['ui']['display']['type'] == 'oledhat': + return OledHat(config) + + elif config['ui']['display']['type'] == 'waveshare_1': + return WaveshareV1(config) + + elif config['ui']['display']['type'] == 'waveshare_2': + return WaveshareV2(config) diff --git a/pwnagotchi/ui/hw/base.py b/pwnagotchi/ui/hw/base.py new file mode 100644 index 0000000..e3e0db0 --- /dev/null +++ b/pwnagotchi/ui/hw/base.py @@ -0,0 +1,40 @@ +import pwnagotchi.ui.fonts as fonts + + +class DisplayImpl(object): + def __init__(self, config, name): + self.name = name + self.config = config['ui']['display'] + self._layout = { + 'width': 0, + 'height': 0, + 'face': (0, 0), + 'name': (0, 0), + 'channel': (0, 0), + 'aps': (0, 0), + 'uptime': (0, 0), + 'line1': (0, 0), + 'line2': (0, 0), + 'friend_face': (0, 0), + 'friend_name': (0, 0), + 'shakes': (0, 0), + 'mode': (0, 0), + # status is special :D + 'status': { + 'pos': (0, 0), + 'font': fonts.Medium, + 'max': 20 + } + } + + def layout(self): + raise NotImplementedError + + def initialize(self): + raise NotImplementedError + + def render(self, canvas): + raise NotImplementedError + + def clear(self): + raise NotImplementedError diff --git a/pwnagotchi/ui/hw/inky.py b/pwnagotchi/ui/hw/inky.py new file mode 100644 index 0000000..9157a4d --- /dev/null +++ b/pwnagotchi/ui/hw/inky.py @@ -0,0 +1,72 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class Inky(DisplayImpl): + def __init__(self, config): + super(Inky, self).__init__(config, 'inky') + self._display = None + + def layout(self): + fonts.setup(10, 8, 10, 28) + self._layout['width'] = 212 + self._layout['height'] = 104 + self._layout['face'] = (0, 37) + self._layout['name'] = (5, 18) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (25, 0) + self._layout['uptime'] = (147, 0) + self._layout['line1'] = [0, 12, 212, 12] + self._layout['line2'] = [0, 92, 212, 92] + self._layout['friend_face'] = (0, 76) + self._layout['friend_name'] = (40, 78) + self._layout['shakes'] = (0, 93) + self._layout['mode'] = (187, 93) + self._layout['status'] = { + 'pos': (102, 18), + 'font': fonts.Small, + 'max': 20 + } + return self._layout + + def initialize(self): + logging.info("initializing inky display") + from pwnagotchi.ui.hw.libs.inkyphat.inkyphatfast import InkyPHATFast + self._display = InkyPHATFast(self.config['color']) + self._display.set_border(InkyPHATFast.BLACK) + + def render(self, canvas): + if self.config['color'] != 'mono': + display_colors = 3 + else: + display_colors = 2 + + img_buffer = canvas.convert('RGB').convert('P', palette=1, colors=display_colors) + if self.config['color'] == 'red': + img_buffer.putpalette([ + 255, 255, 255, # index 0 is white + 0, 0, 0, # index 1 is black + 255, 0, 0 # index 2 is red + ]) + elif self.config['color'] == 'yellow': + img_buffer.putpalette([ + 255, 255, 255, # index 0 is white + 0, 0, 0, # index 1 is black + 255, 255, 0 # index 2 is yellow + ]) + else: + img_buffer.putpalette([ + 255, 255, 255, # index 0 is white + 0, 0, 0 # index 1 is black + ]) + + self._display.set_image(img_buffer) + try: + self._display.show() + except: + logging.exception("error while rendering on inky") + + def clear(self): + self._display.Clear() diff --git a/pwnagotchi/ui/inkyphat/__init__.py b/pwnagotchi/ui/hw/libs/__init__.py similarity index 100% rename from pwnagotchi/ui/inkyphat/__init__.py rename to pwnagotchi/ui/hw/libs/__init__.py diff --git a/pwnagotchi/ui/papirus/__init__.py b/pwnagotchi/ui/hw/libs/inkyphat/__init__.py similarity index 100% rename from pwnagotchi/ui/papirus/__init__.py rename to pwnagotchi/ui/hw/libs/inkyphat/__init__.py diff --git a/pwnagotchi/ui/inkyphat/inkyfast.py b/pwnagotchi/ui/hw/libs/inkyphat/inkyfast.py similarity index 100% rename from pwnagotchi/ui/inkyphat/inkyfast.py rename to pwnagotchi/ui/hw/libs/inkyphat/inkyfast.py diff --git a/pwnagotchi/ui/inkyphat/inkyphatfast.py b/pwnagotchi/ui/hw/libs/inkyphat/inkyphatfast.py similarity index 100% rename from pwnagotchi/ui/inkyphat/inkyphatfast.py rename to pwnagotchi/ui/hw/libs/inkyphat/inkyphatfast.py diff --git a/pwnagotchi/ui/waveshare/__init__.py b/pwnagotchi/ui/hw/libs/papirus/__init__.py similarity index 100% rename from pwnagotchi/ui/waveshare/__init__.py rename to pwnagotchi/ui/hw/libs/papirus/__init__.py diff --git a/pwnagotchi/ui/papirus/epd.py b/pwnagotchi/ui/hw/libs/papirus/epd.py similarity index 99% rename from pwnagotchi/ui/papirus/epd.py rename to pwnagotchi/ui/hw/libs/papirus/epd.py index 923993b..bf8d572 100644 --- a/pwnagotchi/ui/papirus/epd.py +++ b/pwnagotchi/ui/hw/libs/papirus/epd.py @@ -15,7 +15,7 @@ from PIL import Image from PIL import ImageOps -from pwnagotchi.ui.papirus.lm75b import LM75B +from pwnagotchi.ui.hw.libs.papirus import LM75B import re import os import sys diff --git a/pwnagotchi/ui/papirus/lm75b.py b/pwnagotchi/ui/hw/libs/papirus/lm75b.py similarity index 100% rename from pwnagotchi/ui/papirus/lm75b.py rename to pwnagotchi/ui/hw/libs/papirus/lm75b.py diff --git a/pwnagotchi/ui/waveshare/oledhat/__init__.py b/pwnagotchi/ui/hw/libs/waveshare/__init__.py similarity index 100% rename from pwnagotchi/ui/waveshare/oledhat/__init__.py rename to pwnagotchi/ui/hw/libs/waveshare/__init__.py diff --git a/pwnagotchi/ui/waveshare/oledhat/SH1106.py b/pwnagotchi/ui/hw/libs/waveshare/oledhat/SH1106.py similarity index 96% rename from pwnagotchi/ui/waveshare/oledhat/SH1106.py rename to pwnagotchi/ui/hw/libs/waveshare/oledhat/SH1106.py index 68d4f18..a89716d 100644 --- a/pwnagotchi/ui/waveshare/oledhat/SH1106.py +++ b/pwnagotchi/ui/hw/libs/waveshare/oledhat/SH1106.py @@ -1,7 +1,6 @@ from . import config import RPi.GPIO as GPIO import time -import numpy as np Device_SPI = config.Device_SPI Device_I2C = config.Device_I2C @@ -121,9 +120,9 @@ class SH1106(object): GPIO.output(self._dc, GPIO.HIGH); for i in range(0,self.width):#for(int i=0;i<self.width; i++) if(self.Device == Device_SPI): - config.spi_writebyte([~pBuf[i+self.width*page]]); + config.spi_writebyte([~pBuf[i + self.width * page]]); else : - config.i2c_writebyte(0x40, ~pBuf[i+self.width*page]) + config.i2c_writebyte(0x40, ~pBuf[i + self.width * page]) diff --git a/pwnagotchi/ui/waveshare/v1/__init__.py b/pwnagotchi/ui/hw/libs/waveshare/oledhat/__init__.py similarity index 100% rename from pwnagotchi/ui/waveshare/v1/__init__.py rename to pwnagotchi/ui/hw/libs/waveshare/oledhat/__init__.py diff --git a/pwnagotchi/ui/waveshare/oledhat/config.py b/pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py similarity index 100% rename from pwnagotchi/ui/waveshare/oledhat/config.py rename to pwnagotchi/ui/hw/libs/waveshare/oledhat/config.py diff --git a/pwnagotchi/ui/waveshare/oledhat/epd.py b/pwnagotchi/ui/hw/libs/waveshare/oledhat/epd.py similarity index 88% rename from pwnagotchi/ui/waveshare/oledhat/epd.py rename to pwnagotchi/ui/hw/libs/waveshare/oledhat/epd.py index e6de76a..7c1be01 100644 --- a/pwnagotchi/ui/waveshare/oledhat/epd.py +++ b/pwnagotchi/ui/hw/libs/waveshare/oledhat/epd.py @@ -1,10 +1,5 @@ from . import SH1106 -import time from . import config -import traceback - -from PIL import Image,ImageDraw,ImageFont - # Display resolution EPD_WIDTH = 64 diff --git a/pwnagotchi/ui/waveshare/v2/__init__.py b/pwnagotchi/ui/hw/libs/waveshare/v1/__init__.py similarity index 100% rename from pwnagotchi/ui/waveshare/v2/__init__.py rename to pwnagotchi/ui/hw/libs/waveshare/v1/__init__.py diff --git a/pwnagotchi/ui/waveshare/v1/epd2in13.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py similarity index 99% rename from pwnagotchi/ui/waveshare/v1/epd2in13.py rename to pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py index b6de9da..93685e1 100644 --- a/pwnagotchi/ui/waveshare/v1/epd2in13.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13.py @@ -30,7 +30,6 @@ import logging from . import epdconfig -import numpy as np # Display resolution EPD_WIDTH = 122 diff --git a/pwnagotchi/ui/waveshare/v1/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py similarity index 99% rename from pwnagotchi/ui/waveshare/v1/epd2in13bc.py rename to pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py index ec92465..a0bff83 100644 --- a/pwnagotchi/ui/waveshare/v1/epd2in13bc.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py @@ -27,9 +27,7 @@ # THE SOFTWARE. # -import logging from . import epdconfig -from PIL import Image import RPi.GPIO as GPIO # import numpy as np diff --git a/pwnagotchi/ui/waveshare/v1/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py similarity index 100% rename from pwnagotchi/ui/waveshare/v1/epdconfig.py rename to pwnagotchi/ui/hw/libs/waveshare/v1/epdconfig.py diff --git a/pwnagotchi/ui/hw/libs/waveshare/v2/__init__.py b/pwnagotchi/ui/hw/libs/waveshare/v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pwnagotchi/ui/waveshare/v2/waveshare.py b/pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py similarity index 100% rename from pwnagotchi/ui/waveshare/v2/waveshare.py rename to pwnagotchi/ui/hw/libs/waveshare/v2/waveshare.py diff --git a/pwnagotchi/ui/hw/oledhat.py b/pwnagotchi/ui/hw/oledhat.py new file mode 100644 index 0000000..3673630 --- /dev/null +++ b/pwnagotchi/ui/hw/oledhat.py @@ -0,0 +1,45 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class OledHat(DisplayImpl): + def __init__(self, config): + super(OledHat, self).__init__(config, 'oledhat') + self._display = None + + def layout(self): + fonts.setup(8, 8, 8, 8) + self._layout['width'] = 128 + self._layout['height'] = 64 + self._layout['face'] = (0, 32) + self._layout['name'] = (0, 10) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (25, 0) + self._layout['uptime'] = (65, 0) + self._layout['line1'] = [0, 9, 128, 9] + self._layout['line2'] = [0, 53, 128, 53] + self._layout['friend_face'] = (0, 41) + self._layout['friend_name'] = (40, 43) + self._layout['shakes'] = (0, 53) + self._layout['mode'] = (103, 10) + self._layout['status'] = { + 'pos': (30, 18), + 'font': fonts.Small, + 'max': 18 + } + return self._layout + + def initialize(self): + logging.info("initializing oledhat display") + from pwnagotchi.ui.hw.libs.waveshare.oledhat.epd import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + + def render(self, canvas): + self._display.display(canvas) + + def clear(self): + self._display.clear() diff --git a/pwnagotchi/ui/hw/papirus.py b/pwnagotchi/ui/hw/papirus.py new file mode 100644 index 0000000..8da13be --- /dev/null +++ b/pwnagotchi/ui/hw/papirus.py @@ -0,0 +1,47 @@ +import logging +import os + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class Papirus(DisplayImpl): + def __init__(self, config): + super(Papirus, self).__init__(config, 'papirus') + self._display = None + + def layout(self): + fonts.setup(10, 8, 10, 23) + self._layout['width'] = 200 + self._layout['height'] = 96 + self._layout['face'] = (0, 24) + self._layout['name'] = (5, 14) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (25, 0) + self._layout['uptime'] = (135, 0) + self._layout['line1'] = [0, 11, 200, 11] + self._layout['line2'] = [0, 85, 200, 85] + self._layout['friend_face'] = (0, 69) + self._layout['friend_name'] = (40, 71) + self._layout['shakes'] = (0, 86) + self._layout['mode'] = (175, 86) + self._layout['status'] = { + 'pos': (85, 14), + 'font': fonts.Medium, + 'max': 16 + } + return self._layout + + def initialize(self): + logging.info("initializing papirus display") + from pwnagotchi.ui.hw.libs.papirus.epd import EPD + os.environ['EPD_SIZE'] = '2.0' + self._display = EPD() + self._display.clear() + + def render(self, canvas): + self._display.display(canvas) + self._display.partial_update() + + def clear(self): + self._display.clear() diff --git a/pwnagotchi/ui/hw/waveshare1.py b/pwnagotchi/ui/hw/waveshare1.py new file mode 100644 index 0000000..fba8f17 --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare1.py @@ -0,0 +1,86 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class WaveshareV1(DisplayImpl): + def __init__(self, config): + super(WaveshareV1, self).__init__(config, 'waveshare_1') + self._display = None + + def layout(self): + if self.config['color'] == 'black': + fonts.setup(10, 9, 10, 35) + self._layout['width'] = 250 + self._layout['height'] = 122 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (185, 0) + self._layout['line1'] = [0, 14, 250, 14] + self._layout['line2'] = [0, 108, 250, 108] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 109) + self._layout['mode'] = (225, 109) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.Medium, + 'max': 20 + } + else: + fonts.setup(10, 8, 10, 25) + self._layout['width'] = 212 + self._layout['height'] = 104 + self._layout['face'] = (0, 26) + self._layout['name'] = (5, 15) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['status'] = (91, 15) + self._layout['uptime'] = (147, 0) + self._layout['line1'] = [0, 12, 212, 12] + self._layout['line2'] = [0, 92, 212, 92] + self._layout['friend_face'] = (0, 76) + self._layout['friend_name'] = (40, 78) + self._layout['shakes'] = (0, 93) + self._layout['mode'] = (187, 93) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.Medium, + 'max': 14 + } + return self._layout + + def initialize(self): + if self.config['color'] == 'black': + logging.info("initializing waveshare v1 display in monochromatic mode") + from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13 import EPD + self._display = EPD() + self._display.init(self._display.lut_full_update) + self._display.Clear(0xFF) + self._display.init(self._display.lut_partial_update) + + else: + logging.info("initializing waveshare v1 display 3-color mode") + from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13bc import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + + def render(self, canvas): + if self.config['color'] == 'black': + buf = self._display.getbuffer(canvas) + self._display.display(buf) + else: + buf_black = self._display.getbuffer(canvas) + # emptyImage = Image.new('1', (self._display.height, self._display.width), 255) + # buf_color = self._display.getbuffer(emptyImage) + # self._display.display(buf_black,buf_color) + # Custom display function that only handles black + # Was included in epd2in13bc.py + self._display.displayBlack(buf_black) + + def clear(self): + self._display.Clear(0xff) diff --git a/pwnagotchi/ui/hw/waveshare2.py b/pwnagotchi/ui/hw/waveshare2.py new file mode 100644 index 0000000..5065f65 --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare2.py @@ -0,0 +1,69 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class WaveshareV2(DisplayImpl): + def __init__(self, config): + super(WaveshareV2, self).__init__(config, 'waveshare_2') + self._display = None + + def layout(self): + if self.config['color'] == 'black': + fonts.setup(10, 9, 10, 35) + self._layout['width'] = 250 + self._layout['height'] = 122 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (185, 0) + self._layout['line1'] = [0, 14, 250, 14] + self._layout['line2'] = [0, 108, 250, 108] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 109) + self._layout['mode'] = (225, 109) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.Medium, + 'max': 20 + } + else: + fonts.setup(10, 8, 10, 25) + self._layout['width'] = 212 + self._layout['height'] = 104 + self._layout['face'] = (0, 26) + self._layout['name'] = (5, 15) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['status'] = (91, 15) + self._layout['uptime'] = (147, 0) + self._layout['line1'] = [0, 12, 212, 12] + self._layout['line2'] = [0, 92, 212, 92] + self._layout['friend_face'] = (0, 76) + self._layout['friend_name'] = (40, 78) + self._layout['shakes'] = (0, 93) + self._layout['mode'] = (187, 93) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.Medium, + 'max': 14 + } + return self._layout + + def initialize(self): + logging.info("initializing waveshare v2 display") + from pwnagotchi.ui.hw.libs.waveshare.v2.waveshare import EPD + self._display = EPD() + self._display.init(self._display.FULL_UPDATE) + self._display.Clear(0xff) + self._display.init(self._display.PART_UPDATE) + + def render(self, canvas): + buf = self._display.getbuffer(canvas) + self._display.displayPartial(buf) + + def clear(self): + self._display.Clear(0xff) diff --git a/pwnagotchi/ui/layout.py b/pwnagotchi/ui/layout.py deleted file mode 100644 index 7ca872f..0000000 --- a/pwnagotchi/ui/layout.py +++ /dev/null @@ -1,155 +0,0 @@ -import pwnagotchi.ui.fonts as fonts - - -def inkyphat(config, layout): - fonts.setup(10, 8, 10, 28) - layout['width'] = 212 - layout['height'] = 104 - layout['face'] = (0, 37) - layout['name'] = (5, 18) - layout['channel'] = (0, 0) - layout['aps'] = (25, 0) - layout['uptime'] = (147, 0) - layout['line1'] = [0, 12, 212, 12] - layout['line2'] = [0, 92, 212, 92] - layout['friend_face'] = (0, 76) - layout['friend_name'] = (40, 78) - layout['shakes'] = (0, 93) - layout['mode'] = (187, 93) - layout['status'] = { - 'pos': (102, 18), - 'font': fonts.Small, - 'max': 20 - } - return layout - - -def papirus(config, layout): - fonts.setup(10, 8, 10, 23) - layout['width'] = 200 - layout['height'] = 96 - layout['face'] = (0, 24) - layout['name'] = (5, 14) - layout['channel'] = (0, 0) - layout['aps'] = (25, 0) - layout['uptime'] = (135, 0) - layout['line1'] = [0, 11, 200, 11] - layout['line2'] = [0, 85, 200, 85] - layout['friend_face'] = (0, 69) - layout['friend_name'] = (40, 71) - layout['shakes'] = (0, 86) - layout['mode'] = (175, 86) - layout['status'] = { - 'pos': (85, 14), - 'font': fonts.Medium, - 'max': 16 - } - return layout - - -def oledhat(config, layout): - fonts.setup(8, 8, 8, 8) - layout['width'] = 128 - layout['height'] = 64 - layout['face'] = (0, 32) - layout['name'] = (0, 10) - layout['channel'] = (0, 0) - layout['aps'] = (25, 0) - layout['uptime'] = (65, 0) - layout['line1'] = [0, 9, 128, 9] - layout['line2'] = [0, 53, 128, 53] - layout['friend_face'] = (0, 41) - layout['friend_name'] = (40, 43) - layout['shakes'] = (0, 53) - layout['mode'] = (103, 10) - layout['status'] = { - 'pos': (30, 18), - 'font': fonts.Small, - 'max': 18 - } - return layout - - -def waveshare(config, layout): - if config['ui']['display']['color'] == 'black': - fonts.setup(10, 9, 10, 35) - - layout['width'] = 250 - layout['height'] = 122 - layout['face'] = (0, 40) - layout['name'] = (5, 20) - layout['channel'] = (0, 0) - layout['aps'] = (28, 0) - layout['uptime'] = (layout['width'] - 65, 0) - layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] - layout['line2'] = [0, layout['height'] - int(layout['height'] * .12), layout['width'], - layout['height'] - int(layout['height'] * .12)] - layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) - layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) - layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) - layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) - - else: - fonts.setup(10, 8, 10, 25) - - layout['width'] = 212 - layout['height'] = 104 - layout['face'] = (0, int(layout['height'] / 4)) - layout['name'] = (5, int(layout['height'] * .15)) - layout['channel'] = (0, 0) - layout['aps'] = (28, 0) - layout['status'] = (int(layout['width'] / 2) - 15, int(layout['height'] * .15)) - layout['uptime'] = (layout['width'] - 65, 0) - layout['line1'] = [0, int(layout['height'] * .12), layout['width'], int(layout['height'] * .12)] - layout['line2'] = [0, layout['height'] - int(layout['height'] * .12), layout['width'], - layout['height'] - int(layout['height'] * .12)] - layout['friend_face'] = (0, (layout['height'] * 0.88) - 15) - layout['friend_name'] = (40, (layout['height'] * 0.88) - 13) - layout['shakes'] = (0, layout['height'] - int(layout['height'] * .12) + 1) - layout['mode'] = (layout['width'] - 25, layout['height'] - int(layout['height'] * .12) + 1) - - layout['status'] = { - 'pos': (125, 20), - 'font': fonts.Medium, - 'max': (layout['width'] - 125) // 6 - } - return layout - - -def for_display(config): - layout = { - 'width': 0, - 'height': 0, - 'face': (0, 0), - 'name': (0, 0), - 'channel': (0, 0), - 'aps': (0, 0), - 'uptime': (0, 0), - 'line1': (0, 0), - 'line2': (0, 0), - 'friend_face': (0, 0), - 'friend_name': (0, 0), - 'shakes': (0, 0), - 'mode': (0, 0), - # status is special :D - 'status': { - 'pos': (0, 0), - 'font': fonts.Medium, - 'max': 20 - } - } - - if config['ui']['display']['type'] in ('inky', 'inkyphat'): - layout = inkyphat(config, layout) - - elif config['ui']['display']['type'] in ('papirus', 'papi'): - layout = papirus(config, layout) - - if config['ui']['display']['type'] in ('oledhat'): - layout = oledhat(config, layout) - - elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1', - 'ws_2', 'ws2', 'waveshare_2', 'waveshare2'): - layout = waveshare(config, layout) - - return layout diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 787963c..b80a62a 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -10,7 +10,6 @@ from pwnagotchi.voice import Voice import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.faces as faces -import pwnagotchi.ui.layout as layout from pwnagotchi.ui.components import * from pwnagotchi.ui.state import State @@ -20,7 +19,7 @@ ROOT = None class View(object): - def __init__(self, config, state={}): + def __init__(self, config, impl, state=None): global ROOT self._render_cbs = [] @@ -29,7 +28,8 @@ class View(object): self._frozen = False self._lock = Lock() self._voice = Voice(lang=config['main']['lang']) - self._layout = layout.for_display(config) + self._implementation = impl + self._layout = impl.layout(config) self._width = self._layout['width'] self._height = self._layout['height'] self._state = State(state={ @@ -70,8 +70,9 @@ class View(object): font=fonts.Bold, color=BLACK), }) - for key, value in state.items(): - self._state.set(key, value) + if state: + for key, value in state.items(): + self._state.set(key, value) plugins.on('ui_setup', self) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 4afcc69..7aa6e73 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -56,6 +56,26 @@ def load_config(args): if user_config: config = merge_config(user_config, config) + # the very first step is to normalize the display name so we don't need dozens of if/elif around + if config['ui']['display']['type'] in ('inky', 'inkyphat'): + config['ui']['display']['type'] = 'inky' + + elif config['ui']['display']['type'] in ('papirus', 'papi'): + config['ui']['display']['type'] = 'papirus' + + if config['ui']['display']['type'] in ('oledhat'): + config['ui']['display']['type'] = 'oledhat' + + elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1'): + config['ui']['display']['type'] = 'waveshare_1' + + elif config['ui']['display']['type'] in ('ws_2', 'ws2', 'waveshare_2', 'waveshare2'): + config['ui']['display']['type'] = 'waveshare_2' + + else: + print("unsupported display type %s" % config['ui']['display']['type']) + exit(1) + return config From 6645c80db31ec6ae2dd8be706f3f267f918a8cc2 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 15 Oct 2019 11:56:49 +0200 Subject: [PATCH 227/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/voice.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index 22281cc..61689cf 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -14,6 +14,9 @@ class Voice: translation.install() self._ = translation.gettext + def custom(self, s): + return s + def default(self): return self._('ZzzzZZzzzzZzzz') @@ -68,8 +71,8 @@ class Voice: def on_new_peer(self, peer): return random.choice([ - self._('Hello {name}! Nice to meet you. {name}').format(name=peer.name()), - self._('Unit {name} is nearby! {name}').format(name=peer.name())]) + self._('Hello {name}! Nice to meet you.').format(name=peer.name()), + self._('Unit {name} is nearby!').format(name=peer.name())]) def on_lost_peer(self, peer): return random.choice([ From 0e9f9c0f2e7efb486f45f7cc068cc78a788a0221 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 15 Oct 2019 12:05:06 +0200 Subject: [PATCH 228/346] fix: fixed typos due to old configuration paths (fixes #300) --- builder/pwnagotchi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 633dc7b..a52e452 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -379,7 +379,7 @@ copy: dest: /etc/pwnagotchi/config.yml content: | - # Add your configuration overrides on this file any configuration changes done to defaults.yml will be lost! + # Add your configuration overrides on this file any configuration changes done to default.yml will be lost! # Example: # # ui: @@ -461,7 +461,7 @@ If you want to change my configuration, use /etc/pwnagotchi/config.yml - All the configuration options can be found on /etc/pwnagotchi/defaults.yml, + All the configuration options can be found on /etc/pwnagotchi/default.yml, but don't change this file because I will recreate it every time I'm restarted! I'm managed by systemd. Here are some basic commands. From aa60a369a901112a44b04de9419b98aec7893fab Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 15 Oct 2019 13:17:26 +0200 Subject: [PATCH 229/346] fix: fix for bug mentioned in https://github.com/evilsocket/pwnagotchi/commit/13d68c7c2471ab58cfc7c24482c81ef585b6e9da\#commitcomment-35504643 --- pwnagotchi/ui/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index b80a62a..a6efd78 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -29,7 +29,7 @@ class View(object): self._lock = Lock() self._voice = Voice(lang=config['main']['lang']) self._implementation = impl - self._layout = impl.layout(config) + self._layout = impl.layout() self._width = self._layout['width'] self._height = self._layout['height'] self._state = State(state={ From ee3fb285beb9a3bf998e34866ba95227b726f66e Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 15 Oct 2019 13:19:30 +0200 Subject: [PATCH 230/346] misc: small fix or general refactoring i did not bother commenting --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index cd3292d..94c160d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), full and half WPA handshakes. - + Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to. @@ -22,8 +22,6 @@ More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](http **Keep in mind:** Unlike the usual RL simulations, Pwnagotchi learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but ** listen to your Pwnagotchi when it tells you it's boring!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :) - - Multiple units within close physical proximity can "talk" to each other, advertising their presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage. ## Documentation From 1936c309f05bd24008eaba9c3e9c89547e2e6e6e Mon Sep 17 00:00:00 2001 From: amuthmann <amuthmann@pro-vision.de> Date: Tue, 15 Oct 2019 17:43:32 +0200 Subject: [PATCH 231/346] Print effective merge config --- pwnagotchi/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 7aa6e73..20afd1b 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -76,6 +76,8 @@ def load_config(args): print("unsupported display type %s" % config['ui']['display']['type']) exit(1) + print("Effective Configuration:") + print(yaml.dump(config, default_flow_style=False)) return config From beb2fedf02de87c89941c35a020935289e648a3e Mon Sep 17 00:00:00 2001 From: amuthmann <amuthmann@pro-vision.de> Date: Tue, 15 Oct 2019 17:43:32 +0200 Subject: [PATCH 232/346] Print effective merged config Signed-off-by: deveth0 <github@dev-eth0.de> --- pwnagotchi/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 7aa6e73..20afd1b 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -76,6 +76,8 @@ def load_config(args): print("unsupported display type %s" % config['ui']['display']['type']) exit(1) + print("Effective Configuration:") + print(yaml.dump(config, default_flow_style=False)) return config From f97b106858ccbe272157eeeeb65d1a1e7232d5dc Mon Sep 17 00:00:00 2001 From: Ciara Brennan <ciara.brennan@gmail.com> Date: Tue, 15 Oct 2019 23:52:08 +0100 Subject: [PATCH 233/346] Irish translations Signed-off-by: Ciara Brennan <ciara.brennan@gmail.com> --- pwnagotchi/locale/ga/LC_MESSAGES/voice.mo | Bin 0 -> 4379 bytes pwnagotchi/locale/ga/LC_MESSAGES/voice.po | 215 ++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 pwnagotchi/locale/ga/LC_MESSAGES/voice.mo create mode 100644 pwnagotchi/locale/ga/LC_MESSAGES/voice.po diff --git a/pwnagotchi/locale/ga/LC_MESSAGES/voice.mo b/pwnagotchi/locale/ga/LC_MESSAGES/voice.mo new file mode 100644 index 0000000000000000000000000000000000000000..b86e7f19d7c31ce99d18cc212db297581292ff4c GIT binary patch literal 4379 zcmbW3ON<;x8GtJg^O%@NLLfk5sKF4&@y>YH4=@G;eq`+p-t}g^j<E!Xn(o@^%64~+ zx_Z1BmX&hI0R)@?2_YmfNJuMWgp>oqATEOtf<q1w;>J^m2njeKkt2M6P0#K+;zVnE zzkXEz^}ql5-2?Z2O>vy$e1!Ao_b7D{=C9=+$LH@=>W%OV@a^y`@G#tl?|?7C`{3W2 z_y2}(<$nJ@r5=R`pvb)dPeB7U{04j*{0S7fFTyv$zrcs!zZ%~6I;9@u{vdokJOxGm zC*U3U6nqr!=j8+NI266+p?vol_)b_sk^4p1g5QO2fj@x!sh{z$4SxY;-OEt)R&SW- zdjKBc_G3`=d<KgC&q7)EC8*&xl>L7N55PY{k@p`c>mS0I?}kU9=rIp*nR*IhvU(a~ zlKM0hy^`jBy}7TT*!fxbX82|J3j8J%d0*w9?Ds7=3x5Pfzn7t`-$$?>gAYRS&l%W( zD^Toz4n7IL1H16=@V)Sn{YpI!KMLi$K9u*LgR<ZEq3kn;;-5djhv7fqFJX&^XW&cl z1Mq&75kGwhz6&lvS)Vn09!lK44JAK*48?yhLW%dEq3G2jh~nqR;bC|Qiu?+SUSEf@ z{)bT3{}vvEuRxLiFv;|Ocmj%FZ$SAjhhoq35ErOhkS*2ihChY4T>ZM?Z=l5M_YMCF zMelz$+;_iH_i!(JAL3;D=uZtr{@&wwb8~dhL(xt0W0vzhoLx?_Qx3@|IbOplF%*Bu zA#svJ{3f;^;Uo>By@Vy|FFK2zqO0Vg#6S+oL5Yv}2-VdwPVw&y=kz$q#R*P{xA;u* zN_;&%q=meXbFO(n3B=m%K=Gv<68m_ZUC@;^>8PvDhTs!tYFGAk?e*qHX6h|<-urw; zZ&=;;wcad;#g;DZhR#fxhRm$n%|Sk_Oup4t7pxi9nJsIVOoVI}CfQOKy-#)N`dQuO zdMab*`_NLCOtP-|QZMJGv~^ouvU!dIr77%ITQ9l9ib938wI2Clw|T>|QKO~KZG9-{ zp4DrWvuGwVMl(8Lo6_cbv$@#PF0jPfw!Yzql|H+yH(Z|UU`8ll%JtAym)wkAI8^B6 zjnZIqlDW3NTAM2RrQ7YQ1-)jG8N4>RFRh+D8*G$fLuJ=|W%YtK1uGHL<2IeF;m$r= zSGKVE=vB`%b5q=o%wm{idTm&?<JY;1&&4^pTeFjg!KCr!O<RpneJcH?#R0a|Xd<%E zLvN<UrgpyE(!@)zyJQ_@Hq%L@(k-3jxT_9rEar2QivoCg(=@qYStH?Y+#02r+Um{S z_-*ax0&)(rd*ilvo%{ZTXs-`uL>gNLG0ys6SRWB@_2e+rI<<*QQI0UMzhn*@+cfCX z>+5dbwbdulzDGR8xVT{QNDPW;8R}>%33WMUL*GXq7`<Wg^|rd~{knW=DnG;*k8K8< zgspH`Et(XAb~3v$wWStu#GD>jTgA)C3b_qx(S=~swptwNq;iFow2F%RHJuDA=Yy+9 zG3ZNXFxZL4WXH*KoSGU^G#1matS47^kKA4IJ)e%yo6tz+<1g-9oe`Y)^_4-GAz27j zCc-y}q28!Gmn5TJu|4m1()DWQYLsC!efp@nN`Or{V%vJdSL>nOZmU(YtF%(Z)5l^@ z(-l@TuA_O9UJ@t-VP-LEGV)qh#9jN;d2b}1l(JRVN+-20*YbI3P1PH<r*E$t7Zr?2 zy2mZorGltrE4eLB_;_1g&%7UmxW}%%K9$#1H>Pc>tupF4{tK#b<*>F5f<nA6(*{A^ zEhD<Kc~mq}U9S8yHmN%cY3FI8P5GSGtra`)Roz((eV2C54f~<9>Uq3;rL$shI$6}Y zKxLfP$7bg~+?kv0%$?N7j-Nj9!6UPCv$L(srtA+*pZ9&!T3lFsVpsHBceZujmo-7` ztd0gOsc92O3A!skA}y>6wmx%hb-r_IcU@)5ux6{y6J_F4a^SQ+)pIr51cb=T+|<58 z!Rf}GRci|FKh?QtOG{Ufx69t9gy~rKSocIr$tW0ayEJ1K^mXDLy>ooa<#gBSG`Q$> zhN-nlvKhLRab!thXf&~WcACQOP&kJXdTo5$S7~Nlk(F+IN6IaZhAZRS3AtBfWCNSd zi#$p3iL_*h<bVRw6xcK*)B_vKz;KB^Tk7R3=XEJMGk%=W7lu1YM@tJd6~j~*n%txr zR+xHsc8s^Dvm+gEo6<O<uLFuEnaE^-jJIcWJ-($AOkm00DT2jo#^r5&ffp5*h)=y< zUSUe=%D5htDmH1;XU`qgX7&sfDGCG^d{?wJ{;qu(--+qC%BHxYPtCBVlofcW)V_+9 zresr5SUSDLXGW)d(aWmwZ5>#L(!8Woc9n?2?z9@;+WlT07T9n4IvwVkO$xd~W=)T6 zDv9>aCo<b)d4f@iZXz#M6|@bWqg^l_8I?9f$&)O*FMHMag}AF)HF|RMKdd>Ip*BHB zo5miIws&&%hk-KSSAZBBm&*ISGYs$4ZGAq&Q4`0{;0|0ESKl?v2xdk8l|vI}S$ZgT zRY``O<PH4h_<zP&^pn(#BwI|XnoV$3S1*lklRj~%Nq|rw#cIh9{5_d&=XVW^p%I&X zNG5^8Uir99^O7H6p~e$|N-E<>Z>g(XOPxAj%t-vm#TbmX6tW?Eszfr1q^R}`z7<h6 zZqO9mj&EA(T;o1;88}m6ND=E|=f$*2<6Cu}%;9&GWEFp6BU6mHX2$b_gw#rmyy(#b zuzHOFSy9O`+m~W(86xb{Sf>*GJtN^VX-2r1ch=?o$)$`*xr&>Ra%_?g9mbGEXiKsZ z0xG(yqfg>Km%JMU@_9Ad;qVJ>lLoR){?kGFuAC-2O@%4Pm~7%FbMqpd(MU5Ybw@Xu z@5JBy{4&W<sx6MvNloPRkrJm#--w1z`>K2ya0GQAyU+vH7>t#6QiA-R_IX<|P|-vO z7O;w@ML&!q^{g-1Z92b74B&?`t7xnIplue-Bl=YyJkL(OY;8z}^u_DZRSeeX5&fs< zz84Le$}NZxqm}m8G|0A!eHpn)mV4Kq+~V@)wj96U2s}SZcdvqXiIbt*Q0%^IgBX%M MStBY>e($UQ0wM6sxc~qF literal 0 HcmV?d00001 diff --git a/pwnagotchi/locale/ga/LC_MESSAGES/voice.po b/pwnagotchi/locale/ga/LC_MESSAGES/voice.po new file mode 100644 index 0000000..a09b9f7 --- /dev/null +++ b/pwnagotchi/locale/ga/LC_MESSAGES/voice.po @@ -0,0 +1,215 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-09 17:42+0200\n" +"PO-Revision-Date: 2019-10-15 23:46+0100\n" +"Language: ga\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: \n" +"Language-Team: \n" +"X-Generator: Poedit 2.2.4\n" + +msgid "ZzzzZZzzzzZzzz" +msgstr "" + +msgid "Hi, I'm Pwnagotchi! Starting ..." +msgstr "Dia Duit, Pwnagotchi is ainm dom! Ag tosú ..." + +msgid "New day, new hunt, new pwns!" +msgstr "Lá nua, seilg nua, pwns nua!" + +msgid "Hack the Planet!" +msgstr "Haic An Phláinéid!" + +msgid "AI ready." +msgstr "AI réidh." + +msgid "The neural network is ready." +msgstr "Tá an líonra néarach réidh." + +#, python-brace-format +msgid "Hey, channel {channel} is free! Your AP will say thanks." +msgstr "Hé, tá cainéal {channel} ar fail! Déarfaidh do PR go raibh maith agat." + +msgid "I'm bored ..." +msgstr "Tá leadrán orm ..." + +msgid "Let's go for a walk!" +msgstr "Siúil liom, le do thoil!" + +msgid "This is the best day of my life!" +msgstr "Tá sé an lá is fearr i mo shaol!" + +msgid "Shitty day :/" +msgstr "Tá lá damanta agam :/" + +msgid "I'm extremely bored ..." +msgstr "Tá mé ag dul as mo mheabhair le leadrán ..." + +msgid "I'm very sad ..." +msgstr "Ta brón an domhain orm ..." + +msgid "I'm sad" +msgstr "Tá brón orm" + +msgid "I'm living the life!" +msgstr "Tá an saol ar a thoil agam!" + +msgid "I pwn therefore I am." +msgstr "Déanaim pwnáil, dá bhrí sin táim ann." + +msgid "So many networks!!!" +msgstr "Gréasáin - Tá an iliomad acu ann!!!" + +msgid "I'm having so much fun!" +msgstr "Tá craic iontach agam!" + +msgid "My crime is that of curiosity ..." +msgstr "Ní haon pheaca é bheith fiosrach ..." + +#, python-brace-format +msgid "Hello {name}! Nice to meet you. {name}" +msgstr "Dia Duit {name}! Is deas bualadh leat. {name}" + +#, python-brace-format +msgid "Unit {name} is nearby! {name}" +msgstr "Aonad {name} in aice láimhe! {name}" + +#, python-brace-format +msgid "Uhm ... goodbye {name}" +msgstr "Uhm... slán leat {name}" + +#, python-brace-format +msgid "{name} is gone ..." +msgstr "Tá {name} imithe ..." + +#, python-brace-format +msgid "Whoops ... {name} is gone." +msgstr "Hoips … Tá {name} imithe." + +#, python-brace-format +msgid "{name} missed!" +msgstr "Chaill mé ar {name}!" + +msgid "Missed!" +msgstr "Chaill mé é sin !" + +msgid "Nobody wants to play with me ..." +msgstr "Níl aon duine ag iarraidh imirt liom ..." + +msgid "I feel so alone ..." +msgstr "Tá uaigneas an domhain orm ..." + +msgid "Where's everybody?!" +msgstr "Cá bhfuil gach duine?!" + +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "Néal a chodladh ar {secs}s ..." + +msgid "Zzzzz" +msgstr "" + +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "" + +msgid "Good night." +msgstr "Oíche mhaith." + +msgid "Zzz" +msgstr "" + +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "Fan ar {secs}s ..." + +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "Ag amharc uaim ar ({secs}s)" + +#, python-brace-format +msgid "Hey {what} let's be friends!" +msgstr "Hé {what} déanaimis síocháin!" + +#, python-brace-format +msgid "Associating to {what}" +msgstr "Ag coinneáil le {what}" + +#, python-brace-format +msgid "Yo {what}!" +msgstr "Hé {what}!" + +#, python-brace-format +msgid "Just decided that {mac} needs no WiFi!" +msgstr "Tá cinneadh déanta agam. Níl {mac} sin de dhíth air WiFi!" + +#, python-brace-format +msgid "Deauthenticating {mac}" +msgstr "Bain fíordheimhniúde {mac}" + +#, python-brace-format +msgid "Kickbanning {mac}!" +msgstr "Chiceáil mé agus cosc mé ar {mac}!" + +#, python-brace-format +msgid "Cool, we got {num} new handshake{plural}!" +msgstr "Go hiontach, fuaireamar {num} handshake{plural}!" + +msgid "Ops, something went wrong ... Rebooting ..." +msgstr "Hoips...Tháinig ainghléas éigin..." + +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "{num} stáisiún kick\n" + +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "Rinne mé {num} cairde nua\n" + +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "Fuair me {num} cumarsáid thionscantach\n" + +msgid "Met 1 peer" +msgstr "Bhuail mé piara amháin" + +#, python-brace-format +msgid "Met {num} peers" +msgstr "Bhuail me {num} piara" + +#, python-brace-format +msgid "" +"I've been pwning for {duration} and kicked {deauthed} clients! I've also met " +"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" +msgstr "" +"Bhí me ag pwnáil ar {duration} agus chiceáil me ar {deauthed} cliaint! Chomh " +"maith, bhuail me {associated} cairde nua and d'ith mé {handshakes}! " +"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet" + +msgid "hours" +msgstr "uair on chloig" + +msgid "minutes" +msgstr "nóiméad" + +msgid "seconds" +msgstr "soicind" + +msgid "hour" +msgstr "uair an chloig" + +msgid "minute" +msgstr "nóiméad" + +msgid "second" +msgstr "soicind" From ebeb22081b3a1ea538f65c196c35544da2046188 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Wed, 16 Oct 2019 09:28:29 +0200 Subject: [PATCH 234/346] Improve logging and add already_connected check --- pwnagotchi/defaults.yml | 1 + pwnagotchi/plugins/default/bt-tether.py | 50 ++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 5034d49..e207f87 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -73,6 +73,7 @@ main: ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable netmask: 24 interval: 1 # check every x minutes for device + share_internet: false # monitor interface to use iface: mon0 # command to run to bring the mon interface up in case it's not up already diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 5614f25..067b31e 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -154,7 +154,40 @@ class BTNap: return None - def wait_for_device(self, timeout=30): + def is_connected(self): + """ + Check if already connected + """ + bt_dev = self.power(True) + + if not bt_dev: + return False + + try: + dev_remote = BTNap.find_device(self._mac, bt_dev) + return bool(BTNap.prop_get(dev_remote, 'Connected')) + except BTError: + pass + return False + + + def is_paired(self): + """ + Check if already connected + """ + bt_dev = self.power(True) + + if not bt_dev: + return False + + try: + dev_remote = BTNap.find_device(self._mac, bt_dev) + return bool(BTNap.prop_get(dev_remote, 'Paired')) + except BTError: + pass + return False + + def wait_for_device(self, timeout=15): """ Wait for device @@ -409,20 +442,35 @@ def on_ui_update(ui): INTERVAL.update() bt = BTNap(OPTIONS['mac']) + + logging.debug('BT-TETHER: Check if already connected and paired') + if bt.is_connected() and bt.is_paired(): + logging.debug('BT-TETHER: Already connected and paired') + ui.set('bluetooth', 'CON') + return + + logging.debug('BT-TETHER: Try to connect to mac') if bt.connect(): + logging.debug('BT-TETHER: Successfuly connected') btnap_iface = IfaceWrapper('bnep0') + logging.debug('BT-TETHER: Check interface') if btnap_iface.exists(): + logging.debug('BT-TETHER: Interface found') # check ip addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}" + logging.debug('BT-TETHER: Try to set ADDR to interface') if not btnap_iface.set_addr(addr): ui.set('bluetooth', 'ERR1') logging.error("Could not set ip of bnep0 to %s", addr) return + else: + logging.debug('BT-TETHER: Set ADDR to interface') # change route if sharking if OPTIONS['share_internet']: + logging.debug('BT-TETHER: Set routing and change resolv.conf') IfaceWrapper.set_route(".".join(OPTIONS['ip'].split('.')[:-1] + ['1'])) # im not proud about that # fix resolv.conf; dns over https ftw! with open('/etc/resolv.conf', 'r+') as resolv: From 5987f9300901067552913640f05298a4f5cfb200 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Wed, 16 Oct 2019 09:41:47 +0200 Subject: [PATCH 235/346] Refracture code --- pwnagotchi/plugins/default/bt-tether.py | 76 +++++++++++++------------ 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 067b31e..3f686f7 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -447,43 +447,47 @@ def on_ui_update(ui): if bt.is_connected() and bt.is_paired(): logging.debug('BT-TETHER: Already connected and paired') ui.set('bluetooth', 'CON') - return - - logging.debug('BT-TETHER: Try to connect to mac') - if bt.connect(): - logging.debug('BT-TETHER: Successfuly connected') - btnap_iface = IfaceWrapper('bnep0') - - logging.debug('BT-TETHER: Check interface') - if btnap_iface.exists(): - logging.debug('BT-TETHER: Interface found') - # check ip - addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}" - - logging.debug('BT-TETHER: Try to set ADDR to interface') - if not btnap_iface.set_addr(addr): - ui.set('bluetooth', 'ERR1') - logging.error("Could not set ip of bnep0 to %s", addr) - return - else: - logging.debug('BT-TETHER: Set ADDR to interface') - - # change route if sharking - if OPTIONS['share_internet']: - logging.debug('BT-TETHER: Set routing and change resolv.conf') - IfaceWrapper.set_route(".".join(OPTIONS['ip'].split('.')[:-1] + ['1'])) # im not proud about that - # fix resolv.conf; dns over https ftw! - with open('/etc/resolv.conf', 'r+') as resolv: - nameserver = resolv.read() - if 'nameserver 9.9.9.9' not in nameserver: - resolv.seek(0) - resolv.write(nameserver + 'nameserver 9.9.9.9\n') - - ui.set('bluetooth', 'CON') - else: - ui.set('bluetooth', 'ERR2') else: - ui.set('bluetooth', 'NF') + logging.debug('BT-TETHER: Try to connect to mac') + if bt.connect(): + logging.info('BT-TETHER: Successfuly connected') + else: + logging.error('BT-TETHER: Could not connect') + ui.set('bluetooth', 'NF') + return + + btnap_iface = IfaceWrapper('bnep0') + logging.debug('BT-TETHER: Check interface') + if btnap_iface.exists(): + logging.debug('BT-TETHER: Interface found') + + # check ip + addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}" + + logging.debug('BT-TETHER: Try to set ADDR to interface') + if not btnap_iface.set_addr(addr): + ui.set('bluetooth', 'ERR1') + logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr) + return + else: + logging.debug('BT-TETHER: Set ADDR to interface') + + # change route if sharking + if OPTIONS['share_internet']: + logging.debug('BT-TETHER: Set routing and change resolv.conf') + IfaceWrapper.set_route(".".join(OPTIONS['ip'].split('.')[:-1] + ['1'])) # im not proud about that + # fix resolv.conf; dns over https ftw! + with open('/etc/resolv.conf', 'r+') as resolv: + nameserver = resolv.read() + if 'nameserver 9.9.9.9' not in nameserver: + logging.info('BT-TETHER: Added nameserver') + resolv.seek(0) + resolv.write(nameserver + 'nameserver 9.9.9.9\n') + + ui.set('bluetooth', 'CON') + else: + logging.error('BT-TETHER: bnep0 not found') + ui.set('bluetooth', 'ERR2') def on_ui_setup(ui): From 18dd71b9898865b56646337047f6b3c3e799a753 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 16 Oct 2019 16:37:42 +0200 Subject: [PATCH 236/346] :broken_heart: --- .DEREK.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.DEREK.yml b/.DEREK.yml index 9598f24..c63d9d1 100644 --- a/.DEREK.yml +++ b/.DEREK.yml @@ -3,7 +3,6 @@ maintainers: - caquino - dadav - justin-p - - hexwaxwing features: - comments From 970b6922b756e5faf8485d5d09eb8f9654a9d942 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 16 Oct 2019 17:20:48 +0200 Subject: [PATCH 237/346] fix: if -> elif typo (fixes #310) --- pwnagotchi/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 20afd1b..127761c 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -63,7 +63,7 @@ def load_config(args): elif config['ui']['display']['type'] in ('papirus', 'papi'): config['ui']['display']['type'] = 'papirus' - if config['ui']['display']['type'] in ('oledhat'): + elif config['ui']['display']['type'] in ('oledhat'): config['ui']['display']['type'] = 'oledhat' elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1'): From aeb6536959f8d1c3eb2b2c0293a01a3796dac50e Mon Sep 17 00:00:00 2001 From: wytshadow <24534649+wytshadow@users.noreply.github.com> Date: Wed, 16 Oct 2019 15:09:46 -0600 Subject: [PATCH 238/346] added jp translation --- pwnagotchi/locale/jp/LC_MESSAGES/voice.mo | Bin 0 -> 3748 bytes pwnagotchi/locale/jp/LC_MESSAGES/voice.po | 212 ++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 pwnagotchi/locale/jp/LC_MESSAGES/voice.mo create mode 100644 pwnagotchi/locale/jp/LC_MESSAGES/voice.po diff --git a/pwnagotchi/locale/jp/LC_MESSAGES/voice.mo b/pwnagotchi/locale/jp/LC_MESSAGES/voice.mo new file mode 100644 index 0000000000000000000000000000000000000000..be728e2b3f18affff5b875a533ca8192f920a6df GIT binary patch literal 3748 zcmbW3?Qa}M9ml7%kaFdrw4?-DV6L=HllZRZGzqEOQbH0Et8wZirg@MMTHlTDHon_E zcK32zEQ{OSkh*b7RHt$3%9SyR<1~ruKA@&bo$>-9{sM%;3y>J!*?yHGA$S22e1EfZ zXFEtiVzjfLo2TFNto^6^?mENJlIRbipS**yXTg7c3NN%z?quw<;9tQt;6K58z(?<5 zY$ezV-V5#k9|I47B<H8#W^feb;P1iv!M}r_2V<XR>?`0Z%@p`$jCX=7z#oDnSLpd8 z;3|v<K{|IHjDa)Y7s20wbnXv&{m&rB_#fapaMfob{o6pY?*K^aCGY`o7$mt<Aldm_ z@N3`=ko5cwgzN0z;FrKVq3jFbN)Rg8BOqL3YxVdE5L?+s5D$AskGJXZ^Wc4$-v^SM z9`Gn)Ujo-+{K?%>{2oN`Db8KsYVa`l4R8odf$xG(f`0)Ife*mg?ck5WICvGL^EW_> z-%UM#H<I`u#%n>c<NF}(KM0ciKE3`1n85f0@L_Nkd<<OiImYm?Bwi%{X>b#`2iyY= zfMnkv!RNqxkaUvsJV<_2K+-o3()r(j3GfDphpj+RD1IwJ%ELn-<@wtnTxC1-`X0># zAm!&rnrY3fW-mzkY>@nY6}$s%L8nGFLG#qeKB@uI*JzLGA5<f7i9L=^cCJCEhP0`8 zB0SAT`Y7hqzK+h(sYa;IsUgLxQC!JKYDkZ2-_%?O(mnVVI>qO!=u~eM1Bx3p$`|E? zt}N9u#o-ZjxTx;+T99g%;>otOb4M7NN|J50?R*O_3!b$--e;8x{oE2|o-?eBn=^Vv zUol^DjC_Bb?Xc|(x6EwLOX7E9b918`V>^v>FZXhScjgUCcyYE<<nuP98U@iG=ey0c z;GWG3LU_Dlmy&t~+bX=ToHM+Bo)_L~mv;-^<Cp>_kc>(TPeY9*^1M&~=;x-3EkeZk zLA&JeEuFk<=JVV&DlovXdfg=3X}0k8)dk*JwhTCz&Y5w(&odm)w6Z*zOtN;~BOueY zxskUm5zXO1x9x~bb0mJ^Iieu)m0RX>#t{;P(}hwx$9qavoRPY`spcsRd9z1Eldh3b zi${c0fsv8)UCP(2&3kNz8@z1fd*f`EZTFH~!?8<NhCkltinQDBu3;TUMl4B?j!%qr zAegPZD1@VaMk%DB%Q{Tg6`45OZ4`?nlk{kL%Dvrox1Fg#vE{jxm|`B`E1O;pDN-9= zD7r21upqn~oi7W^<7LOjPsES!72USojOIQB!mui+9<OXWy>2`nXI%(_C8&NH)08(| zd0jdD#zT?n7OuxL&~EqeLPZDe2f2dMlSScmSIBv)Bevf#mC{?i*`E`RK=B9)GCB5K zobAuqcF|Q*qSK@>i)1I+K@_H{xi~v?^eA;aN0$VghKJ~-7H9*u<xp+v<+6fll{}&0 zGUUm&G8!&hu1-Z$PZe}lIvxA4NPCI)OyWgk%e1XFo=T>Yt+BnLXggk_!_AtR#P>>B zH_>Ic@mS{zU5Tv-o`<c8ZH6b>`1(}qQ;F78qIDB*-Po4exGuFmmBPlvUU9@Ek+<Eq z%W%C!mt$CN-tcUvjh8DPE`7!>^Jms?*!bjzO&gwC*PQ!q$%UCo3%8?~uOzdmlTvpw zZ5O^1+hthUl93gOE@2efc(Hlo{!ck={BSYW(cZCrF*2>mRBWqlc__g|SEUHwJ>1ME zP_m}=G<_c&S9s5syq?77#eL*jk8l#(t+bs%1-J3d-KG~~v)8WGu1?I2P0WYarT=#Q z+Hfrxk^V{PPfGt4>3=N!v(g`v)qu%hfCg1xU99@>J8J2e3{T1MeHp$igR3%_l)-r! zOe+b23<ht`_)PjIW@lcN{;#EfN&07`e~iQ_DYrk2Z84^{gd{WgK!#^!_?8S`kzpvq zfk^mi8Jw2>W$BMge>$oxEYt?xl!4!H-~vQNOR#2K1}{nfob)e7WnVirDE(ik^N?~$ z1}F3}+IBorG9~>1xGqCH=aj#&;d1jM49#=L#_Pif9pW~EGh7B!G8{upw{&8)er#<1 z3M?9$zgVRJP+&(?RQ+Z4D;?N(L53$}@GIpfywE~xlY`2s3nU^K#QEr9wGd&Qu1%ev z8@vJ~&?f!Ym+e@nR%`Dffh1}19AY5-3o;09Sw@ufLVW^taaM&GN44P;6I9OJYhzGG zZz>gS6s^^R_jGYI^eqwiUu)Wj`k8a}ske~H`EitAqxPCPh(6ck+|Pfhgf9jmswEgi z72K#xWOc!R4f|)Wk1nRvAB(ZpXy@!l?=1`?_(QWZZ!|n>)VeCp|GjOY8Y)x9$mz(y z+Vq>vP{1)Zf9V~BSLNs8?1$C4t3y<)RL56jI1`<g{wZ}e=w9ex;Lb!P3dytAkzADM zNtnAp_Xnk`oLBc!p9wF?aF`^9>XtULu~Z_`1U+XcmyH`F!)ZFR{K7&d`6I(Y8BQpX zxN@*r-?}AdZu`BM{pdvf>;&D6NmTILx?cRr`e=3G^6Qi;TGf?}`heYdE;cPzrefx} zx|1*K>)R}Y+Q&azjKtErKpz_VBH&wtYC3&$MrEfN(5C!Ip!!l_Ym@;*w)w3KXBvMM z)s-2Ai6b(=4N&jusH|4?|0HW}U22T0qpUs>AZ-oQ297i8bCW}=-^PZ>PW{(&S)u(0 Dc<6)8 literal 0 HcmV?d00001 diff --git a/pwnagotchi/locale/jp/LC_MESSAGES/voice.po b/pwnagotchi/locale/jp/LC_MESSAGES/voice.po new file mode 100644 index 0000000..912e89d --- /dev/null +++ b/pwnagotchi/locale/jp/LC_MESSAGES/voice.po @@ -0,0 +1,212 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR 24534649+wytshadow@users.noreply.github.com, 2019. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 0.0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-16 15:05+0200\n" +"PO-Revision-Date: 2019-10-16 15:05+0200\n" +"Last-Translator: wytshadow <24534649+wytshadow@users.noreply.github.com>\n" +"Language-Team: pwnagotchi <24534649+wytshadow@users.noreply.github.com>\n" +"Language: jp\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "ZzzzZZzzzzZzzz" +msgstr "すやすや〜" + +msgid "Hi, I'm Pwnagotchi! Starting ..." +msgstr "こんにちは、ポウナゴッチです!始めている。。。" + +msgid "New day, new hunt, new pwns!" +msgstr "" + +msgid "Hack the Planet!" +msgstr "ハックザプラネット!" + +msgid "AI ready." +msgstr "人工知能の準備ができました。" + +msgid "The neural network is ready." +msgstr "ニューラルネットワークの準備ができました。" + +#, python-brace-format +msgid "Hey, channel {channel} is free! Your AP will say thanks." +msgstr "ねえ、チャンネル{channel}は無料です! キミのAPは感謝を言います。" + +msgid "I'm bored ..." +msgstr "退屈です。。。" + +msgid "Let's go for a walk!" +msgstr "散歩に行きましょう!" + +msgid "This is the best day of my life!" +msgstr "今日は私の人生で最高の日です!" + +msgid "Shitty day :/" +msgstr "" + +msgid "I'm extremely bored ..." +msgstr "とても退屈です。" + +msgid "I'm very sad ..." +msgstr "とても悲しいです。。。" + +msgid "I'm sad" +msgstr "悲しいです。" + +msgid "I'm living the life!" +msgstr "人生を生きている!" + +msgid "I pwn therefore I am." +msgstr "" + +msgid "So many networks!!!" +msgstr "たくさんネットワークがある!!!" + +msgid "I'm having so much fun!" +msgstr "とても楽しんでいます!" + +msgid "My crime is that of curiosity ..." +msgstr "" + +#, python-brace-format +msgid "Hello {name}! Nice to meet you. {name}" +msgstr "こんにちは{name}!初めまして。{name}" + +#, python-brace-format +msgid "Unit {name} is nearby! {name}" +msgstr "" + +#, python-brace-format +msgid "Uhm ... goodbye {name}" +msgstr "ええと。。。さようなら{name}" + +#, python-brace-format +msgid "{name} is gone ..." +msgstr "{name}がなくなった。。。" + +#, python-brace-format +msgid "Whoops ... {name} is gone." +msgstr "おっと。。。{name}がなくなった。" + +#, python-brace-format +msgid "{name} missed!" +msgstr "{name}逃した!" + +msgid "Missed!" +msgstr "逃した!" + +msgid "Nobody wants to play with me ..." +msgstr "誰も僕と一緒にプレーしたくない。。。" + +msgid "I feel so alone ..." +msgstr "僕は孤独を感じる。。。" + +msgid "Where's everybody?!" +msgstr "みんなどこ?!" + +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "{secs}寝ている。" + +msgid "Zzzzz" +msgstr "すや〜" + +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "すやすや〜 ({secs})" + +msgid "Good night." +msgstr "お休みなさい。" + +msgid "Zzz" +msgstr "す〜" + +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "{secs}を待っている。。。" + +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "{secs}を探している。" + +#, python-brace-format +msgid "Hey {what} let's be friends!" +msgstr "ちょっと{what}友だちになりましょう!" + +#, python-brace-format +msgid "Associating to {what}" +msgstr "" + +#, python-brace-format +msgid "Yo {what}!" +msgstr "よー{what}!" + +#, python-brace-format +msgid "Just decided that {mac} needs no WiFi!" +msgstr "" + +#, python-brace-format +msgid "Deauthenticating {mac}" +msgstr "" + +#, python-brace-format +msgid "Kickbanning {mac}!" +msgstr "" + +#, python-brace-format +msgid "Cool, we got {num} new handshake{plural}!" +msgstr "よし、{num}新しいハンドシェイクがある!" + +msgid "Ops, something went wrong ... Rebooting ..." +msgstr "おっと!何かが間違っていた。。。リブートしている。。。" + +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "" + +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "{num}人の新しい友達を作りました\n" + +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "{num}ハンドシェイクがある。\n" + +msgid "Met 1 peer" +msgstr "1人の仲間を会いました。" + +#, python-brace-format +msgid "Met {num} peers" +msgstr "{num}人の仲間を会いました。" + +#, python-brace-format +msgid "" +"I've been pwning for {duration} and kicked {deauthed} clients! I've also met " +"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" +msgstr "" + +msgid "hours" +msgstr "時間" + +msgid "minutes" +msgstr "分" + +msgid "seconds" +msgstr "秒" + +msgid "hour" +msgstr "時間" + +msgid "minute" +msgstr "分" + +msgid "second" +msgstr "秒" From 8a1aad1a99b2b3c891f04197d5042dd392e048cc Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Thu, 17 Oct 2019 13:05:47 +0200 Subject: [PATCH 239/346] changed left and right faces --- pwnagotchi/ui/faces.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/ui/faces.py b/pwnagotchi/ui/faces.py index 48f98d8..4f80e5c 100644 --- a/pwnagotchi/ui/faces.py +++ b/pwnagotchi/ui/faces.py @@ -1,5 +1,5 @@ -LOOK_R = '(⌐■_■)' -LOOK_L = '(■_■¬)' +LOOK_R = '( ⚆_⚆)' +LOOK_L = '(☉_☉ )' SLEEP = '(⇀‿‿↼)' SLEEP2 = '(≖‿‿≖)' AWAKE = '(◕‿‿◕)' From 79688305fd69af7ed5694b72e34f8ff9264e4206 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Thu, 17 Oct 2019 13:42:05 +0200 Subject: [PATCH 240/346] fix: fixed reboot procedure (fixes #313) --- pwnagotchi/__init__.py | 6 ++++++ pwnagotchi/agent.py | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index c75af90..af9d12d 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -65,3 +65,9 @@ def shutdown(): time.sleep(5) os.system("sync") os.system("halt") + + +def reboot(): + logging.warning("rebooting ...") + os.system("sync") + os.system("shutdown -r now") diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 497c91e..24013fb 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -481,9 +481,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): def _reboot(self): self.set_rebooting() self._save_recovery_data() - logging.warning("rebooting the system ...") - os.system("/usr/bin/sync") - os.system("/usr/sbin/shutdown -r now") + pwnagotchi.reboot() def next_epoch(self): was_stale = self.is_stale() From 10688ca7b0b5fe2dc1a2ba1ca685b38a2a30e298 Mon Sep 17 00:00:00 2001 From: spees <speeskonijn@gmail.com> Date: Thu, 17 Oct 2019 20:07:56 +0200 Subject: [PATCH 241/346] Moved BT status to prevent overlap with last recon total APS Signed-off-by: spees <speeskonijn@gmail.com> --- pwnagotchi/plugins/default/bt-tether.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 3f686f7..7c919aa 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -491,5 +491,5 @@ def on_ui_update(ui): def on_ui_setup(ui): - ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 30, 0), + ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0), label_font=fonts.Bold, text_font=fonts.Medium)) From 299bd9a5ca6c838dd152b36355913a78bc52be61 Mon Sep 17 00:00:00 2001 From: spees <speeskonijn@gmail.com> Date: Thu, 17 Oct 2019 20:10:43 +0200 Subject: [PATCH 242/346] Moved APs status to prevent overlap with channel status on inky screens Signed-off-by: spees <speeskonijn@gmail.com> --- pwnagotchi/ui/hw/inky.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/hw/inky.py b/pwnagotchi/ui/hw/inky.py index 9157a4d..3917079 100644 --- a/pwnagotchi/ui/hw/inky.py +++ b/pwnagotchi/ui/hw/inky.py @@ -16,7 +16,7 @@ class Inky(DisplayImpl): self._layout['face'] = (0, 37) self._layout['name'] = (5, 18) self._layout['channel'] = (0, 0) - self._layout['aps'] = (25, 0) + self._layout['aps'] = (30, 0) self._layout['uptime'] = (147, 0) self._layout['line1'] = [0, 12, 212, 12] self._layout['line2'] = [0, 92, 212, 92] From 7b05f10c6f185627a6627ec90b495787330a5736 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Thu, 17 Oct 2019 21:56:12 +0200 Subject: [PATCH 243/346] pwngrid 1.7.6 --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index a52e452..c2f181b 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.5/pwngrid_linux_armv6l_1.7.5.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.6/pwngrid_linux_armv6l_1.7.6.zip" apt: hold: - firmware-atheros From 95b871aadec5694f14490b94bf12d87db21935e5 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Thu, 17 Oct 2019 22:02:53 +0200 Subject: [PATCH 244/346] releasing v1.0.0RC5 --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index af9d12d..04352cf 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -4,7 +4,7 @@ import logging import time import pwnagotchi.ui.view as view -version = '1.0.0RC4' +version = '1.0.0RC5' _name = None From 0aa4f95235a939862142cc9f6e65c5a2a500378d Mon Sep 17 00:00:00 2001 From: spees <speeskonijn@gmail.com> Date: Thu, 17 Oct 2019 23:55:11 +0200 Subject: [PATCH 245/346] Made the memtemp plugin usable and added to default.yml Signed-off-by: spees <speeskonijn@gmail.com> --- pwnagotchi/defaults.yml | 2 ++ pwnagotchi/plugins/default/memtemp.py | 28 +++++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index e207f87..2308496 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -74,6 +74,8 @@ main: netmask: 24 interval: 1 # check every x minutes for device share_internet: false + memtemp: # Display memory usage and cpu temperature on screen + enabled: false # monitor interface to use iface: mon0 # command to run to bring the mon interface up in case it's not up already diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py index 4ef823b..b17225a 100644 --- a/pwnagotchi/plugins/default/memtemp.py +++ b/pwnagotchi/plugins/default/memtemp.py @@ -2,8 +2,19 @@ # # totalmem usedmem freemem cputemp # +############################################################### +# +# Updated 18-10-2019 by spees <speeskonijn@gmail.com> +# - Changed the place where the data was displayed on screen +# - Made the data a bit more compact and easier to read +# - removed the label so we wont waste screen space +# - Updated version to 1.0.1 +# +############################################################### + + __author__ = 'https://github.com/xenDE' -__version__ = '1.0.0' +__version__ = '1.0.1' __name__ = 'memtemp' __license__ = 'GPL3' __description__ = 'A plugin that will add a memory and temperature indicator' @@ -21,9 +32,9 @@ class MEMTEMP: # set the minimum seconds before refresh the values refresh_wait = 30 - + refresh_ts_last = time.time() - refresh_wait - + def __init__(self): # only import when the module is loaded and enabled import os @@ -31,9 +42,9 @@ class MEMTEMP: def get_temp(self): try: temp = os.popen('/opt/vc/bin/vcgencmd measure_temp').readlines()[0].split('=')[1].replace("\n", '').replace("'","") - return 'cpu:' + temp + return 't:' + temp except: - return 'cpu:0.0C' + return 't:-' # cpu:37.4C def get_mem_info(self): @@ -42,9 +53,9 @@ class MEMTEMP: # total, used, free = map(int, os.popen('free -t -m').readlines()[-1].split()[1:]) # without Swap, only real memory: total, used, free = map(int, os.popen('free -t -m').readlines()[-3].split()[1:4]) - return "tm:"+str(total)+" um:"+str(used)+" fm:"+str(free) + return "\nT:"+str(total)+"M U:"+str(used)+"M\nF:"+str(free)+"M" except: - return "tm:0 um:0 fm:0" + return "\nT:- U:-\nF:- " # tm:532 um:82 fm:353 @@ -57,7 +68,7 @@ def on_loaded(): def on_ui_setup(ui): - ui.add_element('memtemp', LabeledValue(color=BLACK, label='SYS', value='tm:0 um:0 fm:0 0.0C', position=(0, ui.height()-28), + ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='\nT:- U:-\nF:- -', position=(ui.width() / 2 + 17, ui.height() / 2), label_font=fonts.Bold, text_font=fonts.Medium)) @@ -66,3 +77,4 @@ def on_ui_update(ui): ui.set('memtemp', "%s %s" % (memtemp.get_mem_info(), memtemp.get_temp())) memtemp.refresh_ts_last = time.time() + From 61a6b77a52e22ea3db55ebfb3207a2b3db02601b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chip=20Wolf=20=E2=80=AE?= <hello@chipwolf.uk> Date: Thu, 17 Oct 2019 23:09:49 +0100 Subject: [PATCH 246/346] Only show ui complication if you have unread pwnmail --- pwnagotchi/plugins/default/grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 264de46..9535652 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -65,7 +65,7 @@ def is_excluded(what): def on_ui_update(ui): new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES) - if not ui.has_element('mailbox') and TOTAL_MESSAGES > 0: + if not ui.has_element('mailbox') and UNREAD_MESSAGES > 0: if ui.is_inky(): pos = (80, 0) else: From 55bac8a8b913c158132953d8186d278621df032a Mon Sep 17 00:00:00 2001 From: colossus700 <mlapinski23@gmail.com> Date: Thu, 17 Oct 2019 19:03:04 -0400 Subject: [PATCH 247/346] Update epd.py Fixed LM75B import error --- pwnagotchi/ui/hw/libs/papirus/epd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/hw/libs/papirus/epd.py b/pwnagotchi/ui/hw/libs/papirus/epd.py index bf8d572..11764d9 100644 --- a/pwnagotchi/ui/hw/libs/papirus/epd.py +++ b/pwnagotchi/ui/hw/libs/papirus/epd.py @@ -15,7 +15,7 @@ from PIL import Image from PIL import ImageOps -from pwnagotchi.ui.hw.libs.papirus import LM75B +from pwnagotchi.ui.hw.libs.papirus.lm75b import LM75B import re import os import sys From 8c73e32853bd05c6bda6c7a07661fa77329a8da3 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 18 Oct 2019 10:30:27 +0200 Subject: [PATCH 248/346] fix: handling corrupted log lines (fixes #321) --- pwnagotchi/log.py | 101 ++++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index c81a17c..ebada1b 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -2,6 +2,7 @@ import hashlib import time import re import os +import logging from datetime import datetime from pwnagotchi.voice import Voice @@ -87,61 +88,65 @@ class LastSession(object): parts = line.split(']') if len(parts) < 2: continue - line_timestamp = parts[0].strip('[') - line = ']'.join(parts[1:]) - stopped_at = self._parse_datetime(line_timestamp) - if started_at is None: - started_at = stopped_at - if LastSession.DEAUTH_TOKEN in line and line not in cache: - self.deauthed += 1 - cache[line] = 1 + try: + line_timestamp = parts[0].strip('[') + line = ']'.join(parts[1:]) + stopped_at = self._parse_datetime(line_timestamp) + if started_at is None: + started_at = stopped_at - elif LastSession.ASSOC_TOKEN in line and line not in cache: - self.associated += 1 - cache[line] = 1 + if LastSession.DEAUTH_TOKEN in line and line not in cache: + self.deauthed += 1 + cache[line] = 1 - elif LastSession.HANDSHAKE_TOKEN in line and line not in cache: - self.handshakes += 1 - cache[line] = 1 + elif LastSession.ASSOC_TOKEN in line and line not in cache: + self.associated += 1 + cache[line] = 1 - elif LastSession.TRAINING_TOKEN in line: - self.train_epochs += 1 + elif LastSession.HANDSHAKE_TOKEN in line and line not in cache: + self.handshakes += 1 + cache[line] = 1 - elif LastSession.EPOCH_TOKEN in line: - self.epochs += 1 - m = LastSession.EPOCH_PARSER.findall(line) - if m: - epoch_num, epoch_data = m[0] - m = LastSession.EPOCH_DATA_PARSER.findall(epoch_data) - for key, value in m: - if key == 'reward': - reward = float(value) - self.avg_reward += reward - if reward < self.min_reward: - self.min_reward = reward + elif LastSession.TRAINING_TOKEN in line: + self.train_epochs += 1 - elif reward > self.max_reward: - self.max_reward = reward + elif LastSession.EPOCH_TOKEN in line: + self.epochs += 1 + m = LastSession.EPOCH_PARSER.findall(line) + if m: + epoch_num, epoch_data = m[0] + m = LastSession.EPOCH_DATA_PARSER.findall(epoch_data) + for key, value in m: + if key == 'reward': + reward = float(value) + self.avg_reward += reward + if reward < self.min_reward: + self.min_reward = reward - elif LastSession.PEER_TOKEN in line: - m = self._peer_parser.findall(line) - if m: - name, pubkey, rssi, sid, pwnd_tot, uptime = m[0] - if pubkey not in cache: - self.last_peer = Peer({ - 'session_id': sid, - 'channel': 1, - 'rssi': int(rssi), - 'identity': pubkey, - 'advertisement':{ - 'name': name, - 'pwnd_tot': int(pwnd_tot) - }}) - self.peers += 1 - cache[pubkey] = self.last_peer - else: - cache[pubkey].adv['pwnd_tot'] = pwnd_tot + elif reward > self.max_reward: + self.max_reward = reward + + elif LastSession.PEER_TOKEN in line: + m = self._peer_parser.findall(line) + if m: + name, pubkey, rssi, sid, pwnd_tot, uptime = m[0] + if pubkey not in cache: + self.last_peer = Peer({ + 'session_id': sid, + 'channel': 1, + 'rssi': int(rssi), + 'identity': pubkey, + 'advertisement':{ + 'name': name, + 'pwnd_tot': int(pwnd_tot) + }}) + self.peers += 1 + cache[pubkey] = self.last_peer + else: + cache[pubkey].adv['pwnd_tot'] = pwnd_tot + except Exception as e: + logging.error("error parsing line '%s': %s" % (line, e)) if started_at is not None: self.duration = stopped_at - started_at From a058d2e00d8def533088496e0a2654f31b533e32 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 18 Oct 2019 15:57:40 +0200 Subject: [PATCH 249/346] fix: removed deprecated script --- scripts/create_sibling.sh | 354 -------------------------------------- 1 file changed, 354 deletions(-) delete mode 100755 scripts/create_sibling.sh diff --git a/scripts/create_sibling.sh b/scripts/create_sibling.sh deleted file mode 100755 index a60a42f..0000000 --- a/scripts/create_sibling.sh +++ /dev/null @@ -1,354 +0,0 @@ -#!/usr/bin/env bash -# based on: https://wiki.debian.org/RaspberryPi/qemu-user-static -## and https://z4ziggy.wordpress.com/2015/05/04/from-bochs-to-chroot/ - -set -eu - -echo "THIS SCRIPT IS DEPRECATED, PLEASE REFER TO THE OFFICIAL DOCUMENTATION AT https://pwnagotchi.ai/contributing/#creating-an-image" -exit 1 - -REQUIREMENTS=( wget gunzip git dd e2fsck resize2fs parted losetup qemu-system-x86_64 ) -DEBREQUIREMENTS=( wget gzip git parted qemu-system-x86 qemu-user-static ) -REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")" -TMP_DIR="${REPO_DIR}/tmp" -MNT_DIR="${TMP_DIR}/mnt" -THIS_DIR=$(pwd) - -PWNI_NAME="pwnagotchi" -PWNI_OUTPUT="pwnagotchi.img" -PWNI_SIZE="8" - -OPT_SPARSE=0 -OPT_PROVISION_ONLY=0 -OPT_CHECK_DEPS_ONLY=0 -OPT_IMAGE_PROVIDED=0 -OPT_RASPBIAN_VERSION='latest' -OPT_APTPROXY="" - -SUPPORTED_RASPBIAN_VERSIONS=( 'latest' 'buster' 'stretch' ) - -if [[ "$EUID" -ne 0 ]]; then - echo "Run this script as root!" - exit 1 -fi - -function check_dependencies() { - if [ -f /etc/debian_version ]; - then - echo "[+] Checking Debian dependencies" - - for REQ in "${DEBREQUIREMENTS[@]}"; do - if ! dpkg -s "$REQ" >/dev/null 2>&1; then - echo "Dependency check failed for ${REQ}; use 'apt-get install ${REQ}' to install" - exit 1 - fi - done - fi - - echo "[+] Checking dependencies" - for REQ in "${REQUIREMENTS[@]}"; do - if ! type "$REQ" >/dev/null 2>&1; then - echo "Dependency check failed for ${REQ}" - exit 1 - fi - done - - if ! test -e /usr/bin/qemu-arm-static; then - echo "[-] You need the package \"qemu-user-static\" for this to work." - exit 1 - fi - - if ! systemctl is-active systemd-binfmt.service >/dev/null 2>&1; then - mkdir -p "/lib/binfmt.d" - echo ':qemu-arm:M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xff\xff\xff:/usr/bin/qemu-arm-static:F' > /lib/binfmt.d/qemu-arm-static.conf - systemctl restart systemd-binfmt.service - fi -} - -function get_raspbian() { - VERSION="$1" - - case "$VERSION" in - latest) - URL="https://downloads.raspberrypi.org/raspbian_lite_latest" - ;; - buster) - URL="https://downloads.raspberrypi.org/raspbian/images/raspbian-2019-07-12/2019-07-10-raspbian-buster.zip" - ;; - stretch) - URL="https://downloads.raspberrypi.org/raspbian/images/raspbian-2019-04-09/2019-04-08-raspbian-stretch.zip" - ;; - esac - - echo "[+] Downloading raspbian.zip" - mkdir -p "${TMP_DIR}" - wget --show-progress -qcO "${TMP_DIR}/raspbian.zip" "$URL" - echo "[+] Unpacking raspbian.zip to raspbian.img" - gunzip -c "${TMP_DIR}/raspbian.zip" > "${TMP_DIR}/raspbian.img" -} - -function provide_raspbian() { - echo "[+] Providing path of raspbian file" - mkdir -p "${TMP_DIR}" - echo "[+] Unpacking raspbian.zip to raspbian.img" - gunzip -c "${PWNI_INPUT}" > "${TMP_DIR}/raspbian.img" -} - -function setup_raspbian(){ - # Detect the ability to create sparse files - if [ "${OPT_SPARSE}" -eq 0 ]; then - if ! type "bmaptool" >/dev/null 2>&1; then - echo "[!] bmaptool not available, not creating a sparse image" - else - echo "[+] Defaulting to sparse image generation as bmaptool is available" - OPT_SPARSE=1 - fi - fi - - # Note that we 'extend' the raspbian.img - if [ "${OPT_SPARSE}" -eq 1 ]; - then - # Resize sparse (so that we can use bmaptool later) - echo "[+] Resizing sparse image of ${PWNI_SIZE}GB (1000s)" - truncate -s ${PWNI_SIZE}GB "${TMP_DIR}/raspbian.img" - else - echo "[+] Resizing full image to ${PWNI_SIZE}G" - # Full disk-space using image (appends to raspbian image) - dd if=/dev/zero bs=1G count="${PWNI_SIZE}" >> "${TMP_DIR}/raspbian.img" - fi - - echo "[+] Setup loop device" - mkdir -p "${MNT_DIR}" - LOOP_PATH="$(losetup --find --partscan --show "${TMP_DIR}/raspbian.img")" - PART2_START="$(parted -s "$LOOP_PATH" -- print | awk '$1==2{ print $2 }')" - parted -s "$LOOP_PATH" rm 2 - parted -s "$LOOP_PATH" mkpart primary "$PART2_START" 100% - echo "[+] Check FS" - e2fsck -y -f "${LOOP_PATH}p2" - echo "[+] Resize FS" - resize2fs "${LOOP_PATH}p2" - echo "[+] Device is ${LOOP_PATH}" - echo "[+] Unmount if already mounted with other img" - mountpoint -q "${MNT_DIR}" && umount -R "${MNT_DIR}" - echo "[+] Mount /" - mount -o rw "${LOOP_PATH}p2" "${MNT_DIR}" - echo "[+] Mount /boot" - mount -o rw "${LOOP_PATH}p1" "${MNT_DIR}/boot" - mount --bind /dev "${MNT_DIR}/dev/" - mount --bind /sys "${MNT_DIR}/sys/" - mount --bind /proc "${MNT_DIR}/proc/" - mount --bind /dev/pts "${MNT_DIR}/dev/pts" - cp /usr/bin/qemu-arm-static "${MNT_DIR}/usr/bin" - cp /etc/resolv.conf "${MNT_DIR}/etc/resolv.conf" -} - -function provision_raspbian() { - cd "${MNT_DIR}" - sed -i'' 's/^\([^#]\)/#\1/g' etc/ld.so.preload # add comments - echo "[+] Run chroot commands" - LANG=C LC_ALL=C LC_CTYPE=C chroot . bin/bash -x <<EOF - set -eu - export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - - if [ ! -z "${OPT_APTPROXY}" ]; - then - echo "[+] Using Proxy ${OPT_APTPROXY}" - echo "Acquire::http { Proxy \"${OPT_APTPROXY}\"; }" >/etc/apt/apt.conf.d/99pwnagotchi_proxy - fi - - uname -a - - apt-get -y update - apt-get -y upgrade - apt-get -y install git vim screen build-essential golang python3-pip gawk - apt-get -y install libpcap-dev libusb-1.0-0-dev libnetfilter-queue-dev - apt-get -y install dphys-swapfile libopenmpi-dev libatlas-base-dev - apt-get -y install libjasper-dev libqtgui4 libqt4-test libopenjp2-7 - apt-get -y install tcpdump libilmbase23 libopenexr23 libgstreamer1.0-0 - apt-get -y install libavcodec58 libavformat58 libswscale5 - - # setup dphys-swapfile - echo "CONF_SWAPSIZE=1024" >/etc/dphys-swapfile - systemctl enable dphys-swapfile.service - - # install pwnagotchi - cd /tmp - git clone https://github.com/evilsocket/pwnagotchi.git - rsync -aP pwnagotchi/sdcard/boot/* /boot/ - rsync -aP pwnagotchi/sdcard/rootfs/* / - rm -rf /tmp/pwnagotchi - - # configure pwnagotchi - echo -e "$PWNI_NAME" > /etc/hostname - sed -i "s@^127\.0\.0\.1 .*@127.0.0.1 localhost "$PWNI_NAME" "$PWNI_NAME".local@g" /etc/hosts - sed -i "s@alpha@$PWNI_NAME@g" /etc/motd - - chmod +x /etc/rc.local - - # need armv6l version of tensorflow and opencv-python, not armv7l - # PIP_OPTS="--upgrade --only-binary :all: --abi cp37m --platform linux_armv6l --target /usr/lib/python3.7/site-packages/" - # pip3 install \$PIP_OPTS opencv-python - # Should work for tensorflow too, but BUG: Hash mismatch; therefore: - wget -P /root/ -c https://www.piwheels.org/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl - wget -P /root/ -c https://www.piwheels.org/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl - # we need to install these on first raspberry start... - sed -i '/startup\.sh/i pip3 install --no-deps --force-reinstall --upgrade /root/tensorflow-1.13.1-cp37-none-linux_armv6l.whl /root/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl && rm /root/tensorflow-1.13.1-cp37-none-linux_armv6l.whl /root/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl && sed -i "/tensorflow/d" /etc/rc.local' /etc/rc.local - - # newer version is broken - pip3 install gast==0.2.2 - - </root/pwnagotchi/scripts/requirements.txt xargs -I{} --max-args=1 --max-procs="$(nproc)"\ - pip3 install --progress-bar off {} - - # waveshare - pip3 install spidev RPi.GPIO - - # install bettercap - export GOPATH=/root/go - taskset -c 1 go get -u github.com/bettercap/bettercap - mv "\$GOPATH/bin/bettercap" /usr/bin/bettercap - - # install bettercap caplets (cant run bettercap in chroot) - cd /tmp - git clone https://github.com/bettercap/caplets.git - cd caplets - make install - rm -rf /tmp/caplets - cd /root # fixes getcwd error that was bugging me - - # Re4son-Kernel - echo "deb http://http.re4son-kernel.com/re4son/ kali-pi main" > /etc/apt/sources.list.d/re4son.list - wget -O - https://re4son-kernel.com/keys/http/archive-key.asc | apt-key add - - apt update - apt install -y kalipi-kernel kalipi-bootloader kalipi-re4son-firmware kalipi-kernel-headers libraspberrypi0 libraspberrypi-dev libraspberrypi-doc libraspberrypi-bin - - # Fix PARTUUID - PUUID_ROOT="\$(blkid "\$(df / --output=source | tail -1)" | grep -Po 'PARTUUID="\K[^"]+')" - PUUID_BOOT="\$(blkid "\$(df /boot --output=source | tail -1)" | grep -Po 'PARTUUID="\K[^"]+')" - - # sed regex info: search for line containing / followed by whitespace or /boot (second sed) - # in this line, search for PARTUUID= followed by letters, numbers or "-" - # replace that match with the new PARTUUID - sed -i "/\/[ ]\+/s/PARTUUID=[A-Za-z0-9-]\+/PARTUUID=\$PUUID_ROOT/g" /etc/fstab - sed -i "/\/boot/s/PARTUUID=[A-Za-z0-9-]\+/PARTUUID=\$PUUID_BOOT/g" /etc/fstab - - sed -i "s/root=[^ ]\+/root=PARTUUID=\${PUUID_ROOT}/g" /boot/cmdline.txt - - # delete keys - find /etc/ssh/ -name "ssh_host_*key*" -delete - - # slows down boot - systemctl disable apt-daily.timer apt-daily.service apt-daily-upgrade.timer apt-daily-upgrade.service - - # unecessary services - systemctl disable triggerhappy bluetooth wpa_supplicant - -EOF - sed -i'' 's/^#//g' etc/ld.so.preload - cd "${REPO_DIR}" - umount -R "${MNT_DIR}" - losetup -D "$(losetup -l | awk '/raspbian\.img/{print $1}')" - mv "${TMP_DIR}/raspbian.img" "${PWNI_OUTPUT}" - if [ "${OPT_SPARSE}" -eq 1 ]; - then - bmaptool create -o "${PWNI_OUTPUT}.bmap" "${PWNI_OUTPUT}" - fi -} - -function usage() { - cat <<EOF - -usage: $0 [OPTIONS] - - Options: - -n <name> # Name of the pwnagotchi (default: pwnagotchi) - -i <file> # Provide the path of an already downloaded raspbian image - -o <file> # Name of the img-file (default: pwnagotchi.img) - -s <size> # Size which should be added to second partition (in Gigabyte) (default: 4) - -v <version> # Version of raspbian (Supported: ${SUPPORTED_RASPBIAN_VERSIONS[*]}; default: latest) - -p # Only run provisioning (assumes the image is already mounted) - -d # Only run dependencies checks - -h # Show this help - -EOF - - exit 0 -} - -while getopts "A:n:i:o:s:v:dph" o; do - case "${o}" in - A) - OPT_APTPROXY="${OPTARG}" - ;; - n) - PWNI_NAME="${OPTARG}" - ;; - i) - PWNI_INPUT="${OPTARG}" - OPT_IMAGE_PROVIDED=1 - ;; - o) - PWNI_OUTPUT="${OPTARG}" - ;; - s) - PWNI_SIZE="${OPTARG}" - ;; - p) - OPT_PROVISION_ONLY=1 - ;; - d) - OPT_CHECK_DEPS_ONLY=1 - ;; - v) - if [[ "${SUPPORTED_RASPBIAN_VERSIONS[*]}" =~ ${OPTARG} ]]; then - OPT_RASPBIAN_VERSION="${OPTARG}" - else - usage - fi - ;; - h) - usage - ;; - *) - usage - ;; - esac -done -shift $((OPTIND-1)) - -if [[ "$OPT_PROVISION_ONLY" -eq 1 ]]; then - provision_raspbian - exit 0 -elif [[ "$OPT_CHECK_DEPS_ONLY" -eq 1 ]]; then - check_dependencies - exit 0 -fi - -check_dependencies - -if [[ "$OPT_IMAGE_PROVIDED" -eq 1 ]]; then - provide_raspbian -else - get_raspbian "$OPT_RASPBIAN_VERSION" -fi - -setup_raspbian -provision_raspbian - -#Make a baby with a random gender, maybe do something fun with this later! -gender[0]="boy" -gender[1]="girl" - -rand=$[ $RANDOM % 2 ] - -echo -e "[+] Congratz, it's a ${gender[$rand]} (⌐■_■)!" -echo -e "[+] One more step: dd if=../${PWNI_OUTPUT} of=<PATH_TO_SDCARD> bs=4M status=progress" - -if [ "${OPT_SPARSE}" -eq 1 ]; -then - echo -e "[t] To transfer use: rsync -vaS --progress $(whoami)@$(hostname -f):${THIS_DIR}/../${PWNI_OUTPUT} <DEST>" - echo -e "[t] To burn with bmaptool: bmaptool copy ~/${PWNI_OUTPUT} /dev/<DEVICE>" -fi - -# Helpful OSX reminder -echo -e "[t] Mac: use 'diskutil list' to figure out which device to burn to; 'diskutil unmountDisk' to unmount that disk'; then use /dev/rdiskX (note the 'r') for faster transfer" From f4a59549fa86dd0174a045debb18098219bcf949 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 18 Oct 2019 15:58:53 +0200 Subject: [PATCH 250/346] fix: update process will be reflash based for a while --- pwnagotchi/defaults.yml | 4 -- pwnagotchi/plugins/default/auto-update.py | 60 ----------------------- 2 files changed, 64 deletions(-) delete mode 100644 pwnagotchi/plugins/default/auto-update.py diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 2308496..986d2fc 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -17,10 +17,6 @@ main: report: false # don't report pwned networks by default! exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs) - YourHomeNetworkHere - auto-update: - enabled: false - system: false # set to true to also enable system updates via apt - interval: 1 # every day auto-backup: enabled: false interval: 1 # every day diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py deleted file mode 100644 index 43ea6ff..0000000 --- a/pwnagotchi/plugins/default/auto-update.py +++ /dev/null @@ -1,60 +0,0 @@ -__author__ = 'evilsocket@gmail.com' -__version__ = '1.0.0' -__name__ = 'auto-update' -__license__ = 'GPL3' -__description__ = 'This plugin performs an "apt update && apt upgrade" when internet is availaible.' - -import logging -import subprocess -from pwnagotchi.utils import StatusFile - -OPTIONS = dict() -READY = False -STATUS = StatusFile('/root/.auto-update') - - -def on_loaded(): - global READY - - if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None): - logging.error("auto-update: Interval is not set.") - return - - READY = True - - -def run(cmd): - return subprocess.Popen(cmd, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, - executable="/bin/bash") - - -def on_internet_available(agent): - global STATUS - - if READY: - if STATUS.newer_then_days(OPTIONS['interval']): - return - - display = agent.view() - - try: - display.set('status', 'Updating ...') - display.update() - - logging.info("auto-update: updating pwnagotchi ...") - run('pip3 install --upgrade --upgrade-strategy only-if-needed pwnagotchi').wait() - - if OPTIONS['system']: - logging.info("auto-update: updating packages index ...") - run('apt update -y').wait() - - logging.info("auto-update: updating packages ...") - run('apt upgrade -y').wait() - - logging.info("auto-update: complete.") - STATUS.update() - except Exception as e: - logging.exception("auto-update ERROR") - - display.set('status', 'Updated!') - display.update() From e220a869e018a96b757e7415e8246d8aacfe397a Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 18 Oct 2019 16:34:58 +0200 Subject: [PATCH 251/346] new: user config is now copied from /boot/config.yml --- pwnagotchi/utils.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 127761c..af6ea59 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -31,11 +31,18 @@ def load_config(args): ref_defaults_file = os.path.join(os.path.dirname(pwnagotchi.__file__), 'defaults.yml') ref_defaults_data = None - if not os.path.exists(args.config): + # check for a config.yml file on /boot/ + if os.path.exists("/boot/config.yml"): # logging not configured here yet + print("installing /boot/config.yml to %s ...", args.user_config) + os.rename("/boot/config.yml", args.user_config) + + # if not config is found, copy the defaults + if not os.path.exists(args.config): print("copying %s to %s ..." % (ref_defaults_file, args.config)) shutil.copy(ref_defaults_file, args.config) else: + # check if the user messed with the defaults with open(ref_defaults_file) as fp: ref_defaults_data = fp.read() @@ -46,9 +53,11 @@ def load_config(args): print("!!! file in %s is different than release defaults, overwriting !!!" % args.config) shutil.copy(ref_defaults_file, args.config) + # load the defaults with open(args.config) as fp: config = yaml.safe_load(fp) + # load the user config if os.path.exists(args.user_config): with open(args.user_config) as fp: user_config = yaml.safe_load(fp) @@ -131,6 +140,7 @@ def blink(times=1, delay=0.3): time.sleep(delay) led(True) + class WifiInfo(Enum): """ Fields you can extract from a pcap file @@ -141,6 +151,7 @@ class WifiInfo(Enum): CHANNEL = 3 RSSI = 4 + class FieldNotFoundError(Exception): pass @@ -172,7 +183,7 @@ def extract_from_pcap(path, fields): if hasattr(packet[Dot11], 'addr3'): results[field] = packet[Dot11].addr3 break - else: # magic + else: # magic raise FieldNotFoundError("Could not find field [BSSID]") except Exception: raise FieldNotFoundError("Could not find field [BSSID]") @@ -188,7 +199,7 @@ def extract_from_pcap(path, fields): if packet.haslayer(Dot11Elt) and hasattr(packet[Dot11Elt], 'info'): results[field] = packet[Dot11Elt].info.decode('utf-8') break - else: # magic + else: # magic raise FieldNotFoundError("Could not find field [ESSID]") except Exception: raise FieldNotFoundError("Could not find field [ESSID]") @@ -202,9 +213,9 @@ def extract_from_pcap(path, fields): if packet.haslayer(Dot11Beacon) and hasattr(packet[Dot11Beacon], 'network_stats'): stats = packet[Dot11Beacon].network_stats() if 'crypto' in stats: - results[field] = stats['crypto'] # set with encryption types + results[field] = stats['crypto'] # set with encryption types break - else: # magic + else: # magic raise FieldNotFoundError("Could not find field [ENCRYPTION]") except Exception: raise FieldNotFoundError("Could not find field [ENCRYPTION]") From fb4d46d71a5793a9c39b7a16ba7ce4c9598dab75 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 18 Oct 2019 17:43:45 +0200 Subject: [PATCH 252/346] fix: fixed Invalid cross-device link error --- pwnagotchi/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index af6ea59..419d80d 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -35,7 +35,8 @@ def load_config(args): if os.path.exists("/boot/config.yml"): # logging not configured here yet print("installing /boot/config.yml to %s ...", args.user_config) - os.rename("/boot/config.yml", args.user_config) + # https://stackoverflow.com/questions/42392600/oserror-errno-18-invalid-cross-device-link + shutil.move("/boot/config.yml", args.user_config) # if not config is found, copy the defaults if not os.path.exists(args.config): From 6cecca67a47aa82d0693aefe6c5b5b1e482d7f16 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Fri, 18 Oct 2019 19:20:25 +0200 Subject: [PATCH 253/346] Fix str comparison and add sleep --- pwnagotchi/plugins/default/bt-tether.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 7c919aa..e53dab5 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -132,7 +132,7 @@ class BTNap: device = ifaces.get(BTNap.IFACE_DEV) if device is None: continue - if device['Address'] == device_address and path.startswith(path_prefix): + if str(device['Address']) == device_address and path.startswith(path_prefix): obj = bus.get_object(BTNap.IFACE_BASE, path) return dbus.Interface(obj, BTNap.IFACE_DEV) raise BTError('Bluetooth device not found') @@ -245,6 +245,7 @@ class BTNap: try: dev_remote.Pair() logging.info('BT-TETHER: Successful paired with device ;)') + time.sleep(10) # wait for bnep0 except Exception: # can fail because of AlreadyExists etc. pass From eea4feee712a797edb740e44b33620aa9d00fe37 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Fri, 18 Oct 2019 19:51:07 +0200 Subject: [PATCH 254/346] new: bump pwngrid binary to 1.8.0 --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index c2f181b..e41efd4 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.6/pwngrid_linux_armv6l_1.7.6.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.8.0/pwngrid_linux_armhf_v1.8.0.zip" apt: hold: - firmware-atheros From 86cbe01081693780ee4658fad505b3731cc0bac9 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Fri, 18 Oct 2019 20:31:10 +0200 Subject: [PATCH 255/346] need old gast version --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f364dbc..78538c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,5 @@ numpy==1.17.2 inky==0.0.5 smbus2==0.3.0 Pillow==5.4.1 -spidev==3.4 \ No newline at end of file +spidev==3.4 +gast==0.2.2 From 2324423cab7e4f03362e994de7ee4a14607c390a Mon Sep 17 00:00:00 2001 From: Szymon Borecki <szymex73@gmail.com> Date: Sat, 19 Oct 2019 00:55:48 +0200 Subject: [PATCH 256/346] Add polish translation files Signed-off-by: Szymon Borecki <szymex73@gmail.com> --- pwnagotchi/locale/pl/LC_MESSAGES/voice.mo | Bin 0 -> 4369 bytes pwnagotchi/locale/pl/LC_MESSAGES/voice.po | 215 ++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 pwnagotchi/locale/pl/LC_MESSAGES/voice.mo create mode 100644 pwnagotchi/locale/pl/LC_MESSAGES/voice.po diff --git a/pwnagotchi/locale/pl/LC_MESSAGES/voice.mo b/pwnagotchi/locale/pl/LC_MESSAGES/voice.mo new file mode 100644 index 0000000000000000000000000000000000000000..303c60b3085236426b18441ada173fef9f1c8d21 GIT binary patch literal 4369 zcmbW3U2Gjk700Jvv@uYi1zISyXB)^z<9lN#g(N1>xQR&|Y{!c2hUNw4?vC%BySp=2 z`;qNl>pox_f&>x|RRvL{Dj-y;f(NRo5{g148efnqRYC{>st^K%1Qj1INIW1F_@CLm zw$nFQdHma%ojK>sIcMhF-`;ckvjWc%w2z?u@fIP@fZx9j|M2|zb|H3vZ-O5H{{ij= zcibVw2f>5jJHgW+e}4jeFBpNlzyZi|zY87)e+&}%FYsRQfp=8v_Ji+6e+Ik{T&fv@ z???a3;GN*NK$d?6yav7m?gArxya#*+WV>DfdEJk|`@x@sEcY+q7<k)VLc9mO8^n-! z5cI%ZAkRAmvfW<<2^@g?z~@1>^EHs||24?-{t6Pf1I}XokANQqr$LtY1(4?#;D^Dl zfo#VMAiMe{@O|LR;N9R)LAXf#5`@deZ|eT<L2Oz431qwfUibf5_um594|hN~%ef!C z?-n6;gRJi{1nWcKY48y+09o%hKr9vCuD^e;9v^}{?^oc*!CT)Y#1yy(+ynX`@Bix{ z`};+Z<-Jmm{|sb5{|)5)cnf5^?u8TCj)%e5z@y-9@J@W>`0fYUZ^uCP+vh=^AAt{m zUjcca-vN2v%OFIH*TIj0Z-5-n|A0F|4@R*4GvHn@1UU}Rf-L6;An)S_$aee=<T&01 zW4Qki$n#EuY|nX+?J*$x^{XJq@fnc!|4k68#fu=v@rNMC>lKjW_mi5h*WZ6pe}AJM z{{zVWd=q57{{|68r~6j$02)90(0JbH;k}Kc9YLE!<2|q+*cP^fpC%gH!1nXQ_VXEP zpm7d7gf@CUffv@l{dp7}-V0KG^FjDroOyuHIs2C5gtT#f;TW(V`Pq-gG2-WwXdIUZ zxr2v&$9ckMmh*1((C7t(D>fgFJ;$AMaRQBFco2=x%)@Ah(D)pR=~+sY49cdMPE+e^ znQ7Ce%+gxXk=Z~Tw>BE5LQ&ghv}W>nKt>hRktRqxGE{55C{JWG@Wcrv^Q@yxru|CD zS}gs6IB9J_Mz=dz6YnD#o9ojtaZ36jVI?g_(x}W6r&JWd03%~H@MuB%iVcdD%BZw? zv;Kl<Wo;!zD%+h>OVMhg6)a;J<v96JV^l<I^~->CiYZEY^rX!bnqH(rM-ip6gaOio zX;Yli<21WFrp3ZY?Ah;VkCrl-IKMQTO)*QW3NlkmGO|WdHP$N(?;%mEHc>Q7GR91Z zX`vfbbI?;yWr>PaRK9JzBQLPqAv4bX4z1?KbE_lm#@RWMURBj#Dg*cNf=Wu5K9YVx z!2!w;EMZx&gVqAXCezjo2=Ss&`yq^33#v#31M(xdD@#4>7|UhE2EfZ}vd#sCIUMfV ztxk%}6s>K=Z?KgMkh2roYqz=g$hIrOdfM4Rq_LNti!<#^!xHfpr}H$UK>0d=aR>wS zbLK#?3Q{tb&gsu;Pkav6w-8U>oSRS$@rGPlrkUF+5^Bz6L)$tZNGfC$dScGnkeABD z=J3UywN&}(Al)nGWq=KBW_G>RF)<HEOj1v&#JyHCxSNW3ou(@A#C%D9qGQEr<qWs0 z<mZXDsm@9l^aa`LZAPQgSdGJ}fkcYB!<3t>dV}|ny9>5ugA#TlG@SYH7kWDDAUJOI znO-`MWI?DpEWAJrsYondk&JX!wXEGt*QJimU<{Pe)PAvq083LsZC2PMOg+yN%g8RH z_!N(Zc|Y~7u-w7B+a9ME2MU6)&A6;Ha-|bHt!<q1R>_{^V=GpS=CjW4yxb_6v`TOE z^_<ksK$oQ3+;Waj5Kgk<+-4^{=81D1YkR5Fu_dpKlh+haj;<+BobUI!;jh0fw%k3> z|J(<jfN32ZefKXFu{L?8YNP^|SQFGp#m(xR=j)-kPJqS4c9owsW`o8PNDQ2`DVk_b zG$+T-s-8`<#(dh=LF4hfoi>*36pbyOX`EFTG|z0Dz)6~-0~3=+8j}-^i6b<5XzI}e z`z8)dOpMLRG;1s;(xj2hY%)b@zl_yWhYr%w(c7_hEOpfMZTuNbH|<=uRbyGnc#7tx z7f!BBpPZqib92Wc-Ly%2Yt9t)Y^2l9*!=AL%$C`c&55z&)?~<~#&X$%Wf^X({m4CS zKE*d_lB(>}dA8a(yfu#vTvbV9#`rcsDo)Yimd?h+(k#~tTgavuere;P-&aN}O7+I& zSFiL(rDZAasbrk`fo+vA2pLtKvCX?>tG1lA{oy*xk#TkQ#R=Pja_cw6d0x*2%N^m- zVq0)w3G*(*R`<=uWltRMtKoASm+Cb1XkHK3E%~quPU#!DacNt1>DGqAM2pL1_%w@3 z`ej%8`o`7adW=iyl<JOCD2*JhZ>ut0J}8Ba3^IPXxN)`1l_u8E5%i%8Rv~%aQl6=9 zE;cI(N`Q>DWS4PqIo{8^Fp#}RnGA8|$2J>%Tr3Qv;da=#>{BJJDHd|*VTEoD%UH@1 zi6`xk;y?y-Ado*!N6Iawlp_K!X_fLfw|aQJ^tFmTahf;9GUGfmcC*`(Nzk|Ts1pa1 z5cvq(g0~dHy;X%Y6=l-TeYhQ;w-ut$6`~tg3l!U)%|iGS6R_LibzCVol_Oh}D3RL( zL7Mtq8B{Vy382!g%H-t!Kc(rMg4DcCF}tm<N_c~X*jbAmU)83^k&3vkN0V@Axouh$ zuPQB(KYmw_iQ^p`46h-PjHI+DeRemJyB%#@43OK-<6EWa=G5N4WgIc*CMcMs&_=4g zad~T~(&>h=b&MljuNmAqC2E%KZd{~(E3tvmcyqam2#2z;?rd;IoDZ<S*wi`j>XkZ> z+ivOwgn^J{YWOrtm&Ff6oTP#a8o#5CLDhl|vuaE18NY+yaeW0}LTgJRV=uXMS>a;l zLw7ZmPa4i7tUw_{C&^s)vsKIZbyKOVtPU&%dC%3g(>NDFKD>@Z)c7UAWo=0-XyC$z zFch*{GP;$PZPhoj8>t?y)REwVVyk0vS)Y4iC9bL)(y)!W^~HhY4-D>vP}T#rFs%aQ zeP}o99PXQtudHp3JIOns6h0`RHubs0!3A!^b#=iZ4dJBWdQ;4dE}{Ra!nOIHF;Y~l e%A2f!;uhlfkE|))uF{319^|N7zh4g5J@H?cEV|?X literal 0 HcmV?d00001 diff --git a/pwnagotchi/locale/pl/LC_MESSAGES/voice.po b/pwnagotchi/locale/pl/LC_MESSAGES/voice.po new file mode 100644 index 0000000..d616bc3 --- /dev/null +++ b/pwnagotchi/locale/pl/LC_MESSAGES/voice.po @@ -0,0 +1,215 @@ +# Polish voice data for pwnagotchi. +# Copyright (C) 2019 +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR szymex73 <szymex73@gmail.com>, 2019. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 0.0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-09 17:42+0200\n" +"PO-Revision-Date: 2019-10-09 17:42+0200\n" +"Last-Translator: szymex73 <szymex73@gmail.com>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: polish\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "ZzzzZZzzzzZzzz" +msgstr "ZzzzZZzzzzZzzz" + +msgid "Hi, I'm Pwnagotchi! Starting ..." +msgstr "Hej, jestem Pwnagotchi! Uruchamianie ..." + +msgid "New day, new hunt, new pwns!" +msgstr "Nowy dzień, nowe łowy, nowe pwny!" + +msgid "Hack the Planet!" +msgstr "Hakujmy planetę!" + +msgid "AI ready." +msgstr "SI gotowa." + +msgid "The neural network is ready." +msgstr "Sieć neuronowa jest gotowa." + +#, python-brace-format +msgid "Hey, channel {channel} is free! Your AP will say thanks." +msgstr "Hej, kanał {channel} jest wolny! Twój AP mi podziękuje." + +msgid "I'm bored ..." +msgstr "Nudzi mi się ..." + +msgid "Let's go for a walk!" +msgstr "Chodźmy na spacer!" + +msgid "This is the best day of my life!" +msgstr "To jest najlepszy dzień w moim życiu!" + +msgid "Shitty day :/" +msgstr "Ten dzień jest do dupy :/" + +msgid "I'm extremely bored ..." +msgstr "Straaaasznie się nudzę ..." + +msgid "I'm very sad ..." +msgstr "Jest mi bardzo smutno ..." + +msgid "I'm sad" +msgstr "Jest mi smutno" + +msgid "I'm living the life!" +msgstr "Cieszę się życiem!" + +msgid "I pwn therefore I am." +msgstr "Pwnuje więc jestem." + +msgid "So many networks!!!" +msgstr "Jak dużo sieci!!!" + +msgid "I'm having so much fun!" +msgstr "Ale jest super!" + +msgid "My crime is that of curiosity ..." +msgstr "Moją zbrodnią jest ciekawość ..." + +#, python-brace-format +msgid "Hello {name}! Nice to meet you. {name}" +msgstr "Cześć {name}! Miło cię poznać. {name}" + +#, python-brace-format +msgid "Unit {name} is nearby! {name}" +msgstr "Jednostka {name} jest niedaleko! {name}" + +#, python-brace-format +msgid "Uhm ... goodbye {name}" +msgstr "Umm ... żegnaj {name}" + +#, python-brace-format +msgid "{name} is gone ..." +msgstr "{name} zniknął ..." + +#, python-brace-format +msgid "Whoops ... {name} is gone." +msgstr "Ups ... {name} zniknął." + +#, python-brace-format +msgid "{name} missed!" +msgstr "{name} przeoczył!" + +msgid "Missed!" +msgstr "Spóźniony!" + +msgid "Nobody wants to play with me ..." +msgstr "Nikt się nie chce ze mną bawić ..." + +msgid "I feel so alone ..." +msgstr "Czuję się tak samotnie ..." + +msgid "Where's everybody?!" +msgstr "Gdzie są wszyscy?!" + +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "Zdrzemnę się przez {secs}s ..." + +msgid "Zzzzz" +msgstr "Zzzzz" + +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "ZzzZzzz ({secs}s)" + +msgid "Good night." +msgstr "Dobranoc." + +msgid "Zzz" +msgstr "Zzz" + +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "Czekam {secs}s ..." + +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "Rozglądam się ({secs}s)" + +#, python-brace-format +msgid "Hey {what} let's be friends!" +msgstr "Hej {what}, zostańmy przyjaciółmi!" + +#, python-brace-format +msgid "Associating to {what}" +msgstr "Łączenie się z {what}" + +#, python-brace-format +msgid "Yo {what}!" +msgstr "Ej {what}!" + +#, python-brace-format +msgid "Just decided that {mac} needs no WiFi!" +msgstr "Według mnie {mac} nie potrzebuje WiFi!" + +#, python-brace-format +msgid "Deauthenticating {mac}" +msgstr "Rozłączam {mac}" + +#, python-brace-format +msgid "Kickbanning {mac}!" +msgstr "Banowanie {mac}!" + +#, python-brace-format +msgid "Cool, we got {num} new handshake{plural}!" +msgstr "Super, zdobylismy {num} handshake{plural}!" + +msgid "Ops, something went wrong ... Rebooting ..." +msgstr "Ups, coś się stało ... Restartuję ..." + +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "Wyrzucono {num} stacji\n" + +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "Zdobyto {num} przyjaciół\n" + +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "Zdobyto {num} handshakow\n" + +msgid "Met 1 peer" +msgstr "Spotkano 1 kolegę" + +#, python-brace-format +msgid "Met {num} peers" +msgstr "Spotkano {num} kolegów" + +#, python-brace-format +msgid "" +"I've been pwning for {duration} and kicked {deauthed} clients! I've also met " +"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" +msgstr "" +"Pwnowalem przez {duration} i wyrzuciłem {deauthed} klientów! Spotkałem także " +"{associated} nowych przyjaciół i zjadłem {handshakes} handshaków! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" + +msgid "hours" +msgstr "godzin" + +msgid "minutes" +msgstr "minut" + +msgid "seconds" +msgstr "sekund" + +msgid "hour" +msgstr "godzina" + +msgid "minute" +msgstr "minuta" + +msgid "second" +msgstr "sekunda" From 0ea67cb97faaa141c5acd5ef43a99550bc803c9a Mon Sep 17 00:00:00 2001 From: root <ggiraudon@prism19.com> Date: Sat, 19 Oct 2019 04:49:38 +0100 Subject: [PATCH 257/346] Adding support for Waveshare LCD Hat (ST7789 chip) --- pwnagotchi/ui/display.py | 4 + pwnagotchi/ui/hw/__init__.py | 5 + pwnagotchi/ui/hw/lcdhat.py | 46 +++++ .../ui/hw/libs/waveshare/lcdhat/ST7789.py | 165 ++++++++++++++++++ .../ui/hw/libs/waveshare/lcdhat/ST7789.pyc | Bin 0 -> 5124 bytes .../ui/hw/libs/waveshare/lcdhat/__init__.py | 0 .../ui/hw/libs/waveshare/lcdhat/config.py | 76 ++++++++ pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py | 28 +++ 8 files changed, 324 insertions(+) create mode 100644 pwnagotchi/ui/hw/lcdhat.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.pyc create mode 100644 pwnagotchi/ui/hw/libs/waveshare/lcdhat/__init__.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 597691f..479324b 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -123,6 +123,10 @@ class Display(View): def is_oledhat(self): return self._implementation.name == 'oledhat' + def is_lcdhat(self): + return self._implementation.name == 'lcdhat' + + def is_waveshare_any(self): return self.is_waveshare_v1() or self.is_waveshare_v2() diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py index 94dc671..ee00a73 100644 --- a/pwnagotchi/ui/hw/__init__.py +++ b/pwnagotchi/ui/hw/__init__.py @@ -1,6 +1,7 @@ from pwnagotchi.ui.hw.inky import Inky from pwnagotchi.ui.hw.papirus import Papirus from pwnagotchi.ui.hw.oledhat import OledHat +from pwnagotchi.ui.hw.lcdhat import LcdHat from pwnagotchi.ui.hw.waveshare1 import WaveshareV1 from pwnagotchi.ui.hw.waveshare2 import WaveshareV2 @@ -16,6 +17,10 @@ def display_for(config): if config['ui']['display']['type'] == 'oledhat': return OledHat(config) + if config['ui']['display']['type'] == 'lcdhat': + return LcdHat(config) + + elif config['ui']['display']['type'] == 'waveshare_1': return WaveshareV1(config) diff --git a/pwnagotchi/ui/hw/lcdhat.py b/pwnagotchi/ui/hw/lcdhat.py new file mode 100644 index 0000000..75779a3 --- /dev/null +++ b/pwnagotchi/ui/hw/lcdhat.py @@ -0,0 +1,46 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class LcdHat(DisplayImpl): + def __init__(self, config): + super(LcdHat, self).__init__(config, 'lcdhat') + self._display = None + + def layout(self): + fonts.setup(10, 9, 10, 35) + self._layout['width'] = 240 + self._layout['height'] = 240 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (175, 0) + self._layout['line1'] = [0, 14, 240, 14] + self._layout['line2'] = [0, 108, 240, 108] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 109) + self._layout['mode'] = (215, 109) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.Medium, + 'max': 20 + } + + return self._layout + + def initialize(self): + logging.info("initializing lcdhat display") + from pwnagotchi.ui.hw.libs.waveshare.lcdhat.epd import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + + def render(self, canvas): + self._display.display(canvas) + + def clear(self): + self._display.clear() diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py new file mode 100644 index 0000000..0edd1d1 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py @@ -0,0 +1,165 @@ +import spidev +import RPi.GPIO as GPIO +import time +import numpy as np + + +class ST7789(object): + """class for ST7789 240*240 1.3inch OLED displays.""" + + def __init__(self,spi,rst = 27,dc = 25,bl = 24): + self.width = 240 + self.height = 240 + #Initialize DC RST pin + self._dc = dc + self._rst = rst + self._bl = bl + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(self._dc,GPIO.OUT) + GPIO.setup(self._rst,GPIO.OUT) + GPIO.setup(self._bl,GPIO.OUT) + GPIO.output(self._bl, GPIO.HIGH) + #Initialize SPI + self._spi = spi + self._spi.max_speed_hz = 40000000 + + """ Write register address and data """ + def command(self, cmd): + GPIO.output(self._dc, GPIO.LOW) + self._spi.writebytes([cmd]) + + def data(self, val): + GPIO.output(self._dc, GPIO.HIGH) + self._spi.writebytes([val]) + + def Init(self): + """Initialize dispaly""" + self.reset() + + self.command(0x36) + self.data(0x70) #self.data(0x00) + + self.command(0x3A) + self.data(0x05) + + self.command(0xB2) + self.data(0x0C) + self.data(0x0C) + self.data(0x00) + self.data(0x33) + self.data(0x33) + + self.command(0xB7) + self.data(0x35) + + self.command(0xBB) + self.data(0x19) + + self.command(0xC0) + self.data(0x2C) + + self.command(0xC2) + self.data(0x01) + + self.command(0xC3) + self.data(0x12) + + self.command(0xC4) + self.data(0x20) + + self.command(0xC6) + self.data(0x0F) + + self.command(0xD0) + self.data(0xA4) + self.data(0xA1) + + self.command(0xE0) + self.data(0xD0) + self.data(0x04) + self.data(0x0D) + self.data(0x11) + self.data(0x13) + self.data(0x2B) + self.data(0x3F) + self.data(0x54) + self.data(0x4C) + self.data(0x18) + self.data(0x0D) + self.data(0x0B) + self.data(0x1F) + self.data(0x23) + + self.command(0xE1) + self.data(0xD0) + self.data(0x04) + self.data(0x0C) + self.data(0x11) + self.data(0x13) + self.data(0x2C) + self.data(0x3F) + self.data(0x44) + self.data(0x51) + self.data(0x2F) + self.data(0x1F) + self.data(0x1F) + self.data(0x20) + self.data(0x23) + + self.command(0x21) + + self.command(0x11) + + self.command(0x29) + + def reset(self): + """Reset the display""" + GPIO.output(self._rst,GPIO.HIGH) + time.sleep(0.01) + GPIO.output(self._rst,GPIO.LOW) + time.sleep(0.01) + GPIO.output(self._rst,GPIO.HIGH) + time.sleep(0.01) + + def SetWindows(self, Xstart, Ystart, Xend, Yend): + #set the X coordinates + self.command(0x2A) + self.data(0x00) #Set the horizontal starting point to the high octet + self.data(Xstart & 0xff) #Set the horizontal starting point to the low octet + self.data(0x00) #Set the horizontal end to the high octet + self.data((Xend - 1) & 0xff) #Set the horizontal end to the low octet + + #set the Y coordinates + self.command(0x2B) + self.data(0x00) + self.data((Ystart & 0xff)) + self.data(0x00) + self.data((Yend - 1) & 0xff ) + + self.command(0x2C) + + def ShowImage(self,Image,Xstart,Ystart): + """Set buffer to value of Python Imaging Library image.""" + """Write display buffer to physical display""" + imwidth, imheight = Image.size + if imwidth != self.width or imheight != self.height: + raise ValueError('Image must be same dimensions as display \ + ({0}x{1}).' .format(self.width, self.height)) + img = np.asarray(Image) + pix = np.zeros((self.width,self.height,2), dtype = np.uint8) + pix[...,[0]] = np.add(np.bitwise_and(img[...,[0]],0xF8),np.right_shift(img[...,[1]],5)) + pix[...,[1]] = np.add(np.bitwise_and(np.left_shift(img[...,[1]],3),0xE0),np.right_shift(img[...,[2]],3)) + pix = pix.flatten().tolist() + self.SetWindows ( 0, 0, self.width, self.height) + GPIO.output(self._dc,GPIO.HIGH) + for i in range(0,len(pix),4096): + self._spi.writebytes(pix[i:i+4096]) + + def clear(self): + """Clear contents of image buffer""" + _buffer = [0xff]*(self.width * self.height * 2) + self.SetWindows ( 0, 0, self.width, self.height) + GPIO.output(self._dc,GPIO.HIGH) + for i in range(0,len(_buffer),4096): + self._spi.writebytes(_buffer[i:i+4096]) diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.pyc b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1837ca2a4eec4c9252d12c8a890cadbc766433c7 GIT binary patch literal 5124 zcmcIoOK%*<5$>5?K9(Xyk*27prJ*13O14Qkc49jg{GuF508Pn5C9;SJgW=9Bx0&79 z&GgU|De@BO9RGwMfKLhFgD(z{OOOOU`zP4{AV4m;Ip0^kGdrYB9m0gmuC40ou71?B zxAL!vv5hzXc)y|ge-(Vci)KCqi16>Iwo<DF?x+n{t-3j@qE;(8URA3@DsojkteTFB zD(bOPkCeK%IzlNc^(rLl@9?*r4Fg@d^ZM&=yjd(v?)zva0En?9L;zwXAb?n=svg_* zhA4Wqs-j^*u<D2)!zvmT1S{4A8CB7kAT<>o6J$*JEA??MbOoT%4vq1e-PE(&@w^|u zcI7gf_saaMI%%}LTT35Y^CE3}?Qq-7>vNc-&k&sE^dSH?UfUv?`3nGcysH$>(MLd5 zq{mge_y-GQRgkL6hSbKez!CMxQ4Z-*mDLJjj017$WE@kETsy?}R(`P83LT6c7YVj^ zLL}_k9UkY+Qu=WfO!B%{>O9D@{wJVTt7~rk1Td_u)RvC2mME?j>-AQaabyrRGAb@e zO}}HUEgjcy-@KKLfMepU(~V*o{r=*I*)iL<6{d+!){Th%dDzA<E`qPko{YWqi#xJd zcQfm4W+L;(&FeSpYNn?%s<0D22uv@IqoB1@=X_zCxZM;P9L8O8b1uHIS$?gx>wrpt znFqGe>UQFVo?htdJXl)17TkF6PSC*47kb-StD7uX`<MqciYY;$6P*RY7;tkIKshz1 z=Az+q!kKVp?FT&#uF1x&ETWm00W#dWD1n*<Ua@)sR#h8A3JO|x!~{eu4rVOZ={3H9 z31XBd=UcjU*PkT8-E5^gi`TZZ*wneGKb4PabfTl3o9b+IJDo6z&I2`6yuva6uLtyx zV-Y!>Plr2jrcc5n>XK)O!YuUgJvb1rn~fJJ8as=-rk_YabBG}*S^bbecydKqeGZff zPwsQ71%Ww31%WxkhY~gv^aYHThYbiwvq7RiY^)dt4xE3mgYkj@2LWKfIbIOpOcn(8 zcA_A_nJNfyrV9d`rwRg`nSuc4WI=#4TM*!!DhO~+mx(h4fnjG00>k#rIalVKFB4Cf zi3?@oVwv#D#9Wzprc69rCiboKT$%HHnfOsb;0P}i#1oIO&khbh!a!M&w(kh_@;aBw z#N{%vZ=?H&f#x83-w|Fc1mHY_y@7a1IMK^U8VsUl8sH{^q7K`7Czg~Mwzu_9(XD#~ zKO<n2)?W}XfNM0RUnTgG;3ou3p*F?-mDo!Je<Pqv>c10kE&UGykKii;MnL^F!Dj@2 zAozxWKCT%tHDjTEir__pcL?qfED<oiXyzw<k>FW^Zwamud_=H7Fh_6+U|F)IB(5|@ z>hhTg{PO^HZdF3>(Lye_&O3b$-)09uxnu5xGm$qW8u{e(Vc&K}KKlFLoOk%g#d>ao zM`A-Lv_Y{0<cHH3(lByZKR_NF0&8at^Q0j*8SWc`0BegALnZpWZZB(DGec#z{&42= z>)-A?erLJPG*!xyyh2GLQX6Et)2}qzaoqDyf%!kumH#wk;jGHgu$4QD`?T%*MdF_@ zfKgj2#5^3srOHVJ6##19cMs!AIefZ20>TE_<)ARQ^%I1llCG+Q!faHESQPR~m-S^p z>#*Muqb*oJqKDeVJa7Q|xvf1|uzYN?FwLZ(yJu0V<>NSsL@W1z9L-T!`K-j*U7bYT zEwcy8%mOGe>?x??WQ7JUnA_muH8gH*5zYJ=hrxwIA-{6fURCX(0+RwAdH&ZGR&2ZI zkxUOVU1r?{^G2=4&b*F;T|-^JbyNaNkg^^!(X*G=_vOJ*b`wHu1<$PR?2X8*nn2X| zc+EgDLo5WFt~$eVA%6w*c_Rf60<buWdAxi|ldf_bxj^E03Rtm&sVE{~9jG1-2zLwN zGGZV+pL!LGZJ?aM4Inish5hq@cu2ta?R6D5Q-#N$P#aS+Vp<VL>3}%!;4PKSaCml- zo5yXjcB!aq_vh5!s0^P~8>h(I1&R0EF?DK=Uh$hxox9OEtdpVLyaoUR@YXh)%{cY4 zu7}!iGxoYo@3s{1-px+9jwgq=q}S3g-S#x-c-~O|WW`=*(_nt=nXtpS(uotJyNU5a zlfVDGgMamhSN0w}d}Z&_yk@N^sUXU>d-1$=lz*esI*R1I3dwH>6y8<Kb!HrK0E3L_ z)1iD3Lu>vexBo$!c2jX1JW)HLea0j`d0B-fOw({%*4&BHu6;{w>LhzZUIk$k$!lv( zXIt9D0m^ovQ@-8;)6&gM9t`ccnUnPMW*f`Ii7c9R+uCIQ^W50}cZZFrJX9Jc>-Krq zj+0WMPXDq2fTQGw{W<nM10V;`9s8ynMThQ`UX{ADE+^>e2bn~fqs0d9WTn;Jl0Ezm zgnh78CY=jT1#QxK!THF!?@T-A5wR~i4X#O572)Ai5O8-mA`RTeZb!|)g=c2=avO&$ z<G(!M5v{(GM+ulXaGU*T_3%_UKWtiJ1iUu4dix}=sSU&c2nPxOtmanVRCnPmEAkjL z7qGF#b{wW&qnl7`h6XQ|-S-47v6@z8Ez%kutDJ$o9jaiTUgfhWD=C&YV--tt39Q5U zZvj7=nrJ2sp5^yI3|&CE=lUwCbHn|IcLMhe0x{hnz;6qiZIZH=AnG=Pz-Qzi)M7p_ zNwTv&=T(w#0}Qq+*nja;fDb8(D@*Y^><qip)3qu-w`;Z9RBft03Z8fpeu{|VPvivt zZ9UJQN32J&)D#y^Haop-|8hQ|yS5QGGFjm0`;>LB+Piw2)@``4aajJiGhX=*2a{~{ literal 0 HcmV?d00001 diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/__init__.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py new file mode 100644 index 0000000..a319364 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py @@ -0,0 +1,76 @@ +# /***************************************************************************** +# * | File : config.py +# * | Author : Guillaume Giraudon +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2019-10-18 +# * | Info : +# ******************************************************************************/ + +import RPi.GPIO as GPIO +import time +from smbus import SMBus +import spidev + +import ctypes +# import spidev + +# Pin definition +RST_PIN = 27 +DC_PIN = 25 +BL_PIN = 24 + +Device_SPI = 1 +Device_I2C = 0 + +Device = Device_SPI +spi = spidev.SpiDev(0, 0) + +def digital_write(pin, value): + GPIO.output(pin, value) + +def digital_read(pin): + return GPIO.input(BUSY_PIN) + +def delay_ms(delaytime): + time.sleep(delaytime / 1000.0) + +def spi_writebyte(data): + # SPI.writebytes(data) + spi.writebytes([data[0]]) + +def i2c_writebyte(reg, value): + bus.write_byte_data(address, reg, value) + + # time.sleep(0.01) +def module_init(): + # print("module_init") + + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(RST_PIN, GPIO.OUT) + GPIO.setup(DC_PIN, GPIO.OUT) + + + # SPI.max_speed_hz = 2000000 + # SPI.mode = 0b00 + # i2c_writebyte(0xff,0xff) + # spi.SYSFS_software_spi_begin() + # spi.SYSFS_software_spi_setDataMode(0); + # spi.SYSFS_software_spi_setClockDivider(1); + #spi.max_speed_hz = 2000000 + #spi.mode = 0b00 + + GPIO.output(BL_PIN, 1) + GPIO.output(DC_PIN, 0) + return 0 + +def module_exit(): + spi.SYSFS_software_spi_end() + GPIO.output(RST_PIN, 0) + GPIO.output(DC_PIN, 0) + + + +### END OF FILE ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py new file mode 100644 index 0000000..cd0d81f --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py @@ -0,0 +1,28 @@ +from . import ST7789 +from . import config + +# Display resolution +EPD_WIDTH = 240 +EPD_HEIGHT = 240 + +disp = ST7789.ST7789(config.spi,config.RST_PIN, config.DC_PIN, config.BL_PIN) + +class EPD(object): + + def __init__(self): + self.reset_pin = config.RST_PIN + self.dc_pin = config.DC_PIN + #self.busy_pin = config.BUSY_PIN + #self.cs_pin = config.CS_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + + def init(self): + disp.Init() + + def Clear(self): + disp.clear() + + def display(self, image): + rgb_im = image.convert('RGB') + disp.ShowImage(rgb_im,0,0) From 49f7c652c7263e9cbc12cb36c6ee1ead69fc99b8 Mon Sep 17 00:00:00 2001 From: Kirill <iam@python273.pw> Date: Sat, 19 Oct 2019 12:53:46 +0300 Subject: [PATCH 258/346] Fix waveshare v1 layout: status pos --- pwnagotchi/ui/hw/waveshare1.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/ui/hw/waveshare1.py b/pwnagotchi/ui/hw/waveshare1.py index fba8f17..47e55e1 100644 --- a/pwnagotchi/ui/hw/waveshare1.py +++ b/pwnagotchi/ui/hw/waveshare1.py @@ -38,7 +38,6 @@ class WaveshareV1(DisplayImpl): self._layout['name'] = (5, 15) self._layout['channel'] = (0, 0) self._layout['aps'] = (28, 0) - self._layout['status'] = (91, 15) self._layout['uptime'] = (147, 0) self._layout['line1'] = [0, 12, 212, 12] self._layout['line2'] = [0, 92, 212, 92] @@ -47,9 +46,9 @@ class WaveshareV1(DisplayImpl): self._layout['shakes'] = (0, 93) self._layout['mode'] = (187, 93) self._layout['status'] = { - 'pos': (125, 20), + 'pos': (91, 15), 'font': fonts.Medium, - 'max': 14 + 'max': 20 } return self._layout From b213f9f2148315d7c6600bc8646bc46dd609a179 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 19 Oct 2019 17:22:34 +0200 Subject: [PATCH 259/346] releasing v1.0.0 --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 04352cf..dd9fe8b 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -4,7 +4,7 @@ import logging import time import pwnagotchi.ui.view as view -version = '1.0.0RC5' +version = '1.0.0' _name = None From 10cba6872ab23e2a6837f74bb26a0849515cd33c Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 19 Oct 2019 17:26:03 +0200 Subject: [PATCH 260/346] fix: fixed travis-ci regexp --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 933c5ea..ba20615 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ deploy: repo: evilsocket/pwnagotchi branches: only: - - "/^v[0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]+?$/" + - "/^v[0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*$/" cache: apt: true before_script: From b3aa5bc2c1c188538ff4c0c013d3f334df1132e8 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 19 Oct 2019 18:43:16 +0200 Subject: [PATCH 261/346] fix: bumped pwngrid to 1.8.1 --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index e41efd4..b0f5e6a 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.8.0/pwngrid_linux_armhf_v1.8.0.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.8.1/pwngrid_linux_armhf_v1.8.1.zip" apt: hold: - firmware-atheros From 2d7b6b54fe2401f5d2ad8ee8f65e44bf8d5efc23 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sat, 19 Oct 2019 18:56:03 +0200 Subject: [PATCH 262/346] releasing v1.0.1 --- pwnagotchi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index dd9fe8b..14737d5 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -4,7 +4,7 @@ import logging import time import pwnagotchi.ui.view as view -version = '1.0.0' +version = '1.0.1' _name = None From f52687642e30c6f9c64a60c480e635cda9a864a3 Mon Sep 17 00:00:00 2001 From: spees <speeskonijn@gmail.com> Date: Sat, 19 Oct 2019 21:32:55 +0200 Subject: [PATCH 263/346] refactored so it actually works Signed-off-by: spees <speeskonijn@gmail.com> --- pwnagotchi/defaults.yml | 5 +- pwnagotchi/plugins/default/memtemp.py | 109 ++++++++++++++------------ 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 986d2fc..952dcfb 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -70,8 +70,9 @@ main: netmask: 24 interval: 1 # check every x minutes for device share_internet: false - memtemp: # Display memory usage and cpu temperature on screen - enabled: false + memtemp: # Display memory usage, cpu load and cpu temperature on screen + enabled: false + orientation: horizontal # horizontal/vertical # monitor interface to use iface: mon0 # command to run to bring the mon interface up in case it's not up already diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py index b17225a..74732d6 100644 --- a/pwnagotchi/plugins/default/memtemp.py +++ b/pwnagotchi/plugins/default/memtemp.py @@ -1,6 +1,6 @@ -# tempmem shows memory infos and cpu temperature +# memtemp shows memory infos and cpu temperature # -# totalmem usedmem freemem cputemp +# mem usage, cpu load, cpu temp # ############################################################### # @@ -10,71 +10,80 @@ # - removed the label so we wont waste screen space # - Updated version to 1.0.1 # +# 20-10-2019 by spees <speeskonijn@gmail.com> +# - Refactored to use the already existing functions +# - Now only shows memory usage in percentage +# - Added CPU load +# - Added horizontal and vertical orientation +# ############################################################### - __author__ = 'https://github.com/xenDE' __version__ = '1.0.1' __name__ = 'memtemp' __license__ = 'GPL3' -__description__ = 'A plugin that will add a memory and temperature indicator' +__description__ = 'A plugin that will display memory/cpu usage and temperature' -import struct from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.view import BLACK import pwnagotchi.ui.fonts as fonts +import subprocess +import logging -import time - - -class MEMTEMP: - - # set the minimum seconds before refresh the values - refresh_wait = 30 - - refresh_ts_last = time.time() - refresh_wait - - def __init__(self): - # only import when the module is loaded and enabled - import os - - def get_temp(self): - try: - temp = os.popen('/opt/vc/bin/vcgencmd measure_temp').readlines()[0].split('=')[1].replace("\n", '').replace("'","") - return 't:' + temp - except: - return 't:-' - # cpu:37.4C - - def get_mem_info(self): - try: - # includes RAM + Swap Memory: -# total, used, free = map(int, os.popen('free -t -m').readlines()[-1].split()[1:]) - # without Swap, only real memory: - total, used, free = map(int, os.popen('free -t -m').readlines()[-3].split()[1:4]) - return "\nT:"+str(total)+"M U:"+str(used)+"M\nF:"+str(free)+"M" - except: - return "\nT:- U:-\nF:- " - # tm:532 um:82 fm:353 - - -memtemp = None +OPTIONS = dict() def on_loaded(): - global memtemp - memtemp = MEMTEMP() + logging.info("memtemp plugin loaded.") +def mem_usage(): + out = subprocess.getoutput("free -m") + for line in out.split("\n"): + line = line.strip() + if line.startswith("Mem:"): + parts = list(map(int, line.split()[1:])) + tot = parts[0] + used = parts[1] + free = parts[2] + return int((used / tot) * 100) + + return 0 + + +def cpu_load(): + with open('/proc/stat', 'rt') as fp: + for line in fp: + line = line.strip() + if line.startswith('cpu '): + parts = list(map(int, line.split()[1:])) + user_n = parts[0] + sys_n = parts[2] + idle_n = parts[3] + tot = user_n + sys_n + idle_n + return int(((user_n + sys_n) / tot) * 100) + return 0 + + +def temperature(celsius=True): + with open('/sys/class/thermal/thermal_zone0/temp', 'rt') as fp: + temp = int(fp.read().strip()) + c = int(temp / 1000) + return c if celsius else ((c * (9 / 5)) + 32) + + def on_ui_setup(ui): - ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='\nT:- U:-\nF:- -', position=(ui.width() / 2 + 17, ui.height() / 2), - label_font=fonts.Bold, text_font=fonts.Medium)) - + if OPTIONS['orientation'] == "horizontal": + ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -', position=(ui.width() / 2 + 30, ui.height() /2 + 15), + label_font=fonts.Small, text_font=fonts.Small)) + elif OPTIONS['orientation'] == "vertical": + ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-', position=(ui.width() / 2 + 55, ui.height() /2), + label_font=fonts.Small, text_font=fonts.Small)) def on_ui_update(ui): - if time.time() > memtemp.refresh_ts_last + memtemp.refresh_wait: - ui.set('memtemp', "%s %s" % (memtemp.get_mem_info(), memtemp.get_temp())) - memtemp.refresh_ts_last = time.time() - - + if OPTIONS['orientation'] == "horizontal": + ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), temperature())) + + elif OPTIONS['orientation'] == "vertical": + ui.set('memtemp', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (mem_usage(), cpu_load(), temperature())) From 58a085188f54983026f5f4c4ce70055f999d2969 Mon Sep 17 00:00:00 2001 From: spees <speeskonijn@gmail.com> Date: Sat, 19 Oct 2019 22:25:12 +0200 Subject: [PATCH 264/346] refactored to use the already existing functions Signed-off-by: spees <speeskonijn@gmail.com> --- pwnagotchi/plugins/default/memtemp.py | 36 ++++----------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py index 74732d6..8262791 100644 --- a/pwnagotchi/plugins/default/memtemp.py +++ b/pwnagotchi/plugins/default/memtemp.py @@ -28,6 +28,7 @@ __description__ = 'A plugin that will display memory/cpu usage and temperature' from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.view import BLACK import pwnagotchi.ui.fonts as fonts +import pwnagotchi import subprocess import logging @@ -39,40 +40,13 @@ def on_loaded(): def mem_usage(): - out = subprocess.getoutput("free -m") - for line in out.split("\n"): - line = line.strip() - if line.startswith("Mem:"): - parts = list(map(int, line.split()[1:])) - tot = parts[0] - used = parts[1] - free = parts[2] - return int((used / tot) * 100) - - return 0 - + return int(pwnagotchi.mem_usage() * 100) def cpu_load(): - with open('/proc/stat', 'rt') as fp: - for line in fp: - line = line.strip() - if line.startswith('cpu '): - parts = list(map(int, line.split()[1:])) - user_n = parts[0] - sys_n = parts[2] - idle_n = parts[3] - tot = user_n + sys_n + idle_n - return int(((user_n + sys_n) / tot) * 100) - return 0 + return int(pwnagotchi.cpu_load() * 100) -def temperature(celsius=True): - with open('/sys/class/thermal/thermal_zone0/temp', 'rt') as fp: - temp = int(fp.read().strip()) - c = int(temp / 1000) - return c if celsius else ((c * (9 / 5)) + 32) - def on_ui_setup(ui): if OPTIONS['orientation'] == "horizontal": ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -', position=(ui.width() / 2 + 30, ui.height() /2 + 15), @@ -83,7 +57,7 @@ def on_ui_setup(ui): def on_ui_update(ui): if OPTIONS['orientation'] == "horizontal": - ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), temperature())) + ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature())) elif OPTIONS['orientation'] == "vertical": - ui.set('memtemp', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (mem_usage(), cpu_load(), temperature())) + ui.set('memtemp', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature())) From 68801adcafedc29327052193e7776717e12e05e5 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sun, 20 Oct 2019 12:10:01 +0200 Subject: [PATCH 265/346] increase version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 78538c8..7c18a47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ gym==0.14.0 stable-baselines==2.7.0 tensorflow==1.13.1 tensorflow-estimator==1.14.0 -tweepy==3.6.0 +tweepy==3.7.0 file-read-backwards==2.0.0 numpy==1.17.2 inky==0.0.5 From d2966098b0f70ecc9309393865876345ea66bff1 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 14:24:40 +0200 Subject: [PATCH 266/346] lol dadav From 6d88cb17f3d4a9ae46e92bd5b5b5c2a542612689 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 14:28:34 +0200 Subject: [PATCH 267/346] minor refactoring --- pwnagotchi/plugins/default/memtemp.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py index 8262791..3d6682b 100644 --- a/pwnagotchi/plugins/default/memtemp.py +++ b/pwnagotchi/plugins/default/memtemp.py @@ -24,12 +24,10 @@ __name__ = 'memtemp' __license__ = 'GPL3' __description__ = 'A plugin that will display memory/cpu usage and temperature' - from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.view import BLACK import pwnagotchi.ui.fonts as fonts import pwnagotchi -import subprocess import logging OPTIONS = dict() @@ -42,22 +40,25 @@ def on_loaded(): def mem_usage(): return int(pwnagotchi.mem_usage() * 100) + def cpu_load(): return int(pwnagotchi.cpu_load() * 100) - def on_ui_setup(ui): if OPTIONS['orientation'] == "horizontal": - ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -', position=(ui.width() / 2 + 30, ui.height() /2 + 15), - label_font=fonts.Small, text_font=fonts.Small)) + ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -', + position=(ui.width() / 2 + 30, ui.height() / 2 + 15), + label_font=fonts.Small, text_font=fonts.Small)) elif OPTIONS['orientation'] == "vertical": - ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-', position=(ui.width() / 2 + 55, ui.height() /2), - label_font=fonts.Small, text_font=fonts.Small)) + ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-', + position=(ui.width() / 2 + 55, ui.height() / 2), + label_font=fonts.Small, text_font=fonts.Small)) + def on_ui_update(ui): if OPTIONS['orientation'] == "horizontal": ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature())) - + elif OPTIONS['orientation'] == "vertical": ui.set('memtemp', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature())) From fb2c65ef0a4138e474d1fde65a7d81fe7797de8e Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 14:31:45 +0200 Subject: [PATCH 268/346] fix: made grid api errors due to rate limiting and stuff way less dramatic --- pwnagotchi/plugins/default/grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 9535652..7bf1cb9 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -143,4 +143,4 @@ def on_internet_available(agent): logging.debug("grid: reporting disabled") except Exception as e: - logging.exception("grid api error") + logging.error("grid api: %s" % e) From 400d0e7290100564afd2c23ea6835817238a2017 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 14:32:15 +0200 Subject: [PATCH 269/346] fix: removed unused variable in the grid plugin --- pwnagotchi/plugins/default/grid.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 7bf1cb9..6f064f2 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -130,7 +130,6 @@ def on_internet_available(agent): essid, bssid = parse_pcap(pcap_file) if bssid: - add_as_reported = False if is_excluded(essid) or is_excluded(bssid): logging.debug("not reporting %s due to exclusion filter" % pcap_file) set_reported(reported, net_id) From f9c0efc24afb4ce545adf0f72c7a122045d67619 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 14:55:15 +0200 Subject: [PATCH 270/346] new: bump pwngrid to 1.9.0 --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index b0f5e6a..c3dc1bf 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -34,7 +34,7 @@ url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: - url: "https://github.com/evilsocket/pwngrid/releases/download/v1.8.1/pwngrid_linux_armhf_v1.8.1.zip" + url: "https://github.com/evilsocket/pwngrid/releases/download/v1.9.0/pwngrid_linux_armhf_v1.9.0.zip" apt: hold: - firmware-atheros From 892fda775d7397ec8da57a84d6ed835c4c6eef4d Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 14:55:34 +0200 Subject: [PATCH 271/346] fix: throttling report requests a bit to avoid rate limits on the API --- pwnagotchi/plugins/default/grid.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index 6f064f2..cfb5d8f 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -1,11 +1,13 @@ __author__ = 'evilsocket@gmail.com' -__version__ = '1.0.0' +__version__ = '1.0.1' __name__ = 'grid' __license__ = 'GPL3' -__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned networks to api.pwnagotchi.ai' +__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \ + 'networks to api.pwnagotchi.ai ' import os import logging +import time import glob import pwnagotchi.grid as grid @@ -136,6 +138,7 @@ def on_internet_available(agent): else: if grid.report_ap(essid, bssid): set_reported(reported, net_id) + time.sleep(1.5) else: logging.warning("no bssid found?!") else: From bd7c64b2af97fdc50f17c52d5d89e5aff5c3b2f2 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 15:41:06 +0200 Subject: [PATCH 272/346] misc: refactored web ui code --- pwnagotchi/ui/display.py | 108 +++-------------------------- pwnagotchi/ui/web.py | 144 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 100 deletions(-) create mode 100644 pwnagotchi/ui/web.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 479324b..eac519f 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -1,113 +1,22 @@ -import _thread -from threading import Lock - -import shutil import logging -import pwnagotchi, pwnagotchi.plugins as plugins +import pwnagotchi.plugins as plugins import pwnagotchi.ui.hw as hw +import pwnagotchi.ui.web as web from pwnagotchi.ui.view import View -from http.server import BaseHTTPRequestHandler, HTTPServer - - -class VideoHandler(BaseHTTPRequestHandler): - _lock = Lock() - _index = """<html> - <head> - <title>%s</title> - <style> - .block { - -webkit-appearance: button; - -moz-appearance: button; - appearance: button; - - display: block; - cursor: pointer; - text-align: center; - } - </style> - </head> - <body> - <div style="position: absolute; top:0; left:0; width:100%%;"> - <img src="/ui" id="ui" style="width:100%%"/> - <br/> - <hr/> - <form action="/shutdown" onsubmit="return confirm('This will halt the unit, continue?');"> - <input type="submit" class="block" value="Shutdown"/> - </form> - </div> - - <script type="text/javascript"> - window.onload = function() { - var image = document.getElementById("ui"); - function updateImage() { - image.src = image.src.split("?")[0] + "?" + new Date().getTime(); - } - setInterval(updateImage, %d); - } - </script> - </body> -</html>""" - - @staticmethod - def render(img): - with VideoHandler._lock: - img.save("/root/pwnagotchi.png", format='PNG') - - def log_message(self, format, *args): - return - - def do_GET(self): - if self.path == '/': - self.send_response(200) - self.send_header('Content-type', 'text/html') - self.end_headers() - try: - self.wfile.write(bytes(self._index % (pwnagotchi.name(), 1000), "utf8")) - except: - pass - - elif self.path.startswith('/shutdown'): - pwnagotchi.shutdown() - - elif self.path.startswith('/ui'): - with self._lock: - self.send_response(200) - self.send_header('Content-type', 'image/png') - self.end_headers() - try: - with open("/root/pwnagotchi.png", 'rb') as fp: - shutil.copyfileobj(fp, self.wfile) - except: - pass - else: - self.send_response(404) - class Display(View): def __init__(self, config, state={}): super(Display, self).__init__(config, hw.display_for(config), state) - self._enabled = config['ui']['display']['enabled'] - self._rotation = config['ui']['display']['rotation'] - self._video_enabled = config['ui']['display']['video']['enabled'] - self._video_port = config['ui']['display']['video']['port'] - self._video_address = config['ui']['display']['video']['address'] - self._httpd = None + config = config['ui']['display'] + + self._enabled = config['enabled'] + self._rotation = config['rotation'] + self._webui = web.Server(config) self.init_display() - if self._video_enabled: - _thread.start_new_thread(self._http_serve, ()) - - def _http_serve(self): - if self._video_address is not None: - self._httpd = HTTPServer((self._video_address, self._video_port), VideoHandler) - logging.info("ui available at http://%s:%d/" % (self._video_address, self._video_port)) - self._httpd.serve_forever() - else: - logging.info("could not get ip of usb0, video server not starting") - def is_inky(self): return self._implementation.name == 'inky' @@ -126,7 +35,6 @@ class Display(View): def is_lcdhat(self): return self._implementation.name == 'lcdhat' - def is_waveshare_any(self): return self.is_waveshare_v1() or self.is_waveshare_v2() @@ -148,7 +56,7 @@ class Display(View): return img def _on_view_rendered(self, img): - VideoHandler.render(img) + web.update_frame(img) if self._enabled: self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation)) if self._implementation is not None: diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py new file mode 100644 index 0000000..1032e57 --- /dev/null +++ b/pwnagotchi/ui/web.py @@ -0,0 +1,144 @@ +import _thread +from http.server import BaseHTTPRequestHandler, HTTPServer +from threading import Lock +import shutil +import logging + +import pwnagotchi + +frame_path = '/root/pwnagotchi.png' +frame_format = 'PNG' +frame_ctype = 'image/png' +frame_lock = Lock() + + +def update_frame(img): + global frame_lock, frame_path, frame_format + with frame_lock: + img.save(frame_path, format=frame_format) + + +STYLE = """ +.block { + -webkit-appearance: button; + -moz-appearance: button; + appearance: button; + + display: block; + cursor: pointer; + text-align: center; +} +""" + +SCRIPT = """ +window.onload = function() { + var image = document.getElementById("ui"); + function updateImage() { + image.src = image.src.split("?")[0] + "?" + new Date().getTime(); + } + setInterval(updateImage, %d); +} +""" + +INDEX = """<html> + <head> + <title>%s</title> + <style>""" + STYLE + """</style> + </head> + <body> + <div style="position: absolute; top:0; left:0; width:100%%;"> + <img src="/ui" id="ui" style="width:100%%"/> + <br/> + <hr/> + <form action="/shutdown" onsubmit="return confirm('This will halt the unit, continue?');"> + <input type="submit" class="block" value="Shutdown"/> + </form> + </div> + + <script type="text/javascript">""" + SCRIPT + """</script> + </body> +</html>""" + +SHUTDOWN = """<html> + <head> + <title>%s</title> + <style>""" + STYLE + """</style> + </head> + <body> + <div style="position: absolute; top:0; left:0; width:100%%;"> + Shutting down ... + </div> + </body> +</html>""" + +class Handler(BaseHTTPRequestHandler): + # suppress internal logging + def log_message(self, format, *args): + return + + # just render some html in a 200 response + def _html(self, html): + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + try: + self.wfile.write(bytes(html, "utf8")) + except: + pass + + # serve the main html page + def _index(self): + self._html(INDEX % (pwnagotchi.name(), 1000)) + + # serve a message and shuts down the unit + def _shutdown(self): + self._html(SHUTDOWN % pwnagotchi.name()) + pwnagotchi.shutdown() + + # serve the PNG file with the display image + def _image(self): + global frame_path, frame_ctype + + with frame_lock: + self.send_response(200) + self.send_header('Content-type', frame_ctype) + self.end_headers() + try: + with open(frame_path, 'rb') as fp: + shutil.copyfileobj(fp, self.wfile) + except: + pass + + # main entry point of the http server + def do_GET(self): + global frame_lock + + if self.path == '/': + self._index() + + elif self.path.startswith('/shutdown'): + self._shutdown() + + elif self.path.startswith('/ui'): + self._image() + else: + self.send_response(404) + + +class Server(object): + def __init__(self, config): + self._enabled = config['video']['enabled'] + self._port = config['video']['port'] + self._address = config['video']['address'] + self._httpd = None + + if self._enabled: + _thread.start_new_thread(self._http_serve, ()) + + def _http_serve(self): + if self._address is not None: + self._httpd = HTTPServer((self._address, self._port), Handler) + logging.info("web ui available at http://%s:%d/" % (self._address, self._port)) + self._httpd.serve_forever() + else: + logging.info("could not get ip of usb0, video server not starting") From 539df810ed321eef1c7331e3be7d3bc1efb4e3f0 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 15:42:08 +0200 Subject: [PATCH 273/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/web.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index 1032e57..9a47b55 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -71,6 +71,7 @@ SHUTDOWN = """<html> </body> </html>""" + class Handler(BaseHTTPRequestHandler): # suppress internal logging def log_message(self, format, *args): From d45e8c7ba02899d6e9e7b8793306157171e1b6a0 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 16:09:27 +0200 Subject: [PATCH 274/346] new: secured the web ui with CORS --- pwnagotchi/defaults.yml | 1 + pwnagotchi/ui/web.py | 49 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 952dcfb..5c455fa 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -175,6 +175,7 @@ ui: video: enabled: true address: '0.0.0.0' + origin: '*' port: 8080 diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index 9a47b55..9eb5b8f 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -73,13 +73,30 @@ SHUTDOWN = """<html> class Handler(BaseHTTPRequestHandler): + AllowedOrigin = '*' + # suppress internal logging def log_message(self, format, *args): return + def _send_cors_headers(self): + # misc security + self.send_header("X-Frame-Options", "DENY") + self.send_header("X-Content-Type-Options", "nosniff") + self.send_header("X-XSS-Protection", "1; mode=block") + self.send_header("Referrer-Policy", "same-origin") + # cors + self.send_header("Access-Control-Allow-Origin", Handler.AllowedOrigin) + self.send_header('Access-Control-Allow-Credentials', 'true') + self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + self.send_header("Access-Control-Allow-Headers", + "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") + self.send_header("Vary", "Origin") + # just render some html in a 200 response def _html(self, html): self.send_response(200) + self._send_cors_headers() self.send_header('Content-type', 'text/html') self.end_headers() try: @@ -98,10 +115,11 @@ class Handler(BaseHTTPRequestHandler): # serve the PNG file with the display image def _image(self): - global frame_path, frame_ctype + global frame_lock, frame_path, frame_ctype with frame_lock: self.send_response(200) + self._send_cors_headers() self.send_header('Content-type', frame_ctype) self.end_headers() try: @@ -110,13 +128,32 @@ class Handler(BaseHTTPRequestHandler): except: pass + def do_OPTIONS(self): + self.send_response(200) + self._send_cors_headers() + self.end_headers() + + # check the Origin header vs CORS + def _is_allowed(self): + origin = self.headers.get('origin') + if origin == "": + logging.warning("request with no Origin header from %s" % self.address_string()) + return False + + if Handler.AllowedOrigin != '*': + if origin != Handler.AllowedOrigin and not origin.starts_with(Handler.AllowedOrigin): + logging.warning("request with blocked Origin from %s: %s" % (self.address_string(), origin)) + return False + + return True + # main entry point of the http server def do_GET(self): - global frame_lock + if not self._is_allowed(): + return if self.path == '/': self._index() - elif self.path.startswith('/shutdown'): self._shutdown() @@ -133,6 +170,12 @@ class Server(object): self._address = config['video']['address'] self._httpd = None + if 'origin' in config['video'] and config['video']['origin'] != '*': + Handler.AllowedOrigin = config['video']['origin'] + else: + logging.warning("THE WEB UI IS RUNNING WITH ALLOWED ORIGIN SET TO *, READ WHY YOU SHOULD CHANGE IT HERE \ + https://developer.mozilla.org/it/docs/Web/HTTP/CORS") + if self._enabled: _thread.start_new_thread(self._http_serve, ()) From 5f6cc378f1ca4576281ef6ed3a462b5821aa0436 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Tue, 15 Oct 2019 08:54:29 +0200 Subject: [PATCH 275/346] Implement webhook --- pwnagotchi/plugins/default/example.py | 12 ++++++++++++ pwnagotchi/ui/web.py | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/pwnagotchi/plugins/default/example.py b/pwnagotchi/plugins/default/example.py index 3f3e570..72087cf 100644 --- a/pwnagotchi/plugins/default/example.py +++ b/pwnagotchi/plugins/default/example.py @@ -14,6 +14,18 @@ import pwnagotchi.ui.fonts as fonts # Will be set with the options in config.yml config['main']['plugins'][__name__] OPTIONS = dict() +# called when <host>:<port>/plugins/<pluginname> is opened +def on_webhook(response, path): + res = "<html><body><a>Hook triggered</a></body></html>" + response.send_response(200) + response.send_header('Content-type', 'text/html') + response.end_headers() + + try: + response.wfile.write(bytes(res, "utf-8")) + except Exception as ex: + logging.error(ex) + # called when the plugin is loaded def on_loaded(): logging.warning("WARNING: plugin %s should be disabled!" % __name__) diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index 9eb5b8f..e318846 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -1,3 +1,4 @@ +import re import _thread from http.server import BaseHTTPRequestHandler, HTTPServer from threading import Lock @@ -5,6 +6,7 @@ import shutil import logging import pwnagotchi +from pwnagotchi import plugins frame_path = '/root/pwnagotchi.png' frame_format = 'PNG' @@ -159,6 +161,13 @@ class Handler(BaseHTTPRequestHandler): elif self.path.startswith('/ui'): self._image() + elif self.path.startswith('/plugins'): + plugin_from_path = re.match(r'\/plugins\/([^\/]+)(\/.*)?', self.path) + if plugin_from_path: + plugin_name = plugin_from_path.groups()[0] + right_path = plugin_from_path.groups()[1] if len(plugin_from_path.groups()) == 2 else None + if plugin_name in plugins.loaded and hasattr(plugins.loaded[plugin_name], 'on_webhook'): + plugins.loaded[plugin_name].on_webhook(self, right_path) else: self.send_response(404) From 99e0a31ea87d12838c4de5df5f82e8b173661d64 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 16:31:34 +0200 Subject: [PATCH 276/346] misc: changed readme image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94c160d..c03021e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), full and half WPA handshakes. - + Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to. From cd5d783c52cedaab09d8f5ad1bbb7b62eec91401 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 17:13:05 +0200 Subject: [PATCH 277/346] new: users can now customize the faces via config.yml (ui.faces) --- pwnagotchi/defaults.yml | 142 +++++++++++++++++++++++----------------- pwnagotchi/ui/faces.py | 5 ++ pwnagotchi/ui/view.py | 3 + 3 files changed, 89 insertions(+), 61 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 5c455fa..3142761 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -12,67 +12,67 @@ main: custom_plugins: # which plugins to load and enable plugins: - grid: - enabled: true - report: false # don't report pwned networks by default! - exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs) - - YourHomeNetworkHere - auto-backup: - enabled: false - interval: 1 # every day - files: - - /root/brain.nn - - /root/brain.json - - /root/.api-report.json - - /root/handshakes/ - - /etc/pwnagotchi/ - - /etc/hostname - - /etc/hosts - - /etc/motd - - /var/log/pwnagotchi.log - commands: - - 'tar czf /tmp/backup.tar.gz {files}' - - 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz' - net-pos: - enabled: false - api_key: 'test' - gps: - enabled: false - speed: 19200 - device: /dev/ttyUSB0 - twitter: - enabled: false - consumer_key: aaa - consumer_secret: aaa - access_token_key: aaa - access_token_secret: aaa - onlinehashcrack: - enabled: false - email: ~ - wpa-sec: - enabled: false - api_key: ~ - wigle: - enabled: false - api_key: ~ - screen_refresh: - enabled: false - refresh_interval: 50 - quickdic: - enabled: false - wordlist_folder: /opt/wordlists/ - AircrackOnly: - enabled: false - bt-tether: - enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0 - mac: ~ # mac of your bluetooth device - ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable - netmask: 24 - interval: 1 # check every x minutes for device - share_internet: false - memtemp: # Display memory usage, cpu load and cpu temperature on screen - enabled: false - orientation: horizontal # horizontal/vertical + grid: + enabled: true + report: false # don't report pwned networks by default! + exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs) + - YourHomeNetworkHere + auto-backup: + enabled: false + interval: 1 # every day + files: + - /root/brain.nn + - /root/brain.json + - /root/.api-report.json + - /root/handshakes/ + - /etc/pwnagotchi/ + - /etc/hostname + - /etc/hosts + - /etc/motd + - /var/log/pwnagotchi.log + commands: + - 'tar czf /tmp/backup.tar.gz {files}' + - 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz' + net-pos: + enabled: false + api_key: 'test' + gps: + enabled: false + speed: 19200 + device: /dev/ttyUSB0 + twitter: + enabled: false + consumer_key: aaa + consumer_secret: aaa + access_token_key: aaa + access_token_secret: aaa + onlinehashcrack: + enabled: false + email: ~ + wpa-sec: + enabled: false + api_key: ~ + wigle: + enabled: false + api_key: ~ + screen_refresh: + enabled: false + refresh_interval: 50 + quickdic: + enabled: false + wordlist_folder: /opt/wordlists/ + AircrackOnly: + enabled: false + bt-tether: + enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0 + mac: ~ # mac of your bluetooth device + ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable + netmask: 24 + interval: 1 # check every x minutes for device + share_internet: false + memtemp: # Display memory usage, cpu load and cpu temperature on screen + enabled: false + orientation: horizontal # horizontal/vertical # monitor interface to use iface: mon0 # command to run to bring the mon interface up in case it's not up already @@ -160,6 +160,26 @@ personality: # ui configuration ui: + # here you can customize the faces + faces: + look_r: '( ⚆_⚆)' + look_l: '(☉_☉ )' + sleep: '(⇀‿‿↼)' + sleep2: '(≖‿‿≖)' + awake: '(◕‿‿◕)' + bored: '(-__-)' + intense: '(°▃▃°)' + cool: '(⌐■_■)' + happy: '(•‿‿•)' + excited: '(ᵔ◡◡ᵔ)' + motivated: '(☼‿‿☼)' + demotivated: '(≖__≖)' + smart: '(✜‿‿✜)' + lonely: '(ب__ب)' + sad: '(╥☁╥ )' + friend: '(♥‿‿♥)' + broken: '(☓‿‿☓)' + debug: '(#__#)' # ePaper display can update every 3 secs anyway, set to 0 to only refresh for major data changes # IMPORTANT: The lifespan of an eINK display depends on the cumulative amount of refreshes. If you want to # preserve your display over time, you should set this value to 0.0 so that the display will be refreshed only diff --git a/pwnagotchi/ui/faces.py b/pwnagotchi/ui/faces.py index 4f80e5c..d4cbd32 100644 --- a/pwnagotchi/ui/faces.py +++ b/pwnagotchi/ui/faces.py @@ -16,3 +16,8 @@ SAD = '(╥☁╥ )' FRIEND = '(♥‿‿♥)' BROKEN = '(☓‿‿☓)' DEBUG = '(#__#)' + + +def load_from_config(config): + for face_name, face_value in config.items(): + globals()[face_name.upper()] = face_value diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index a6efd78..4d72097 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -22,6 +22,9 @@ class View(object): def __init__(self, config, impl, state=None): global ROOT + # setup faces from the configuration in case the user customized them + faces.load_from_config(config['ui']['faces']) + self._render_cbs = [] self._config = config self._canvas = None From ff6bf5c19802e985a6de346bb30959f7334f5086 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 17:36:34 +0200 Subject: [PATCH 278/346] started working on #343 --- pwnagotchi/defaults.yml | 6 ++ pwnagotchi/plugins/default/auto-update.py | 67 +++++++++++++++++++++++ pwnagotchi/utils.py | 3 + 3 files changed, 76 insertions(+) create mode 100644 pwnagotchi/plugins/default/auto-update.py diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 3142761..0aed0f7 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -17,6 +17,12 @@ main: report: false # don't report pwned networks by default! exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs) - YourHomeNetworkHere + + auto-update: + enabled: false + interval: 12 # every 12 hours + install: true # if false, it will only warn that updates are available, if true it will install them + auto-backup: enabled: false interval: 1 # every day diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py new file mode 100644 index 0000000..87969a0 --- /dev/null +++ b/pwnagotchi/plugins/default/auto-update.py @@ -0,0 +1,67 @@ +__author__ = 'evilsocket@gmail.com' +__version__ = '1.1.0' +__name__ = 'auto-update' +__license__ = 'GPL3' +__description__ = 'This plugin checks when updates are available and applies them when internet is available.' + +import logging +import subprocess +from pwnagotchi.utils import StatusFile + +OPTIONS = dict() +READY = False +STATUS = StatusFile('/root/.auto-update') + + +def on_loaded(): + global READY + if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None): + logging.error("[update] main.plugins.auto-update.interval is not set") + return + READY = True + logging.info("[update] plugin loaded.") + + +def run(cmd): + return subprocess.Popen(cmd, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, + executable="/bin/bash") + + +def on_internet_available(agent): + global STATUS + + if READY: + if STATUS.newer_then_hours(OPTIONS['interval']): + logging.debug("[update] last check happened less than %d hours ago" % OPTIONS['interval']) + return + + logging.debug("[update] start") + + display = agent.view() + prev_status = display.get('status') + + try: + display.set('status', 'Checking for updates ...') + display.update() + + """ + logging.info("auto-update: updating pwnagotchi ...") + run('pip3 install --upgrade --upgrade-strategy only-if-needed pwnagotchi').wait() + + if OPTIONS['system']: + logging.info("auto-update: updating packages index ...") + run('apt update -y').wait() + + logging.info("auto-update: updating packages ...") + run('apt upgrade -y').wait() + """ + + logging.info("[update] done") + + STATUS.update() + + except Exception as e: + logging.error("[update] %s" % e) + + display.set('status', prev_status if prev_status is not None else '') + display.update() diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 419d80d..46f1103 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -263,6 +263,9 @@ class StatusFile(object): def newer_then_minutes(self, minutes): return self._updated is not None and ((datetime.now() - self._updated).seconds / 60) < minutes + def newer_then_hours(self, hours): + return self._updated is not None and ((datetime.now() - self._updated).seconds / (60*60)) < hours + def newer_then_days(self, days): return self._updated is not None and (datetime.now() - self._updated).days < days From ca2becd9cecaf6dd49f587268118813cf7911147 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:05:53 +0200 Subject: [PATCH 279/346] working on auto-update plugin --- pwnagotchi/plugins/default/auto-update.py | 55 +++++++++++++++++------ 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 87969a0..9017803 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -6,6 +6,10 @@ __description__ = 'This plugin checks when updates are available and applies the import logging import subprocess +import requests +import platform + +import pwnagotchi from pwnagotchi.utils import StatusFile OPTIONS = dict() @@ -22,9 +26,30 @@ def on_loaded(): logging.info("[update] plugin loaded.") -def run(cmd): - return subprocess.Popen(cmd, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, - executable="/bin/bash") +def check(version, repo, native=True): + logging.debug("checking remote version for %s, local is %s" % (repo, version)) + info = { + 'current': version, + 'available': None, + 'url': None + } + + resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo) + latest = resp.json() + latest_ver = latest['tag_name'].replace('v', ' ') + arch = platform.machine() + is_arm = arch.startswith('arm') + + if latest_ver != info['current']: + # check if this release is compatible with arm6 + for asset in latest['assets']: + download_url = asset['browser_download_url'] + if download_url.endswith('.zip') and ( + native is False or arch in download_url or is_arm and 'armhf' in download_url): + logging.info("found new update: %s" % download_url) + info['url'] = download_url + + return info def on_internet_available(agent): @@ -35,7 +60,7 @@ def on_internet_available(agent): logging.debug("[update] last check happened less than %d hours ago" % OPTIONS['interval']) return - logging.debug("[update] start") + logging.info("[update] checking for updates ...") display = agent.view() prev_status = display.get('status') @@ -44,17 +69,21 @@ def on_internet_available(agent): display.set('status', 'Checking for updates ...') display.update() - """ - logging.info("auto-update: updating pwnagotchi ...") - run('pip3 install --upgrade --upgrade-strategy only-if-needed pwnagotchi').wait() + to_install = [] + to_check = [ + ( + 'bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), True), + ('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True), + ('evilsocket/pwnagotchi', pwnagotchi.version, False) + ] - if OPTIONS['system']: - logging.info("auto-update: updating packages index ...") - run('apt update -y').wait() + for repo, local_version, is_native in to_check.items(): + info = check(local_version, repo, is_native) + if info['url'] is not None: + to_install.append(info) - logging.info("auto-update: updating packages ...") - run('apt upgrade -y').wait() - """ + if len(to_install) > 0 & & OPTIONS['install']: + logging.info("[update] TODO: install %d updates" % len(to_install)) logging.info("[update] done") From 40c01db1d127c9dd5d20aee3f15b41a5b5ab0e2f Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:08:14 +0200 Subject: [PATCH 280/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 9017803..153cbeb 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -82,7 +82,7 @@ def on_internet_available(agent): if info['url'] is not None: to_install.append(info) - if len(to_install) > 0 & & OPTIONS['install']: + if len(to_install) > 0 and OPTIONS['install']: logging.info("[update] TODO: install %d updates" % len(to_install)) logging.info("[update] done") From fbd12bf87b81826bc2944209c685be785f793f28 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:11:18 +0200 Subject: [PATCH 281/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 153cbeb..e65e511 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -55,6 +55,8 @@ def check(version, repo, native=True): def on_internet_available(agent): global STATUS + logging.debug("[update] internet connectivity is available (ready %s)" % READY) + if READY: if STATUS.newer_then_hours(OPTIONS['interval']): logging.debug("[update] last check happened less than %d hours ago" % OPTIONS['interval']) From e8fa6823026c57315ddf1f9562e02d325866777b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:20:07 +0200 Subject: [PATCH 282/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/log.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index ebada1b..f5c235e 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -168,6 +168,8 @@ class LastSession(object): self.avg_reward /= (self.epochs if self.epochs else 1) def parse(self): + logging.debug("parsing last session logs ...") + lines = [] if os.path.exists(self.path): From c6b3e11e04c089b9d0e489f98886f1785414c3f4 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:29:36 +0200 Subject: [PATCH 283/346] new: new --skip-session for manual mode to skip session parsing --- bin/pwnagotchi | 23 +++++++++++++---------- pwnagotchi/log.py | 41 ++++++++++++++++++++++------------------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index a5412f3..cb9d982 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -21,6 +21,9 @@ if __name__ == '__main__': 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.") @@ -49,16 +52,16 @@ if __name__ == '__main__': elif args.do_manual: logging.info("entering manual mode ...") - agent.last_session.parse() - - 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)) + agent.last_session.parse(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) diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py index f5c235e..dda28e0 100644 --- a/pwnagotchi/log.py +++ b/pwnagotchi/log.py @@ -167,30 +167,33 @@ class LastSession(object): self.duration_human = ', '.join(self.duration_human) self.avg_reward /= (self.epochs if self.epochs else 1) - def parse(self): - logging.debug("parsing last session logs ...") + def parse(self, skip=False): + if skip: + logging.debug("skipping parsing of the last session logs ...") + else: + logging.debug("parsing last session logs ...") - lines = [] + lines = [] - if os.path.exists(self.path): - with FileReadBackwards(self.path, encoding="utf-8") as fp: - for line in fp: - line = line.strip() - if line != "" and line[0] != '[': - continue - lines.append(line) - if LastSession.START_TOKEN in line: - break - lines.reverse() + if os.path.exists(self.path): + with FileReadBackwards(self.path, encoding="utf-8") as fp: + for line in fp: + line = line.strip() + if line != "" and line[0] != '[': + continue + lines.append(line) + if LastSession.START_TOKEN in line: + break + lines.reverse() - if len(lines) == 0: - lines.append("Initial Session"); + if len(lines) == 0: + lines.append("Initial Session"); - self.last_session = lines - self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest() - self.last_saved_session_id = self._get_last_saved_session_id() + self.last_session = lines + self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest() + self.last_saved_session_id = self._get_last_saved_session_id() - self._parse_stats() + self._parse_stats() self.parsed = True def is_new(self): From d49cefe1e4126cce6724cac7550a83252ff44621 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:32:24 +0200 Subject: [PATCH 284/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/view.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 4d72097..506b800 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -123,6 +123,9 @@ class View(object): def set(self, key, value): self._state.set(key, value) + def get(self, key): + return self._state.get(key) + def on_starting(self): self.set('status', self._voice.on_starting()) self.set('face', faces.AWAKE) From 4a4b973f60ce6e3534f467373d332f9601c814ed Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:33:43 +0200 Subject: [PATCH 285/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 5 ++--- pwnagotchi/ui/web.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index e65e511..1d7223e 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -73,13 +73,12 @@ def on_internet_available(agent): to_install = [] to_check = [ - ( - 'bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), True), + ('bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), True), ('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True), ('evilsocket/pwnagotchi', pwnagotchi.version, False) ] - for repo, local_version, is_native in to_check.items(): + for repo, local_version, is_native in to_check: info = check(local_version, repo, is_native) if info['url'] is not None: to_install.append(info) diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index 9eb5b8f..c6a304b 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -173,8 +173,8 @@ class Server(object): if 'origin' in config['video'] and config['video']['origin'] != '*': Handler.AllowedOrigin = config['video']['origin'] else: - logging.warning("THE WEB UI IS RUNNING WITH ALLOWED ORIGIN SET TO *, READ WHY YOU SHOULD CHANGE IT HERE \ - https://developer.mozilla.org/it/docs/Web/HTTP/CORS") + logging.warning("THE WEB UI IS RUNNING WITH ALLOWED ORIGIN SET TO *, READ WHY YOU SHOULD CHANGE IT HERE " + + "https://developer.mozilla.org/it/docs/Web/HTTP/CORS") if self._enabled: _thread.start_new_thread(self._http_serve, ()) From 916be1f63ef2200edd561d4a89e5e61ce60c240c Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:36:06 +0200 Subject: [PATCH 286/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 1d7223e..74e71dc 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -41,13 +41,15 @@ def check(version, repo, native=True): is_arm = arch.startswith('arm') if latest_ver != info['current']: - # check if this release is compatible with arm6 - for asset in latest['assets']: - download_url = asset['browser_download_url'] - if download_url.endswith('.zip') and ( - native is False or arch in download_url or is_arm and 'armhf' in download_url): - logging.info("found new update: %s" % download_url) - info['url'] = download_url + if not native: + info['url'] = latest['zipball_url'] + else: + # check if this release is compatible with arm6 + for asset in latest['assets']: + download_url = asset['browser_download_url'] + if download_url.endswith('.zip') and (arch in download_url or is_arm and 'armhf' in download_url): + logging.info("found new update: %s" % download_url) + info['url'] = download_url return info @@ -73,7 +75,8 @@ def on_internet_available(agent): to_install = [] to_check = [ - ('bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), True), + ( + 'bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), True), ('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True), ('evilsocket/pwnagotchi', pwnagotchi.version, False) ] From 26bb5d6183b1eabf5c5d2eac6558c9434a8f5f93 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:39:01 +0200 Subject: [PATCH 287/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 74e71dc..42effb8 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -48,8 +48,8 @@ def check(version, repo, native=True): for asset in latest['assets']: download_url = asset['browser_download_url'] if download_url.endswith('.zip') and (arch in download_url or is_arm and 'armhf' in download_url): - logging.info("found new update: %s" % download_url) info['url'] = download_url + break return info @@ -84,6 +84,7 @@ def on_internet_available(agent): for repo, local_version, is_native in to_check: info = check(local_version, repo, is_native) if info['url'] is not None: + logging.warning("new update for %s is available: %s" % (repo, info['url'])) to_install.append(info) if len(to_install) > 0 and OPTIONS['install']: From c947d5c43b68c7c1ba5e4bb0e0bd1769b1b98ba3 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:41:57 +0200 Subject: [PATCH 288/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 42effb8..5757925 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -71,7 +71,7 @@ def on_internet_available(agent): try: display.set('status', 'Checking for updates ...') - display.update() + display.update(force=True) to_install = [] to_check = [ @@ -87,8 +87,12 @@ def on_internet_available(agent): logging.warning("new update for %s is available: %s" % (repo, info['url'])) to_install.append(info) - if len(to_install) > 0 and OPTIONS['install']: - logging.info("[update] TODO: install %d updates" % len(to_install)) + num_updates = len(to_install) + if num_updates > 0: + if OPTIONS['install']: + logging.info("[update] TODO: install %d updates" % len(to_install)) + else: + prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '') logging.info("[update] done") @@ -98,4 +102,4 @@ def on_internet_available(agent): logging.error("[update] %s" % e) display.set('status', prev_status if prev_status is not None else '') - display.update() + display.update(force=True) From 4653c5d95dffa5a90494a378a9e608bebac8424e Mon Sep 17 00:00:00 2001 From: Kirill <iam@python273.pw> Date: Sun, 20 Oct 2019 19:45:43 +0300 Subject: [PATCH 289/346] Fix Origin header check bypass --- pwnagotchi/ui/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index 9eb5b8f..ad727f7 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -141,7 +141,7 @@ class Handler(BaseHTTPRequestHandler): return False if Handler.AllowedOrigin != '*': - if origin != Handler.AllowedOrigin and not origin.starts_with(Handler.AllowedOrigin): + if origin != Handler.AllowedOrigin: logging.warning("request with blocked Origin from %s: %s" % (self.address_string(), origin)) return False From f7a23b32c139563ba91301130a87e107044ddcc7 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:45:56 +0200 Subject: [PATCH 290/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 5757925..8427029 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -42,7 +42,7 @@ def check(version, repo, native=True): if latest_ver != info['current']: if not native: - info['url'] = latest['zipball_url'] + info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name']) else: # check if this release is compatible with arm6 for asset in latest['assets']: @@ -101,5 +101,6 @@ def on_internet_available(agent): except Exception as e: logging.error("[update] %s" % e) + logging.debug("[update] setting status '%s'" % prev_status) display.set('status', prev_status if prev_status is not None else '') display.update(force=True) From 2e8f2aa1b8308cf0abe6b9d4684243a11dfde081 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:48:05 +0200 Subject: [PATCH 291/346] misc: small fix or general refactoring i did not bother commenting --- bin/pwnagotchi | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index cb9d982..a23524e 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -64,7 +64,9 @@ if __name__ == '__main__': agent.last_session.max_reward)) while True: - display.on_manual_mode(agent.last_session) + if not args.skip_session: + display.on_manual_mode(agent.last_session) + time.sleep(1) if grid.is_connected(): From 0ce1fa9d1282537ab50b6359fb735e6e0b2f9cc9 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:49:45 +0200 Subject: [PATCH 292/346] misc: small fix or general refactoring i did not bother commenting --- bin/pwnagotchi | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bin/pwnagotchi b/bin/pwnagotchi index a23524e..d6303d5 100755 --- a/bin/pwnagotchi +++ b/bin/pwnagotchi @@ -63,12 +63,10 @@ if __name__ == '__main__': agent.last_session.min_reward, agent.last_session.max_reward)) + display.on_manual_mode(agent.last_session) + while True: - if not args.skip_session: - display.on_manual_mode(agent.last_session) - time.sleep(1) - if grid.is_connected(): plugins.on('internet_available', agent) From 152676f65115ed6c5e686dbbc937b0e910b54bc6 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 18:51:33 +0200 Subject: [PATCH 293/346] fix: don't show sad face in manual mode for very short sessions --- pwnagotchi/ui/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 506b800..977dbe8 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -138,7 +138,7 @@ class View(object): def on_manual_mode(self, last_session): self.set('mode', 'MANU') - self.set('face', faces.SAD if last_session.handshakes == 0 else faces.HAPPY) + self.set('face', faces.SAD if (last_session.epochs > 3 and last_session.handshakes == 0) else faces.HAPPY) self.set('status', self._voice.on_last_session_data(last_session)) self.set('epoch', "%04d" % last_session.epochs) self.set('uptime', last_session.duration) From f762c3ac0d8e3f739ff084b94872d3ba4b693acc Mon Sep 17 00:00:00 2001 From: Kirill <iam@python273.pw> Date: Sun, 20 Oct 2019 20:03:17 +0300 Subject: [PATCH 294/346] Fix headers.get('origin') == None check --- pwnagotchi/ui/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index ad727f7..7a08b8c 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -136,7 +136,7 @@ class Handler(BaseHTTPRequestHandler): # check the Origin header vs CORS def _is_allowed(self): origin = self.headers.get('origin') - if origin == "": + if not origin: logging.warning("request with no Origin header from %s" % self.address_string()) return False From 677b335403907d655fc81205b6d550e3705e67ea Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:18:50 +0200 Subject: [PATCH 295/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 35 +++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 8427029..ebf84ec 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -8,8 +8,10 @@ import logging import subprocess import requests import platform +import shutil import pwnagotchi +import os from pwnagotchi.utils import StatusFile OPTIONS = dict() @@ -29,14 +31,16 @@ def on_loaded(): def check(version, repo, native=True): logging.debug("checking remote version for %s, local is %s" % (repo, version)) info = { + 'repo': repo, 'current': version, 'available': None, - 'url': None + 'url': None, + 'native': native, } resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo) latest = resp.json() - latest_ver = latest['tag_name'].replace('v', ' ') + info['available'] = latest_ver = latest['tag_name'].replace('v', ' ') arch = platform.machine() is_arm = arch.startswith('arm') @@ -54,6 +58,25 @@ def check(version, repo, native=True): return info +def install(display, update): + name = update['repo'].split('/')[1] + + display.set('status', 'Updating %s ...' % name) + display.update(force=True) + + path = os.path.join("/tmp/updates/%s_%s" % (name, update['available'])) + if os.path.exists(path): + shutil.rmtree(path, ignore_errors=True, onerror=None) + + os.makedirs(path) + + target = "%s_%s.zip" % (name, update['available']) + + logging.info("[update] downloading %s to %s ..." % (update['url'], target)) + + os.system("wget '%s' -O '%s'" % (update['url'], target)) + + def on_internet_available(agent): global STATUS @@ -76,7 +99,8 @@ def on_internet_available(agent): to_install = [] to_check = [ ( - 'bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), True), + 'bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), + True), ('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True), ('evilsocket/pwnagotchi', pwnagotchi.version, False) ] @@ -84,13 +108,14 @@ def on_internet_available(agent): for repo, local_version, is_native in to_check: info = check(local_version, repo, is_native) if info['url'] is not None: - logging.warning("new update for %s is available: %s" % (repo, info['url'])) + logging.warning("update for %s available: %s" % (repo, info['url'])) to_install.append(info) num_updates = len(to_install) if num_updates > 0: if OPTIONS['install']: - logging.info("[update] TODO: install %d updates" % len(to_install)) + for update in to_install: + install(display, update) else: prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '') From a4e072cf3362361610e72f8fe123ea4f594332b2 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:22:04 +0200 Subject: [PATCH 296/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index ebf84ec..6580377 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -71,10 +71,11 @@ def install(display, update): os.makedirs(path) target = "%s_%s.zip" % (name, update['available']) + target_path = os.path.join(path, target) - logging.info("[update] downloading %s to %s ..." % (update['url'], target)) + logging.info("[update] downloading %s to %s ..." % (update['url'], target_path)) - os.system("wget '%s' -O '%s'" % (update['url'], target)) + os.system('wget -q "%s" -O "%s"' % (update['url'], target_path)) def on_internet_available(agent): From ddeefa037d6d69ef12d938d77e43153771479282 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:27:31 +0200 Subject: [PATCH 297/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 6580377..7c04506 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -36,13 +36,13 @@ def check(version, repo, native=True): 'available': None, 'url': None, 'native': native, + 'arch': platform.machine() } resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo) latest = resp.json() - info['available'] = latest_ver = latest['tag_name'].replace('v', ' ') - arch = platform.machine() - is_arm = arch.startswith('arm') + info['available'] = latest_ver = latest['tag_name'].replace('v', '') + is_arm = info['arch'].startswith('arm') if latest_ver != info['current']: if not native: @@ -61,11 +61,12 @@ def check(version, repo, native=True): def install(display, update): name = update['repo'].split('/')[1] - display.set('status', 'Updating %s ...' % name) + display.set('status', 'Downloading %s ...' % name) display.update(force=True) - path = os.path.join("/tmp/updates/%s_%s" % (name, update['available'])) + path = os.path.join("/tmp/updates/", name) if os.path.exists(path): + logging.debug("[update] deleting %s" % path) shutil.rmtree(path, ignore_errors=True, onerror=None) os.makedirs(path) @@ -77,6 +78,15 @@ def install(display, update): os.system('wget -q "%s" -O "%s"' % (update['url'], target_path)) + logging.info("[update] extracting %s to %s ..." % (target_path, path)) + + display.set('status', 'Extracting %s ...' % name) + display.update(force=True) + + os.system('unzip "%s" -d "%s"' % (target_path, path)) + + logging.info("TODO") + def on_internet_available(agent): global STATUS From 0cdb8c32211c2381114598bca0eec5ad144f9fc4 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:29:11 +0200 Subject: [PATCH 298/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 7c04506..f96dcd1 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -51,7 +51,7 @@ def check(version, repo, native=True): # check if this release is compatible with arm6 for asset in latest['assets']: download_url = asset['browser_download_url'] - if download_url.endswith('.zip') and (arch in download_url or is_arm and 'armhf' in download_url): + if download_url.endswith('.zip') and (info['arch'] in download_url or (is_arm and 'armhf' in download_url)): info['url'] = download_url break From c72cb5b9621d55fae8477e3ef64cd36ebd122420 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:42:15 +0200 Subject: [PATCH 299/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 28 ++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index f96dcd1..314acd8 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -9,6 +9,7 @@ import subprocess import requests import platform import shutil +import glob import pwnagotchi import os @@ -85,7 +86,32 @@ def install(display, update): os.system('unzip "%s" -d "%s"' % (target_path, path)) - logging.info("TODO") + checksums = glob.glob("%s/*.sha256") + if len(checksums) == 0: + if update['native']: + logging.warning("native update without SHA256 checksum file") + + else: + binary_path = os.path.join(path, name) + checksum = checksums[0] + + logging.info("[update] verifying %s for %s ..." % (checksum, binary_path)) + + with open(checksums) as fp: + expected = fp.read().strip().lower() + + real = subprocess.getoutput('sha256sum "%s"' % binary_path).split(' ')[0].strip().lower() + + if real != expected: + logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (binary_path, expected, real)) + return + + if update['native']: + dest_path = subprocess.getoutput("which %s" % name) + logging.info("[update] installing %s to %s ... TODO" % (binary_path, dest_path)) + + else: + logging.info("[update] installing %s ... TODO" % binary_path) def on_internet_available(agent): From 63568f1725d062b1316b10434d7ccf38e1408b70 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:45:45 +0200 Subject: [PATCH 300/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 16 ++++++++-------- pwnagotchi/ui/view.py | 5 ++++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 314acd8..0d8f17c 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -62,8 +62,7 @@ def check(version, repo, native=True): def install(display, update): name = update['repo'].split('/')[1] - display.set('status', 'Downloading %s ...' % name) - display.update(force=True) + display.update(force=True, new_data={'status': 'Downloading %s ...' % name}) path = os.path.join("/tmp/updates/", name) if os.path.exists(path): @@ -81,8 +80,7 @@ def install(display, update): logging.info("[update] extracting %s to %s ..." % (target_path, path)) - display.set('status', 'Extracting %s ...' % name) - display.update(force=True) + display.update(force=True, new_data={'status': 'Extracting %s ...' % name}) os.system('unzip "%s" -d "%s"' % (target_path, path)) @@ -92,6 +90,8 @@ def install(display, update): logging.warning("native update without SHA256 checksum file") else: + display.update(force=True, new_data={'status': 'Verifying %s ...' % name}) + binary_path = os.path.join(path, name) checksum = checksums[0] @@ -106,6 +106,8 @@ def install(display, update): logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (binary_path, expected, real)) return + display.update(force=True, new_data={'status': 'Installing %s ...' % name}) + if update['native']: dest_path = subprocess.getoutput("which %s" % name) logging.info("[update] installing %s to %s ... TODO" % (binary_path, dest_path)) @@ -130,8 +132,7 @@ def on_internet_available(agent): prev_status = display.get('status') try: - display.set('status', 'Checking for updates ...') - display.update(force=True) + display.update(force=True, new_data={'status': 'Checking for updates ...'}) to_install = [] to_check = [ @@ -164,5 +165,4 @@ def on_internet_available(agent): logging.error("[update] %s" % e) logging.debug("[update] setting status '%s'" % prev_status) - display.set('status', prev_status if prev_status is not None else '') - display.update(force=True) + display.update(force=True, new_data={'status': prev_status if prev_status is not None else ''}) diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index 977dbe8..a2f2d2c 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -310,7 +310,10 @@ class View(object): self.set('status', self._voice.custom(text)) self.update() - def update(self, force=False): + def update(self, force=False, new_data={}): + for key, val in new_data.items(): + self.set(key, val) + with self._lock: if self._frozen: return From 4018071bbaf0ab8cbb6e282e48e6459a9f857dcb Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:47:08 +0200 Subject: [PATCH 301/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 0d8f17c..6450ed0 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -84,6 +84,7 @@ def install(display, update): os.system('unzip "%s" -d "%s"' % (target_path, path)) + source_path = os.path.join(path, name) checksums = glob.glob("%s/*.sha256") if len(checksums) == 0: if update['native']: @@ -92,28 +93,27 @@ def install(display, update): else: display.update(force=True, new_data={'status': 'Verifying %s ...' % name}) - binary_path = os.path.join(path, name) checksum = checksums[0] - logging.info("[update] verifying %s for %s ..." % (checksum, binary_path)) + logging.info("[update] verifying %s for %s ..." % (checksum, source_path)) with open(checksums) as fp: expected = fp.read().strip().lower() - real = subprocess.getoutput('sha256sum "%s"' % binary_path).split(' ')[0].strip().lower() + real = subprocess.getoutput('sha256sum "%s"' % source_path).split(' ')[0].strip().lower() if real != expected: - logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (binary_path, expected, real)) + logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (source_path, expected, real)) return display.update(force=True, new_data={'status': 'Installing %s ...' % name}) if update['native']: dest_path = subprocess.getoutput("which %s" % name) - logging.info("[update] installing %s to %s ... TODO" % (binary_path, dest_path)) + logging.info("[update] installing %s to %s ... TODO" % (source_path, dest_path)) else: - logging.info("[update] installing %s ... TODO" % binary_path) + logging.info("[update] installing %s ... TODO" % source_path) def on_internet_available(agent): From 6d5054387bb00a1d4480f870a8054fec6ed6c101 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:48:52 +0200 Subject: [PATCH 302/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 6450ed0..e78e67c 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -85,7 +85,7 @@ def install(display, update): os.system('unzip "%s" -d "%s"' % (target_path, path)) source_path = os.path.join(path, name) - checksums = glob.glob("%s/*.sha256") + checksums = glob.glob("%s/*.sha256" % path) if len(checksums) == 0: if update['native']: logging.warning("native update without SHA256 checksum file") From 8d0d3df2b04b6b3d0786ce11cf7c1301407b6174 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:50:05 +0200 Subject: [PATCH 303/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index e78e67c..8e0ca32 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -88,7 +88,8 @@ def install(display, update): checksums = glob.glob("%s/*.sha256" % path) if len(checksums) == 0: if update['native']: - logging.warning("native update without SHA256 checksum file") + logging.warning("[update] native update without SHA256 checksum file") + return else: display.update(force=True, new_data={'status': 'Verifying %s ...' % name}) @@ -97,7 +98,7 @@ def install(display, update): logging.info("[update] verifying %s for %s ..." % (checksum, source_path)) - with open(checksums) as fp: + with open(checksums, 'rt') as fp: expected = fp.read().strip().lower() real = subprocess.getoutput('sha256sum "%s"' % source_path).split(' ')[0].strip().lower() From 682b373c661949aef15a5838bb5d8316d92ca15b Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:51:30 +0200 Subject: [PATCH 304/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 8e0ca32..fdd1802 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -98,7 +98,7 @@ def install(display, update): logging.info("[update] verifying %s for %s ..." % (checksum, source_path)) - with open(checksums, 'rt') as fp: + with open(checksum, 'rt') as fp: expected = fp.read().strip().lower() real = subprocess.getoutput('sha256sum "%s"' % source_path).split(' ')[0].strip().lower() From f9cbef969781aa987b8bbe374442420f3d810eab Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:53:02 +0200 Subject: [PATCH 305/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index fdd1802..eaa7396 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -99,7 +99,7 @@ def install(display, update): logging.info("[update] verifying %s for %s ..." % (checksum, source_path)) with open(checksum, 'rt') as fp: - expected = fp.read().strip().lower() + expected = fp.read().split('=')[1].strip().lower() real = subprocess.getoutput('sha256sum "%s"' % source_path).split(' ')[0].strip().lower() From 61523740247c16d28ce42a7891051a8def97427c Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 19:56:31 +0200 Subject: [PATCH 306/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/__init__.py | 2 +- pwnagotchi/plugins/default/auto-update.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index 14737d5..caa4bc9 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -4,7 +4,7 @@ import logging import time import pwnagotchi.ui.view as view -version = '1.0.1' +version = '1.1.0b' _name = None diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index eaa7396..b172e97 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -111,6 +111,10 @@ def install(display, update): if update['native']: dest_path = subprocess.getoutput("which %s" % name) + if dest_path == "": + logging.warning("[update] can't find path for %s" % name) + return + logging.info("[update] installing %s to %s ... TODO" % (source_path, dest_path)) else: From 2a450e64efb37d76a6c2a282a81f1f77320e590d Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 20:32:18 +0200 Subject: [PATCH 307/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 31 +++++++++++++++-------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index b172e97..878d707 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -89,7 +89,7 @@ def install(display, update): if len(checksums) == 0: if update['native']: logging.warning("[update] native update without SHA256 checksum file") - return + return False else: display.update(force=True, new_data={'status': 'Verifying %s ...' % name}) @@ -105,7 +105,7 @@ def install(display, update): if real != expected: logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (source_path, expected, real)) - return + return False display.update(force=True, new_data={'status': 'Installing %s ...' % name}) @@ -113,12 +113,16 @@ def install(display, update): dest_path = subprocess.getoutput("which %s" % name) if dest_path == "": logging.warning("[update] can't find path for %s" % name) - return + return False - logging.info("[update] installing %s to %s ... TODO" % (source_path, dest_path)) + logging.info("[update] service %s stop" % update['service']) + logging.info("[update] mv %s %s" % (source_path, dest_path)) + logging.info("[update] service %s start" % update['service']) else: - logging.info("[update] installing %s ... TODO" % source_path) + logging.info("[update] cd %s && pip3 install ." % source_path) + + return True def on_internet_available(agent): @@ -143,22 +147,26 @@ def on_internet_available(agent): to_check = [ ( 'bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), - True), - ('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True), - ('evilsocket/pwnagotchi', pwnagotchi.version, False) + True, 'bettercap'), + ('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True, 'pwndrid-peer'), + ('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi') ] - for repo, local_version, is_native in to_check: + for repo, local_version, is_native, svc_name in to_check: info = check(local_version, repo, is_native) if info['url'] is not None: logging.warning("update for %s available: %s" % (repo, info['url'])) + info['service'] = svc_name to_install.append(info) num_updates = len(to_install) + num_installed = 0 + if num_updates > 0: if OPTIONS['install']: for update in to_install: - install(display, update) + if install(display, update): + num_installed += 1 else: prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '') @@ -166,6 +174,9 @@ def on_internet_available(agent): STATUS.update() + if num_installed > 0: + logging.info("[update] pwnagotchi.reboot()") + except Exception as e: logging.error("[update] %s" % e) From 999b130224fb3a147ed1d4ba2a2f19090b0f7094 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 20:46:18 +0200 Subject: [PATCH 308/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 878d707..9e5be0e 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -82,7 +82,7 @@ def install(display, update): display.update(force=True, new_data={'status': 'Extracting %s ...' % name}) - os.system('unzip "%s" -d "%s"' % (target_path, path)) + os.system('unzip "%s" -q -d "%s"' % (target_path, path)) source_path = os.path.join(path, name) checksums = glob.glob("%s/*.sha256" % path) @@ -120,6 +120,9 @@ def install(display, update): logging.info("[update] service %s start" % update['service']) else: + if not os.path.exists(source_path): + source_path = "%s-%s" % (source_path, update['available']) + logging.info("[update] cd %s && pip3 install ." % source_path) return True From 649091766f28b20e7e1963c2de9444e3869f23ed Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 20:47:52 +0200 Subject: [PATCH 309/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 9e5be0e..f9e8d9e 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -82,7 +82,7 @@ def install(display, update): display.update(force=True, new_data={'status': 'Extracting %s ...' % name}) - os.system('unzip "%s" -q -d "%s"' % (target_path, path)) + os.system('unzip "%s" -d "%s"' % (target_path, path)) source_path = os.path.join(path, name) checksums = glob.glob("%s/*.sha256" % path) From 399e78e675e24181d3a5a56b25fac819a9c10aaa Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 20:52:22 +0200 Subject: [PATCH 310/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index f9e8d9e..86ee736 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -115,15 +115,17 @@ def install(display, update): logging.warning("[update] can't find path for %s" % name) return False - logging.info("[update] service %s stop" % update['service']) - logging.info("[update] mv %s %s" % (source_path, dest_path)) - logging.info("[update] service %s start" % update['service']) + os.system("service %s stop" % update['service']) + os.system("mv %s %s" % (source_path, dest_path)) else: if not os.path.exists(source_path): source_path = "%s-%s" % (source_path, update['available']) - logging.info("[update] cd %s && pip3 install ." % source_path) + os.system("service %s stop" % update['service']) + os.system("cd %s && pip3 install ." % source_path) + + os.system("service %s start" % update['service']) return True @@ -178,7 +180,8 @@ def on_internet_available(agent): STATUS.update() if num_installed > 0: - logging.info("[update] pwnagotchi.reboot()") + display.update(force=True, new_data={'status': 'Rebooting ...'}) + pwnagotchi.reboot() except Exception as e: logging.error("[update] %s" % e) From 74a75f03b8cfa632c6a78e0f6fb5676434468753 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 20:57:15 +0200 Subject: [PATCH 311/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 86ee736..2be968a 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -79,7 +79,6 @@ def install(display, update): os.system('wget -q "%s" -O "%s"' % (update['url'], target_path)) logging.info("[update] extracting %s to %s ..." % (target_path, path)) - display.update(force=True, new_data={'status': 'Extracting %s ...' % name}) os.system('unzip "%s" -d "%s"' % (target_path, path)) @@ -92,11 +91,10 @@ def install(display, update): return False else: - display.update(force=True, new_data={'status': 'Verifying %s ...' % name}) - checksum = checksums[0] logging.info("[update] verifying %s for %s ..." % (checksum, source_path)) + display.update(force=True, new_data={'status': 'Verifying %s ...' % name}) with open(checksum, 'rt') as fp: expected = fp.read().split('=')[1].strip().lower() @@ -107,6 +105,7 @@ def install(display, update): logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (source_path, expected, real)) return False + logging.info("[update] installing %s ..." % name) display.update(force=True, new_data={'status': 'Installing %s ...' % name}) if update['native']: @@ -125,6 +124,7 @@ def install(display, update): os.system("service %s stop" % update['service']) os.system("cd %s && pip3 install ." % source_path) + logging.info("[update] restarting %s ..." % update['service']) os.system("service %s start" % update['service']) return True @@ -153,7 +153,7 @@ def on_internet_available(agent): ( 'bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), True, 'bettercap'), - ('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True, 'pwndrid-peer'), + ('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True, 'pwngrid-peer'), ('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi') ] From 132666935a396b3528e854bbc58eb8ff7b7d0381 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 20:59:59 +0200 Subject: [PATCH 312/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 2be968a..6889277 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -114,19 +114,17 @@ def install(display, update): logging.warning("[update] can't find path for %s" % name) return False + logging.info("[update] stopping %s ..." % update['service']) os.system("service %s stop" % update['service']) os.system("mv %s %s" % (source_path, dest_path)) - + logging.info("[update] restarting %s ..." % update['service']) + os.system("service %s start" % update['service']) else: if not os.path.exists(source_path): source_path = "%s-%s" % (source_path, update['available']) - os.system("service %s stop" % update['service']) os.system("cd %s && pip3 install ." % source_path) - logging.info("[update] restarting %s ..." % update['service']) - os.system("service %s start" % update['service']) - return True From 0263777e4b4cb22c30d1672277f6dc305d11cb18 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 21:18:43 +0200 Subject: [PATCH 313/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 37 ++++++++++++++++------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 6889277..735b492 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -52,29 +52,29 @@ def check(version, repo, native=True): # check if this release is compatible with arm6 for asset in latest['assets']: download_url = asset['browser_download_url'] - if download_url.endswith('.zip') and (info['arch'] in download_url or (is_arm and 'armhf' in download_url)): + if download_url.endswith('.zip') and ( + info['arch'] in download_url or (is_arm and 'armhf' in download_url)): info['url'] = download_url break return info -def install(display, update): - name = update['repo'].split('/')[1] - - display.update(force=True, new_data={'status': 'Downloading %s ...' % name}) - +def make_path_for(name): path = os.path.join("/tmp/updates/", name) if os.path.exists(path): logging.debug("[update] deleting %s" % path) shutil.rmtree(path, ignore_errors=True, onerror=None) - os.makedirs(path) + return path + +def download_and_unzip(name, path, display, update): target = "%s_%s.zip" % (name, update['available']) target_path = os.path.join(path, target) logging.info("[update] downloading %s to %s ..." % (update['url'], target_path)) + display.update(force=True, new_data={'status': 'Downloading %s ...' % name}) os.system('wget -q "%s" -O "%s"' % (update['url'], target_path)) @@ -83,7 +83,10 @@ def install(display, update): os.system('unzip "%s" -d "%s"' % (target_path, path)) - source_path = os.path.join(path, name) + +def verify(name, path, source_path, display, update): + display.update(force=True, new_data={'status': 'Verifying %s ...' % name}) + checksums = glob.glob("%s/*.sha256" % path) if len(checksums) == 0: if update['native']: @@ -94,7 +97,6 @@ def install(display, update): checksum = checksums[0] logging.info("[update] verifying %s for %s ..." % (checksum, source_path)) - display.update(force=True, new_data={'status': 'Verifying %s ...' % name}) with open(checksum, 'rt') as fp: expected = fp.read().split('=')[1].strip().lower() @@ -105,6 +107,20 @@ def install(display, update): logging.warning("[update] checksum mismatch for %s: expected=%s got=%s" % (source_path, expected, real)) return False + return True + + +def install(display, update): + name = update['repo'].split('/')[1] + + path = make_path_for(name) + + download_and_unzip(name, path, display, update) + + source_path = os.path.join(path, name) + if not verify(name, path, source_path, update): + return False + logging.info("[update] installing %s ..." % name) display.update(force=True, new_data={'status': 'Installing %s ...' % name}) @@ -148,8 +164,7 @@ def on_internet_available(agent): to_install = [] to_check = [ - ( - 'bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), + ('bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), True, 'bettercap'), ('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True, 'pwngrid-peer'), ('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi') From 375486f9d06130d76ae1014c38a79973ed639301 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 21:20:55 +0200 Subject: [PATCH 314/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 735b492..af340e1 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -118,7 +118,7 @@ def install(display, update): download_and_unzip(name, path, display, update) source_path = os.path.join(path, name) - if not verify(name, path, source_path, update): + if not verify(name, path, source_path, display, update): return False logging.info("[update] installing %s ..." % name) From 855b4930405304648669a5af4d88a4050e1a307d Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 21:25:27 +0200 Subject: [PATCH 315/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index af340e1..86b854d 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -74,18 +74,18 @@ def download_and_unzip(name, path, display, update): target_path = os.path.join(path, target) logging.info("[update] downloading %s to %s ..." % (update['url'], target_path)) - display.update(force=True, new_data={'status': 'Downloading %s ...' % name}) + display.update(force=True, new_data={'status': 'Downloading %s %s ...' % (name, update['available'])}) os.system('wget -q "%s" -O "%s"' % (update['url'], target_path)) logging.info("[update] extracting %s to %s ..." % (target_path, path)) - display.update(force=True, new_data={'status': 'Extracting %s ...' % name}) + display.update(force=True, new_data={'status': 'Extracting %s %s ...' % (name, update['available'])}) os.system('unzip "%s" -d "%s"' % (target_path, path)) def verify(name, path, source_path, display, update): - display.update(force=True, new_data={'status': 'Verifying %s ...' % name}) + display.update(force=True, new_data={'status': 'Verifying %s %s ...' % (name, update['available'])}) checksums = glob.glob("%s/*.sha256" % path) if len(checksums) == 0: @@ -122,7 +122,7 @@ def install(display, update): return False logging.info("[update] installing %s ..." % name) - display.update(force=True, new_data={'status': 'Installing %s ...' % name}) + display.update(force=True, new_data={'status': 'Installing %s %s ...' % (name, update['available'])}) if update['native']: dest_path = subprocess.getoutput("which %s" % name) From 376a74d7ac10d737d3e42d8c8b73dde624b2f210 Mon Sep 17 00:00:00 2001 From: SpiderDead <mimij68@live.nl> Date: Sun, 20 Oct 2019 21:26:32 +0200 Subject: [PATCH 316/346] Added documentation for waveshare27inch Signed-off-by: Mike van der Vrugt <mimij68@live.nl> --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 3142761..dc4e336 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -188,7 +188,7 @@ ui: display: enabled: true rotation: 180 - # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat + # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, waveshare27inch type: 'waveshare_2' # Possible options red/yellow/black (black used for monocromatic displays) color: 'black' From 3c97dbf8dc630cdc534c392581e6973b1c7f5b66 Mon Sep 17 00:00:00 2001 From: SpiderDead <mimij68@live.nl> Date: Sun, 20 Oct 2019 21:27:10 +0200 Subject: [PATCH 317/346] Added waveshare27inch as a known display Signed-off-by: Mike van der Vrugt <mimij68@live.nl> --- pwnagotchi/ui/display.py | 3 +++ pwnagotchi/ui/hw/__init__.py | 4 ++++ pwnagotchi/utils.py | 3 +++ 3 files changed, 10 insertions(+) diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index eac519f..c4fd495 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -29,6 +29,9 @@ class Display(View): def is_waveshare_v2(self): return self._implementation.name == 'waveshare_2' + def is_waveshare27inch(self): + return self._implementation.name == 'waveshare27inch' + def is_oledhat(self): return self._implementation.name == 'oledhat' diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py index ee00a73..1e0bf31 100644 --- a/pwnagotchi/ui/hw/__init__.py +++ b/pwnagotchi/ui/hw/__init__.py @@ -4,6 +4,7 @@ from pwnagotchi.ui.hw.oledhat import OledHat from pwnagotchi.ui.hw.lcdhat import LcdHat from pwnagotchi.ui.hw.waveshare1 import WaveshareV1 from pwnagotchi.ui.hw.waveshare2 import WaveshareV2 +from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch def display_for(config): @@ -26,3 +27,6 @@ def display_for(config): elif config['ui']['display']['type'] == 'waveshare_2': return WaveshareV2(config) + + elif config['ui']['display']['type'] == 'waveshare27inch': + return Waveshare27inch(config) \ No newline at end of file diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 419d80d..f431680 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -82,6 +82,9 @@ def load_config(args): elif config['ui']['display']['type'] in ('ws_2', 'ws2', 'waveshare_2', 'waveshare2'): config['ui']['display']['type'] = 'waveshare_2' + elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare_27inch', 'waveshare27inch'): + config['ui']['display']['type'] = 'waveshare27inch' + else: print("unsupported display type %s" % config['ui']['display']['type']) exit(1) From 9df1dbe077d1c464165586017429cc09db1e9042 Mon Sep 17 00:00:00 2001 From: SpiderDead <mimij68@live.nl> Date: Sun, 20 Oct 2019 21:27:44 +0200 Subject: [PATCH 318/346] Added configuration file for waveshare27inch Signed-off-by: Mike van der Vrugt <mimij68@live.nl> --- pwnagotchi/ui/hw/waveshare27inch.py | 46 +++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 pwnagotchi/ui/hw/waveshare27inch.py diff --git a/pwnagotchi/ui/hw/waveshare27inch.py b/pwnagotchi/ui/hw/waveshare27inch.py new file mode 100644 index 0000000..9dff62d --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare27inch.py @@ -0,0 +1,46 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class Waveshare27inch(DisplayImpl): + def __init__(self, config): + super(Waveshare27inch, self).__init__(config, 'waveshare_2_7inch') + self._display = None + + def layout(self): + fonts.setup(10, 9, 10, 35) + self._layout['width'] = 264 + self._layout['height'] = 176 + self._layout['face'] = (0, 54) + self._layout['name'] = (5, 34) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (199, 0) + self._layout['line1'] = [0, 14, 264, 14] + self._layout['line2'] = [0, 162, 264, 162] + self._layout['friend_face'] = (0, 146) + self._layout['friend_name'] = (40, 146) + self._layout['shakes'] = (0, 163) + self._layout['mode'] = (239, 163) + self._layout['status'] = { + 'pos': (139, 34), + 'font': fonts.Medium, + 'max': 20 + } + return self._layout + + def initialize(self): + logging.info("initializing waveshare v1 2.7 inch display") + from pwnagotchi.ui.hw.libs.waveshare.v27inch.epd2in7 import EPD + self._display = EPD() + self._display.init() + self._display.Clear(0xFF) + + def render(self, canvas): + buf = self._display.getbuffer(canvas) + self._display.display(buf) + + def clear(self): + self._display.Clear(0xff) From d6d810497ecd0137cb53e109d90ca8efcfb65486 Mon Sep 17 00:00:00 2001 From: SpiderDead <mimij68@live.nl> Date: Sun, 20 Oct 2019 21:28:29 +0200 Subject: [PATCH 319/346] Added libraries for the 2.7" display Signed-off-by: Mike van der Vrugt <mimij68@live.nl> --- .../ui/hw/libs/waveshare/v27inch/__init__.py | 0 .../ui/hw/libs/waveshare/v27inch/epd2in7.py | 300 ++++++++++++++++++ .../ui/hw/libs/waveshare/v27inch/epdconfig.py | 73 +++++ 3 files changed, 373 insertions(+) create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v27inch/__init__.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py diff --git a/pwnagotchi/ui/hw/libs/waveshare/v27inch/__init__.py b/pwnagotchi/ui/hw/libs/waveshare/v27inch/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py new file mode 100644 index 0000000..01e8473 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epd2in7.py @@ -0,0 +1,300 @@ +# /***************************************************************************** +# * | File : EPD_1in54.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V3.0 +# * | Date : 2018-11-06 +# * | Info : python2 demo +# * 1.Remove: +# digital_write(self, pin, value) +# digital_read(self, pin) +# delay_ms(self, delaytime) +# set_lut(self, lut) +# self.lut = self.lut_full_update +# * 2.Change: +# display_frame -> TurnOnDisplay +# set_memory_area -> SetWindow +# set_memory_pointer -> SetCursor +# get_frame_buffer -> getbuffer +# set_frame_memory -> display +# * 3.How to use +# epd = epd2in7.EPD() +# epd.init(epd.lut_full_update) +# image = Image.new('1', (epd1in54.EPD_WIDTH, epd1in54.EPD_HEIGHT), 255) +# ... +# drawing ...... +# ... +# epd.display(getbuffer(image)) +# ******************************************************************************/ +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + + +from . import epdconfig +from PIL import Image +import RPi.GPIO as GPIO + +# Display resolution +EPD_WIDTH = 176 +EPD_HEIGHT = 264 + +# EPD2IN7 commands +PANEL_SETTING = 0x00 +POWER_SETTING = 0x01 +POWER_OFF = 0x02 +POWER_OFF_SEQUENCE_SETTING = 0x03 +POWER_ON = 0x04 +POWER_ON_MEASURE = 0x05 +BOOSTER_SOFT_START = 0x06 +DEEP_SLEEP = 0x07 +DATA_START_TRANSMISSION_1 = 0x10 +DATA_STOP = 0x11 +DISPLAY_REFRESH = 0x12 +DATA_START_TRANSMISSION_2 = 0x13 +PARTIAL_DATA_START_TRANSMISSION_1 = 0x14 +PARTIAL_DATA_START_TRANSMISSION_2 = 0x15 +PARTIAL_DISPLAY_REFRESH = 0x16 +LUT_FOR_VCOM = 0x20 +LUT_WHITE_TO_WHITE = 0x21 +LUT_BLACK_TO_WHITE = 0x22 +LUT_WHITE_TO_BLACK = 0x23 +LUT_BLACK_TO_BLACK = 0x24 +PLL_CONTROL = 0x30 +TEMPERATURE_SENSOR_COMMAND = 0x40 +TEMPERATURE_SENSOR_CALIBRATION = 0x41 +TEMPERATURE_SENSOR_WRITE = 0x42 +TEMPERATURE_SENSOR_READ = 0x43 +VCOM_AND_DATA_INTERVAL_SETTING = 0x50 +LOW_POWER_DETECTION = 0x51 +TCON_SETTING = 0x60 +TCON_RESOLUTION = 0x61 +SOURCE_AND_GATE_START_SETTING = 0x62 +GET_STATUS = 0x71 +AUTO_MEASURE_VCOM = 0x80 +VCOM_VALUE = 0x81 +VCM_DC_SETTING_REGISTER = 0x82 +PROGRAM_MODE = 0xA0 +ACTIVE_PROGRAM = 0xA1 +READ_OTP_DATA = 0xA2 + +class EPD: + def __init__(self): + self.reset_pin = epdconfig.RST_PIN + self.dc_pin = epdconfig.DC_PIN + self.busy_pin = epdconfig.BUSY_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + + lut_vcom_dc = [ + 0x00, 0x00, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x60, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ] + lut_ww = [ + 0x40, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x40, 0x14, 0x00, 0x00, 0x00, 0x01, + 0xA0, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + lut_bw = [ + 0x40, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x40, 0x14, 0x00, 0x00, 0x00, 0x01, + 0xA0, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + lut_bb = [ + 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x80, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + lut_wb = [ + 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, + 0x80, 0x14, 0x00, 0x00, 0x00, 0x01, + 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + # Hardware reset + def reset(self): + epdconfig.digital_write(self.reset_pin, GPIO.HIGH) + epdconfig.delay_ms(200) + epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset + epdconfig.delay_ms(200) + epdconfig.digital_write(self.reset_pin, GPIO.HIGH) + epdconfig.delay_ms(200) + + def send_command(self, command): + epdconfig.digital_write(self.dc_pin, GPIO.LOW) + epdconfig.spi_writebyte([command]) + + def send_data(self, data): + epdconfig.digital_write(self.dc_pin, GPIO.HIGH) + epdconfig.spi_writebyte([data]) + + def wait_until_idle(self): + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + self.send_command(0x71) + epdconfig.delay_ms(100) + + def set_lut(self): + self.send_command(LUT_FOR_VCOM) # vcom + for count in range(0, 44): + self.send_data(self.lut_vcom_dc[count]) + self.send_command(LUT_WHITE_TO_WHITE) # ww -- + for count in range(0, 42): + self.send_data(self.lut_ww[count]) + self.send_command(LUT_BLACK_TO_WHITE) # bw r + for count in range(0, 42): + self.send_data(self.lut_bw[count]) + self.send_command(LUT_WHITE_TO_BLACK) # wb w + for count in range(0, 42): + self.send_data(self.lut_bb[count]) + self.send_command(LUT_BLACK_TO_BLACK) # bb b + for count in range(0, 42): + self.send_data(self.lut_wb[count]) + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + self.send_command(POWER_SETTING) + self.send_data(0x03) # VDS_EN, VDG_EN + self.send_data(0x00) # VCOM_HV, VGHL_LV[1], VGHL_LV[0] + self.send_data(0x2b) # VDH + self.send_data(0x2b) # VDL + self.send_data(0x09) # VDHR + self.send_command(BOOSTER_SOFT_START) + self.send_data(0x07) + self.send_data(0x07) + self.send_data(0x17) + # Power optimization + self.send_command(0xF8) + self.send_data(0x60) + self.send_data(0xA5) + # Power optimization + self.send_command(0xF8) + self.send_data(0x89) + self.send_data(0xA5) + # Power optimization + self.send_command(0xF8) + self.send_data(0x90) + self.send_data(0x00) + # Power optimization + self.send_command(0xF8) + self.send_data(0x93) + self.send_data(0x2A) + # Power optimization + self.send_command(0xF8) + self.send_data(0xA0) + self.send_data(0xA5) + # Power optimization + self.send_command(0xF8) + self.send_data(0xA1) + self.send_data(0x00) + # Power optimization + self.send_command(0xF8) + self.send_data(0x73) + self.send_data(0x41) + self.send_command(PARTIAL_DISPLAY_REFRESH) + self.send_data(0x00) + self.send_command(POWER_ON) + self.wait_until_idle() + + self.send_command(PANEL_SETTING) + self.send_data(0xAF) # KW-BF KWR-AF BWROTP 0f + self.send_command(PLL_CONTROL) + self.send_data(0x3A) # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ + self.send_command(VCM_DC_SETTING_REGISTER) + self.send_data(0x12) + self.set_lut() + # EPD hardware init end + return 0 + + def getbuffer(self, image): + # print "bufsiz = ",(self.width/8) * self.height + buf = [0xFF] * ((self.width//8) * self.height) + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + # print "imwidth = %d, imheight = %d",imwidth,imheight + if(imwidth == self.width and imheight == self.height): + print("Vertical") + for y in range(imheight): + for x in range(imwidth): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0: + buf[(x + y * self.width) // 8] &= ~(0x80 >> (x % 8)) + elif(imwidth == self.height and imheight == self.width): + print("Horizontal") + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] == 0: + buf[(newx + newy*self.width) // 8] &= ~(0x80 >> (y % 8)) + return buf + + def display(self, image): + self.send_command(DATA_START_TRANSMISSION_1) + for i in range(0, self.width * self.height // 8): + self.send_data(0xFF) + self.send_command(DATA_START_TRANSMISSION_2) + for i in range(0, self.width * self.height // 8): + self.send_data(image[i]) + self.send_command(DISPLAY_REFRESH) + self.wait_until_idle() + + def Clear(self, color): + self.send_command(DATA_START_TRANSMISSION_1) + for i in range(0, self.width * self.height // 8): + self.send_data(0xFF) + self.send_command(DATA_START_TRANSMISSION_2) + for i in range(0, self.width * self.height // 8): + self.send_data(0xFF) + self.send_command(DISPLAY_REFRESH) + self.wait_until_idle() + + def sleep(self): + self.send_command(0X50) + self.send_data(0xf7) + self.send_command(0X02) + self.send_command(0X07) + self.send_data(0xA5) +### END OF FILE ### + diff --git a/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py new file mode 100644 index 0000000..78ff647 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v27inch/epdconfig.py @@ -0,0 +1,73 @@ +# /***************************************************************************** +# * | File : EPD_1in54.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V2.0 +# * | Date : 2018-11-01 +# * | Info : +# * 1.Remove: +# digital_write(self, pin, value) +# digital_read(self, pin) +# delay_ms(self, delaytime) +# set_lut(self, lut) +# self.lut = self.lut_full_update +# ******************************************************************************/ +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + + +import spidev +import RPi.GPIO as GPIO +import time + +# Pin definition +RST_PIN = 17 +DC_PIN = 25 +CS_PIN = 8 +BUSY_PIN = 24 + +# SPI device, bus = 0, device = 0 +SPI = spidev.SpiDev(0, 0) + +def digital_write(pin, value): + GPIO.output(pin, value) + +def digital_read(pin): + return GPIO.input(BUSY_PIN) + +def delay_ms(delaytime): + time.sleep(delaytime / 1000.0) + +def spi_writebyte(data): + SPI.writebytes(data) + +def module_init(): + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(RST_PIN, GPIO.OUT) + GPIO.setup(DC_PIN, GPIO.OUT) + GPIO.setup(CS_PIN, GPIO.OUT) + GPIO.setup(BUSY_PIN, GPIO.IN) + SPI.max_speed_hz = 2000000 + SPI.mode = 0b00 + return 0; + +### END OF FILE ### From df246b255a293dc074c97361952e3978a580281d Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 21:35:36 +0200 Subject: [PATCH 320/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 86b854d..6155ad5 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -4,15 +4,16 @@ __name__ = 'auto-update' __license__ = 'GPL3' __description__ = 'This plugin checks when updates are available and applies them when internet is available.' +import os import logging import subprocess import requests import platform import shutil import glob +import pkg_resources import pwnagotchi -import os from pwnagotchi.utils import StatusFile OPTIONS = dict() @@ -45,7 +46,10 @@ def check(version, repo, native=True): info['available'] = latest_ver = latest['tag_name'].replace('v', '') is_arm = info['arch'].startswith('arm') - if latest_ver != info['current']: + local = pkg_resources.parse_version(info['current']) + remote = pkg_resources.parse_requirements(latest_ver) + + if remote > local: if not native: info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name']) else: @@ -165,7 +169,7 @@ def on_internet_available(agent): to_install = [] to_check = [ ('bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), - True, 'bettercap'), + True, 'bettercap'), ('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True, 'pwngrid-peer'), ('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi') ] From bceb3c4c4f6d8cfb2ca63b49d1d62d183e654fed Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 21:36:37 +0200 Subject: [PATCH 321/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/plugins/default/auto-update.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 6155ad5..5aa8b55 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -47,8 +47,7 @@ def check(version, repo, native=True): is_arm = info['arch'].startswith('arm') local = pkg_resources.parse_version(info['current']) - remote = pkg_resources.parse_requirements(latest_ver) - + remote = pkg_resources.parse_version(latest_ver) if remote > local: if not native: info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name']) From 64e677f5df8f9bffd6fc56b71c94cbddc07c13e1 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Sun, 20 Oct 2019 21:39:14 +0200 Subject: [PATCH 322/346] new: implemented auto-update plugin (closes #343) --- pwnagotchi/plugins/default/auto-update.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 5aa8b55..c359505 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -202,5 +202,4 @@ def on_internet_available(agent): except Exception as e: logging.error("[update] %s" % e) - logging.debug("[update] setting status '%s'" % prev_status) display.update(force=True, new_data={'status': prev_status if prev_status is not None else ''}) From 16832cd414da26834d2fe969dc44e5af6d51e5a5 Mon Sep 17 00:00:00 2001 From: friedphish <50951356+friedphish@users.noreply.github.com> Date: Mon, 21 Oct 2019 09:48:55 +0200 Subject: [PATCH 323/346] Update defaults.yml --- pwnagotchi/defaults.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 0516168..d2e9d27 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -58,6 +58,7 @@ main: wpa-sec: enabled: false api_key: ~ + api_url: "https://wpa-sec.stanev.org" wigle: enabled: false api_key: ~ From 4a2091e688ef3bc8b6b2294639b8e2fcbf8e7c04 Mon Sep 17 00:00:00 2001 From: friedphish <50951356+friedphish@users.noreply.github.com> Date: Mon, 21 Oct 2019 09:53:32 +0200 Subject: [PATCH 324/346] Update wpa-sec.py Part 2 of closing https://github.com/evilsocket/pwnagotchi/issues/347. --- pwnagotchi/plugins/default/wpa-sec.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py index efa3faa..6bf9994 100644 --- a/pwnagotchi/plugins/default/wpa-sec.py +++ b/pwnagotchi/plugins/default/wpa-sec.py @@ -25,19 +25,23 @@ def on_loaded(): logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org") return + if 'api_url' not in OPTIONS or ('api_url' in OPTIONS and OPTIONS['api_url'] is None): + logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.") + return + READY = True def _upload_to_wpasec(path, timeout=30): """ - Uploads the file to wpa-sec.stanev.org + Uploads the file to https://wpa-sec.stanev.org, or another endpoint. """ with open(path, 'rb') as file_to_upload: cookie = {'key': OPTIONS['api_key']} payload = {'file': file_to_upload} try: - result = requests.post('https://wpa-sec.stanev.org', + result = requests.post(OPTIONS['api_url'], cookies=cookie, files=payload, timeout=timeout) From 0ac940f63665a2399d00cbac02ad67d2bce6985e Mon Sep 17 00:00:00 2001 From: gkrs <457603+gkrs@users.noreply.github.com> Date: Mon, 21 Oct 2019 10:54:45 +0200 Subject: [PATCH 325/346] Updated polish language pack. New phrase, corrections and cleanup. --- pwnagotchi/locale/pl/LC_MESSAGES/voice.mo | Bin 4369 -> 4301 bytes pwnagotchi/locale/pl/LC_MESSAGES/voice.po | 76 +++++++++++----------- pwnagotchi/locale/voice.pot | 9 ++- 3 files changed, 45 insertions(+), 40 deletions(-) diff --git a/pwnagotchi/locale/pl/LC_MESSAGES/voice.mo b/pwnagotchi/locale/pl/LC_MESSAGES/voice.mo index 303c60b3085236426b18441ada173fef9f1c8d21..f762e958595820217655bf580f7c29baeaaa8c65 100644 GIT binary patch delta 1982 zcma)+PfQ$D9LFC8T3OgaTU%;d>07E`UDz&As3FF*v{H#Z5VqKqgK>6tSY~%;W;3(v zW{2V$6noGfbhJvnfL?6;(*yNl&;+_`4{B_0nwWagQxg*tjvoAehrw|4mwoej@6GSM z_xpW+Z}#2JpCXk9dmBCwupzV~Xg}5oF$P|##{;(1AVd?m4DJU%2akhm-~sR<_yoA4 zQHW-+8>HU=*anV)N5LtO`@7&X;D?|Tq9X30vkw!0gIw6SQ;0Bl7;FcR2Rs8lit%M| z7ifY!;8pM&@G95^j^O29PzM>{6_D#Z@F4gZ$bHwq5Y89BqSK0rKS2D%1|AXcACQh6 zk8DOh1WJq(U=%EX40H)(0$+j5a2=H3?;r!9d}KSw{bC^fN5MllUreDB0*fHj6t9C2 zLo9$uO1up+u=j%TjbOYCGSi#jZg3SGt`p)1ko)aKJ(&3(Fa~ykd%#JMB};=9_=<PX z=>V?>4qt=EFun`Y@gaByY-<tXDR2UO9GnGNvPF;^-vX(?8c0PS1n>U>8DInCdJNnL zeh>DBp?^0z8<?QO5j^M+1u4<<!T2)BKpc=6eH6@p2~wH6!TX=V2*w*A6F7h<o&<-% z9`F^A`(6X7+;S`QXT-NL(FxuMS*s0@8#LpF(eVVxg(DyXd<kUj^B@Cw4P>d_3V01< z3EvOi-w5VE0hz!}5HX7HD(IkOzEMgZ&XR0d939>X+$G;AI}f5A+Ju0*zEPneG=6Rj zn#Le`>1Yge5RD0MS>+^pPown*GdsWmv}iD9U0I@Dv=eBQlbKNsUNU}C%!GCrjoI~~ z@jk`SAf*3U;tC0g#xewdEmXM6|1;sGqOo?Am!;TlZQh>^)5kPZvz3Bwre#(uJ3TUG zNz*FGLeVy*H9ak3v6wg3m{|F=@pirUa#O<ltx2ux_fCiUEB$@4FFrgt7>)PG<Dm<R zQ|P^DE2fiE3YI-A(^=b*&z>ASH54C+a&WrnXtonGEnCay%CWRwm?<V>s%1Qf#b&yw zq_y6Qnqmyg@eBWBVLeuQ_e15%Ijc5bT~J-cVDz)48Kp2M62-h`_eeKoCCfRz_OVfx zvu4qlLw2RIIwNNkGv&-ES*`odN+}{HEcd@nW(`H16X&T+F;iWX*<4X|wH|5enk<zA zw+xjL=d7gNshE};6H`2?Hm@1MIol8L)|;aT&+6DKZ@H#Yy&4lQXqg~XFWfS^KUrN& zx!C`#E=%ZC7gg6RdrC{+(e)*V3kynCmyTjs1+(lQJzg>yop-CnYv8foi{UrwwQkG1 z7f!bOar=2h#HF(B7FFH%8QVQFxh=*(i@fcYGm5I;S*gv3L?Uk$vWoB3C$m;gOIH`S zPj1TSzkqaNyqK!ZTi!3b?JxyWXH-oxV((IG$3Wh4pewEv-XH@8E+;gnplm8j3D08_ zQ&*4`1*pC0ebIVq%2YBrE$_HnlhUv<TH2De)v~H95iw~P{$90JEe&5tt{B>!n6%yM rLdw;6+~$<b;51o+?n&H&)nddOZ5s(Ebj?r#`6J%9ZDYIqBqHKpDwTi( delta 2033 zcma)*YiJx*6vuB~CcAl>YE!LE??z4YXf{pKc1;MH6l2;DH8zb(zUX9j*z99wmYrSJ znXoLTzCb}_D7GJzC{iejSYts-ThL?`3IP#ODIzKbEk3@8eh{ku|C8A$e)6zqfA`)w zbMHC-b7v#;Qb+OY*6I@iHVnTFezih~2f??h@CW;$T8IF64ZIor73>BBH9~9$hroJp z3gq*L!4@zDc7g?v`@R9*1%3cZ@DFgC5Jj=`hRqlI!A3lo0NcP>pGoj0%#VY$;7O1N ztbpgivtTEf!oyb31)11sknddtcYvRQ-1kQ?2v*ez(Twv&69!ll?O+J(1Q~c3Waf{9 z5-fnd;Hw}Ly$qrP@g>N(pFjx)AQuzp0e6ApAoqI=WPBdH6?`5H;(T!$1FC%%yb(MH zHh~|5P)U3aLUr+#KmQg)(c*iMiT~oyfA{CtK`K#$GP%zVu&qLfJs>N;2mQJQoC1qI z7(_AP!7qY%SG?v2yy>r(K*oIm-U(JV2r&Y7fnA^qQh^shN`409ewY09Przo(e+GF+ zu7gZ$TO;&mCLPf8GB^tE0c-J;J?;l7-55ye9t9bn0(XMXfUNX&ka6cg>@7CHJHRU- zTlyDxI~YP1EMNlc29wRupS?JN1#a{X$V%2gCh`r)Ue+NS&Ub^1n*^EAJjjF$kkUQ} zvXw4K1zrYmwm1W_mG6UW*(H!I{HW*;HvE9k{D3R|`gb5Dy$15&KS8w8JG>GcgwuNA zj4K<f+Y29t55QRjm0&VVgcgPunFTALu>yWdeQ@4`-SDy<z<>$-+xBC^YH;zlj9&&s z^Nim+)n%)2bv$L*1}aJG=l9P((GJ4dn|4lMl#iE%-!3m-*<^VDLX0hA|Ji$9$bE3O zaR|;Yrvn~=^K%sAM`T7*qIOt}TbBEF^_<&P^H8l^G8A1atlBm2Rh1`hUtqRp-f;vR zKSy>gS(<7UtZuG2^ZfH3cvHtM2BH-M?$y9x-M(Qt5E<D&=(Y#j#w^FywI?D&a<n`e zi|I-{6;@6C?%=dy#BxeZ>pP|?`iPt!e_(QMd~!mLPEU`e;$brrb592C*3y&fPpgh* z#5HNf*H3-8;<)Yg+h;7@M&>!aAQu%QYAq^BZ7H3~Wt3DQB#xR+X_>Dnx?Iu~wIJ>_ z7cz=rs$nr7H5crx`Jb0f_ucwlsp0^cHXTD*e>Ut3BMxf`xs+d2vW30wZ}s;z>2ZWa z9fT%x3C*2p7}(`yCbLRXT8eIFnQJ&47BhK+5i1RDAf@RtopChBjWiywTw8T7HZHW8 zd4-`~h9U8YopExiX_)T8rbG8{Wr(-zgrdgRR!hr4F`G8CNfaUnWYSD&vGr5`EN#l+ zEw8QSv5r^H8*$UH-HoP9Bh`~UPhC_sce=Sf;;mR&rL=4^UwKr+B$Lf05Xk(GX@*3h zTIng7z#|zoWi*$z-B+3;s<&V$iIkQ`bIW0Qshe?KmM++8JQosky6$aUx}e1nTUK;R xiyEedI{byr-H}>UNoh$l<S)D7mO}?7%HoH3*Z%K?EVLY{huWs<*rnx=_#33mdszSg diff --git a/pwnagotchi/locale/pl/LC_MESSAGES/voice.po b/pwnagotchi/locale/pl/LC_MESSAGES/voice.po index d616bc3..70fd08a 100644 --- a/pwnagotchi/locale/pl/LC_MESSAGES/voice.po +++ b/pwnagotchi/locale/pl/LC_MESSAGES/voice.po @@ -1,24 +1,23 @@ # Polish voice data for pwnagotchi. # Copyright (C) 2019 -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR szymex73 <szymex73@gmail.com>, 2019. +# This file is distributed under the same license as the pwnagotchi package. +# szymex73 <szymex73@gmail.com>, 2019. # -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: 0.0.1\n" +"Project-Id-Version: 0.0.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-10-09 17:42+0200\n" -"PO-Revision-Date: 2019-10-09 17:42+0200\n" -"Last-Translator: szymex73 <szymex73@gmail.com>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" +"POT-Creation-Date: 2019-10-21 08:39+0200\n" +"PO-Revision-Date: 2019-10-21 10:55+0200\n" +"Last-Translator: gkrs <457603+gkrs@users.noreply.github.com>\n" +"Language-Team: PL <457603+gkrs@users.noreply.github.com>\n" "Language: polish\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "ZzzzZZzzzzZzzz" -msgstr "ZzzzZZzzzzZzzz" +msgstr "" msgid "Hi, I'm Pwnagotchi! Starting ..." msgstr "Hej, jestem Pwnagotchi! Uruchamianie ..." @@ -35,9 +34,12 @@ msgstr "SI gotowa." msgid "The neural network is ready." msgstr "Sieć neuronowa jest gotowa." +msgid "Generating keys, do not turn off ..." +msgstr "Generuję klucze, nie wyłączaj ..." + #, python-brace-format msgid "Hey, channel {channel} is free! Your AP will say thanks." -msgstr "Hej, kanał {channel} jest wolny! Twój AP mi podziękuje." +msgstr "Hej, kanał {channel} jest wolny! Twój AP będzie Ci wdzięczny." msgid "I'm bored ..." msgstr "Nudzi mi się ..." @@ -46,10 +48,10 @@ msgid "Let's go for a walk!" msgstr "Chodźmy na spacer!" msgid "This is the best day of my life!" -msgstr "To jest najlepszy dzień w moim życiu!" +msgstr "To najlepszy dzień mojego życia!" msgid "Shitty day :/" -msgstr "Ten dzień jest do dupy :/" +msgstr "Gówniany dzień :/" msgid "I'm extremely bored ..." msgstr "Straaaasznie się nudzę ..." @@ -64,7 +66,7 @@ msgid "I'm living the life!" msgstr "Cieszę się życiem!" msgid "I pwn therefore I am." -msgstr "Pwnuje więc jestem." +msgstr "Pwnuję więc jestem." msgid "So many networks!!!" msgstr "Jak dużo sieci!!!" @@ -76,12 +78,12 @@ msgid "My crime is that of curiosity ..." msgstr "Moją zbrodnią jest ciekawość ..." #, python-brace-format -msgid "Hello {name}! Nice to meet you. {name}" -msgstr "Cześć {name}! Miło cię poznać. {name}" +msgid "Hello {name}! Nice to meet you." +msgstr "Cześć {name}! Miło Cię poznać." #, python-brace-format -msgid "Unit {name} is nearby! {name}" -msgstr "Jednostka {name} jest niedaleko! {name}" +msgid "Unit {name} is nearby!" +msgstr "Urządzenie {name} jest w pobliżu!" #, python-brace-format msgid "Uhm ... goodbye {name}" @@ -97,16 +99,16 @@ msgstr "Ups ... {name} zniknął." #, python-brace-format msgid "{name} missed!" -msgstr "{name} przeoczył!" +msgstr "{name} pudło!" msgid "Missed!" -msgstr "Spóźniony!" +msgstr "Pudło!" msgid "Nobody wants to play with me ..." -msgstr "Nikt się nie chce ze mną bawić ..." +msgstr "Nikt nie chce się ze mną bawić ..." msgid "I feel so alone ..." -msgstr "Czuję się tak samotnie ..." +msgstr "Czuję się taki samotny ..." msgid "Where's everybody?!" msgstr "Gdzie są wszyscy?!" @@ -116,17 +118,17 @@ msgid "Napping for {secs}s ..." msgstr "Zdrzemnę się przez {secs}s ..." msgid "Zzzzz" -msgstr "Zzzzz" +msgstr "" #, python-brace-format msgid "ZzzZzzz ({secs}s)" -msgstr "ZzzZzzz ({secs}s)" +msgstr "" msgid "Good night." msgstr "Dobranoc." msgid "Zzz" -msgstr "Zzz" +msgstr "" #, python-brace-format msgid "Waiting for {secs}s ..." @@ -138,15 +140,15 @@ msgstr "Rozglądam się ({secs}s)" #, python-brace-format msgid "Hey {what} let's be friends!" -msgstr "Hej {what}, zostańmy przyjaciółmi!" +msgstr "Hej {what} zostańmy przyjaciółmi!" #, python-brace-format msgid "Associating to {what}" -msgstr "Łączenie się z {what}" +msgstr "Dołączam do {what}" #, python-brace-format msgid "Yo {what}!" -msgstr "Ej {what}!" +msgstr "Siema {what}!" #, python-brace-format msgid "Just decided that {mac} needs no WiFi!" @@ -158,33 +160,33 @@ msgstr "Rozłączam {mac}" #, python-brace-format msgid "Kickbanning {mac}!" -msgstr "Banowanie {mac}!" +msgstr "Banuję {mac}!" #, python-brace-format msgid "Cool, we got {num} new handshake{plural}!" -msgstr "Super, zdobylismy {num} handshake{plural}!" +msgstr "Super, zdobyliśmy {num} nowych handshake'ów!" msgid "Ops, something went wrong ... Rebooting ..." -msgstr "Ups, coś się stało ... Restartuję ..." +msgstr "Ups, coś poszło nie tak ... Restaruję ..." #, python-brace-format msgid "Kicked {num} stations\n" -msgstr "Wyrzucono {num} stacji\n" +msgstr "Wyrzuciłem {num} stacji\n" #, python-brace-format msgid "Made {num} new friends\n" -msgstr "Zdobyto {num} przyjaciół\n" +msgstr "Zdobyłem {num} nowych przyjaciół\n" #, python-brace-format msgid "Got {num} handshakes\n" -msgstr "Zdobyto {num} handshakow\n" +msgstr "Zdobyłem {num} handshake'ów\n" msgid "Met 1 peer" -msgstr "Spotkano 1 kolegę" +msgstr "Spotkałem 1 kolegę" #, python-brace-format msgid "Met {num} peers" -msgstr "Spotkano {num} kolegów" +msgstr "Spotkałem {num} kolegów" #, python-brace-format msgid "" @@ -192,8 +194,8 @@ msgid "" "{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " "#pwnlog #pwnlife #hacktheplanet #skynet" msgstr "" -"Pwnowalem przez {duration} i wyrzuciłem {deauthed} klientów! Spotkałem także " -"{associated} nowych przyjaciół i zjadłem {handshakes} handshaków! #pwnagotchi " +"Pwnowałem {duration} i wyrzuciłem {deauthed} klientów! Spotkałem także " +"{associated} nowych przyjaciół i zjadłem {handshakes} handshake'ow! #pwnagotchi " "#pwnlog #pwnlife #hacktheplanet #skynet" msgid "hours" diff --git a/pwnagotchi/locale/voice.pot b/pwnagotchi/locale/voice.pot index cf02362..10de71f 100644 --- a/pwnagotchi/locale/voice.pot +++ b/pwnagotchi/locale/voice.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-10-09 17:42+0200\n" +"POT-Creation-Date: 2019-10-21 10:49+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -35,6 +35,9 @@ msgstr "" msgid "The neural network is ready." msgstr "" +msgid "Generating keys, do not turn off ..." +msgstr "" + #, python-brace-format msgid "Hey, channel {channel} is free! Your AP will say thanks." msgstr "" @@ -76,11 +79,11 @@ msgid "My crime is that of curiosity ..." msgstr "" #, python-brace-format -msgid "Hello {name}! Nice to meet you. {name}" +msgid "Hello {name}! Nice to meet you." msgstr "" #, python-brace-format -msgid "Unit {name} is nearby! {name}" +msgid "Unit {name} is nearby!" msgstr "" #, python-brace-format From 1e015fe6f6a210c30eebc26507ce25671f0b008d Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 21 Oct 2019 11:33:48 +0200 Subject: [PATCH 326/346] misc: replaced ssh-keygen with pwngrid -generate --- pwnagotchi/identity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/identity.py b/pwnagotchi/identity.py index d53f5b2..0ac5935 100644 --- a/pwnagotchi/identity.py +++ b/pwnagotchi/identity.py @@ -27,7 +27,7 @@ class KeyPair(object): if not os.path.exists(self.priv_path) or not os.path.exists(self.pub_path): self._view.on_keys_generation() logging.info("generating %s ..." % self.priv_path) - os.system("/usr/bin/ssh-keygen -t rsa -m PEM -b 4096 -N '' -f '%s'" % self.priv_path) + os.system("pwngrid -generate -keys '%s'" % self.path) # load keys: they might be corrupted if the unit has been turned off during the generation, in this case # the exception will remove the files and go back at the beginning of this loop. From e943cfad707ed38fca89cf7ee299f6a414010ff2 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 21 Oct 2019 12:27:16 +0200 Subject: [PATCH 327/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/ui/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/ui/web.py b/pwnagotchi/ui/web.py index 021c37a..82af269 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -138,7 +138,7 @@ class Handler(BaseHTTPRequestHandler): # check the Origin header vs CORS def _is_allowed(self): origin = self.headers.get('origin') - if not origin: + if not origin and Handler.AllowedOrigin != '*': logging.warning("request with no Origin header from %s" % self.address_string()) return False From 41ea0e0747126e0ff5fc94445c63db5faa79de88 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 21 Oct 2019 14:01:21 +0200 Subject: [PATCH 328/346] new: new ui.display.video.on_frame configuration to use fbi on framebuffer based screens --- pwnagotchi/defaults.yml | 4 ++++ pwnagotchi/ui/display.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index d2e9d27..cc4fa51 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -204,6 +204,10 @@ ui: address: '0.0.0.0' origin: '*' port: 8080 + # command to be executed when a new png frame is available + # for instance, to use with framebuffer based displays: + # on_frame: 'fbi -a -d /dev/fb1 -T 1 /root/pwnagotchi.png' + on_frame: '' # bettercap rest api configuration diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index c4fd495..c057922 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -1,3 +1,4 @@ +import os import logging import pwnagotchi.plugins as plugins @@ -60,6 +61,12 @@ class Display(View): def _on_view_rendered(self, img): web.update_frame(img) + try: + if self._config['ui']['display']['video']['on_frame'] != '': + os.system(self._config['ui']['display']['video']['on_frame']) + except Exception as e: + logging.error("%s" % e) + if self._enabled: self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation)) if self._implementation is not None: From c954bf8ffa424b7cd9ec3aadc9d15f0b2f2e9eb9 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 21 Oct 2019 15:41:45 +0200 Subject: [PATCH 329/346] fix: refactored backup.sh script to not require root login --- scripts/backup.sh | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/scripts/backup.sh b/scripts/backup.sh index c943058..f66ca78 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -4,8 +4,6 @@ UNIT_HOSTNAME=${1:-10.0.0.2} # output backup zip file OUTPUT=${2:-pwnagotchi-backup.zip} -# temporary folder -TEMP_BACKUP_FOLDER=/tmp/pwnagotchi_backup # what to backup FILES_TO_BACKUP=( /root/brain.nn @@ -13,9 +11,6 @@ FILES_TO_BACKUP=( /root/.api-report.json /root/handshakes /etc/pwnagotchi/ - /etc/hostname - /etc/hosts - /etc/motd /var/log/pwnagotchi.log ) @@ -26,17 +21,24 @@ ping -c 1 $UNIT_HOSTNAME >/dev/null || { echo "@ backing up $UNIT_HOSTNAME to $OUTPUT ..." -rm -rf "$TEMP_BACKUP_FOLDER" +ssh pi@$UNIT_HOSTNAME "sudo rm -rf /tmp/backup && sudo rm -rf /tmp/backup.zip" > /dev/null for file in "${FILES_TO_BACKUP[@]}"; do dir=$(dirname $file) - echo " $file -> $TEMP_BACKUP_FOLDER$dir/" - mkdir -p "$TEMP_BACKUP_FOLDER/$dir" - scp -Cr root@$UNIT_HOSTNAME:$file "$TEMP_BACKUP_FOLDER$dir/" + + echo "@ copying $file to /tmp/backup$dir" + + ssh pi@$UNIT_HOSTNAME "mkdir -p /tmp/backup$dir" > /dev/null + ssh pi@$UNIT_HOSTNAME "sudo cp -r $file /tmp/backup$dir" > /dev/null done -ZIPFILE="$PWD/$OUTPUT" -pushd $PWD -cd "$TEMP_BACKUP_FOLDER" -zip -r -9 -q "$ZIPFILE" . -popd +echo "@ pulling from $UNIT_HOSTNAME ..." + +rm -rf /tmp/backup +scp -rC pi@$UNIT_HOSTNAME:/tmp/backup /tmp/ + +echo "@ compressing ..." + +zip -r -9 -q $OUTPUT /tmp/backup +rm -rf /tmp/backup + From 5cb721f490892f5a73745518f29723b36f64d7a9 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 21 Oct 2019 16:10:09 +0200 Subject: [PATCH 330/346] fix: correct services dependencies --- builder/pwnagotchi.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index c3dc1bf..627a491 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -497,7 +497,6 @@ Description=pwngrid peer service. Documentation=https://pwnagotchi.ai Wants=network.target - After=bettercap.service [Service] Type=simple @@ -519,7 +518,7 @@ Description=bettercap api.rest service. Documentation=https://bettercap.org Wants=network.target - After=network.target + After=pwngrid.service [Service] Type=simple From 90998be24cdee2fcf56147f6bb87fff2f78bf212 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 21 Oct 2019 16:22:38 +0200 Subject: [PATCH 331/346] fix: handling exceptions when bettercap is not running yet --- pwnagotchi/agent.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 24013fb..cd4dcfa 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -307,14 +307,15 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): time.sleep(1) new_shakes = 0 - s = self.session() - self._update_uptime(s) - - self._update_advertisement(s) - self._update_peers() - self._update_counters() try: + s = self.session() + self._update_uptime(s) + + self._update_advertisement(s) + self._update_peers() + self._update_counters() + for h in [e for e in self.events() if e['tag'] == 'wifi.client.handshake']: filename = h['data']['file'] sta_mac = h['data']['station'] @@ -340,7 +341,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): plugins.on('handshake', self, filename, ap, sta) except Exception as e: - logging.exception("error") + logging.error("error: %s" % e) finally: self._update_handshakes(new_shakes) From 56079dfd9d0f3f2ffbbd6a5dee901b22850cb4b3 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 21 Oct 2019 16:26:07 +0200 Subject: [PATCH 332/346] fix: waiting for bettercap's API to start --- pwnagotchi/agent.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index cd4dcfa..16fa722 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -135,8 +135,18 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer): self.start_advertising() + def _wait_bettercap(self): + while True: + try: + s = self.session() + return + except: + logging.info("waiting for bettercap API to be available ...") + time.sleep(1) + def start(self): self.start_ai() + self._wait_bettercap() self.setup_events() self.set_starting() self.start_monitor_mode() From 7a721be7dc5ec9ce07de057f5ef0714a4a65b750 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Mon, 21 Oct 2019 16:38:53 +0100 Subject: [PATCH 333/346] papirus, fbi and idempotency changes --- builder/pwnagotchi.yml | 81 ++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index b0f5e6a..6356d18 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -9,10 +9,12 @@ system: boot_options: - "dtoverlay=dwc2" - - "dtparam=spi=on" - "dtoverlay=spi1-3cs" - - "dtoverlay=i2c_arm=on" - - "dtoverlay=i2c1=on" + - "dtparam=spi=on" + - "dtparam=i2c_arm=on" + - "dtparam=i2c1=on" + modules: + - "i2c-dev" services: enable: - dphys-swapfile.service @@ -95,27 +97,22 @@ - libfuse-dev - bc - fonts-freefont-ttf + - fbi tasks: - - - name: selected hostname - debug: - msg: "{{ pwnagotchi.hostname }}" - - - name: build version - debug: - msg: "{{ pwnagotchi.version }}" - - name: change hostname hostname: name: "{{pwnagotchi.hostname}}" + when: lookup('file', '/etc/hostname') == "raspberrypi" + register: hostname - name: add hostname to /etc/hosts lineinfile: dest: /etc/hosts - regexp: '^127\.0\.0\.1[ \t]+localhost' - line: '127.0.0.1 localhost {{pwnagotchi.hostname}} {{pwnagotchi.hostname}}.local' + regexp: '^127\.0\.1\.1[ \t]+raspberrypi' + line: '127.0.1.1\t{{pwnagotchi.hostname}}' state: present + when: hostname.changed - name: Add re4son-kernel repo key apt_key: @@ -161,6 +158,7 @@ git: repo: https://github.com/repaper/gratis.git dest: /usr/local/src/gratis + register: gratisgit - name: build papirus service make: @@ -169,6 +167,7 @@ params: EPD_IO: epd_io.h PANEL_VERSION: 'V231_G2' + when: gratisgit.changed - name: install papirus service make: @@ -177,6 +176,7 @@ params: EPD_IO: epd_io.h PANEL_VERSION: 'V231_G2' + when: gratisgit.changed - name: configure papirus display size lineinfile: @@ -184,6 +184,16 @@ regexp: "#EPD_SIZE=2.0" line: "EPD_SIZE=2.0" + - name: collect python pip package list + command: "pip3 list" + register: pip_output + + - name: set python pip package facts + set_fact: + pip_packages: > + {{ pip_packages | default({}) | combine( { item.split()[0]: item.split()[1] } ) }} + with_items: "{{ pip_output.stdout_lines }}" + - name: acquire python3 pip target command: "python3 -c 'import sys;print(sys.path.pop())'" register: pip_target @@ -192,26 +202,39 @@ git: repo: https://github.com/evilsocket/pwnagotchi.git dest: /usr/local/src/pwnagotchi + register: pwnagotchigit + + - name: fetch pwnagotchi version + set_fact: + pwnagotchi_version: "{{ lookup('file', '/usr/local/src/pwnagotchi/pwnagotchi/__init__.py') | replace('\n', ' ') | regex_replace('.*version.*=.*''([0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*)''.*', '\\1') }}" + + - name: pwnagotchi version found + debug: + msg: "{{ pwnagotchi_version }}" - name: build pwnagotchi wheel command: "python3 setup.py sdist bdist_wheel" args: chdir: /usr/local/src/pwnagotchi + when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi.version) - name: install opencv-python pip: name: "https://www.piwheels.hostedpi.com/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl" extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}" + when: (pip_packages['opencv-python'] is undefined) or (pip_packages['opencv-python'] != '3.4.3.18') - name: install tensorflow pip: name: "https://www.piwheels.hostedpi.com/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl" extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}" + when: (pip_packages['tensorflow'] is undefined) or (pip_packages['tensorflow'] != '1.13.1') - name: install pwnagotchi wheel and dependencies pip: name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}" extra_args: "--no-cache-dir" + when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi.version) - name: download and install pwngrid unarchive: @@ -234,11 +257,13 @@ git: repo: https://github.com/bettercap/caplets.git dest: /tmp/caplets + register: capletsgit - name: install bettercap caplets make: chdir: /tmp/caplets target: install + when: capletsgit.changed - name: download and install bettercap ui unarchive: @@ -247,26 +272,6 @@ remote_src: yes mode: 0755 - - name: create cpuusage script - copy: - dest: /usr/bin/cpuusage - mode: 0755 - content: | - #!/usr/bin/env bash - while true - do - top -b -n1 | awk '/Cpu\(s\)/ { printf("%d %", $2 + $4 + 0.5) }' - sleep 3 - done - - - name: create memusage script - copy: - dest: /usr/bin/memusage - mode: 0755 - content: | - #!/usr/bin/env bash - free -m | awk '/Mem/ { printf( "%d %", $3 / $2 * 100 + 0.5 ) }' - - name: create bootblink script copy: dest: /usr/bin/bootblink @@ -434,6 +439,13 @@ line: '{{ item }}' with_items: "{{system.boot_options}}" + - name: adjust /etc/modules + lineinfile: + dest: /etc/modules + insertafter: EOF + line: '{{ item }}' + with_items: "{{system.modules}}" + - name: change root partition replace: dest: /boot/cmdline.txt @@ -480,6 +492,7 @@ touch /root/.pwnagotchi-auto && systemctl restart pwnagotchi You learn more about me at https://pwnagotchi.ai/ + when: hostname.changed - name: clean apt cache apt: From 139c9df88c2384ba94eaf46a3f47bd110f3be438 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Mon, 21 Oct 2019 18:51:22 +0200 Subject: [PATCH 334/346] fix: fixed auto-backup plugin to only create local backups --- pwnagotchi/defaults.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index cc4fa51..5bd9511 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -32,13 +32,9 @@ main: - /root/.api-report.json - /root/handshakes/ - /etc/pwnagotchi/ - - /etc/hostname - - /etc/hosts - - /etc/motd - /var/log/pwnagotchi.log commands: - - 'tar czf /tmp/backup.tar.gz {files}' - - 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz' + - 'tar czf /root/pwnagotchi-backup.tar.gz {files}' net-pos: enabled: false api_key: 'test' From 84fa293a11d70d0d6438ffc531f458298e2faabb Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Mon, 21 Oct 2019 17:58:00 +0100 Subject: [PATCH 335/346] change quotes to allow tab expansion --- builder/pwnagotchi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 6356d18..e60e345 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -110,7 +110,7 @@ lineinfile: dest: /etc/hosts regexp: '^127\.0\.1\.1[ \t]+raspberrypi' - line: '127.0.1.1\t{{pwnagotchi.hostname}}' + line: "127.0.1.1\t{{pwnagotchi.hostname}}" state: present when: hostname.changed From ace1244d1028aa50448088fd6fbc9868cbb8f760 Mon Sep 17 00:00:00 2001 From: Cassiano Aquino <cassianoaquino@me.com> Date: Mon, 21 Oct 2019 18:41:36 +0100 Subject: [PATCH 336/346] disable sap plugin for bluetooth service --- builder/pwnagotchi.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index e60e345..4d3ab94 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -114,6 +114,13 @@ state: present when: hostname.changed + - name: disable sap plugin for bluetooth.service + lineinfile: + dest: /lib/systemd/system/bluetooth.service + regexp: '^ExecStart=/usr/lib/bluetooth/bluetoothd$' + line: 'ExecStart=/usr/lib/bluetooth/bluetoothd --noplugin=sap' + state: present + - name: Add re4son-kernel repo key apt_key: url: https://re4son-kernel.com/keys/http/archive-key.asc From b0dab7b5891c09678185978e9aa7b5f21de0fa8b Mon Sep 17 00:00:00 2001 From: SpiderDead <mimij68@live.nl> Date: Mon, 21 Oct 2019 23:45:35 +0200 Subject: [PATCH 337/346] Changed the overall look of the layout on the 2.7" Signed-off-by: Mike van der Vrugt <mimij68@live.nl> --- pwnagotchi/ui/hw/waveshare27inch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pwnagotchi/ui/hw/waveshare27inch.py b/pwnagotchi/ui/hw/waveshare27inch.py index 9dff62d..c554744 100644 --- a/pwnagotchi/ui/hw/waveshare27inch.py +++ b/pwnagotchi/ui/hw/waveshare27inch.py @@ -13,8 +13,8 @@ class Waveshare27inch(DisplayImpl): fonts.setup(10, 9, 10, 35) self._layout['width'] = 264 self._layout['height'] = 176 - self._layout['face'] = (0, 54) - self._layout['name'] = (5, 34) + self._layout['face'] = (66, 27) + self._layout['name'] = (5, 73) self._layout['channel'] = (0, 0) self._layout['aps'] = (28, 0) self._layout['uptime'] = (199, 0) @@ -25,9 +25,9 @@ class Waveshare27inch(DisplayImpl): self._layout['shakes'] = (0, 163) self._layout['mode'] = (239, 163) self._layout['status'] = { - 'pos': (139, 34), + 'pos': (38, 93), 'font': fonts.Medium, - 'max': 20 + 'max': 40 } return self._layout From 414a6b4c7a5dbc672a00afd2fd4f0f8ec47138c3 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 22 Oct 2019 11:35:35 +0200 Subject: [PATCH 338/346] misc: small fix or general refactoring i did not bother commenting --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 5bd9511..6bd9435 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -202,7 +202,7 @@ ui: port: 8080 # command to be executed when a new png frame is available # for instance, to use with framebuffer based displays: - # on_frame: 'fbi -a -d /dev/fb1 -T 1 /root/pwnagotchi.png' + # on_frame: 'fbi --noverbose -a -d /dev/fb1 -T 1 /root/pwnagotchi.png > /dev/null 2>&1' on_frame: '' From 538b547560b53f688186ebfc93c868d9d38f5a72 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 22 Oct 2019 12:09:41 +0200 Subject: [PATCH 339/346] fix: on rpi4 sometimes systemd fails to monstart --- builder/pwnagotchi.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index e0bd2a5..0b449ac 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -324,6 +324,7 @@ #!/usr/bin/env bash # blink 10 times to signal ready state /usr/bin/bootblink 10 & + /usr/bin/monstart if ifconfig | grep usb0 | grep RUNNING; then # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then @@ -543,9 +544,7 @@ [Service] Type=simple PermissionsStartOnly=true - ExecStartPre=/usr/bin/monstart ExecStart=/usr/bin/bettercap-launcher - ExecStopPost=/usr/bin/monstop Restart=always RestartSec=30 From 8c22b1d6f1d951a47707e32518ef8f82c1c7c566 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Tue, 22 Oct 2019 12:14:56 +0200 Subject: [PATCH 340/346] fix: if bettercap starts and no active interfaces are found, if mon0 is not explicitly passed it will fail with a 'No active interfces' error --- builder/pwnagotchi.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 0b449ac..a9fb322 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -329,12 +329,12 @@ # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then rm /root/.pwnagotchi-auto - /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto + /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 else - /usr/bin/bettercap -no-colors -caplet pwnagotchi-manual + /usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0 fi else - /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto + /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 fi - name: create monstart script From 9ca2424df1f7dae54d5605d10fccbc7620203b04 Mon Sep 17 00:00:00 2001 From: python273 <iam@python273.pw> Date: Tue, 22 Oct 2019 20:53:15 +0300 Subject: [PATCH 341/346] Add non-blocking screen updating Signed-off-by: python273 <iam@python273.pw> --- pwnagotchi/ui/display.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index c057922..c28b4d7 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -1,7 +1,8 @@ import os import logging -import pwnagotchi.plugins as plugins +import threading +import pwnagotchi.plugins as plugins import pwnagotchi.ui.hw as hw import pwnagotchi.ui.web as web from pwnagotchi.ui.view import View @@ -18,6 +19,14 @@ class Display(View): self.init_display() + self._canvas_next_event = threading.Event() + self._canvas_next = None + self._render_thread_instance = threading.Thread( + target=self._render_thread, + daemon=True + ) + self._render_thread_instance.start() + def is_inky(self): return self._implementation.name == 'inky' @@ -59,6 +68,14 @@ class Display(View): img = self._canvas if self._rotation == 0 else self._canvas.rotate(-self._rotation) return img + def _render_thread(self): + """Used for non-blocking screen updating.""" + + while True: + self._canvas_next_event.wait() + self._canvas_next_event.clear() + self._implementation.render(self._canvas_next) + def _on_view_rendered(self, img): web.update_frame(img) try: @@ -70,4 +87,5 @@ class Display(View): if self._enabled: self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation)) if self._implementation is not None: - self._implementation.render(self._canvas) + self._canvas_next = self._canvas + self._canvas_next_event.set() From 4fa7e9f0779bc202d464db8693629cccd1a840a5 Mon Sep 17 00:00:00 2001 From: python273 <iam@python273.pw> Date: Tue, 22 Oct 2019 20:59:15 +0300 Subject: [PATCH 342/346] Add slight delay for waveshare v1 ReadBusy Signed-off-by: python273 <iam@python273.pw> --- .../ui/hw/libs/waveshare/v1/epd2in13bc.py | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py index a0bff83..f17f0af 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py +++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py @@ -47,11 +47,11 @@ class EPD: # Hardware reset def reset(self): epdconfig.digital_write(self.reset_pin, GPIO.HIGH) - epdconfig.delay_ms(200) + epdconfig.delay_ms(200) epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset epdconfig.delay_ms(200) epdconfig.digital_write(self.reset_pin, GPIO.HIGH) - epdconfig.delay_ms(200) + epdconfig.delay_ms(200) def send_command(self, command): epdconfig.digital_write(self.dc_pin, GPIO.LOW) @@ -64,8 +64,9 @@ class EPD: epdconfig.digital_write(self.cs_pin, GPIO.LOW) epdconfig.spi_writebyte([data]) epdconfig.digital_write(self.cs_pin, GPIO.HIGH) - + def ReadBusy(self): + epdconfig.delay_ms(20) while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy epdconfig.delay_ms(100) @@ -79,16 +80,16 @@ class EPD: self.send_data(0x17) self.send_data(0x17) self.send_data(0x17) - + self.send_command(0x04) # POWER_ON self.ReadBusy() - + self.send_command(0x00) # PANEL_SETTING self.send_data(0x8F) - + self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING self.send_data(0xF0) - + self.send_command(0x61) # RESOLUTION_SETTING self.send_data(self.width & 0xff) self.send_data(self.height >> 8) @@ -120,7 +121,7 @@ class EPD: for i in range(0, int(self.width * self.height / 8)): self.send_data(imageblack[i]) self.send_command(0x92) - + self.send_command(0x12) # REFRESH self.ReadBusy() @@ -129,26 +130,26 @@ class EPD: for i in range(0, int(self.width * self.height / 8)): self.send_data(imageblack[i]) self.send_command(0x92) - + self.send_command(0x13) for i in range(0, int(self.width * self.height / 8)): self.send_data(imagecolor[i]) self.send_command(0x92) - + self.send_command(0x12) # REFRESH self.ReadBusy() - + def Clear(self): self.send_command(0x10) for i in range(0, int(self.width * self.height / 8)): self.send_data(0xFF) - self.send_command(0x92) - + self.send_command(0x92) + self.send_command(0x13) for i in range(0, int(self.width * self.height / 8)): self.send_data(0xFF) self.send_command(0x92) - + self.send_command(0x12) # REFRESH self.ReadBusy() @@ -157,7 +158,7 @@ class EPD: self.ReadBusy() self.send_command(0x07) # DEEP_SLEEP self.send_data(0xA5) # check code - + # epdconfig.module_exit() ### END OF FILE ### From 23ef17d4c79da51ea43b0dffc09647efc8f7184a Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 23 Oct 2019 15:14:55 +0200 Subject: [PATCH 343/346] fix: using normal status to signal unread messages in order to avoid BT overlap bug --- pwnagotchi/plugins/default/grid.py | 14 ++------------ pwnagotchi/ui/view.py | 5 +++++ pwnagotchi/voice.py | 4 ++++ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py index cfb5d8f..5b32635 100644 --- a/pwnagotchi/plugins/default/grid.py +++ b/pwnagotchi/plugins/default/grid.py @@ -66,18 +66,8 @@ def is_excluded(what): def on_ui_update(ui): - new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES) - if not ui.has_element('mailbox') and UNREAD_MESSAGES > 0: - if ui.is_inky(): - pos = (80, 0) - else: - pos = (100, 0) - ui.add_element('mailbox', - LabeledValue(color=BLACK, label='MSG', value=new_value, - position=pos, - label_font=fonts.Bold, - text_font=fonts.Medium)) - ui.set('mailbox', new_value) + if UNREAD_MESSAGES > 0: + ui.on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES) def set_reported(reported, net_id): diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py index a2f2d2c..dcb31a2 100644 --- a/pwnagotchi/ui/view.py +++ b/pwnagotchi/ui/view.py @@ -300,6 +300,11 @@ class View(object): self.set('status', self._voice.on_handshakes(new_shakes)) self.update() + def on_unread_messages(self, count, total): + self.set('face', faces.EXCITED) + self.set('status', self._voice.on_unread_messages(count, total)) + self.update() + def on_rebooting(self): self.set('face', faces.BROKEN) self.set('status', self._voice.on_rebooting()) diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index 61689cf..530c815 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -129,6 +129,10 @@ class Voice: s = 's' if new_shakes > 1 else '' return self._('Cool, we got {num} new handshake{plural}!').format(num=new_shakes, plural=s) + def on_unread_messages(self, count, total): + s = 's' if count > 1 else '' + return self._('You have {count} new message{plural}!').format(num=count, plural=s) + def on_rebooting(self): return self._("Ops, something went wrong ... Rebooting ...") From 3ad426916f203c5b96afa08a1ba627cad38d2d33 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 23 Oct 2019 15:20:16 +0200 Subject: [PATCH 344/346] small refactoring to facilitate integration of the bond equation --- pwnagotchi/defaults.yml | 1 + pwnagotchi/mesh/peer.py | 50 ++++++++++++++++++++++++++-------------- pwnagotchi/mesh/utils.py | 16 +++++++++---- scripts/backup.sh | 1 + 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 6bd9435..6e91965 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -31,6 +31,7 @@ main: - /root/brain.json - /root/.api-report.json - /root/handshakes/ + - /root/peers/ - /etc/pwnagotchi/ - /var/log/pwnagotchi.log commands: diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py index 14f0057..a3ddd55 100644 --- a/pwnagotchi/mesh/peer.py +++ b/pwnagotchi/mesh/peer.py @@ -1,17 +1,27 @@ import time import logging +import datetime import pwnagotchi.ui.faces as faces +def parse_rfc3339(dt): + return datetime.datetime.strptime(dt.split('.')[0], "%Y-%m-%dT%H:%M:%S") + + class Peer(object): def __init__(self, obj): - self.first_seen = time.time() - self.last_seen = self.first_seen - self.session_id = obj['session_id'] - self.last_channel = obj['channel'] - self.rssi = obj['rssi'] - self.adv = obj['advertisement'] + now = time.time() + just_met = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") + self.first_met = parse_rfc3339(obj.get('met_at', just_met)) + self.first_seen = parse_rfc3339(obj.get('detected_at', just_met)) + self.prev_seen = parse_rfc3339(obj.get('prev_seen_at', just_met)) + self.last_seen = now # should be seen_at + self.encounters = obj.get('encounters', 0) + self.session_id = obj.get('session_id', '') + self.last_channel = obj.get('channel', 1) + self.rssi = obj.get('rssi', 0) + self.adv = obj.get('advertisement', {}) def update(self, new): if self.name() != new.name(): @@ -24,39 +34,45 @@ class Peer(object): self.rssi = new.rssi self.session_id = new.session_id self.last_seen = time.time() + self.prev_seen = new.prev_seen + self.first_met = new.first_met + self.encounters = new.encounters def inactive_for(self): return time.time() - self.last_seen - def _adv_field(self, name, default='???'): - return self.adv[name] if name in self.adv else default + def first_encounter(self): + return self.encounters == 1 + + def days_since_first_met(self): + return (datetime.datetime.now() - self.first_met).days def face(self): - return self._adv_field('face', default=faces.FRIEND) + return self.adv.get('face', faces.FRIEND) def name(self): - return self._adv_field('name') + return self.adv.get('name') def identity(self): - return self._adv_field('identity') + return self.adv.get('identity') def version(self): - return self._adv_field('version') + return self.adv.get('version') def pwnd_run(self): - return int(self._adv_field('pwnd_run', default=0)) + return int(self.adv.get('pwnd_run', 0)) def pwnd_total(self): - return int(self._adv_field('pwnd_tot', default=0)) + return int(self.adv.get('pwnd_tot', 0)) def uptime(self): - return self._adv_field('uptime', default=0) + return self.adv.get('uptime', 0) def epoch(self): - return self._adv_field('epoch', default=0) + return self.adv.get('epoch', 0) def full_name(self): return '%s@%s' % (self.name(), self.identity()) def is_closer(self, other): - return self.rssi > other.rssi + return self.rssi > other.rssi \ No newline at end of file diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py index 3849acc..8591f4b 100644 --- a/pwnagotchi/mesh/utils.py +++ b/pwnagotchi/mesh/utils.py @@ -53,6 +53,14 @@ class AsyncAdvertiser(object): self._advertisement['face'] = new grid.set_advertisement_data(self._advertisement) + def _on_new_peer(self, peer): + self._view.on_new_peer(peer) + plugins.on('peer_detected', self, peer) + + def _on_lost_peer(self, peer): + self._view.on_lost_peer(peer) + plugins.on('peer_lost', self, peer) + def _adv_poller(self): while True: logging.debug("polling pwngrid-peer for peers ...") @@ -72,24 +80,22 @@ class AsyncAdvertiser(object): to_delete = [] for ident, peer in self._peers.items(): if ident not in new_peers: - self._view.on_lost_peer(peer) - plugins.on('peer_lost', self, peer) to_delete.append(ident) for ident in to_delete: + self._on_lost_peer(peer) del self._peers[ident] for ident, peer in new_peers.items(): # check who's new if ident not in self._peers: self._peers[ident] = peer - self._view.on_new_peer(peer) - plugins.on('peer_detected', self, peer) + self._on_new_peer(peer) # update the rest else: self._peers[ident].update(peer) except Exception as e: - logging.exception("error while polling pwngrid-peer") + logging.warning("error while polling pwngrid-peer: %s" % e) time.sleep(1) diff --git a/scripts/backup.sh b/scripts/backup.sh index f66ca78..71c4f61 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -10,6 +10,7 @@ FILES_TO_BACKUP=( /root/brain.json /root/.api-report.json /root/handshakes + /root/peers /etc/pwnagotchi/ /var/log/pwnagotchi.log ) From a78a4b0b3e5cfbdfea481e140e6c02e0bdb1f72d Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 23 Oct 2019 15:26:53 +0200 Subject: [PATCH 345/346] fix: fixes a race condition in the launcher scripts and enables MANU if eth0 is up (closes #365) --- builder/pwnagotchi.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index a9fb322..b4c9680 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -304,7 +304,7 @@ # blink 10 times to signal ready state /usr/bin/bootblink 10 & # start a detached screen session with bettercap - if ifconfig | grep usb0 | grep RUNNING; then + if [[ ifconfig | grep usb0 | grep RUNNING ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then rm /root/.pwnagotchi-auto @@ -322,13 +322,10 @@ mode: 0755 content: | #!/usr/bin/env bash - # blink 10 times to signal ready state - /usr/bin/bootblink 10 & /usr/bin/monstart - if ifconfig | grep usb0 | grep RUNNING; then + if [[ ifconfig | grep usb0 | grep RUNNING ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then # if override file exists, go into auto mode if [ -f /root/.pwnagotchi-auto ]; then - rm /root/.pwnagotchi-auto /usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0 else /usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0 From 277906a6738553263db0174d003dd1367137423e Mon Sep 17 00:00:00 2001 From: Simone Margaritelli <evilsocket@gmail.com> Date: Wed, 23 Oct 2019 15:37:12 +0200 Subject: [PATCH 346/346] fix: fixed bogus support for waveshare lcd displays (fixes #364) --- .../ui/hw/libs/waveshare/lcdhat/ST7789.py | 85 ++++++++++--------- .../ui/hw/libs/waveshare/lcdhat/config.py | 61 +------------ pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py | 21 ++--- 3 files changed, 53 insertions(+), 114 deletions(-) diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py index 0edd1d1..88409ec 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py @@ -7,24 +7,25 @@ import numpy as np class ST7789(object): """class for ST7789 240*240 1.3inch OLED displays.""" - def __init__(self,spi,rst = 27,dc = 25,bl = 24): + def __init__(self, spi, rst=27, dc=25, bl=24): self.width = 240 self.height = 240 - #Initialize DC RST pin + # Initialize DC RST pin self._dc = dc self._rst = rst self._bl = bl GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) - GPIO.setup(self._dc,GPIO.OUT) - GPIO.setup(self._rst,GPIO.OUT) - GPIO.setup(self._bl,GPIO.OUT) + GPIO.setup(self._dc, GPIO.OUT) + GPIO.setup(self._rst, GPIO.OUT) + GPIO.setup(self._bl, GPIO.OUT) GPIO.output(self._bl, GPIO.HIGH) - #Initialize SPI + # Initialize SPI self._spi = spi self._spi.max_speed_hz = 40000000 """ Write register address and data """ + def command(self, cmd): GPIO.output(self._dc, GPIO.LOW) self._spi.writebytes([cmd]) @@ -34,13 +35,13 @@ class ST7789(object): self._spi.writebytes([val]) def Init(self): - """Initialize dispaly""" + """Initialize dispaly""" self.reset() self.command(0x36) - self.data(0x70) #self.data(0x00) + self.data(0x70) # self.data(0x00) - self.command(0x3A) + self.command(0x3A) self.data(0x05) self.command(0xB2) @@ -51,7 +52,7 @@ class ST7789(object): self.data(0x33) self.command(0xB7) - self.data(0x35) + self.data(0x35) self.command(0xBB) self.data(0x19) @@ -63,13 +64,13 @@ class ST7789(object): self.data(0x01) self.command(0xC3) - self.data(0x12) + self.data(0x12) self.command(0xC4) self.data(0x20) self.command(0xC6) - self.data(0x0F) + self.data(0x0F) self.command(0xD0) self.data(0xA4) @@ -106,7 +107,7 @@ class ST7789(object): self.data(0x1F) self.data(0x20) self.data(0x23) - + self.command(0x21) self.command(0x11) @@ -115,51 +116,51 @@ class ST7789(object): def reset(self): """Reset the display""" - GPIO.output(self._rst,GPIO.HIGH) + GPIO.output(self._rst, GPIO.HIGH) time.sleep(0.01) - GPIO.output(self._rst,GPIO.LOW) + GPIO.output(self._rst, GPIO.LOW) time.sleep(0.01) - GPIO.output(self._rst,GPIO.HIGH) + GPIO.output(self._rst, GPIO.HIGH) time.sleep(0.01) - + def SetWindows(self, Xstart, Ystart, Xend, Yend): - #set the X coordinates + # set the X coordinates self.command(0x2A) - self.data(0x00) #Set the horizontal starting point to the high octet - self.data(Xstart & 0xff) #Set the horizontal starting point to the low octet - self.data(0x00) #Set the horizontal end to the high octet - self.data((Xend - 1) & 0xff) #Set the horizontal end to the low octet - - #set the Y coordinates + self.data(0x00) # Set the horizontal starting point to the high octet + self.data(Xstart & 0xff) # Set the horizontal starting point to the low octet + self.data(0x00) # Set the horizontal end to the high octet + self.data((Xend - 1) & 0xff) # Set the horizontal end to the low octet + + # set the Y coordinates self.command(0x2B) self.data(0x00) self.data((Ystart & 0xff)) self.data(0x00) - self.data((Yend - 1) & 0xff ) + self.data((Yend - 1) & 0xff) - self.command(0x2C) - - def ShowImage(self,Image,Xstart,Ystart): + self.command(0x2C) + + def ShowImage(self, Image, Xstart, Ystart): """Set buffer to value of Python Imaging Library image.""" """Write display buffer to physical display""" imwidth, imheight = Image.size if imwidth != self.width or imheight != self.height: raise ValueError('Image must be same dimensions as display \ - ({0}x{1}).' .format(self.width, self.height)) + ({0}x{1}).'.format(self.width, self.height)) img = np.asarray(Image) - pix = np.zeros((self.width,self.height,2), dtype = np.uint8) - pix[...,[0]] = np.add(np.bitwise_and(img[...,[0]],0xF8),np.right_shift(img[...,[1]],5)) - pix[...,[1]] = np.add(np.bitwise_and(np.left_shift(img[...,[1]],3),0xE0),np.right_shift(img[...,[2]],3)) + pix = np.zeros((self.width, self.height, 2), dtype=np.uint8) + pix[..., [0]] = np.add(np.bitwise_and(img[..., [0]], 0xF8), np.right_shift(img[..., [1]], 5)) + pix[..., [1]] = np.add(np.bitwise_and(np.left_shift(img[..., [1]], 3), 0xE0), np.right_shift(img[..., [2]], 3)) pix = pix.flatten().tolist() - self.SetWindows ( 0, 0, self.width, self.height) - GPIO.output(self._dc,GPIO.HIGH) - for i in range(0,len(pix),4096): - self._spi.writebytes(pix[i:i+4096]) - + self.SetWindows(0, 0, self.width, self.height) + GPIO.output(self._dc, GPIO.HIGH) + for i in range(0, len(pix), 4096): + self._spi.writebytes(pix[i:i + 4096]) + def clear(self): """Clear contents of image buffer""" - _buffer = [0xff]*(self.width * self.height * 2) - self.SetWindows ( 0, 0, self.width, self.height) - GPIO.output(self._dc,GPIO.HIGH) - for i in range(0,len(_buffer),4096): - self._spi.writebytes(_buffer[i:i+4096]) + _buffer = [0xff] * (self.width * self.height * 2) + self.SetWindows(0, 0, self.width, self.height) + GPIO.output(self._dc, GPIO.HIGH) + for i in range(0, len(_buffer), 4096): + self._spi.writebytes(_buffer[i:i + 4096]) diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py index a319364..24b5cc8 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py @@ -7,70 +7,15 @@ # * | Date : 2019-10-18 # * | Info : # ******************************************************************************/ - -import RPi.GPIO as GPIO -import time -from smbus import SMBus import spidev -import ctypes -# import spidev - # Pin definition -RST_PIN = 27 -DC_PIN = 25 -BL_PIN = 24 +RST_PIN = 27 +DC_PIN = 25 +BL_PIN = 24 Device_SPI = 1 Device_I2C = 0 Device = Device_SPI spi = spidev.SpiDev(0, 0) - -def digital_write(pin, value): - GPIO.output(pin, value) - -def digital_read(pin): - return GPIO.input(BUSY_PIN) - -def delay_ms(delaytime): - time.sleep(delaytime / 1000.0) - -def spi_writebyte(data): - # SPI.writebytes(data) - spi.writebytes([data[0]]) - -def i2c_writebyte(reg, value): - bus.write_byte_data(address, reg, value) - - # time.sleep(0.01) -def module_init(): - # print("module_init") - - GPIO.setmode(GPIO.BCM) - GPIO.setwarnings(False) - GPIO.setup(RST_PIN, GPIO.OUT) - GPIO.setup(DC_PIN, GPIO.OUT) - - - # SPI.max_speed_hz = 2000000 - # SPI.mode = 0b00 - # i2c_writebyte(0xff,0xff) - # spi.SYSFS_software_spi_begin() - # spi.SYSFS_software_spi_setDataMode(0); - # spi.SYSFS_software_spi_setClockDivider(1); - #spi.max_speed_hz = 2000000 - #spi.mode = 0b00 - - GPIO.output(BL_PIN, 1) - GPIO.output(DC_PIN, 0) - return 0 - -def module_exit(): - spi.SYSFS_software_spi_end() - GPIO.output(RST_PIN, 0) - GPIO.output(DC_PIN, 0) - - - -### END OF FILE ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py index cd0d81f..1a559f4 100644 --- a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py @@ -1,28 +1,21 @@ from . import ST7789 from . import config -# Display resolution -EPD_WIDTH = 240 -EPD_HEIGHT = 240 - -disp = ST7789.ST7789(config.spi,config.RST_PIN, config.DC_PIN, config.BL_PIN) class EPD(object): - def __init__(self): self.reset_pin = config.RST_PIN self.dc_pin = config.DC_PIN - #self.busy_pin = config.BUSY_PIN - #self.cs_pin = config.CS_PIN - self.width = EPD_WIDTH - self.height = EPD_HEIGHT + self.width = 240 + self.height = 240 + self.st7789 = ST7789.ST7789(config.spi, config.RST_PIN, config.DC_PIN, config.BL_PIN) def init(self): - disp.Init() + self.st7789.Init() - def Clear(self): - disp.clear() + def clear(self): + self.st7789.clear() def display(self, image): rgb_im = image.convert('RGB') - disp.ShowImage(rgb_im,0,0) + self.st7789.ShowImage(rgb_im, 0, 0)