From 4fe0662ce1951432455cf5fc3abeffb3050dfe8d Mon Sep 17 00:00:00 2001 From: Jeroen Oudshoorn Date: Wed, 23 Aug 2023 14:06:28 +0200 Subject: [PATCH] Added couple of plugins, and some other stuff to work on more than 1 raspberry Signed-off-by: Jeroen Oudshoorn Signed-off-by: Jeroen Oudshoorn --- Makefile | 8 +- builder/pwnagotchi.json | 82 +--- builder/pwnagotchi.yml | 256 +++++++++--- pwnagotchi/_version.py | 2 +- pwnagotchi/defaults.toml | 18 + pwnagotchi/plugins/default/auto-update.py | 4 +- .../plugins/default/bluetoothsniffer.py | 182 +++++++++ pwnagotchi/plugins/default/exp.py | 336 +++++++++++++++ .../plugins/default/fix_brcmf_plugin.py | 384 ++++++++++++++++++ 9 files changed, 1131 insertions(+), 141 deletions(-) create mode 100644 pwnagotchi/plugins/default/bluetoothsniffer.py create mode 100644 pwnagotchi/plugins/default/exp.py create mode 100644 pwnagotchi/plugins/default/fix_brcmf_plugin.py diff --git a/Makefile b/Makefile index 442fc6e5..e3fe6796 100644 --- a/Makefile +++ b/Makefile @@ -20,11 +20,11 @@ install: image: cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json - sudo mv builder/output-pwnagotchi/image pwnagotchi-raspberrypi-os-lite-$(PWN_VERSION).img - sudo sha256sum pwnagotchi-raspberrypi-os-lite-$(PWN_VERSION).img > pwnagotchi-raspberrypi-os-lite-$(PWN_VERSION).sha256 - sudo zip pwnagotchi-raspberrypi-os-lite-$(PWN_VERSION).zip pwnagotchi-raspberrypi-os-lite-$(PWN_VERSION).sha256 pwnagotchi-raspberrypi-os-lite-$(PWN_VERSION).img + sudo mv builder/output-pwnagotchi/image pwnagotchi-rpi-os-lite-$(PWN_VERSION).img + sudo sha256sum pwnagotchi-rpi-os-lite-$(PWN_VERSION).img > pwnagotchi-rpi-os-lite-$(PWN_VERSION).sha256 + sudo zip pwnagotchi-rpi-os-lite-$(PWN_VERSION).zip pwnagotchi-rpi-os-lite-$(PWN_VERSION).sha256 pwnagotchi-rpi-os-lite-$(PWN_VERSION).img clean: rm -rf /tmp/packer-builder-arm-image - rm -f pwnagotchi-raspberrypi-os-lite-*.zip pwnagotchi-raspberrypi-os-lite-*.img pwnagotchi-raspberrypi-os-lite-*.sha256 + rm -f pwnagotchi-rpi-os-lite-*.zip pwnagotchi-rpi-os-lite-*.img pwnagotchi-rpi-os-lite-*.sha256 rm -rf builder/output-pwnagotchi builder/packer_cache diff --git a/builder/pwnagotchi.json b/builder/pwnagotchi.json index d463d094..675aa661 100644 --- a/builder/pwnagotchi.json +++ b/builder/pwnagotchi.json @@ -11,86 +11,24 @@ { "type": "shell", "inline": [ - "sed -i 's/^\\([^#]\\)/#\\1/g' /etc/ld.so.preload", + "mv /etc/ld.so.preload /etc/ld.so.preload.DISABLED", + "uname -a", "dpkg-architecture", - "apt-get -y update", - "apt-get install -y ansible" + "mkdir -p /usr/local/src/pwnagotchi" ] }, { "type": "file", - "source": "data/usr/bin/pwnlib", - "destination": "/usr/bin/pwnlib" - }, - { - "type": "file", - "source": "data/usr/bin/bettercap-launcher", - "destination": "/usr/bin/bettercap-launcher" - }, - { - "type": "file", - "source": "data/usr/bin/pwnagotchi-launcher", - "destination": "/usr/bin/pwnagotchi-launcher" - }, - { - "type": "file", - "source": "data/usr/bin/monstop", - "destination": "/usr/bin/monstop" - }, - { - "type": "file", - "source": "data/usr/bin/monstart", - "destination": "/usr/bin/monstart" - }, - { - "type": "file", - "source": "data/usr/bin/hdmion", - "destination": "/usr/bin/hdmion" - }, - { - "type": "file", - "source": "data/usr/bin/hdmioff", - "destination": "/usr/bin/hdmioff" - }, - { - "type": "file", - "source": "data/etc/network/interfaces.d/lo-cfg", - "destination": "/etc/network/interfaces.d/lo-cfg" - }, - { - "type": "file", - "source": "data/etc/network/interfaces.d/wlan0-cfg", - "destination": "/etc/network/interfaces.d/wlan0-cfg" - }, - { - "type": "file", - "source": "data/etc/network/interfaces.d/usb0-cfg", - "destination": "/etc/network/interfaces.d/usb0-cfg" - }, - { - "type": "file", - "source": "data/etc/network/interfaces.d/eth0-cfg", - "destination": "/etc/network/interfaces.d/eth0-cfg" - }, - { - "type": "file", - "source": "data/etc/systemd/system/pwngrid-peer.service", - "destination": "/etc/systemd/system/pwngrid-peer.service" - }, - { - "type": "file", - "source": "data/etc/systemd/system/pwnagotchi.service", - "destination": "/etc/systemd/system/pwnagotchi.service" - }, - { - "type": "file", - "source": "data/etc/systemd/system/bettercap.service", - "destination": "/etc/systemd/system/bettercap.service" + "sources": [ + "../dist/pwnagotchi-{{user `pwn_version`}}.tar.gz" + ], + "destination": "/usr/local/src/pwnagotchi/" }, { "type": "shell", "inline": [ - "chmod +x /usr/bin/*" + "apt-get -y --allow-releaseinfo-change update", + "apt-get install -y --no-install-recommends ansible" ] }, { @@ -102,7 +40,7 @@ { "type": "shell", "inline": [ - "sed -i 's/^#\\(.+\\)/\\1/g' /etc/ld.so.preload" + "mv /etc/ld.so.preload.DISABLED /etc/ld.so.preload" ] } ] diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml index 781b98f8..22b15282 100644 --- a/builder/pwnagotchi.yml +++ b/builder/pwnagotchi.yml @@ -36,17 +36,16 @@ - dnsmasq.service packages: bettercap: - url: "https://github.com/bettercap/bettercap/releases/download/v2.31.1/bettercap_linux_aarch64_v2.31.1.zip" + # We will install bettercap from source + # url: "https://github.com/jayofelony/bettercap/releases/download/2.32.1/bettercap-2.32.1.zip" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip" pwngrid: url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.3/pwngrid_linux_aarch64_v1.10.3.zip" apt: hold: - - firmware-atheros - - firmware-brcm80211 - - firmware-libertas - - firmware-misc-nonfree - - firmware-realtek + - libpcap-dev + - libpcap0.8 + - libpcap0.8-dev remove: - raspberrypi-net-mods - dhcpcd5 @@ -58,59 +57,96 @@ - libraspberrypi-doc - libraspberrypi-bin install: + - bluez + - raspberrypi-kernel-headers + - git + - libgmp3-dev + - gawk + - qpdf + - bison + - flex + - make + - autoconf + - libtool + - texinfo + - gcc-arm-none-eabi + - wl + - libfl-dev + - g++ + - xxd + - aircrack-ng + - time - rsync - vim + - wget - screen - - golang - - git - build-essential + - dkms - python3-pip - - python3-mpi4py - python3-smbus - unzip - - gawk - libopenmpi-dev - libatlas-base-dev - - libjasper-dev - - libqtgui4 - - libqt4-test + - libelf-dev - libopenjp2-7 - libtiff5 - tcpdump - lsof - - libilmbase23 - - libopenexr23 - libgstreamer1.0-0 - libavcodec58 - libavformat58 - libswscale5 - - libpcap-dev - libusb-1.0-0-dev - libnetfilter-queue-dev - libopenmpi3 - dphys-swapfile - - kalipi-kernel - - kalipi-bootloader - - kalipi-re4son-firmware - - kalipi-kernel-headers - - libraspberrypi0 - - libraspberrypi-dev - - libraspberrypi-doc - - libraspberrypi-bin + - libdbus-1-dev + - libdbus-glib-1-dev + - liblapack-dev + - libhdf5-dev + - libc-ares-dev + - libeigen3-dev - fonts-dejavu - fonts-dejavu-core - fonts-dejavu-extra - python3-pil - python3-smbus - libfuse-dev + - libatlas-base-dev + - libopenblas-dev + - libblas-dev - bc + - libgl1-mesa-glx + - libncursesw5-dev + - libssl-dev + - libsqlite3-dev + - tk-dev + - libgdbm-dev + - libc6-dev + - libbz2-dev + - libffi-dev + - zlib1g-dev - fonts-freefont-ttf - fbi - - fonts-ipaexfont-gothic - - cryptsetup - - dnsmasq + - python3-flask + - python3-flask-cors + - python3-flaskext.wtf + - build-essential + - libpcap-dev + - libusb-1.0-0-dev + - libnetfilter-queue-dev tasks: + - name: download old libpcap packages + shell: "wget http://ports.ubuntu.com/pool/main/libp/libpcap/libpcap0.8_1.9.1-3_arm64.deb && wget http://ports.ubuntu.com/pool/main/libp/libpcap/libpcap0.8-dev_1.9.1-3_arm64.deb && wget http://ports.ubuntu.com/pool/main/libp/libpcap/libpcap-dev_1.9.1-3_arm64.deb" + dest: /usr/local/src/ + + - name: install old libpcap packages + shell: dpkg -i /usr/local/src/libpcap* + args: + executable: /bin/bash + register: libpcap + - name: change hostname hostname: name: "{{pwnagotchi.hostname}}" @@ -132,37 +168,18 @@ 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 - state: present - - - name: Add re4son-kernel repository - apt_repository: - repo: deb http://http.re4son-kernel.com/re4son/ kali-pi main - state: present - - - name: create /etc/apt/preferences.d/kali.pref - copy: - dest: /etc/apt/preferences.d/kali.pref - force: yes - content: | - # ensure kali packages that are installed take precedence - Package: * - Pin: release n=kali-pi - Pin-Priority: 999 - - name: add firmware packages to hold dpkg_selections: name: "{{ item }}" selection: hold with_items: "{{ packages.apt.hold }}" + when: libpcap.changed - name: update apt package cache apt: update_cache: yes - - name: remove unecessary apt packages + - name: remove unnecessary apt packages apt: name: "{{ packages.apt.remove }}" state: absent @@ -180,7 +197,7 @@ - name: configure dphys-swapfile file: path: /etc/dphys-swapfile - content: "CONF_SWAPSIZE=1024" + content: "CONF_SWAPSIZE=2048" - name: clone papirus repository git: @@ -206,6 +223,12 @@ PANEL_VERSION: 'V231_G2' when: gratisgit.changed + - name: Creates custom plugin directory + file: + path: /usr/local/share/pwnagotchi/custom-plugins/ + state: directory + when: gratisgit.changed + - name: configure papirus display size lineinfile: dest: /etc/default/epd-fuse @@ -228,7 +251,7 @@ - name: clone pwnagotchi repository git: - repo: https://github.com/evilsocket/pwnagotchi.git + repo: https://github.com/jayofelony/pwnagotchi.git dest: /usr/local/src/pwnagotchi register: pwnagotchigit @@ -256,18 +279,6 @@ 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.org/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.org/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') }}" @@ -281,6 +292,34 @@ remote_src: yes mode: 0755 + # Install go-1.20.6 + - name: Install go-1.21 + unarchive: + src: https://go.dev/dl/go1.21.0.linux-arm64.tar.gz + dest: /usr/local + remote_src: yes + register: golang + + - name: Update .bashrc for go-1.21 + blockinfile: + dest: /home/pi/.bashrc + state: present + block: | + export GOPATH=$HOME/go + export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin + insertafter: EOF + when: golang.changed + + - name: Install bettercap v2.32.1 + shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go env -w GO111MODULE=off && go get github.com/jayofelony/bettercap && cd $GOPATH/src/github.com/jayofelony/bettercap && make build && make install" + args: + executable: /bin/bash + register: bettercap + + - name: Link bettercap v2.32.1 + command: ln -s /usr/local/bin/bettercap /usr/bin/bettercap + when: bettercap.changed + - name: download and install bettercap unarchive: src: "{{ packages.bettercap.url }}" @@ -297,6 +336,99 @@ dest: /tmp/caplets register: capletsgit +# Install nexmon to fix wireless scanning (takes 2.5G of space) + - name: clone nexmon repository + git: + repo: https://github.com/jayofelony/nexmon.git + dest: /usr/local/src/nexmon + register: nexmongit + + - name: make firmware + shell: "source ./setup_env.sh && make" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + +# Raspberry Pi Zero 2w (chipset 43436b0) + + - name: make firmware patch (bcm43436b0) + shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + + - name: backup original firmware + shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make backup-firmware" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + + - name: install new firmware + shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make install-firmware" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + +# Raspberry Pi zero 2w (chipset 43430a1) + + - name: make firmware patch (bcm43430a1) + shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + + - name: backup original firmware + shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make backup-firmware" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + + - name: install new firmware + shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make install-firmware" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + +# Raspberry Pi 4 + + - name: make firmware patch (bcm43455c0) + shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + + - name: backup original firmware + shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make backup-firmware" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + + - name: install new firmware + shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make install-firmware" + args: + executable: /bin/bash + chdir: /usr/local/src/nexmon/ + + - name: choose kernel + shell: "uname -r" + register: kernel + + - name: copy modified driver + copy: + src: /usr/local/src/nexmon/patches/driver/brcmfmac_6.1.y-nexmon/brcmfmac.ko + dest: "/lib/modules/{{ kernel.stdout }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko" + + - name: ensure depmod runs on reboot to load modified driver (brcmfmac) + lineinfile: + dest: /etc/rc.local + line: "/sbin/depmod -a" + + # To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation + - name: Delete nexmon content & directory + file: + state: absent + path: /usr/local/src/nexmon/ + - name: install bettercap caplets make: chdir: /tmp/caplets diff --git a/pwnagotchi/_version.py b/pwnagotchi/_version.py index 3b3dacb9..d980f276 100644 --- a/pwnagotchi/_version.py +++ b/pwnagotchi/_version.py @@ -1 +1 @@ -__version__ = '2.0' +__version__ = '2.1' diff --git a/pwnagotchi/defaults.toml b/pwnagotchi/defaults.toml index dde2ff43..c9512d34 100644 --- a/pwnagotchi/defaults.toml +++ b/pwnagotchi/defaults.toml @@ -5,6 +5,10 @@ main.custom_plugins = "" main.custom_plugin_repos = [ "https://github.com/evilsocket/pwnagotchi-plugins-contrib/archive/master.zip" ] +main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/" + +main.fix_brcmf_plugin.enabled = true + main.iface = "wlan0mon" main.mon_start_cmd = "/usr/bin/monstart" main.mon_stop_cmd = "/usr/bin/monstop" @@ -35,6 +39,20 @@ main.plugins.gps.enabled = false main.plugins.gps.speed = 19200 main.plugins.gps.device = "/dev/ttyUSB0" +main.plugins.exp.enabled = true +main.plugins.exp.lvl_x_coord = 0 +main.plugins.exp.lvl_y_coord = 81 +main.plugins.exp.exp_x_coord = 38 +main.plugins.exp.exp_y_coord = 81 +main.plugins.exp.bar_symbols_count = 12 + +main.plugins.bluetoothsniffer.enabled = false +main.plugins.bluetoothsniffer.timer = 45 # On how may seconds to scan for bluetooth devices +main.plugins.bluetoothsniffer.devices_file = "/root/handshakes/bluetooth_devices.json" # Path to the JSON file with bluetooth devices +main.plugins.bluetoothsniffer.count_interval = 86400 # On how may seconds to update count bluetooth devices +main.plugins.bluetoothsniffer.bt_x_coord = 160 +main.plugins.bluetoothsniffer.bt_y_coord = 66 + main.plugins.webgpsmap.enabled = false main.plugins.onlinehashcrack.enabled = false diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py index 17231617..3b2f9a4f 100644 --- a/pwnagotchi/plugins/default/auto-update.py +++ b/pwnagotchi/plugins/default/auto-update.py @@ -183,9 +183,9 @@ class AutoUpdate(plugins.Plugin): to_install = [] to_check = [ - ('bettercap/bettercap', parse_version('bettercap -version'), True, 'bettercap'), + ('jayofelony/bettercap', parse_version('bettercap -version'), True, 'bettercap'), ('evilsocket/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'), - ('evilsocket/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi') + ('jayofelony/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi') ] for repo, local_version, is_native, svc_name in to_check: diff --git a/pwnagotchi/plugins/default/bluetoothsniffer.py b/pwnagotchi/plugins/default/bluetoothsniffer.py new file mode 100644 index 00000000..b78c93bf --- /dev/null +++ b/pwnagotchi/plugins/default/bluetoothsniffer.py @@ -0,0 +1,182 @@ +import logging +import os +import subprocess +import json +import re +import time + +import pwnagotchi +import pwnagotchi.agent +import pwnagotchi.plugins as plugins +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.components import LabeledValue +from pwnagotchi.ui.view import BLACK +from datetime import datetime + +class BluetoothSniffer(plugins.Plugin): + __author__ = 'diytechtinker' + __version__ = '0.1.3' + __license__ = 'GPL3' + __description__ = 'A plugin that sniffs Bluetooth devices and saves their MAC addresses, name and counts to a JSON file' + + def __init__(self): + # Defining the instance variables + self.options = { + 'timer': 45, + 'devices_file': '/root/handshakes/bluetooth_devices.json', + 'count_interval': 86400, + 'bt_x_coord': 160, + 'bt_y_coord': 66 + } + self.data = {} + self.last_scan_time = 0 + + + def on_loaded(self): + logging.info("[BtS] bluetoothsniffer plugin loaded.") + logging.info("[BtS] Bluetooth devices file location: %s", self.options['devices_file']) + # Creating the device file path if it does not exist + if not os.path.exists(os.path.dirname(self.options['devices_file'])): + os.makedirs(os.path.dirname(self.options['devices_file'])) + + # Creating the device file if it does not exist + if not os.path.exists(self.options['devices_file']): + with open(self.options['devices_file'], 'w') as f: + json.dump({}, f) + + # Loading the data from the device file + with open(self.options['devices_file'], 'r') as f: + self.data = json.load(f) + + + def on_ui_setup(self, ui): + ui.add_element('BtS', LabeledValue(color=BLACK, + label='BT SNFD', + value=" ", + position=(int(self.options["bt_x_coord"]), + int(self.options["bt_y_coord"])), + label_font=fonts.Small, + text_font=fonts.Small)) + + def on_unload(self, ui): + with ui._lock: + ui.remove_element('BtS') + + + def on_ui_update(self, ui): + current_time = time.time() + # Checking the time elapsed since last scan + if current_time - self.last_scan_time >= self.options['timer']: + self.last_scan_time = current_time + #logging.info("[BtS] Bluetooth sniffed: %s", str(self.bt_sniff_info())) + ui.set('BtS', str(self.bt_sniff_info())) + self.scan() + + + # Method for scanning the nearby bluetooth devices + def scan(self): + logging.info("[BtS] Scanning for bluetooths...") + current_time = time.time() + changed = False + + # Running the system command hcitool to scan nearby bluetooth devices + cmd_inq = "hcitool inq --flush" + try: + inq_output = subprocess.check_output(cmd_inq.split()) + except subprocess.CalledProcessError as e: + logging.error("[BtS] Error running command: %s", e) + + for line in inq_output.splitlines()[1:]: + fields = line.split() + mac_address = fields[0].decode() + for i in range(len(fields)): + if fields[i].decode() == "class:" and i+1 < len(fields): + device_class = fields[i+1].decode() + logging.info("[BtS] Found bluetooth %s", mac_address) + + # Update the count, first_seen, and last_seen time of the device + if mac_address in self.data and len(self.data) > 0: + if 'Unknown' == self.data[mac_address]['name']: + name = self.get_device_name(mac_address) + self.data[mac_address]['name'] = name + self.data[mac_address]['new_info'] = 2 + logging.info("[BtS] Updated bluetooth name: %s", name) + changed = True + + if 'Unknown' == self.data[mac_address]['manufacturer']: + manufacturer = self.get_device_manufacturer(mac_address) + self.data[mac_address]['manufacturer'] = manufacturer + self.data[mac_address]['new_info'] = 2 + logging.info("[BtS] Updated bluetooth manufacturer: %s", manufacturer) + changed = True + + if device_class != self.data[mac_address]['class']: + self.data[mac_address]['class'] = device_class + self.data[mac_address]['new_info'] = 2 + logging.info("[BtS] Updated bluetooth class: %s", device_class) + changed = True + + last_seen_time = int(datetime.strptime(self.data[mac_address]['last_seen'], '%H:%M:%S %d-%m-%Y').timestamp()) + if current_time - last_seen_time >= self.options['count_interval']: + self.data[mac_address]['count'] += 1 + self.data[mac_address]['last_seen'] = time.strftime('%H:%M:%S %d-%m-%Y', time.localtime(current_time)) + self.data[mac_address]['new_info'] = 2 + logging.info("[BtS] Updated bluetooth count.") + changed = True + else: + name = self.get_device_name(mac_address) + manufacturer = self.get_device_manufacturer(mac_address) + self.data[mac_address] = {'name': name, 'count': 1, 'class': device_class, 'manufacturer': manufacturer, 'first_seen': time.strftime('%H:%M:%S %d-%m-%Y', time.localtime(current_time)), 'last_seen': time.strftime('%H:%M:%S %d-%m-%Y', time.localtime(current_time)), 'new_info': True} + logging.info("[BtS] Added new bluetooth device %s with MAC: %s", name, mac_address) + changed = True + + # Save the updated devices to the JSON file + if changed: + with open(self.options['devices_file'], 'w') as f: + logging.info("[BtS] Saving bluetooths %s into json.", name) + json.dump(self.data, f) + display.set('status', 'Bluetooth sniffed and stored!') + display.update(force=True) + + # Method to get the device name + def get_device_name(self, mac_address): + logging.info("[BtS] Trying to get name for %s", mac_address) + name = 'Unknown' + hcitool_process = subprocess.Popen(["hcitool", "name", mac_address], stdout=subprocess.PIPE) + output, error = hcitool_process.communicate() + if output.decode().strip() != '': + name = output.decode().strip() + logging.info("[BtS] Got name %s for %s", name, mac_address) + return name + + # Method to get the device manufacturer + def get_device_manufacturer(self, mac_address): + manufacturer = 'Unknown' + cmd_info = f"hcitool info {mac_address} | grep 'Manufacturer:' | cut -d ' ' -f 2-" + try: + logging.info("[BtS] Trying to get manufacturer for %s", mac_address) + start_time = time.time() + process = subprocess.Popen(cmd_info, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + while process.poll() is None: + time.sleep(0.1) + if time.time() - start_time > 7: + logging.info("[BtS] Timeout while trying to get manufacturer for %s", mac_address) + process.kill() + return manufacturer + output, error = process.communicate(timeout=1) + if output.decode().strip() != '': + manufacturer = output.decode().strip() + logging.info("[BtS] Got manufacturer %s for %s", manufacturer, mac_address) + except Exception as e: + logging.info("[BtS] Error while trying to get manufacturer for %s: %s", mac_address, str(e)) + return manufacturer + + def bt_sniff_info(self): + num_devices = len(self.data) + if num_devices > 0: + num_unknown = sum(1 for device in self.data.values() if device['name'] == 'Unknown' or device['manufacturer'] == 'Unknown') + num_known = num_devices - num_unknown + return_text = "%s|%s" % (num_devices, num_known) + else: + return_text = "0|0" + return return_text \ No newline at end of file diff --git a/pwnagotchi/plugins/default/exp.py b/pwnagotchi/plugins/default/exp.py new file mode 100644 index 00000000..11812340 --- /dev/null +++ b/pwnagotchi/plugins/default/exp.py @@ -0,0 +1,336 @@ +import logging +import os +import random +import json + +import pwnagotchi +import pwnagotchi.agent +import pwnagotchi.plugins as plugins +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.components import LabeledValue +from pwnagotchi.ui.view import BLACK + +# Static Variables +MULTIPLIER_ASSOCIATION = 1 +MULTIPLIER_DEAUTH = 2 +MULTIPLIER_HANDSHAKE = 3 +MULTIPLIER_AI_BEST_REWARD = 5 +TAG = "[EXP Plugin]" +FACE_LEVELUP = '(≧◡◡≦)' +BAR_ERROR = "| error |" +FILE_SAVE = "exp_stats" +FILE_SAVE_LEGACY = "exp" +JSON_KEY_LEVEL = "level" +JSON_KEY_EXP = "exp" +JSON_KEY_EXP_TOT = "exp_tot" + + +class EXP(plugins.Plugin): + __author__ = 'GaelicThunder' + __version__ = '1.0.5' + __license__ = 'GPL3' + __description__ = 'Get exp every time a handshake get captured.' + + # Attention number masking + def LogInfo(self, text): + logging.info(TAG + " " + text) + + # Attention number masking + def LogDebug(self, text): + logging.debug(TAG + " " + text) + + def __init__(self): + self.percent = 0 + self.calculateInitialXP = False + self.exp = 0 + self.lv = 1 + self.exp_tot = 0 + # Sets the file type I recommend json + self.save_file_mode = self.save_file_modes("json") + self.save_file = self.getSaveFileName(self.save_file_mode) + # Migrate from old save system + self.migrateLegacySave() + + # Create save file + if not os.path.exists(self.save_file): + self.Save(self.save_file, self.save_file_mode) + else: + try: + # Try loading + self.Load(self.save_file, self.save_file_mode) + except: + # Likely throws an exception if json file is corrupted, so we need to calculate from scratch + self.calculateInitialXP = True + + # No previous data, try get it + if self.lv == 1 and self.exp == 0: + self.calculateInitialXP = True + if self.exp_tot == 0: + self.LogInfo("Need to calculate Total Exp") + self.exp_tot = self.calcActualSum(self.lv, self.exp) + self.Save(self.save_file, self.save_file_mode) + + self.expneeded = self.calcExpNeeded(self.lv) + + def on_loaded(self): + # logging.info("Exp plugin loaded for %s" % self.options['device']) + self.LogInfo("Plugin Loaded") + + def save_file_modes(self, argument): + switcher = { + "txt": 0, + "json": 1, + } + return switcher.get(argument, 0) + + def Save(self, file, save_file_mode): + self.LogDebug('Saving Exp') + if save_file_mode == 0: + self.saveToTxtFile(file) + if save_file_mode == 1: + self.saveToJsonFile(file) + + def saveToTxtFile(self, file): + outfile = open(file, 'w') + print(self.exp, file=outfile) + print(self.lv, file=outfile) + print(self.exp_tot, file=outfile) + outfile.close() + + def loadFromTxtFile(self, file): + if os.path.exists(file): + outfile = open(file, 'r+') + lines = outfile.readlines() + linecounter = 1 + for line in lines: + if linecounter == 1: + self.exp = int(line) + elif linecounter == 2: + self.lv == int(line) + elif linecounter == 3: + self.exp_tot == int(line) + linecounter += 1 + outfile.close() + + def saveToJsonFile(self, file): + data = { + JSON_KEY_LEVEL: self.lv, + JSON_KEY_EXP: self.exp, + JSON_KEY_EXP_TOT: self.exp_tot + } + + with open(file, 'w') as f: + f.write(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))) + + def loadFromJsonFile(self, file): + # Tot exp is introduced with json, no check needed + data = {} + with open(file, 'r') as f: + data = json.loads(f.read()) + + if bool(data): + self.lv = data[JSON_KEY_LEVEL] + self.exp = data[JSON_KEY_EXP] + self.exp_tot = data[JSON_KEY_EXP_TOT] + else: + self.LogInfo("Empty json") + + # TODO: one day change save file mode to file date + def Load(self, file, save_file_mode): + self.LogDebug('Loading Exp') + if save_file_mode == 0: + self.loadFromTxtFile(file) + if save_file_mode == 1: + self.loadFromJsonFile(file) + + def getSaveFileName(self, save_file_mode): + file = os.path.dirname(os.path.realpath(__file__)) + file = file + "/" + FILE_SAVE + if save_file_mode == 0: + file = file + ".txt" + elif save_file_mode == 1: + file = file + ".json" + else: + # See switcher + file = file + ".txt" + return file + + def migrateLegacySave(self): + legacyFile = os.path.dirname(os.path.realpath(__file__)) + legacyFile = legacyFile + "/" + FILE_SAVE_LEGACY + ".txt" + if os.path.exists(legacyFile): + self.loadFromTxtFile(legacyFile) + self.LogInfo("Migrating Legacy Save...") + self.Save(self.save_file, self.save_file_mode) + os.remove(legacyFile) + + def barString(self, symbols_count, p): + if p > 100: + return BAR_ERROR + length = symbols_count - 2 + bar_char = '▥' + blank_char = ' ' + bar_length = int(round((length / 100) * p)) + blank_length = length - bar_length + res = '|' + bar_char * bar_length + blank_char * blank_length + '|' + return res + + def on_ui_setup(self, ui): + ui.add_element('Lv', LabeledValue(color=BLACK, label='Lv', value=0, + position=(int(self.options["lvl_x_coord"]), + int(self.options["lvl_y_coord"])), + label_font=fonts.Bold, text_font=fonts.Medium)) + ui.add_element('Exp', LabeledValue(color=BLACK, label='Exp', value=0, + position=(int(self.options["exp_x_coord"]), + int(self.options["exp_y_coord"])), + label_font=fonts.Bold, text_font=fonts.Medium)) + + def on_ui_update(self, ui): + self.expneeded = self.calcExpNeeded(self.lv) + self.percent = int((self.exp / self.expneeded) * 100) + symbols_count = int(self.options["bar_symbols_count"]) + bar = self.barString(symbols_count, self.percent) + ui.set('Lv', "%d" % self.lv) + ui.set('Exp', "%s" % bar) + + def calcExpNeeded(self, level): + # If the pwnagotchi is lvl <1 it causes the keys to be deleted + if level == 1: + return 5 + return int((level ** 3) / 2) + + def exp_check(self, agent): + self.LogDebug("EXP CHECK") + if self.exp >= self.expneeded: + self.exp = 1 + self.lv = self.lv + 1 + self.expneeded = self.calcExpNeeded(self.lv) + self.displayLevelUp(agent) + + def parseSessionStats(self): + sum = 0 + dir = pwnagotchi.config['main']['plugins']['session-stats']['save_directory'] + # TODO: remove + self.LogInfo("Session-Stats dir: " + dir) + for filename in os.listdir(dir): + self.LogInfo("Parsing " + filename + "...") + if filename.endswith(".json") & filename.startswith("stats"): + try: + sum += self.parseSessionStatsFile(os.path.join(dir, filename)) + except: + self.LogInfo("ERROR parsing File: " + filename) + + return sum + + def parseSessionStatsFile(self, path): + sum = 0 + deauths = 0 + handshakes = 0 + associations = 0 + with open(path) as json_file: + data = json.load(json_file) + for entry in data["data"]: + deauths += data["data"][entry]["num_deauths"] + handshakes += data["data"][entry]["num_handshakes"] + associations += data["data"][entry]["num_associations"] + + sum += deauths * MULTIPLIER_DEAUTH + sum += handshakes * MULTIPLIER_HANDSHAKE + sum += associations * MULTIPLIER_ASSOCIATION + + return sum + + # If initial sum is 0, we try to parse it + def calculateInitialSum(self, agent): + sessionStatsActive = False + sum = 0 + # Check if session stats is loaded + for plugin in pwnagotchi.plugins.loaded: + if plugin == "session-stats": + sessionStatsActive = True + break + + if sessionStatsActive: + try: + self.LogInfo("parsing session-stats") + sum = self.parseSessionStats() + except: + self.LogInfo("Error parsing session-stats") + + + else: + self.LogInfo("parsing last session") + sum = self.lastSessionPoints(agent) + + self.LogInfo(str(sum) + " Points calculated") + return sum + + # Get Last Sessions Points + def lastSessionPoints(self, agent): + summary = 0 + summary += agent.LastSession.handshakes * MULTIPLIER_HANDSHAKE + summary += agent.LastSession.associated * MULTIPLIER_ASSOCIATION + summary += agent.LastSession.deauthed * MULTIPLIER_DEAUTH + return summary + + # Helper function to calculate multiple Levels from a sum of EXPs + def calcLevelFromSum(self, sum, agent): + sum1 = sum + level = 1 + while sum1 > self.calcExpNeeded(level): + sum1 -= self.calcExpNeeded(level) + level += 1 + self.lv = level + self.exp = sum1 + self.expneeded = self.calcExpNeeded(level) - sum1 + if level > 1: + # get Excited ;-) + self.displayLevelUp(agent) + + def calcActualSum(self, level, exp): + lvlCounter = 1 + sum = exp + # I know it wouldn't work if you change the lvl algorithm + while lvlCounter < level: + sum += self.calcExpNeeded(lvlCounter) + lvlCounter += 1 + return sum + + def displayLevelUp(self, agent): + view = agent.view() + view.set('face', FACE_LEVELUP) + view.set('status', "Level Up!") + view.update(force=True) + + # Event Handling + def on_association(self, agent, access_point): + self.exp += MULTIPLIER_ASSOCIATION + self.exp_tot += MULTIPLIER_ASSOCIATION + self.exp_check(agent) + self.Save(self.save_file, self.save_file_mode) + + def on_deauthentication(self, agent, access_point, client_station): + self.exp += MULTIPLIER_DEAUTH + self.exp_tot += MULTIPLIER_DEAUTH + self.exp_check(agent) + self.Save(self.save_file, self.save_file_mode) + + def on_handshake(self, agent, filename, access_point, client_station): + self.exp += MULTIPLIER_HANDSHAKE + self.exp_tot += MULTIPLIER_HANDSHAKE + self.exp_check(agent) + self.Save(self.save_file, self.save_file_mode) + + def on_ai_best_reward(self, agent, reward): + self.exp += MULTIPLIER_AI_BEST_REWARD + self.exp_tot += MULTIPLIER_AI_BEST_REWARD + self.exp_check(agent) + self.Save(self.save_file, self.save_file_mode) + + def on_ready(self, agent): + if self.calculateInitialXP: + self.LogInfo("Initial point calculation") + sum = self.calculateInitialSum(agent) + self.exp_tot = sum + self.calcLevelFromSum(sum, agent) + self.Save(self.save_file, self.save_file_mode) diff --git a/pwnagotchi/plugins/default/fix_brcmf_plugin.py b/pwnagotchi/plugins/default/fix_brcmf_plugin.py new file mode 100644 index 00000000..99fd461d --- /dev/null +++ b/pwnagotchi/plugins/default/fix_brcmf_plugin.py @@ -0,0 +1,384 @@ +import logging +import re +import subprocess +import time +import random +from io import TextIOWrapper +from pwnagotchi import plugins + +import pwnagotchi.ui.faces as faces +from pwnagotchi.bettercap import Client + +from pwnagotchi.ui.components import Text, LabeledValue +from pwnagotchi.ui.view import BLACK +import pwnagotchi.ui.fonts as fonts + + +class Fix_BRCMF(plugins.Plugin): + __author__ = 'xBits' + __version__ = '0.1.1' + __license__ = 'GPL3' + __description__ = 'Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG.' + __name__ = 'Fix_BRCMF' + __help__ = """ + Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG. + """ + __dependencies__ = { + 'pip': ['scapy'] + } + __defaults__ = { + 'enabled': False, + } + + def __init__(self): + self.options = dict() + self.pattern = re.compile(r'brcmf_cfg80211_nexmon_set_channel.*?Set Channel failed') + self.pattern2 = re.compile(r'wifi error while hopping to channel') + self.isReloadingMon = False + self.connection = None + self.LASTTRY = 0 + self._status = "--" + self._count = 0 + + def on_loaded(self): + """ + Gets called when the plugin gets loaded + """ + self.pattern = re.compile(r'brcmf_cfg80211_nexmon_set_channel.*?Set Channel failed') + self._status = "ld" + logging.info("[FixBRCMF] plugin loaded.") + + def on_ready(self, agent): + try: + cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True) + logging.info("[FixBRCMF ip link show wlan0mon]: %s" % repr(cmd_output)) + if ",UP," in str(cmd_output): + logging.info("wlan0mon is up.") + self._status = "up" + last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'], + stdout=subprocess.PIPE).stdout))[-10:]) + if len(self.pattern.findall(last_lines)) >= 3: + self._status = "XX" + if hasattr(agent, 'view'): + display = agent.view() + display.set('status', 'Blind-Bug detected. Restarting.') + display.update(force=True) + logging.info('[FixBRCMF] Blind-Bug detected. Restarting.\n%s' % repr(last_lines)) + try: + self._tryTurningItOffAndOnAgain(agent) + except Exception as err: + logging.warning("[FixBRCMF turnOffAndfOn] %s" % repr(err)) + else: + logging.info("[FixBRCMF] Logs look good, too:\n%s" % last_lines) + self._status = "" + + except Exception as err: + logging.error("[FixBRCMF ip link show wlan0mon]: %s" % repr(err)) + try: + self._status = "xx" + self._tryTurningItOffAndOnAgain(agent) + except Exception as err: + logging.error("[FixBRCMF OffNOn]: %s" % repr(err)) + + # bettercap sys_log event + # search syslog events for the brcmf channel fail, and reset when it shows up + # apparently this only gets messages from bettercap going to syslog, not from syslog + def on_bcap_sys_log(self, agent, event): + if re.search('wifi error while hopping to channel', event['data']['Message']): + logging.info("[FixBRCMF]SYSLOG MATCH: %s" % event['data']['Message']) + logging.info("[FixBRCMF]**** restarting wifi.recon") + try: + result = agent.run("wifi.recon off; wifi.recon on") + if result["success"]: + logging.info("[FixBRCMF] wifi.recon flip: success!") + if hasattr(agent, 'view'): + display = agent.view() + if display: display.update(force=True, new_data={"status": "Wifi recon flipped!", + "face": faces.COOL}) + else: + print("Wifi recon flipped") + else: + logging.warning("[FixBRCMF] wifi.recon flip: FAILED: %s" % repr(result)) + except Exception as err: + logging.error("[FixBRCMF]SYSLOG wifi.recon flip fail: %s" % err) + + def on_epoch(self, agent, epoch, epoch_data): + # don't check if we ran a reset recently + logging.debug("[FixBRCMF]**** epoch") + if time.time() - self.LASTTRY > 180: + # get last 10 lines + display = None + last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'], + stdout=subprocess.PIPE).stdout))[-10:]) + other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'], + stdout=subprocess.PIPE).stdout))[-10:]) + logging.debug("[FixBRCMF]**** checking") + if len(self.pattern.findall(last_lines)) >= 3: + logging.info("[FixBRCMF]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines) + if hasattr(agent, 'view'): + display = agent.view() + display.set('status', 'Blind-Bug detected. Restarting.') + display.update(force=True) + logging.info('[FixBRCMF] Blind-Bug detected. Restarting.') + try: + self._tryTurningItOffAndOnAgain(agent) + except Exception as err: + logging.warning("[FixBRCMF] TTOAOA: %s" % repr(err)) + elif len(self.pattern2.findall(other_last_lines)) >= 5: + if hasattr(agent, 'view'): + display = agent.view() + display.set('status', 'Wifi channel stuck. Restarting recon.') + display.update(force=True) + logging.info('[FixBRCMF] Wifi channel stuck. Restarting recon.') + + try: + result = agent.run("wifi.recon off; wifi.recon on") + if result["success"]: + logging.info("[FixBRCMF] wifi.recon flip: success!") + if display: + display.update(force=True, new_data={"status": "Wifi recon flipped!", + "brcmfmac_status": self._status, + "face": faces.COOL}) + else: + print("Wifi recon flipped\nthat was easy!") + else: + logging.warning("[FixBRCMF] wifi.recon flip: FAILED: %s" % repr(result)) + + except Exception as err: + logging.error("[FixBRCMF wifi.recon flip] %s" % repr(err)) + + else: + print("logs look good") + + def logPrintView(self, level, message, ui=None, displayData=None, force=True): + try: + if level is "error": + logging.error(message) + elif level is "warning": + logging.warning(message) + elif level is "debug": + logging.debug(message) + else: + logging.info(message) + + if ui: + ui.update(force=force, new_data=displayData) + elif displayData and "status" in displayData: + print(displayData["status"]) + else: + print("[%s] %s" % (level, message)) + except Exception as err: + logging.error("[logPrintView] ERROR %s" % repr(err)) + + def _tryTurningItOffAndOnAgain(self, connection): + # avoid overlapping restarts, but allow it if it's been a while + # (in case the last attempt failed before resetting "isReloadingMon") + if self.isReloadingMon and (time.time() - self.LASTTRY) < 180: + logging.info("[FixBRCMF] Duplicate attempt ignored") + else: + self.isReloadingMon = True + self.LASTTRY = time.time() + + self._status = "BL" + if hasattr(connection, 'view'): + display = connection.view() + if display: display.update(force=True, new_data={"status": "I'm blind! Try turning it off and on again", + "brcmfmac_status": self._status, + "face": faces.BORED}) + else: + display = None + + # main divergence from WATCHDOG starts here + # + # instead of rebooting, and losing all that energy loading up the AI + # pause wifi.recon, close wlan0mon, reload the brcmfmac kernel module + # then recreate wlan0mon, ..., and restart wifi.recon + + # Turn it off + + # attempt a sanity check. does wlan0mon exist? + # is it up? + try: + cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True) + logging.info("[FixBRCMF ip link show wlan0mon]: %s" % repr(cmd_output)) + if ",UP," in str(cmd_output): + logging.info("wlan0mon is up. Skip reset?") + # not reliable, so don't skip just yet + # print("wlan0mon is up. Skipping reset.") + # self.isReloadingMon = False + # return + except Exception as err: + logging.error("[FixBRCMF ip link show wlan0mon]: %s" % repr(err)) + + try: + result = connection.run("wifi.recon off") + if "success" in result: + self.logPrintView("info", "[FixBRCMF] wifi.recon off: %s!" % repr(result), + display, {"status": "Wifi recon paused!", "face": faces.COOL}) + time.sleep(2) + else: + self.logPrintView("warning", "[FixBRCMF] wifi.recon off: FAILED: %s" % repr(result), + display, {"status": "Recon was busted (probably)", + "face": random.choice((faces.BROKEN, faces.DEBUG))}) + except Exception as err: + logging.error("[FixBRCMF wifi.recon off] error %s" % (repr(err))) + + logging.info("[FixBRCMF] recon paused. Now trying wlan0mon reload") + + try: + cmd_output = subprocess.check_output("sudo ifconfig wlan0mon down && sudo iw dev wlan0mon del", shell=True) + self._status = "dn" + self.logPrintView("info", "[FixBRCMF] wlan0mon down and deleted: %s" % cmd_output, + display, {"status": "wlan0mon d-d-d-down!", "face": faces.BORED}) + except Exception as nope: + logging.error("[FixBRCMF delete wlan0mon] %s" % nope) + pass + + logging.debug("[FixBRCMF] Now trying modprobe -r") + + # Try this sequence 3 times until it is reloaded + # + # Future: while "not fixed yet": blah blah blah. if "max_attemts", then reboot like the old days + # + tries = 0 + while tries < 3: + try: + # unload the module + cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True) + self.logPrintView("info", "[FixBRCMF] unloaded brcmfmac", display, + {"status": "Turning it off #%d" % tries, "face": faces.SMART}) + self._status = "ul" + time.sleep(1 + tries) + + # reload the module + try: + # reload the brcmfmac kernel module + cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True) + + self.logPrintView("info", "[FixBRCMF] reloaded brcmfmac") + self._status = "rl" + time.sleep(10 + 4 * tries) # give it some time for wlan device to stabilize, or whatever + + # success! now make the mon0 + try: + cmd_output = subprocess.check_output( + "sudo iw phy \"$(iw phy | head -1 | cut -d' ' -f2)\" interface add mon0 type monitor && sudo ifconfig mon0 up", + shell=True) + self.logPrintView("info", + "[FixBRCMF interface add mon0] worked #%d: %s" % (tries, cmd_output)) + self._status = "up" + time.sleep(tries + 5) + try: + # try accessing mon0 in bettercap + result = connection.run("set wifi.interface mon0") + if "success" in result: + logging.info("[FixBRCMF set wifi.interface mon0] worked: %s" % repr(result)) + self._status = "" + self._count = self._count + 1 + time.sleep(1) + # stop looping and get back to recon + break + else: + logging.info("[FixBRCMF set wifi.interfaceface mon0] failed? %s" % repr(result)) + except Exception as err: + logging.info( + "[FixBRCMF set wifi.interface mon0] except: %s" % (repr(result), repr(err))) + except Exception as cerr: # + if not display: print("failed loading mon0 attempt #%d: %s" % (tries, repr(cerr))) + except Exception as err: # from modprobe + if not display: print("Failed reloading brcmfmac") + logging.error("[FixBRCMF] Failed reloading brcmfmac %s" % repr(err)) + + except Exception as nope: # from modprobe -r + # fails if already unloaded, so probably fine + logging.error("[FixBRCMF #%d modprobe -r] %s" % (tries, repr(nope))) + if not display: print("[FixBRCMF #%d modprobe -r] %s" % (tries, repr(nope))) + pass + + tries = tries + 1 + if tries < 3: + logging.info("[FixBRCMF] wlan0mon didn't make it. trying again") + if not display: print(" wlan0mon didn't make it. trying again") + + # exited the loop, so hopefully it loaded + if tries < 3: + if display: + display.update(force=True, new_data={"status": "And back on again...", + "brcmfmac_status": self._status, + "face": faces.INTENSE}) + else: + print("And back on again...") + logging.info("[FixBRCMF] mon0 back up") + else: + self.LASTTRY = time.time() + + time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap + self.isReloadingMon = False + + logging.info("[FixBRCMF] re-enable recon") + try: + result = connection.run("wifi.clear; wifi.recon on") + + if "success" in result: # and result["success"] is True: + self._status = "" + if display: + display.update(force=True, new_data={"status": "I can see again! (probably): %s" % repr(result), + "brcmfmac_status": self._status, + "face": faces.HAPPY}) + else: + print("I can see again") + logging.info("[FixBRCMF] wifi.recon on %s" % repr(result)) + self.LASTTRY = time.time() + 120 # 2-minute pause until next time. + else: + logging.error("[FixBRCMF] wifi.recon did not start up: %s" % repr(result)) + self.LASTTRY = time.time() - 300 # failed, so try again ASAP + self.isReloadingMon = False + + except Exception as err: + logging.error("[FixBRCMF wifi.recon on] %s" % repr(err)) + + # called to setup the ui elements + def on_ui_setup(self, ui): + # add custom UI elements + if "position" in self.options: + pos = self.options['position'].split(',') + pos = [int(x.strip()) for x in pos] + else: + pos = (ui.width() / 2 + 35, ui.height() - 11) + + logging.info("Got here") + ui.add_element('brcmfmac_status', Text(color=BLACK, value='--', position=pos, font=fonts.Small)) + + # called when the ui is updated + + def on_ui_update(self, ui): + # update those elements + if self._status: + ui.set('brcmfmac_status', "wlan0mon %s" % self._status) + else: + ui.set('brcmfmac_status', "rst#%s" % self._count) + + def on_unload(self, ui): + try: + ui.remove_element('brcmfmac_status') + logging.info("[FixBRCMF] unloaded") + except Exception as err: + logging.info("[FixBRCMF] unload err %s " % repr(err)) + pass + + +# run from command line to brute force a reload +if __name__ == "__main__": + print("Performing brcmfmac reload and restart mon0 in 5 seconds...") + fb = Fix_BRCMF() + + data = {'Message': "kernel: brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=1234"} + event = {'data': data} + + agent = Client('localhost', port=8081, username="pwnagotchi", password="pwnagotchi"); + + time.sleep(2) + print("3 seconds") + time.sleep(3) + # fb.on_epoch(agent, event, None) + fb._tryTurningItOffAndOnAgain(agent)