diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b173709 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +dist: bionic +language: generic +env: + global: + - LANG=C + - LC_ALL=C +before_cache: +- mountpoint -q $TRAVIS_BUILD_DIR/tmp/mnt && sudo umount -R $TRAVIS_BUILD_DIR/tmp/mnt +- sudo find $TRAVIS_BUILD_DIR/tmp/ -name '*.img' -delete +cache: + apt: true + directories: + - tmp/ +before_script: +- sudo apt-get -y update +- sudo apt-get -y install qemu-user-static binfmt-support qemu +- sudo update-binfmts --display +- unset GOROOT +script: +- sudo ./scripts/create_sibling.sh -n pwnagotchi -o pwnagotchi.img -s 4 +- zip -s 2g pwnagotchi.zip pwnagotchi.img + +# TODO: deploy! diff --git a/README.md b/README.md index 7bb826d..f568794 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # Pwnagotchi +

+

+ Release + Software License + Travis +

+

+ [Pwnagotchi](https://twitter.com/pwnagotchi) is an "AI" that learns from the WiFi environment and instruments bettercap in order to maximize the WPA key material (any form of handshake that is crackable, 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) captured. ![handshake](https://i.imgur.com/pdA4vCZ.png) @@ -14,9 +22,9 @@ Multiple units can talk to each other, advertising their own presence using a pa 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. -If instead you are a boring person, you can disable the AI and have the algorithm run just with the preconfigured default parameters and enjoy a very portable bettercap + webui dedicated hardware. +If instead you just want to use your own parameters and save battery and CPU cycles, you can disable the AI in `config.yml` and enjoy an automated deauther, WPA handshake sniffer and portable bettercap + webui dedicated hardware. -**NOTE:** The software **requires bettercap v2.25**. +**NOTE:** The software **requires at least bettercap v2.25**. ![units](https://i.imgur.com/MStjXZF.png) @@ -28,12 +36,22 @@ For hackers to learn reinforcement learning, WiFi networking and have an excuse **THIS IS STILL ALPHA STAGE SOFTWARE, IF YOU DECIDE TO TRY TO USE IT, YOU ARE ON YOUR OWN, NO SUPPORT WILL BE PROVIDED, NEITHER FOR INSTALLATION OR FOR BUGS** +However, there's [a Slack channel](https://join.slack.com/t/pwnagotchi/shared_invite/enQtNzc4NzY3MDE2OTAzLTg5NmNmNDJiMDM3ZWFkMWUwN2Y5NDk0Y2JlZWZjODlhMmRhNDZiOGMwYjJhM2UzNzA3YjA5NjJmZGY5NGI5NmI). ### Hardware - Raspberry Pi Zero W -- [Waveshare eInk Display](https://www.waveshare.com/2.13inch-e-paper-hat.htm) (optional if you connect to usb0 and point your browser to the web ui, see config.yml) - A decent power bank (with 1500 mAh you get ~2 hours with AI on) +#### Display (optional) + +The display is optional if you connect to `usb0` (by using the data port on the unit) and point your browser to the web ui (see config.yml). + +The supported models are: + +- [Waveshare eInk Display (both V1 and V2)](https://www.waveshare.com/2.13inch-e-paper-hat.htm) +- [Pimoroni Inky pHAT](https://shop.pimoroni.com/products/inky-phat) +- [PaPiRus eInk Screen](https://uk.pi-supply.com/products/papirus-zero-epaper-screen-phat-pi-zero) + ### 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). @@ -48,13 +66,14 @@ You can use the `scripts/create_sibling.sh` script to create an - ready to flash 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) - -p # Only run provisioning (assumes the image is already mounted) - -d # Only run dependencies checks - -h # Show this help + -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 ``` #### Host Connection Share @@ -63,7 +82,7 @@ If you connect to the unit via `usb0` (thus using the data port), you might want ### UI -The UI is available either via display if installed, or via http://10.0.0.2:8080/ if you connect to the unit via `usb0` and set a static address on the network 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/XgIrcur.png) @@ -73,6 +92,34 @@ The UI is available either via display if installed, or via http://10.0.0.2:8080 * **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. +#### Languages + +Pwnagotchi is able to speak multiple languages!! Currently supported are: + +* **english** (default) +* german + +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 +``` + ### Random Info - `hostname` sets the unit name. @@ -81,7 +128,7 @@ The UI is available either via display if installed, or via http://10.0.0.2:8080 - `/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 +- 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`. ## License diff --git a/scripts/create_sibling.sh b/scripts/create_sibling.sh index 6b8ea34..5ae58ef 100755 --- a/scripts/create_sibling.sh +++ b/scripts/create_sibling.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # based on: https://wiki.debian.org/RaspberryPi/qemu-user-static ## and https://z4ziggy.wordpress.com/2015/05/04/from-bochs-to-chroot/ @@ -16,6 +16,9 @@ PWNI_SIZE="4" OPT_PROVISION_ONLY=0 OPT_CHECK_DEPS_ONLY=0 OPT_IMAGE_PROVIDED=0 +OPT_RASPBIAN_VERSION='latest' + +SUPPORTED_RASPBIAN_VERSIONS=( 'latest' 'buster' 'stretch' ) if [[ "$EUID" -ne 0 ]]; then echo "Run this script as root!" @@ -44,9 +47,23 @@ function check_dependencies() { } function get_raspbian() { + VERSION="$1" + + case "$VERSION" in + latest) + URL="https://downloads.raspberrypi.org/raspbian_lite_latest" + ;; + buster) + URL="https://downloads.raspberrypi.org/raspbian/images/raspbian-2019-07-12/2019-07-10-raspbian-buster.zip" + ;; + stretch) + URL="https://downloads.raspberrypi.org/raspbian/images/raspbian-2019-04-09/2019-04-08-raspbian-stretch.zip" + ;; + esac + echo "[+] Downloading raspbian.zip" mkdir -p "${TMP_DIR}" - wget --show-progress -qcO "${TMP_DIR}/raspbian.zip" "https://downloads.raspberrypi.org/raspbian_lite_latest" + wget --show-progress -qcO "${TMP_DIR}/raspbian.zip" "$URL" echo "[+] Unpacking raspbian.zip to raspbian.img" gunzip -c "${TMP_DIR}/raspbian.zip" > "${TMP_DIR}/raspbian.img" } @@ -68,7 +85,7 @@ function setup_raspbian(){ parted -s "$LOOP_PATH" rm 2 parted -s "$LOOP_PATH" mkpart primary "$PART2_START" 100% echo "[+] Check FS" - e2fsck -f "${LOOP_PATH}p2" + e2fsck -y -f "${LOOP_PATH}p2" echo "[+] Resize FS" resize2fs "${LOOP_PATH}p2" echo "[+] Device is ${LOOP_PATH}" @@ -82,9 +99,8 @@ function setup_raspbian(){ mount --bind /sys "${MNT_DIR}/sys/" mount --bind /proc "${MNT_DIR}/proc/" mount --bind /dev/pts "${MNT_DIR}/dev/pts" - mount --bind /etc/ssl/certs "${MNT_DIR}/etc/ssl/certs" - mount --bind /etc/ca-certificates "${MNT_DIR}/etc/ca-certificates" cp /usr/bin/qemu-arm-static "${MNT_DIR}/usr/bin" + cp /etc/resolv.conf "${MNT_DIR}/etc/resolv.conf" } function provision_raspbian() { @@ -94,12 +110,17 @@ function provision_raspbian() { LANG=C chroot . bin/bash -x </etc/dphys-swapfile @@ -110,19 +131,32 @@ function provision_raspbian() { git clone https://github.com/evilsocket/pwnagotchi.git rsync -aP pwnagotchi/sdcard/boot/* /boot/ rsync -aP pwnagotchi/sdcard/rootfs/* / + rm -rf /tmp/pwnagotchi # configure pwnagotchi echo -e "$PWNI_NAME" > /etc/hostname sed -i "s@^127\.0\.0\.1 .*@127.0.0.1 localhost "$PWNI_NAME" "$PWNI_NAME".local@g" /etc/hosts - sed -i "s@pwnagotchi@$PWNI_NAME@g" /etc/motd + sed -i "s@alpha@$PWNI_NAME@g" /etc/motd chmod +x /etc/rc.local + # need armv6l version of tensorflow and opencv-python, not armv7l + # PIP_OPTS="--upgrade --only-binary :all: --abi cp37m --platform linux_armv6l --target /usr/lib/python3.7/site-packages/" + # pip3 install \$PIP_OPTS opencv-python + # Should work for tensorflow too, but BUG: Hash mismatch; therefore: + wget -P /root/ -c https://www.piwheels.org/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl + wget -P /root/ -c https://www.piwheels.org/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl + # we need to install these on first raspberry start... + sed -i '/startup\.sh/i pip3 install --no-deps --force-reinstall --upgrade /root/tensorflow-1.13.1-cp37-none-linux_armv6l.whl /root/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl && rm /root/tensorflow-1.13.1-cp37-none-linux_armv6l.whl /root/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl && sed -i "/tensorflow/d" /etc/rc.local' /etc/rc.local + + # newer version is broken + pip3 install gast==0.2.2 + /dev/null 2>&1 + pip3 install --progress-bar off {} # waveshare - pip3 install --trusted-host www.piwheels.org spidev RPi.GPIO + pip3 install spidev RPi.GPIO # install bettercap export GOPATH=/root/go @@ -134,33 +168,36 @@ function provision_raspbian() { git clone https://github.com/bettercap/caplets.git cd caplets make install - - # monstart + monstop - cat <<"STOP" > /usr/bin/monstop - #!/bin/bash - interface=mon0 - ifconfig \${interface} down - sleep 1 - iw dev \${interface} del -STOP - - cat <<"STOP" > /usr/bin/monstart - interface=mon0 - echo "Bring up monitor mode interface \${interface}" - iw phy phy0 interface add \${interface} type monitor - ifconfig \${interface} up - if [ \$? -eq 0 ]; then - echo "started monitor interface on \${interface}" - fi -STOP - - chmod +x /usr/bin/{monstart,monstop} + rm -rf /tmp/caplets + cd /root # fixes getcwd error that was bugging me # Re4son-Kernel echo "deb http://http.re4son-kernel.com/re4son/ kali-pi main" > /etc/apt/sources.list.d/re4son.list wget -O - https://re4son-kernel.com/keys/http/archive-key.asc | apt-key add - apt update apt install -y kalipi-kernel kalipi-bootloader kalipi-re4son-firmware kalipi-kernel-headers libraspberrypi0 libraspberrypi-dev libraspberrypi-doc libraspberrypi-bin + + # Fix PARTUUID + PUUID_ROOT="\$(blkid "\$(df / --output=source | tail -1)" | grep -Po 'PARTUUID="\K[^"]+')" + PUUID_BOOT="\$(blkid "\$(df /boot --output=source | tail -1)" | grep -Po 'PARTUUID="\K[^"]+')" + + # sed regex info: search for line containing / followed by whitespace or /boot (second sed) + # in this line, search for PARTUUID= followed by letters, numbers or "-" + # replace that match with the new PARTUUID + sed -i "/\/[ ]\+/s/PARTUUID=[A-Za-z0-9-]\+/PARTUUID=\$PUUID_ROOT/g" /etc/fstab + sed -i "/\/boot/s/PARTUUID=[A-Za-z0-9-]\+/PARTUUID=\$PUUID_BOOT/g" /etc/fstab + + sed -i "s/root=[^ ]\+/root=PARTUUID=\${PUUID_ROOT}/g" /boot/cmdline.txt + + # delete keys + find /etc/ssh/ -name "ssh_host_*key*" -delete + + # slows down boot + systemctl disable apt-daily.timer apt-daily.service apt-daily-upgrade.timer apt-daily-upgrade.service + + # unecessary services + systemctl disable triggerhappy bluetooth wpa_supplicant + EOF sed -i'' 's/^#//g' etc/ld.so.preload cd "${REPO_DIR}" @@ -175,20 +212,21 @@ function usage() { usage: $0 [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) - -p # Only run provisioning (assumes the image is already mounted) - -d # Only run dependencies checks - -h # Show this help + -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: ${SUPPORTED_RASPBIAN_VERSIONS[*]}; default: latest) + -p # Only run provisioning (assumes the image is already mounted) + -d # Only run dependencies checks + -h # Show this help EOF exit 0 } -while getopts ":n:i:o:s:dph" o; do +while getopts ":n:i:o:s:v:dph" o; do case "${o}" in n) PWNI_NAME="${OPTARG}" @@ -209,6 +247,13 @@ while getopts ":n:i:o:s:dph" o; do d) OPT_CHECK_DEPS_ONLY=1 ;; + v) + if [[ "${SUPPORTED_RASPBIAN_VERSIONS[*]}" =~ ${OPTARG} ]]; then + OPT_RASPBIAN_VERSION="${OPTARG}" + else + usage + fi + ;; h) usage ;; @@ -232,7 +277,7 @@ check_dependencies if [[ "$OPT_IMAGE_PROVIDED" -eq 1 ]]; then provide_raspbian else - get_raspbian + get_raspbian "$OPT_RASPBIAN_VERSION" fi setup_raspbian diff --git a/scripts/language.sh b/scripts/language.sh new file mode 100755 index 0000000..c56b784 --- /dev/null +++ b/scripts/language.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +set -eu + +DEPENDENCIES=( 'xgettext' 'msgfmt' 'msgmerge' ) +COMMANDS=( 'add' 'update' 'delete' 'compile' ) + +REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")" +LOCALE_DIR="${REPO_DIR}/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale" +VOICE_FILE="${REPO_DIR}/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/voice.py" + +function usage() { +cat < [options] + + Commands: + add + delete + compile + update + +EOF +} + +for REQ in "${DEPENDENCIES[@]}"; do + if ! type "$REQ" >/dev/null 2>&1; then + echo "Dependency check failed for ${REQ}" + exit 1 + fi +done + + +if [[ ! "${COMMANDS[*]}" =~ $1 ]]; then + usage +fi + + +function add_lang() { + mkdir -p "$LOCALE_DIR/$1/LC_MESSAGES" + cp -n "$LOCALE_DIR/voice.pot" "$LOCALE_DIR/$1/LC_MESSAGES/voice.po" +} + +function del_lang() { + # set -eu is present; so not dangerous + rm -rf "$LOCALE_DIR/$1" +} + +function comp_lang() { + msgfmt -o "$LOCALE_DIR/$1/LC_MESSAGES/voice.mo" "$LOCALE_DIR/$1/LC_MESSAGES/voice.po" +} + +function update_lang() { + xgettext -d voice -o "$LOCALE_DIR/voice.pot" "$VOICE_FILE" + msgmerge --update "$LOCALE_DIR/$1/LC_MESSAGES/voice.po" "$LOCALE_DIR/voice.pot" +} + +case "$1" in + add) + add_lang "$2" + ;; + delete) + del_lang "$2" + ;; + compile) + comp_lang "$2" + ;; + update) + update_lang "$2" + ;; +esac diff --git a/scripts/linux_connection_share.sh b/scripts/linux_connection_share.sh index 57fe718..a926255 100755 --- a/scripts/linux_connection_share.sh +++ b/scripts/linux_connection_share.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # name of the ethernet gadget interface on the host USB_IFACE=${1:-enp0s20f0u1} @@ -7,12 +7,12 @@ USB_IFACE_NET=10.0.0.0/24 # host interface to use for upstream connection UPSTREAM_IFACE=${2:-enxe4b97aa99867} -ip addr add $USB_IFACE_IP/24 dev $USB_IFACE -ip link set $USB_IFACE up +ip addr add "$USB_IFACE_IP/24" dev "$USB_IFACE" +ip link set "$USB_IFACE" up -iptables -A FORWARD -o $UPSTREAM_IFACE -i $USB_IFACE -s $USB_IFACE_NET -m conntrack --ctstate NEW -j ACCEPT +iptables -A FORWARD -o "$UPSTREAM_IFACE" -i "$USB_IFACE" -s "$USB_IFACE_NET" -m conntrack --ctstate NEW -j ACCEPT iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -t nat -F POSTROUTING -iptables -t nat -A POSTROUTING -o $UPSTREAM_IFACE -j MASQUERADE +iptables -t nat -A POSTROUTING -o "$UPSTREAM_IFACE" -j MASQUERADE echo 1 > /proc/sys/net/ipv4/ip_forward diff --git a/sdcard/boot/config.txt b/sdcard/boot/config.txt index 98231bc..4d69af8 100755 --- a/sdcard/boot/config.txt +++ b/sdcard/boot/config.txt @@ -1195,3 +1195,8 @@ dtoverlay=dwc2 dtparam=spi=on dtoverlay=spi1-3cs +# Disable bluetooth +dtoverlay=pi3-disable-bt +# Disable audio +dtparam=audio=off + diff --git a/sdcard/rootfs/etc/network/interfaces b/sdcard/rootfs/etc/network/interfaces index 1d8ee6b..2c84f3c 100644 --- a/sdcard/rootfs/etc/network/interfaces +++ b/sdcard/rootfs/etc/network/interfaces @@ -5,6 +5,9 @@ iface lo inet loopback allow-hotplug wlan0 iface wlan0 inet static +allow-hotplug eth0 +iface eth0 inet dhcp + allow-hotplug usb0 iface usb0 inet static address 10.0.0.2 diff --git a/sdcard/rootfs/etc/rc.local b/sdcard/rootfs/etc/rc.local index 15fb894..e93d2d8 100755 --- a/sdcard/rootfs/etc/rc.local +++ b/sdcard/rootfs/etc/rc.local @@ -10,5 +10,7 @@ # bits. # # By default this script does nothing. +# Powersave (Disable HDMI) ~30ma +/opt/vc/bin/tvservice -o /root/pwnagotchi/scripts/startup.sh & exit 0 diff --git a/sdcard/rootfs/root/pwnagotchi/config.yml b/sdcard/rootfs/root/pwnagotchi/config.yml index 540b872..1fb846e 100644 --- a/sdcard/rootfs/root/pwnagotchi/config.yml +++ b/sdcard/rootfs/root/pwnagotchi/config.yml @@ -1,5 +1,7 @@ # main algorithm configuration main: + # currently implemented: en (default), de + lang: en # monitor interface to use iface: mon0 # command to run to bring the mon interface up in case it's not up already @@ -11,13 +13,11 @@ main: # if true, will not restart the wifi module no_restart: false # access points to ignore - whitelist: - - Casa-2.4 - - LOTS_OF_MALWARE + whitelist: [] # if not null, filter access points by this regular expression filter: null # cryptographic key for identity - pubkey: /etc/ssh/ssh_host_rsa_key.pub + pubkey: /etc/ssh/ssh_host_rsa_key.pub ai: # if false, only the default 'personality' will be used @@ -36,7 +36,7 @@ ai: vf_coef: 0.25 # entropy coefficient for the loss calculation ent_coef: 0.01 - # maximum value for the gradient clipping + # maximum value for the gradient clipping max_grad_norm: 0.5 # the learning rate learning_rate: 0.0010 @@ -83,7 +83,7 @@ personality: # 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 + bored_num_epochs: 15 # number of inactive epochs that triggers the sad state sad_num_epochs: 25 @@ -94,6 +94,12 @@ ui: display: enabled: true rotation: 180 + # Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2 + type: 'waveshare_2' + # Possible options red/yellow/black (black used for monocromatic displays) + color: 'black' + # How often to do a full refresh 0 all the time, -1 never + refresh: 50 video: enabled: true address: '10.0.0.2' @@ -115,7 +121,7 @@ bettercap: port: 8081 username: user password: pass - # folder where bettercap stores the WPA handshakes, given that + # folder where bettercap stores the WPA handshakes, given that # wifi.handshakes.aggregate will be set to false and individual # pcap files will be created in order to minimize the chances # of a single pcap file to get corrupted diff --git a/sdcard/rootfs/root/pwnagotchi/data/screenrc.auto b/sdcard/rootfs/root/pwnagotchi/data/screenrc.auto index 0d775f9..9319672 100644 --- a/sdcard/rootfs/root/pwnagotchi/data/screenrc.auto +++ b/sdcard/rootfs/root/pwnagotchi/data/screenrc.auto @@ -5,6 +5,7 @@ defscrollback 1024 startup_message off altscreen on autodetach on +zombie kr activity "activity in %n (%t)" bell_msg "bell in %n (%t)" diff --git a/sdcard/rootfs/root/pwnagotchi/data/screenrc.manual b/sdcard/rootfs/root/pwnagotchi/data/screenrc.manual index 4e66adf..1d62528 100644 --- a/sdcard/rootfs/root/pwnagotchi/data/screenrc.manual +++ b/sdcard/rootfs/root/pwnagotchi/data/screenrc.manual @@ -5,6 +5,7 @@ defscrollback 1024 startup_message off altscreen on autodetach on +zombie kr activity "activity in %n (%t)" bell_msg "bell in %n (%t)" diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/blink.sh b/sdcard/rootfs/root/pwnagotchi/scripts/blink.sh index 48345ef..48673dc 100755 --- a/sdcard/rootfs/root/pwnagotchi/scripts/blink.sh +++ b/sdcard/rootfs/root/pwnagotchi/scripts/blink.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash -for i in `seq 1 $1`; +for i in $(seq 1 "$1"); do echo 0 >/sys/class/leds/led0/brightness sleep 0.3 @@ -10,3 +10,8 @@ done echo 0 >/sys/class/leds/led0/brightness sleep 0.3 + +# Powersave options +# Disable power LED ~30ma +echo none >/sys/class/leds/led0/trigger +echo 1 >/sys/class/leds/led0/brightness diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/main.py b/sdcard/rootfs/root/pwnagotchi/scripts/main.py index 4aeb781..de44012 100755 --- a/sdcard/rootfs/root/pwnagotchi/scripts/main.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/main.py @@ -8,7 +8,7 @@ import core import pwnagotchi from pwnagotchi.log import SessionParser -import pwnagotchi.voice as voice +from pwnagotchi.voice import Voice from pwnagotchi.agent import Agent from pwnagotchi.ui.display import Display @@ -62,7 +62,7 @@ if args.do_manual: core.log("detected a new session and internet connectivity!") - picture = '/tmp/pwnagotchi.png' + picture = '/dev/shm/pwnagotchi.png' display.update() display.image().save(picture, 'png') @@ -74,7 +74,7 @@ if args.do_manual: auth.set_access_token(config['twitter']['access_token_key'], config['twitter']['access_token_secret']) api = tweepy.API(auth) - tweet = voice.on_log_tweet(log) + tweet = Voice(lang=config['main']['lang']).on_log_tweet(log) api.update_with_media(filename=picture, status=tweet) log.save_session_id() @@ -121,9 +121,9 @@ while True: # An interesting effect of this: # # From Pwnagotchi's perspective, the more new access points - # and / or client stations nearby, the longer one epoch of + # and / or client stations nearby, the longer one epoch of # its relative time will take ... basically, in Pwnagotchi's universe, - # WiFi electromagnetic fields affect time like gravitational fields + # WiFi electromagnetic fields affect time like gravitational fields # affect ours ... neat ^_^ agent.next_epoch() except Exception as e: diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py index f081681..97e9a6d 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py @@ -54,7 +54,7 @@ class Stats(object): def load(self): with self._lock: - if os.path.exists(self.path): + if os.path.exists(self.path) and os.path.getsize(self.path) > 0: core.log("[ai] loading %s" % self.path) with open(self.path, 'rt') as fp: obj = json.load(fp) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/de/LC_MESSAGES/voice.mo b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/de/LC_MESSAGES/voice.mo new file mode 100644 index 0000000..dcede01 Binary files /dev/null and b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/de/LC_MESSAGES/voice.mo differ diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/de/LC_MESSAGES/voice.po b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/de/LC_MESSAGES/voice.po new file mode 100644 index 0000000..c85e254 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/de/LC_MESSAGES/voice.po @@ -0,0 +1,344 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 0.0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-09-29 13:34+0200\n" +"PO-Revision-Date: 2019-09-29 14:00+0200\n" +"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n" +"Language-Team: DE <33197631+dadav@users.noreply.github.com>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: voice.py:16 +msgid "ZzzzZZzzzzZzzz" +msgstr "" + +#: voice.py:21 +msgid "" +"Hi, I'm Pwnagotchi!\n" +"Starting ..." +msgstr "" +"Hi, ich bin\n" +"ein Pwnagotchi!\n" +"Starte ..." + +#: voice.py:22 +msgid "" +"New day, new hunt,\n" +"new pwns!" +msgstr "" +"Neuer Tag, neue Jagd,\n" +"neue Pwns!" + +#: voice.py:23 +msgid "Hack the Planet!" +msgstr "Hack den Planet!" + +#: voice.py:28 +msgid "AI ready." +msgstr "KI bereit." + +#: voice.py:29 +msgid "" +"The neural network\n" +"is ready." +msgstr "" +"Das neurale Netz\n" +"ist bereit." + +#: voice.py:39 +#, python-brace-format +msgid "" +"Hey, channel {channel} is\n" +"free! Your AP will\n" +"say thanks." +msgstr "" +"Hey, Channel {channel} ist\n" +"frei! Dein AP wird\n" +"es dir danken." + +#: voice.py:44 +msgid "I'm bored ..." +msgstr "Mir ist langweilig..." + +#: voice.py:45 +msgid "Let's go for a walk!" +msgstr "Lass uns laufen gehen!" + +#: voice.py:49 +msgid "" +"This is the best\n" +"day of my life!" +msgstr "" +"Das ist der beste\n" +"Tag meines Lebens." + +#: voice.py:53 +msgid "Shitty day :/" +msgstr "Scheis Tag :/" + +#: voice.py:58 +msgid "I'm extremely bored ..." +msgstr "Mir ist sau langweilig..." + +#: voice.py:59 +msgid "I'm very sad ..." +msgstr "Ich bin sehr traurig..." + +#: voice.py:60 +msgid "I'm sad" +msgstr "Ich bin traurig" + +#: voice.py:66 +msgid "I'm living the life!" +msgstr "Ich lebe das Leben!" + +#: voice.py:67 +msgid "I pwn therefore I am." +msgstr "Ich pwne, also bin ich." + +#: voice.py:68 +msgid "So many networks!!!" +msgstr "So viele Netwerke!!!" + +#: voice.py:69 +msgid "" +"I'm having so much\n" +"fun!" +msgstr "" +"Ich habe sooo viel\n" +"Spaß!" + +#: voice.py:70 +msgid "" +"My crime is that of\n" +"curiosity ..." +msgstr "" +"Mein Verbrechen ist\n" +"das der Neugier ..." + +#: voice.py:75 +#, python-brace-format +msgid "" +"Hello\n" +"{name}!\n" +"Nice to meet you. {name}" +msgstr "" +"Hallo {name},\n" +"Nett Dich\n" +"kennenzulernen." + +#: voice.py:76 +#, python-brace-format +msgid "" +"Unit\n" +"{name}\n" +"is nearby! {name}" +msgstr "" +"Gerät {name}\n" +"ist in der\n" +"nähe!!" + +#: voice.py:81 +#, python-brace-format +msgid "" +"Uhm ...\n" +"goodbye\n" +"{name}" +msgstr "" +"Uhm ...\n" +"tschüß\n" +"{name}" + +#: voice.py:82 +#, python-brace-format +msgid "" +"{name}\n" +"is gone ..." +msgstr "" +"{name}\n" +"ist weg ..." + +#: voice.py:87 +#, python-brace-format +msgid "" +"Whoops ...\n" +"{name}\n" +"is gone." +msgstr "" +"Whoops ...\n" +"{name}\n" +"ist weg." + +#: voice.py:88 +#, python-brace-format +msgid "" +"{name}\n" +"missed!" +msgstr "" +"{name}\n" +"verpasst!" + +#: voice.py:89 +msgid "Missed!" +msgstr "Verpasst!" + +#: voice.py:94 +msgid "" +"Nobody wants to\n" +"play with me ..." +msgstr "" +"Niemand will mit\n" +"mir spielen ..." + +#: voice.py:95 +msgid "I feel so alone ..." +msgstr "" +"Ich fühl mich\n" +"so alleine ..." + +#: voice.py:96 +msgid "Where's everybody?!" +msgstr "Wo sind denn alle?" + +#: voice.py:101 +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "Schlafe für {secs}s" + +#: voice.py:102 +msgid "Zzzzz" +msgstr "" + +#: voice.py:103 +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "" + +#: voice.py:112 +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "Warte für {secs}s ..." + +#: voice.py:114 +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "Schaue mich um ({secs}s)" + +#: voice.py:121 +#, python-brace-format +msgid "" +"Hey\n" +"{what}\n" +"let's be friends!" +msgstr "" +"Hey\n" +"{what}\n" +"lass uns Freunde sein!" + +#: voice.py:122 +#, python-brace-format +msgid "" +"Associating to\n" +"{what}" +msgstr "" +"Verbinde mit\n" +"{what}" + +#: voice.py:123 +#, python-brace-format +msgid "" +"Yo\n" +"{what}!" +msgstr "" + +#: voice.py:128 +#, python-brace-format +msgid "" +"Just decided that\n" +"{mac}\n" +"needs no WiFi!" +msgstr "" +"Habe gerade entschieden,\n" +"dass {mac}\n" +"kein WiFi brauch!" + +#: voice.py:129 +#, python-brace-format +msgid "" +"Deauthenticating\n" +"{mac}" +msgstr "" +"Deauthentifiziere\n" +"{mac}" + +#: voice.py:130 +#, python-brace-format +msgid "" +"Kickbanning\n" +"{mac}!" +msgstr "" +"Kicke\n" +"{mac}!" + +#: voice.py:135 +#, python-brace-format +msgid "" +"Cool, we got {num}\n" +"new handshake{plural}!" +msgstr "" +"Cool, wir haben {num}\n" +"neue Handshake{plural}!" + +#: voice.py:139 +msgid "" +"Ops, something\n" +"went wrong ...\n" +"Rebooting ..." +msgstr "" +"Ops, da ist etwas\n" +"schief gelaufen ...\n" +"Starte neu ..." + +#: voice.py:143 +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "{num} Stationen gekicked\n" + +#: voice.py:144 +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "{num} Freunde gefunden\n" + +#: voice.py:145 +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "{num} Handshakes aufgez.\n" + +#: voice.py:147 +msgid "Met 1 peer" +msgstr "1 Peer getroffen." + +#: voice.py:149 +#, python-brace-format +msgid "Met {num} peers" +msgstr "{num} Peers getroffen" + +#: voice.py:154 +#, python-brace-format +msgid "" +"I've been pwning for {duration} and kicked {deauthed} clients! I've also met " +"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" +msgstr "" +"Ich war {duration} am Pwnen und habe {deauthed} Clients gekickt! Außerdem habe ich " +"{associated} neue Freunde getroffen und {handshakes} Handshakes gefressen! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/voice.pot b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/voice.pot new file mode 100644 index 0000000..671e44d --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/voice.pot @@ -0,0 +1,288 @@ +# pwnigotchi voice data +# Copyright (C) 2019 +# This file is distributed under the same license as the pwnagotchi package. +# FIRST AUTHOR <33197631+dadav@users.noreply.github.com>, 2019. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 0.0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-09-29 13:42+0200\n" +"PO-Revision-Date: 2019-09-29 14:00+0200\n" +"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n" +"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n" +"Language: english\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: voice.py:16 +msgid "ZzzzZZzzzzZzzz" +msgstr "" + +#: voice.py:21 +msgid "" +"Hi, I'm Pwnagotchi!\n" +"Starting ..." +msgstr "" + +#: voice.py:22 +msgid "" +"New day, new hunt,\n" +"new pwns!" +msgstr "" + +#: voice.py:23 +msgid "Hack the Planet!" +msgstr "" + +#: voice.py:28 +msgid "AI ready." +msgstr "" + +#: voice.py:29 +msgid "" +"The neural network\n" +"is ready." +msgstr "" + +#: voice.py:39 +#, python-brace-format +msgid "" +"Hey, channel {channel} is\n" +"free! Your AP will\n" +"say thanks." +msgstr "" + +#: voice.py:44 +msgid "I'm bored ..." +msgstr "" + +#: voice.py:45 +msgid "Let's go for a walk!" +msgstr "" + +#: voice.py:49 +msgid "" +"This is the best\n" +"day of my life!" +msgstr "" + +#: voice.py:53 +msgid "Shitty day :/" +msgstr "" + +#: voice.py:58 +msgid "I'm extremely bored ..." +msgstr "" + +#: voice.py:59 +msgid "I'm very sad ..." +msgstr "" + +#: voice.py:60 +msgid "I'm sad" +msgstr "" + +#: voice.py:66 +msgid "I'm living the life!" +msgstr "" + +#: voice.py:67 +msgid "I pwn therefore I am." +msgstr "" + +#: voice.py:68 +msgid "So many networks!!!" +msgstr "" + +#: voice.py:69 +msgid "" +"I'm having so much\n" +"fun!" +msgstr "" + +#: voice.py:70 +msgid "" +"My crime is that of\n" +"curiosity ..." +msgstr "" + +#: voice.py:75 +#, python-brace-format +msgid "" +"Hello\n" +"{name}!\n" +"Nice to meet you. {name}" +msgstr "" + +#: voice.py:76 +#, python-brace-format +msgid "" +"Unit\n" +"{name}\n" +"is nearby! {name}" +msgstr "" + +#: voice.py:81 +#, python-brace-format +msgid "" +"Uhm ...\n" +"goodbye\n" +"{name}" +msgstr "" + +#: voice.py:82 +#, python-brace-format +msgid "" +"{name}\n" +"is gone ..." +msgstr "" + +#: voice.py:87 +#, python-brace-format +msgid "" +"Whoops ...\n" +"{name}\n" +"is gone." +msgstr "" + +#: voice.py:88 +#, python-brace-format +msgid "" +"{name}\n" +"missed!" +msgstr "" + +#: voice.py:89 +msgid "Missed!" +msgstr "" + +#: voice.py:94 +msgid "" +"Nobody wants to\n" +"play with me ..." +msgstr "" + +#: voice.py:95 +msgid "I feel so alone ..." +msgstr "" + +#: voice.py:96 +msgid "Where's everybody?!" +msgstr "" + +#: voice.py:101 +#, python-brace-format +msgid "Napping for {secs}s ..." +msgstr "" + +#: voice.py:102 +msgid "Zzzzz" +msgstr "" + +#: voice.py:103 +#, python-brace-format +msgid "ZzzZzzz ({secs}s)" +msgstr "" + +#: voice.py:112 +#, python-brace-format +msgid "Waiting for {secs}s ..." +msgstr "" + +#: voice.py:114 +#, python-brace-format +msgid "Looking around ({secs}s)" +msgstr "" + +#: voice.py:121 +#, python-brace-format +msgid "" +"Hey\n" +"{what}\n" +"let's be friends!" +msgstr "" + +#: voice.py:122 +#, python-brace-format +msgid "" +"Associating to\n" +"{what}" +msgstr "" + +#: voice.py:123 +#, python-brace-format +msgid "" +"Yo\n" +"{what}!" +msgstr "" + +#: voice.py:128 +#, python-brace-format +msgid "" +"Just decided that\n" +"{mac}\n" +"needs no WiFi!" +msgstr "" + +#: voice.py:129 +#, python-brace-format +msgid "" +"Deauthenticating\n" +"{mac}" +msgstr "" + +#: voice.py:130 +#, python-brace-format +msgid "" +"Kickbanning\n" +"{mac}!" +msgstr "" + +#: voice.py:135 +#, python-brace-format +msgid "" +"Cool, we got {num}\n" +"new handshake{plural}!" +msgstr "" + +#: voice.py:139 +msgid "" +"Ops, something\n" +"went wrong ...\n" +"Rebooting ..." +msgstr "" + +#: voice.py:143 +#, python-brace-format +msgid "Kicked {num} stations\n" +msgstr "" + +#: voice.py:144 +#, python-brace-format +msgid "Made {num} new friends\n" +msgstr "" + +#: voice.py:145 +#, python-brace-format +msgid "Got {num} handshakes\n" +msgstr "" + +#: voice.py:147 +msgid "Met 1 peer" +msgstr "" + +#: voice.py:149 +#, python-brace-format +msgid "Met {num} peers" +msgstr "" + +#: voice.py:154 +#, python-brace-format +msgid "" +"I've been pwning for {duration} and kicked {deauthed} clients! I've also met " +"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi " +"#pwnlog #pwnlife #hacktheplanet #skynet" +msgstr "" diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py index 360bf01..d66008d 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py @@ -2,6 +2,7 @@ import os import hashlib import time import re +import os from datetime import datetime from pwnagotchi.mesh.peer import Peer @@ -129,7 +130,7 @@ class SessionParser(object): self.duration_human.append('%d seconds' % secs) self.duration_human = ', '.join(self.duration_human) - self.avg_reward /= self.epochs + self.avg_reward /= (self.epochs if self.epochs else 1) def __init__(self, path='/var/log/pwnagotchi.log'): self.path = path @@ -147,15 +148,17 @@ class SessionParser(object): 'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]') lines = [] - with FileReadBackwards(self.path, encoding="utf-8") as fp: - for line in fp: - line = line.strip() - if line != "" and line[0] != '[': - continue - lines.append(line) - if SessionParser.START_TOKEN in line: - break - lines.reverse() + + if os.path.exists(self.path): + with FileReadBackwards(self.path, encoding="utf-8") as fp: + for line in fp: + line = line.strip() + if line != "" and line[0] != '[': + continue + lines.append(line) + if SessionParser.START_TOKEN in line: + break + lines.reverse() self.last_session = lines self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest() diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py index 5a1d03d..4f9bb4a 100644 --- a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py @@ -3,6 +3,7 @@ from threading import Lock import io import core +import os import pwnagotchi from pwnagotchi.ui.view import WHITE, View @@ -19,7 +20,7 @@ class VideoHandler(BaseHTTPRequestHandler): - +