From d3a8dc85c3939c619fcac1e20a48531e627d5a23 Mon Sep 17 00:00:00 2001 From: Andreas Kupfer 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 4c693a69..1064c514 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 19e79999..0d76b7e1 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 00000000..a94c7ff9 --- /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 78ff6479..76d8ca9d 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" 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 1064c514..66f2e36a 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 a94c7ff9..c22da4b6 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" 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 d6e00d14..88ea5d4f 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 c22da4b6..ec92465f 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 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 b2e30f8f..58c5a520 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,11 @@ full and half WPA handshakes. ![handshake](https://i.imgur.com/pdA4vCZ.png) -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 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 d2bb0f5a..a7c251f6 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 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 b44c5bfd..5074aea7 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Contributors Travis Slack + follow on Twitter

From dac09fccf4d6f9cb2eb673bb1a230d21bdfc0bac Mon Sep 17 00:00:00 2001 From: Simone Margaritelli 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 5074aea7..b6ea9aa3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

Release Software License - Contributors + Contributors Travis Slack follow on Twitter From b765a642ae0147929cea9836d528da9890ab3b4a Mon Sep 17 00:00:00 2001 From: waxwing 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 b1ee1d76..9f143e8e 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 1172f7fe..1d061f77 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 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 00000000..4a711251 --- /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 2b459332..1d291191 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 75b7ea0f..534b3984 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 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 1d291191..b2988657 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 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 ff19087a..fcc67821 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 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 ff797a35..84286d25 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 6a9a00a5..6fe231eb 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 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 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 a1e6e84f..41d74750 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 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 02537cd5..1e07a12e 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 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 1e07a12e..650d40c3 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 fc54ebf8..0cac4ae4 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 b2988657..4f69a3d7 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 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 f113ce12..b1cfa69a 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 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 fab9c2e7..2fe3ff97 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 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 8fa5e8a7..57579fd3 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] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 57579fd3..f364dbcc 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 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 58c5a520..f492955c 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 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 eacbd733..f08fa431 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. + +

+ +
+image taken from wifi-professionals.com +

+ +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 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 fcc67821..47305b42 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,55 +1,201 @@ # FAQ + [**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 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 47305b42..348e0397 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 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 eacbd733..28832061 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. + +

+ +
+image taken from wifi-professionals.com +

+ +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 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 184cff04..5e47d516 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 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 5e47d516..6f58c3fb 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 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 28832061..4777c215 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 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 a7c251f6..f85fd353 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 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 f85fd353..b232e3ef 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 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 14403d28..9e68dc81 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 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 8883cb70..fff0fb1b 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). -![ui](https://i.imgur.com/XgIrcur.png) +![ui](https://i.imgur.com/c7xh4hN.png) * **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 4f69a3d7..06be3c63 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 00000000..b87e71bd --- /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 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 47331058..9b0b561c 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 cceac714..813b97fb 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 68e41717..cfe35da9 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 249ea19a..2319d967 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 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 9b0b561c..56300af1 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 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 41d74750..98ee45cd 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 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 4fb4153a..02b95060 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. -![handshake](https://i.imgur.com/pdA4vCZ.png) +![ui](https://i.imgur.com/c7xh4hN.png) 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 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 cceac714..c1fc2b61 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 00000000..b7f79824 --- /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 b87e71bd..cba5745b 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 02b95060..670ed47f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ full and half WPA handshakes. ![ui](https://i.imgur.com/c7xh4hN.png) -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 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 56300af1..f72d84c1 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 98f8aabe..0e897632 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 813b97fb..dff6b3bf 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 00000000..ae12490c --- /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 9e68dc81..e1c033fe 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 3b63d092..a775ad40 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 00000000..afd882c5 --- /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 731a40ff..48d85976 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 4a1f1352..dab44b3c 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 c18177c2..2a82a18d 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 2319d967..9771b885 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 560903a0..8f21f256 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 cba5745b..da57f947 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 cfe35da9..7d61b7b6 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 d1b2ba0d..28346641 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 98ee45cd..f8e22b31 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 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 afd882c5..6747d95e 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 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 6747d95e..480f07cf 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 4a711251..265a56ae 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 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 9905b0fe..ff434946 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 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 359a38ca..190025fc 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 78e75b6c..58476626 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 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 416ae4f8..dab58e49 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 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 480f07cf..58280850 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 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 58280850..60792af4 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 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 dff6b3bf..e9b45b64 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 60792af4..eae93432 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 28346641..e6e15f53 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 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 eae93432..b912bb8c 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 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 b912bb8c..0b2f1be5 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 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 0b2f1be5..71d78e39 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 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 71d78e39..f906b064 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 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 650d40c3..c0d75581 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 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 e9b45b64..3cda0cb7 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 f906b064..09ee08aa 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 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 3cda0cb7..3f2d871d 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 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 b232e3ef..a3fc7c20 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" 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 7fb6a6f2..4b824431 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 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 a3fc7c20..11d204d8 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 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 11d204d8..8c9c6a9e 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 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 8c9c6a9e..935197f7 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 3f2d871d..7d4d92bd 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 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 935197f7..47fe0428 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 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 47fe0428..dfc33aec 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 7d4d92bd..330218f6 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 09ee08aa..6c151b39 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 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 670ed47f..363d7ece 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 4777c215..00000000 --- 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. - -![handshake](https://i.imgur.com/pdA4vCZ.png) - -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. - -![peers](https://i.imgur.com/Ywr5aqx.png) - -[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. - -

- -
-image taken from wifi-professionals.com -

- -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 dfc33aec..00000000 --- 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 534b3984..00000000 --- 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 of the pwnagotchi (default: pwnagotchi) - -i # Provide the path of an already downloaded raspbian image - -o # Name of the img-file (default: pwnagotchi.img) - -s # Size which should be added to second partition (in Gigabyte) (default: 4) - -v # 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 348e0397..00000000 --- a/docs/faq.md +++ /dev/null @@ -1,208 +0,0 @@ -# FAQ - - -[**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 6f58c3fb..00000000 --- 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 b998af0c..00000000 --- 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 9f143e8e..00000000 --- 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 0038df9f..00000000 --- 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 fff0fb1b..00000000 --- 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). - -![ui](https://i.imgur.com/c7xh4hN.png) - -* **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/`. - -![webui](https://raw.githubusercontent.com/bettercap/media/master/ui-events.png) - -## 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 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 0e897632..71e87f51 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 58476626..3289eb13 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 06be3c63..e0ef7c41 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 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 ff434946..c343bb31 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 76e9a24d..ffefa18c 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -15,11 +15,29 @@ class VideoHandler(BaseHTTPRequestHandler): _lock = Lock() _index = """ - %s + %s + - - +
+ +
+
+
+ +
+
+ - -""" - - @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 00000000..1032e570 --- /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 = """ + + %s + + + +
+ +
+
+
+ +
+
+ + + +""" + +SHUTDOWN = """ + + %s + + + +
+ Shutting down ... +
+ +""" + +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 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 1032e570..9a47b55f 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -71,6 +71,7 @@ SHUTDOWN = """ """ + class Handler(BaseHTTPRequestHandler): # suppress internal logging def log_message(self, format, *args): From d45e8c7ba02899d6e9e7b8793306157171e1b6a0 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli 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 952dcfba..5c455fab 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 9a47b55f..9eb5b8f6 100644 --- a/pwnagotchi/ui/web.py +++ b/pwnagotchi/ui/web.py @@ -73,13 +73,30 @@ SHUTDOWN = """ 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 3f3e570c..72087cfb 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 :/plugins/ is opened +def on_webhook(response, path): + res = "Hook triggered" + 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 9eb5b8f6..e318846e 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 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 94c160d5..c03021e2 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. -![ui](https://i.imgur.com/c7xh4hN.png) +![ui](https://i.imgur.com/X68GXrn.png) 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 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 5c455fab..31427612 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 4f80e5ca..d4cbd32a 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 a6efd78d..4d72097c 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 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 31427612..0aed0f7f 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 00000000..87969a09 --- /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 419d80d7..46f1103b 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 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 87969a09..90178031 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 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 90178031..153cbebd 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 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 153cbebd..e65e511f 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 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 ebada1b4..f5c235e1 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 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 a5412f39..cb9d9825 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 f5c235e1..dda28e0a 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 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 4d72097c..506b800b 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 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 e65e511f..1d7223e8 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 9eb5b8f6..c6a304b7 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 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 1d7223e8..74e71dc5 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 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 74e71dc5..42effb80 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 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 42effb80..57579252 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 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 9eb5b8f6..ad727f75 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 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 57579252..84270292 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 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 cb9d9825..a23524ec 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 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 a23524ec..d6303d56 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 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 506b800b..977dbe8b 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 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 ad727f75..7a08b8cd 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 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 84270292..ebf84ec1 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 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 ebf84ec1..6580377b 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 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 6580377b..7c045069 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 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 7c045069..f96dcd19 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 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 f96dcd19..314acd85 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 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 314acd85..0d8f17c7 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 977dbe8b..a2f2d2cf 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 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 0d8f17c7..6450ed02 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 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 6450ed02..e78e67c9 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 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 e78e67c9..8e0ca324 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 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 8e0ca324..fdd18021 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 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 fdd18021..eaa7396b 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 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 14737d5e..caa4bc97 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 eaa7396b..b172e976 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 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 b172e976..878d707c 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 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 878d707c..9e5be0ec 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 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 9e5be0ec..f9e8d9e1 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 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 f9e8d9e1..86ee7366 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 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 86ee7366..2be968a3 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 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 2be968a3..68892773 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 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 68892773..735b4920 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 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 735b4920..af340e15 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 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 af340e15..86b854d4 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 Date: Sun, 20 Oct 2019 21:26:32 +0200 Subject: [PATCH 316/346] Added documentation for waveshare27inch Signed-off-by: Mike van der Vrugt --- pwnagotchi/defaults.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index 31427612..dc4e3369 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 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 --- 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 eac519f2..c4fd4952 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 ee00a73e..1e0bf31a 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 419d80d7..f4316802 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 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 --- 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 00000000..9dff62d1 --- /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 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 --- .../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 00000000..e69de29b 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 00000000..01e84736 --- /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 00000000..78ff6479 --- /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 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 86b854d4..6155ad5f 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 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 6155ad5f..5aa8b555 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 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 5aa8b555..c3595059 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 0516168d..d2e9d271 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 efa3faaf..6bf99940 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_=V?>4qt=EFun`Y@gaByY-U>8DInCdJNnL zeh>DBp?^0z8d_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)PGASH54C+a&WrnXtonGEnCay%CWRwm?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|5enkyop-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!n6%yM rLdw;6+~$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 zj4KyWdeQ@4`-SDy$-+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(Qt5EpP|?`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, 2019. +# This file is distributed under the same license as the pwnagotchi package. +# szymex73 , 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 \n" -"Language-Team: LANGUAGE \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 cf023626..10de71fe 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 \n" "Language-Team: LANGUAGE \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 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 d53f5b2f..0ac5935f 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 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 021c37a9..82af2693 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 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 d2e9d271..cc4fa517 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 c4fd4952..c0579221 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 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 c9430589..f66ca783 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 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 c3dc1bfe..627a491c 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 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 24013fb0..cd4dcfae 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 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 cd4dcfae..16fa722a 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 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 b0f5e6a1..6356d18f 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 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 cc4fa517..5bd95118 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 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 6356d18f..e60e345d 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 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 e60e345d..4d3ab945 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 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 --- 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 9dff62d1..c554744b 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 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 5bd95118..6bd94351 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 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 e0bd2a55..0b449acc 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 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 0b449acc..a9fb322c 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 Date: Tue, 22 Oct 2019 20:53:15 +0300 Subject: [PATCH 341/346] Add non-blocking screen updating Signed-off-by: python273 --- 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 c0579221..c28b4d75 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 Date: Tue, 22 Oct 2019 20:59:15 +0300 Subject: [PATCH 342/346] Add slight delay for waveshare v1 ReadBusy Signed-off-by: python273 --- .../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 a0bff834..f17f0af0 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 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 cfb5d8fc..5b32635c 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 a2f2d2cf..dcb31a20 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 61689cf9..530c815a 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 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 6bd94351..6e919650 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 14f00577..a3ddd553 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 3849acc5..8591f4bf 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 f66ca783..71c4f612 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 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 a9fb322c..b4c9680a 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 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 0edd1d12..88409ecc 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 a319364f..24b5cc88 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 cd0d81f4..1a559f46 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)